* Returns null when state cannot be reliably determined — callers that use * this as a safety gate must treat null as "unknown, assume unsafe" * (fail-closed). A silent 0/0 would let cleanupWorktree destroy real work. * * Null is returned when: * - git status or rev-list exit non-zero (lock fil
( worktreePath: string, originalHeadCommit: string | undefined, )
| 77 | * commits without a baseline, so we cannot prove the branch is clean. |
| 78 | */ |
| 79 | async function countWorktreeChanges( |
| 80 | worktreePath: string, |
| 81 | originalHeadCommit: string | undefined, |
| 82 | ): Promise<ChangeSummary | null> { |
| 83 | const status = await execFileNoThrow('git', [ |
| 84 | '-C', |
| 85 | worktreePath, |
| 86 | 'status', |
| 87 | '--porcelain', |
| 88 | ]) |
| 89 | if (status.code !== 0) { |
| 90 | return null |
| 91 | } |
| 92 | const changedFiles = count(status.stdout.split('\n'), l => l.trim() !== '') |
| 93 | |
| 94 | if (!originalHeadCommit) { |
| 95 | // git status succeeded → this is a git repo, but without a baseline |
| 96 | // commit we cannot count commits. Fail-closed rather than claim 0. |
| 97 | return null |
| 98 | } |
| 99 | |
| 100 | const revList = await execFileNoThrow('git', [ |
| 101 | '-C', |
| 102 | worktreePath, |
| 103 | 'rev-list', |
| 104 | '--count', |
| 105 | `${originalHeadCommit}..HEAD`, |
| 106 | ]) |
| 107 | if (revList.code !== 0) { |
| 108 | return null |
| 109 | } |
| 110 | const commits = parseInt(revList.stdout.trim(), 10) || 0 |
| 111 | |
| 112 | return { changedFiles, commits } |
| 113 | } |
| 114 | |
| 115 | /** |
| 116 | * Restore session state to reflect the original directory. |
no test coverage detected