@@ -1453,6 +1453,72 @@ export const makeGitCore = (options?: { executeOverride?: GitCoreShape["execute"
14531453 } ) ) ,
14541454 ) ;
14551455
1456+ const readUntrackedPatches = ( cwd : string , operationPrefix : string ) =>
1457+ runGitStdout (
1458+ `${ operationPrefix } .untrackedFiles` ,
1459+ cwd ,
1460+ [ "ls-files" , "--others" , "--exclude-standard" , "-z" ] ,
1461+ true ,
1462+ ) . pipe (
1463+ Effect . map ( ( stdout ) => stdout . split ( "\0" ) . filter ( ( entry ) => entry . length > 0 ) ) ,
1464+ Effect . flatMap ( ( untrackedFiles ) =>
1465+ Effect . forEach (
1466+ untrackedFiles ,
1467+ ( filePath ) =>
1468+ // Git diff omits untracked files, so synthesize a normal patch for each one.
1469+ executeGit (
1470+ `${ operationPrefix } .untrackedPatch` ,
1471+ cwd ,
1472+ [
1473+ "diff" ,
1474+ "--no-index" ,
1475+ "--patch" ,
1476+ "--no-color" ,
1477+ "--src-prefix=a/" ,
1478+ "--dst-prefix=b/" ,
1479+ "--" ,
1480+ "/dev/null" ,
1481+ filePath ,
1482+ ] ,
1483+ {
1484+ allowNonZeroExit : true ,
1485+ timeoutMs : WORKING_TREE_DIFF_TIMEOUT_MS ,
1486+ } ,
1487+ ) . pipe ( Effect . map ( ( result ) => result . stdout ) ) ,
1488+ { concurrency : MAX_UNTRACKED_DIFF_CONCURRENCY } ,
1489+ ) ,
1490+ ) ,
1491+ ) ;
1492+
1493+ const readUnstagedPatch : GitCoreShape [ "readUnstagedPatch" ] = ( cwd ) =>
1494+ Effect . gen ( function * ( ) {
1495+ const trackedPatch = yield * executeGit (
1496+ "GitCore.readUnstagedPatch.trackedPatch" ,
1497+ cwd ,
1498+ [ "diff" , "--patch" , "--no-color" , "--no-ext-diff" ] ,
1499+ {
1500+ allowNonZeroExit : true ,
1501+ timeoutMs : WORKING_TREE_DIFF_TIMEOUT_MS ,
1502+ } ,
1503+ ) . pipe ( Effect . map ( ( result ) => result . stdout ) ) ;
1504+ const untrackedPatches = yield * readUntrackedPatches ( cwd , "GitCore.readUnstagedPatch" ) ;
1505+
1506+ return {
1507+ patch : joinPatchSegments ( [ trackedPatch , ...untrackedPatches ] ) ,
1508+ } ;
1509+ } ) ;
1510+
1511+ const readStagedPatch : GitCoreShape [ "readStagedPatch" ] = ( cwd ) =>
1512+ executeGit (
1513+ "GitCore.readStagedPatch" ,
1514+ cwd ,
1515+ [ "diff" , "--cached" , "--patch" , "--no-color" , "--no-ext-diff" ] ,
1516+ {
1517+ allowNonZeroExit : true ,
1518+ timeoutMs : WORKING_TREE_DIFF_TIMEOUT_MS ,
1519+ } ,
1520+ ) . pipe ( Effect . map ( ( result ) => ( { patch : result . stdout } ) ) ) ;
1521+
14561522 const readWorkingTreePatch : GitCoreShape [ "readWorkingTreePatch" ] = ( cwd ) =>
14571523 Effect . gen ( function * ( ) {
14581524 const headExists = yield * executeGit (
@@ -1474,43 +1540,49 @@ export const makeGitCore = (options?: { executeOverride?: GitCoreShape["execute"
14741540 } ,
14751541 ) . pipe ( Effect . map ( ( result ) => result . stdout ) ) ;
14761542
1477- const untrackedFiles = yield * runGitStdout (
1478- "GitCore.readWorkingTreePatch.untrackedFiles" ,
1479- cwd ,
1480- [ "ls-files" , "--others" , "--exclude-standard" , "-z" ] ,
1481- true ,
1482- ) . pipe ( Effect . map ( ( stdout ) => stdout . split ( "\0" ) . filter ( ( entry ) => entry . length > 0 ) ) ) ;
1483-
1484- const untrackedPatches = yield * Effect . forEach (
1485- untrackedFiles ,
1486- ( filePath ) =>
1487- executeGit (
1488- "GitCore.readWorkingTreePatch.untrackedPatch" ,
1489- cwd ,
1490- [
1491- "diff" ,
1492- "--no-index" ,
1493- "--patch" ,
1494- "--no-color" ,
1495- "--src-prefix=a/" ,
1496- "--dst-prefix=b/" ,
1497- "--" ,
1498- "/dev/null" ,
1499- filePath ,
1500- ] ,
1501- {
1502- allowNonZeroExit : true ,
1503- timeoutMs : WORKING_TREE_DIFF_TIMEOUT_MS ,
1504- } ,
1505- ) . pipe ( Effect . map ( ( result ) => result . stdout ) ) ,
1506- { concurrency : MAX_UNTRACKED_DIFF_CONCURRENCY } ,
1507- ) ;
1543+ const untrackedPatches = yield * readUntrackedPatches ( cwd , "GitCore.readWorkingTreePatch" ) ;
15081544
15091545 return {
15101546 patch : joinPatchSegments ( [ trackedPatch , ...untrackedPatches ] ) ,
15111547 } ;
15121548 } ) ;
15131549
1550+ const readBranchPatch : GitCoreShape [ "readBranchPatch" ] = ( cwd ) =>
1551+ Effect . gen ( function * ( ) {
1552+ const details = yield * statusDetails ( cwd ) ;
1553+ const baseBranch =
1554+ details . upstreamRef ??
1555+ ( details . branch
1556+ ? yield * resolveBaseBranchForNoUpstream ( cwd , details . branch ) . pipe (
1557+ Effect . catch ( ( ) => Effect . succeed ( null ) ) ,
1558+ )
1559+ : null ) ;
1560+ if ( ! baseBranch ) {
1561+ return yield * createGitCommandError (
1562+ "GitCore.readBranchPatch.base" ,
1563+ cwd ,
1564+ [ "diff" , "--patch" , "--minimal" , "<base>...HEAD" ] ,
1565+ "Cannot resolve a base branch for the current branch diff." ,
1566+ ) ;
1567+ }
1568+
1569+ const result = yield * execute ( {
1570+ operation : "GitCore.readBranchPatch.diffPatch" ,
1571+ cwd,
1572+ args : [
1573+ "diff" ,
1574+ "--patch" ,
1575+ "--minimal" ,
1576+ "--no-color" ,
1577+ "--no-ext-diff" ,
1578+ `${ baseBranch } ...HEAD` ,
1579+ ] ,
1580+ maxOutputBytes : 10_000_000 ,
1581+ } ) ;
1582+
1583+ return { patch : result . stdout } ;
1584+ } ) ;
1585+
15141586 const prepareCommitContext : GitCoreShape [ "prepareCommitContext" ] = ( cwd , filePaths ) =>
15151587 Effect . gen ( function * ( ) {
15161588 if ( filePaths && filePaths . length > 0 ) {
@@ -2465,6 +2537,9 @@ export const makeGitCore = (options?: { executeOverride?: GitCoreShape["execute"
24652537 status,
24662538 statusDetails,
24672539 readWorkingTreePatch,
2540+ readUnstagedPatch,
2541+ readStagedPatch,
2542+ readBranchPatch,
24682543 prepareCommitContext,
24692544 commit,
24702545 pushCurrentBranch,
0 commit comments