@@ -1058,4 +1058,125 @@ describe("loadAppBootstrap", () => {
10581058 expect ( bootstrap . changeset . files [ 0 ] ?. path . endsWith ( "after.ts" ) ) . toBe ( true ) ;
10591059 expect ( bootstrap . changeset . files [ 0 ] ?. stats . additions ) . toBeGreaterThan ( 0 ) ;
10601060 } ) ;
1061+
1062+ test ( "loads patch text emitted with diff.noprefix=true (e.g. from `hunk pager` stdin)" , async ( ) => {
1063+ const bootstrap = await loadAppBootstrap ( {
1064+ kind : "patch" ,
1065+ text : [
1066+ "diff --git src/example.ts src/example.ts" ,
1067+ "index 0000000..1111111 100644" ,
1068+ "--- src/example.ts" ,
1069+ "+++ src/example.ts" ,
1070+ "@@ -1,1 +1,2 @@" ,
1071+ " const value = 1;" ,
1072+ "+const added = 2;" ,
1073+ ] . join ( "\n" ) ,
1074+ options : { mode : "auto" } ,
1075+ } ) ;
1076+
1077+ expect ( bootstrap . changeset . files ) . toHaveLength ( 1 ) ;
1078+ expect ( bootstrap . changeset . files [ 0 ] ) . toMatchObject ( {
1079+ path : "src/example.ts" ,
1080+ metadata : { name : "src/example.ts" , type : "change" } ,
1081+ } ) ;
1082+ expect ( bootstrap . changeset . files [ 0 ] ?. stats . additions ) . toBe ( 1 ) ;
1083+ } ) ;
1084+
1085+ test ( "loads noprefix rename patches by recovering the rename pair from the headers" , async ( ) => {
1086+ const bootstrap = await loadAppBootstrap ( {
1087+ kind : "patch" ,
1088+ text : [
1089+ "diff --git old/path.ts new/path.ts" ,
1090+ "similarity index 100%" ,
1091+ "rename from old/path.ts" ,
1092+ "rename to new/path.ts" ,
1093+ ] . join ( "\n" ) ,
1094+ options : { mode : "auto" } ,
1095+ } ) ;
1096+
1097+ expect ( bootstrap . changeset . files ) . toHaveLength ( 1 ) ;
1098+ expect ( bootstrap . changeset . files [ 0 ] ) . toMatchObject ( {
1099+ path : "new/path.ts" ,
1100+ previousPath : "old/path.ts" ,
1101+ metadata : { type : "rename-pure" } ,
1102+ } ) ;
1103+ } ) ;
1104+
1105+ test ( "loads quoted noprefix patch text emitted for escaped git paths" , async ( ) => {
1106+ const dir = createTempRepo ( "hunk-patch-quoted-noprefix-" ) ;
1107+ const fileName = "src\tfile.txt" ;
1108+
1109+ writeFileSync ( join ( dir , fileName ) , "one\n" ) ;
1110+ git ( dir , "add" , "." ) ;
1111+ git ( dir , "commit" , "-m" , "initial" ) ;
1112+
1113+ writeFileSync ( join ( dir , fileName ) , "two\n" ) ;
1114+ const patchText = git ( dir , "-c" , "diff.noprefix=true" , "diff" , "--" , fileName ) ;
1115+
1116+ expect ( patchText ) . toContain ( 'diff --git "src\\tfile.txt" "src\\tfile.txt"' ) ;
1117+
1118+ const bootstrap = await loadAppBootstrap ( {
1119+ kind : "patch" ,
1120+ text : patchText ,
1121+ options : { mode : "auto" } ,
1122+ } ) ;
1123+
1124+ expect ( bootstrap . changeset . files ) . toHaveLength ( 1 ) ;
1125+ expect ( bootstrap . changeset . files [ 0 ] ) . toMatchObject ( {
1126+ path : "src\\tfile.txt" ,
1127+ metadata : { name : "src\\tfile.txt" , type : "change" } ,
1128+ } ) ;
1129+ expect ( bootstrap . changeset . files [ 0 ] ?. stats ) . toEqual ( { additions : 1 , deletions : 1 } ) ;
1130+ } ) ;
1131+
1132+ test ( "does not mangle a deleted SQL `-- comment` line in a noprefix patch" , async ( ) => {
1133+ // The original source line `-- drop table users;` (a SQL comment) is encoded in a unified
1134+ // diff deletion as `--- drop table users;` — three dashes (one for the deletion marker,
1135+ // two from the comment) and a space. That looks identical to a `--- a/path` file header
1136+ // on its own, so the noprefix prefix-restorer must stop rewriting `--- ` lines once the
1137+ // `+++ ` line of the current block has been emitted.
1138+ const bootstrap = await loadAppBootstrap ( {
1139+ kind : "patch" ,
1140+ text : [
1141+ "diff --git db/schema.sql db/schema.sql" ,
1142+ "index 0000000..1111111 100644" ,
1143+ "--- db/schema.sql" ,
1144+ "+++ db/schema.sql" ,
1145+ "@@ -1,3 +1,2 @@" ,
1146+ " CREATE TABLE users (id INT);" ,
1147+ "--- drop table users;" ,
1148+ " CREATE TABLE posts (id INT);" ,
1149+ ] . join ( "\n" ) ,
1150+ options : { mode : "auto" } ,
1151+ } ) ;
1152+
1153+ expect ( bootstrap . changeset . files ) . toHaveLength ( 1 ) ;
1154+ const file = bootstrap . changeset . files [ 0 ] ! ;
1155+ expect ( file . path ) . toBe ( "db/schema.sql" ) ;
1156+ expect ( file . stats . deletions ) . toBe ( 1 ) ;
1157+ // The deleted content must round-trip as `-- drop table users;` (the original SQL line),
1158+ // not as `-- a/drop table users;` (the corruption produced when the rewriter is still
1159+ // active inside the hunk body).
1160+ expect ( file . metadata . deletionLines ) . toContain ( "-- drop table users;\n" ) ;
1161+ expect ( file . metadata . deletionLines . some ( ( line ) => line . includes ( "a/" ) ) ) . toBe ( false ) ;
1162+ } ) ;
1163+
1164+ test ( "leaves correctly prefixed patches untouched even when paths sit inside an `a/` directory" , async ( ) => {
1165+ const bootstrap = await loadAppBootstrap ( {
1166+ kind : "patch" ,
1167+ text : [
1168+ "diff --git a/a/inner.ts b/a/inner.ts" ,
1169+ "index 0000000..1111111 100644" ,
1170+ "--- a/a/inner.ts" ,
1171+ "+++ b/a/inner.ts" ,
1172+ "@@ -1,1 +1,2 @@" ,
1173+ " const x = 1;" ,
1174+ "+const y = 2;" ,
1175+ ] . join ( "\n" ) ,
1176+ options : { mode : "auto" } ,
1177+ } ) ;
1178+
1179+ expect ( bootstrap . changeset . files ) . toHaveLength ( 1 ) ;
1180+ expect ( bootstrap . changeset . files [ 0 ] ?. path ) . toBe ( "a/inner.ts" ) ;
1181+ } ) ;
10611182} ) ;
0 commit comments