fix(core): use the amend delta for loosely spelled amend commits (#16073)

Co-authored-by: Qwen-Coder <[email protected]>
This commit is contained in:
易良andQwen-Coder authored and GitHub committed 2026-11-02 00:43:20 +08:00
1 parent 4a5c487681
commit dfeae12de0
2 files changed
+50 -5

No files matched your search

@@ -944,6 +944,45 @@ describe.skipIf(process.platform === 'win32')(
expect(amendSpy.mock.calls[0]![2]).toBe(true);
});
it.each([
[
'control-flow keywords',
() => 'if true; then git commit --amend --no-edit; fi',
],
[
'absolute git path',
() =>
`${execSync('command -v git', { encoding: 'utf-8' }).trim()} commit --amend --no-edit`,
],
['leading time keyword', () => 'time git commit --amend --no-edit'],
])(
'uses the amend delta in the attribution note for an amend spelled with %s (issue #12514)',
async (_label, buildCommand) => {
// #12514-C: `isAmendCommit` was the one recogniser that still required
// `tokens[0] === 'git'`. A loose-spelled amend therefore reached
// attribution classified as a fresh commit, so the note diffed
// `postHead~1..postHead` instead of `preHead..postHead`. The extra file
// below makes that observable: it landed in the pre-amend commit but
// was untouched by the amend, so only the wrong range includes it.
rawCommit('first.txt', 'first version\n', 'add first');
rawCommit('second.txt', 'second version\n', 'add second');
fs.writeFileSync(path.join(repoDir, 'first.txt'), 'first amended\n');
rawGit('add first.txt');
CommitAttributionService.getInstance().recordEdit(
path.join(repoDir, 'first.txt'),
'first version\n',
'first amended\n',
);
await runShellCommand(buildCommand());
const note = JSON.parse(
rawGit('notes --ref=refs/notes/ai-attribution show HEAD'),
);
expect(Object.keys(note.files)).toEqual(['first.txt']);
},
);
it('registers the commit even when gitCoAuthor.commit attribution is disabled', async () => {
// Regression ④: session tracking is deliberately independent of the
// commit-attribution toggle. A user who turned attribution off must not
+11 -5
View File
@@ -687,8 +687,8 @@ const COMMIT_RECOGNITION_LEADING_NOISE: ReadonlySet<string> = new Set([
]);
/**
* The program recogniser shared by `gitCommitContext` and
* `findAttributableCommitSegment` (#12514): skip leading
* The program recogniser shared by `gitCommitContext`,
* `findAttributableCommitSegment` and `isAmendCommit` (#12514): skip leading
* control-flow/noise keywords and match the program on its
* basename (see `programBasename`), so `then git commit`,
* `time git commit`, `! git commit` and `/usr/bin/git commit` all
@@ -936,13 +936,19 @@ function cdTargetMayChangeRepo(tokens: string[], cwd: string): boolean {
* shell tool counts. `git -C ../other commit --amend && git commit -m x`
* must not flip the diff range for the second (fresh) commit, since
* `preHead` would be the inner repo's SHA there, not ours.
*
* Recognition matches the other commit walks: leading shell
* control-flow/noise keywords are skipped and the program is matched on
* its basename, so a loose spelling still gets the amend-range diff.
*/
function isAmendCommit(command: string, cwd: string): boolean {
let cwdShifted = false;
for (const sub of splitCommands(command)) {
const tokens = tokeniseSegment(sub);
if (!tokens || tokens.length === 0) continue;
const program = tokens[0]!;
const rawTokens = tokeniseSegment(sub);
if (!rawTokens || rawTokens.length === 0) continue;
const tokens = skipCommitRecognitionNoise(rawTokens);
if (!tokens) continue;
const program = programBasename(tokens[0]!);
if (program === 'cd' || program === 'pushd') {
if (!cwdShifted && cdTargetMayChangeRepo(tokens, cwd)) cwdShifted = true;
continue;