| ... | ... | @@ -415,6 +415,12 @@ function isMissingGitCwdError(error: GitCommandError): boolean { |
| 415 | 415 | function isNonRepositoryGitStderr(stderr: string): boolean { |
| 416 | 416 | return stderr.toLowerCase().includes("not a git repository"); |
| 417 | 417 | } |
| 418 | function isUnbornHeadStderr(stderr: string): boolean { |
| 419 | return ( |
| 420 | stderr.toLowerCase().includes("unknown revision") && |
| 421 | stderr.toLowerCase().includes("path not in the working tree") |
| 422 | ); |
| 423 | } |
| 418 | 424 | |
| 419 | 425 | interface Trace2Monitor { |
| 420 | 426 | readonly env: NodeJS.ProcessEnv; |
| ... | ... | @@ -1511,27 +1517,73 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* |
| 1511 | 1517 | }); |
| 1512 | 1518 | } |
| 1513 | 1519 | |
| 1514 | | const [unstagedNumstatStdout, stagedNumstatStdout, defaultRefResult, hasPrimaryRemote] = |
| 1515 | | yield* Effect.all( |
| 1516 | | [ |
| 1517 | | runGitStdout("GitVcsDriver.statusDetails.unstagedNumstat", cwd, ["diff", "--numstat"]), |
| 1518 | | runGitStdout("GitVcsDriver.statusDetails.stagedNumstat", cwd, [ |
| 1519 | | "diff", |
| 1520 | | "--cached", |
| 1521 | | "--numstat", |
| 1522 | | ]), |
| 1523 | | executeGit( |
| 1524 | | "GitVcsDriver.statusDetails.defaultRef", |
| 1525 | | cwd, |
| 1526 | | ["symbolic-ref", "refs/remotes/origin/HEAD"], |
| 1527 | | { |
| 1528 | | allowNonZeroExit: true, |
| 1529 | | }, |
| 1530 | | ), |
| 1531 | | originRemoteExists(cwd).pipe(Effect.orElseSucceed(() => false)), |
| 1532 | | ], |
| 1533 | | { concurrency: "unbounded" }, |
| 1534 | | ); |
| 1520 | const [numstatStdout, defaultRefResult, hasPrimaryRemote] = yield* Effect.all( |
| 1521 | [ |
| 1522 | executeGitWithStableDiagnostics( |
| 1523 | "GitVcsDriver.statusDetails.numstat", |
| 1524 | cwd, |
| 1525 | ["diff", "HEAD", "--numstat"], |
| 1526 | { allowNonZeroExit: true }, |
| 1527 | ).pipe( |
| 1528 | Effect.flatMap((result) => { |
| 1529 | if (result.exitCode === 0) return Effect.succeed(result.stdout); |
| 1530 | if (isUnbornHeadStderr(result.stderr)) { |
| 1531 | return Effect.map( |
| 1532 | Effect.all([ |
| 1533 | runGitStdout("GitVcsDriver.statusDetails.numstat.unborn", cwd, [ |
| 1534 | "diff", |
| 1535 | "--numstat", |
| 1536 | ]), |
| 1537 | runGitStdout("GitVcsDriver.statusDetails.numstat.unborn.staged", cwd, [ |
| 1538 | "diff", |
| 1539 | "--cached", |
| 1540 | "--numstat", |
| 1541 | ]), |
| 1542 | ]), |
| 1543 | ([unstagedStdout, stagedStdout]) => { |
| 1544 | const staged = parseNumstatEntries(stagedStdout); |
| 1545 | const unstaged = parseNumstatEntries(unstagedStdout); |
| 1546 | const map = new Map<string, { insertions: number; deletions: number }>(); |
| 1547 | for (const entry of [...staged, ...unstaged]) { |
| 1548 | const existing = map.get(entry.path) ?? { |
| 1549 | insertions: 0, |
| 1550 | deletions: 0, |
| 1551 | }; |
| 1552 | existing.insertions += entry.insertions; |
| 1553 | existing.deletions += entry.deletions; |
| 1554 | map.set(entry.path, existing); |
| 1555 | } |
| 1556 | return Array.from(map.entries()) |
| 1557 | .map(([p, s]) => `${s.insertions}\t${s.deletions}\t${p}`) |
| 1558 | .join("\n"); |
| 1559 | }, |
| 1560 | ); |
| 1561 | } |
| 1562 | return Effect.fail( |
| 1563 | new GitCommandError({ |
| 1564 | ...gitCommandContext({ |
| 1565 | operation: "GitVcsDriver.statusDetails.numstat", |
| 1566 | cwd, |
| 1567 | args: ["diff", "HEAD", "--numstat"], |
| 1568 | }), |
| 1569 | detail: "git diff HEAD --numstat failed.", |
| 1570 | exitCode: result.exitCode, |
| 1571 | stdoutLength: result.stdout.length, |
| 1572 | stderrLength: result.stderr.length, |
| 1573 | }), |
| 1574 | ); |
| 1575 | }), |
| 1576 | ), |
| 1577 | executeGit( |
| 1578 | "GitVcsDriver.statusDetails.defaultRef", |
| 1579 | cwd, |
| 1580 | ["symbolic-ref", "refs/remotes/origin/HEAD"], |
| 1581 | { allowNonZeroExit: true }, |
| 1582 | ), |
| 1583 | originRemoteExists(cwd).pipe(Effect.orElseSucceed(() => false)), |
| 1584 | ], |
| 1585 | { concurrency: "unbounded" }, |
| 1586 | ); |
| 1535 | 1587 | const statusStdout = statusResult.stdout; |
| 1536 | 1588 | const defaultBranch = |
| 1537 | 1589 | defaultRefResult.exitCode === 0 |
| ... | ... | @@ -1592,14 +1644,10 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* |
| 1592 | 1644 | : yield* computeAheadCountAgainstBase(cwd, refName).pipe(Effect.orElseSucceed(() => 0)); |
| 1593 | 1645 | } |
| 1594 | 1646 | |
| 1595 | | const stagedEntries = parseNumstatEntries(stagedNumstatStdout); |
| 1596 | | const unstagedEntries = parseNumstatEntries(unstagedNumstatStdout); |
| 1647 | const numstatEntries = parseNumstatEntries(numstatStdout); |
| 1597 | 1648 | const fileStatMap = new Map<string, { insertions: number; deletions: number }>(); |
| 1598 | | for (const entry of [...stagedEntries, ...unstagedEntries]) { |
| 1599 | | const existing = fileStatMap.get(entry.path) ?? { insertions: 0, deletions: 0 }; |
| 1600 | | existing.insertions += entry.insertions; |
| 1601 | | existing.deletions += entry.deletions; |
| 1602 | | fileStatMap.set(entry.path, existing); |
| 1649 | for (const entry of numstatEntries) { |
| 1650 | fileStatMap.set(entry.path, { insertions: entry.insertions, deletions: entry.deletions }); |
| 1603 | 1651 | } |
| 1604 | 1652 | |
| 1605 | 1653 | let insertions = 0; |