diff --git a/docs/features/assistant-workbar/progress.md b/docs/features/assistant-workbar/progress.md index 65bc22d9..36a3bbd6 100644 --- a/docs/features/assistant-workbar/progress.md +++ b/docs/features/assistant-workbar/progress.md @@ -1,5 +1,20 @@ # 工作栏实现与验证进度 +## 2026-09-10 Git 历史测试夹具修复 + +- 桌面候选 `877cf58` 的 CI 在四项工作区测试准备 Git 提交时因缺少作者身份失败。 + `c037d7f` 移除测试身份后引入了机器配置依赖;本机已有身份使该问题未在本地暴露。 +- 测试现在把已暂存的 tree 写入固定 Git commit 对象,并仅更新临时仓库的分支引用, + 不调用 `git commit`,不修改或覆盖任何 Git 身份配置。历史、重命名差异、分页和脏文件 + 分支冲突仍由真实 Git 与生产 `manageWorkspace` 验证,不用 mock 或跳过用例替代。 +- 新增夹具自检确认 `git fsck --strict` 通过、父提交链有效、未暂存内容保持不变且仓库配置未改。 + 聚焦测试 11 项通过、2 项因当前文件系统大小写不敏感而跳过;无身份 Runner 的验证待新候选 CI。 +- 完整 `npm test` 为 3845 项通过、66 项跳过、1 项未修改的活动列表测试超时; + 随后 `ActivityPanel.test.tsx` 的 16 项独立复跑全部通过,未放宽时限或删除断言。 + `npm run release:notes:verify`、`npm run typecheck`、`npm run lint` 通过。 + 未将这次本地整库结果记为全绿,发布仍以新候选的 CI 校验和生产构建通过为前提。 +- 本轮只改测试和验证记录,产品功能、Agent 源码及已批准的双语发布说明不变,无外部模型调用。 + ## 2026-09-10 工作区布局与分支输入样式 - 按 [FR-W1、FR-W4](./prd.md#76-工作区) 将视图切换和刷新合并到顶部行, diff --git a/src/main/workspace/workspace-management.test.ts b/src/main/workspace/workspace-management.test.ts index b7792ce4..449244cc 100644 --- a/src/main/workspace/workspace-management.test.ts +++ b/src/main/workspace/workspace-management.test.ts @@ -16,6 +16,42 @@ async function repository(): Promise { function git(root: string, ...args: string[]): string { return execFileSync('git', ['-C', root, ...args], { encoding: 'utf8', windowsHide: true }).trim() } +function commitFixture(root: string, subject: string): string { + const tree = git(root, 'write-tree') + const branch = git(root, 'symbolic-ref', 'HEAD') + const parent = git(root, 'for-each-ref', '--format=%(objectname)', branch) + // Fixed object data, not a Git identity override: these tests read history + // and switch branches, and do not exercise Git's commit authoring command. + const input = [ + `tree ${tree}`, + ...(parent ? [`parent ${parent}`] : []), + 'author Workspace Test 1788998400 +0000', + 'committer Workspace Test 1788998400 +0000', + '', + subject, + '' + ].join('\n') + const oid = execFileSync('git', ['-C', root, 'hash-object', '-t', 'commit', '-w', '--stdin'], { + input, encoding: 'utf8', windowsHide: true + }).trim() + git(root, 'update-ref', 'HEAD', oid) + return oid +} +it('creates valid fixture history without changing repository configuration or staging dirty content', async () => { + const root = await repository() + const configuration = await readFile(join(root, '.git/config'), 'utf8') + await writeFile(join(root, 'test.txt'), 'staged\n') + git(root, 'add', '.') + const first = commitFixture(root, 'initial') + await writeFile(join(root, 'test.txt'), 'dirty\n') + const second = commitFixture(root, 'empty') + expect(git(root, 'rev-parse', `${second}^`)).toBe(first) + expect(git(root, 'show', `${second}:test.txt`)).toBe('staged') + expect(await readFile(join(root, 'test.txt'), 'utf8')).toBe('dirty\n') + expect(git(root, 'log', '-1', '--format=%an')).toBe('Workspace Test') + expect(git(root, 'fsck', '--strict')).toBe('') + expect(await readFile(join(root, '.git/config'), 'utf8')).toBe(configuration) +}) it('creates, moves, inspects and deletes without overwriting an existing file', async () => { const root = await repository() await manageWorkspace(root, { kind: 'createDirectory', path: 'docs' }) @@ -33,9 +69,9 @@ it('reads unborn history, initial commit files and literal paths, and switches w await expect(manageWorkspace(root, { kind: 'history', offset: 0 })).resolves.toMatchObject({ commits: [] }) await writeFile(join(root, '[test].txt'), 'initial\n') git(root, 'add', '.') - git(root, 'commit', '-m', 'Initial subject') + commitFixture(root, 'Initial subject') const oid = git(root, 'rev-parse', 'HEAD') - await expect(manageWorkspace(root, { kind: 'history', offset: 0 })).resolves.toMatchObject({ head: oid, commits: [{ subject: 'Initial subject', author: git(root, 'log', '-1', '--format=%an') }] }) + await expect(manageWorkspace(root, { kind: 'history', offset: 0 })).resolves.toMatchObject({ head: oid, commits: [{ subject: 'Initial subject', author: 'Workspace Test' }] }) await expect(manageWorkspace(root, { kind: 'commitFiles', oid })).resolves.toMatchObject({ files: [{ path: '[test].txt', status: 'A' }] }) await expect(manageWorkspace(root, { kind: 'commitDiff', oid, path: '[test].txt' })).resolves.toMatchObject({ patch: expect.stringContaining('+initial') }) await writeFile(join(root, '[test].txt'), 'dirty\n') @@ -48,15 +84,15 @@ it('reads unborn history, initial commit files and literal paths, and switches w it('reports switch conflicts without discarding dirty content and pages stable history', async () => { const root = await repository() await writeFile(join(root, 'test.txt'), 'initial') - git(root, 'add', '.'); git(root, 'commit', '-m', 'initial') + git(root, 'add', '.'); commitFixture(root, 'initial') git(root, 'switch', '-c', 'other') await writeFile(join(root, 'test.txt'), 'other') - git(root, 'commit', '-am', 'other') + git(root, 'add', '.'); commitFixture(root, 'other') git(root, 'switch', 'main') await writeFile(join(root, 'test.txt'), 'dirty') await expect(manageWorkspace(root, { kind: 'switchBranch', branch: 'other', remote: false })).rejects.toThrow(/overwritten/) expect(await readFile(join(root, 'test.txt'), 'utf8')).toBe('dirty') - for (let index = 0; index < 51; index++) git(root, 'commit', '--allow-empty', '-m', `page ${index}`) + for (let index = 0; index < 51; index++) commitFixture(root, `page ${index}`) const first = await manageWorkspace(root, { kind: 'history', offset: 0 }) expect(first.kind).toBe('history') if (first.kind !== 'history') return @@ -73,11 +109,11 @@ it.each([false, true])('preserves rename metadata and actual edits in a literal const original = Array.from({ length: 20 }, (_, index) => `original line ${index}\n`).join('') await writeFile(join(root, previousPath), original) await writeFile(join(root, 'unrelated.txt'), 'unrelated before\n') - git(root, 'add', '.'); git(root, 'commit', '-m', 'before rename') + git(root, 'add', '.'); commitFixture(root, 'before rename') await rename(join(root, previousPath), join(root, path)) if (edited) await writeFile(join(root, path), original.replace('original line 10\n', 'edited line 10\n')) await writeFile(join(root, 'unrelated.txt'), 'unrelated after\n') - git(root, 'add', '.'); git(root, 'commit', '-m', 'rename') + git(root, 'add', '.'); commitFixture(root, 'rename') const oid = git(root, 'rev-parse', 'HEAD') const files = await manageWorkspace(root, { kind: 'commitFiles', oid }) expect(files).toMatchObject({ files: expect.arrayContaining([{ path, previousPath, status: edited ? expect.stringMatching(/^R\d+$/) : 'R100' }]) })