test(helpers): 加上「有遠端」的臨時 repo,並收掉三份重複的 git 執行器

branch-prep 的重點行為都繞著遠端打轉——來源分支在遠端已存在時要 pull 而不是重建、
目標分支已存在時不能覆蓋他人進度。這些事沒有遠端就驗不出來,所以補一個 bare origin
加工作用 clone,並附 pushFromElsewhere 模擬「別人推了東西上去」。

順手把 makeTempRepo 與新 helper 各自重寫一次的 git 執行器與 seed 收成共用的兩支。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-17 07:07:01 +00:00
co-authored by Claude Opus 5
parent d55f1682b7
commit e1897f2d33
+69 -11
View File
@@ -13,19 +13,77 @@ import { tmpRoot } from './run-script.js';
export function makeTempRepo() { export function makeTempRepo() {
mkdirSync(tmpRoot, { recursive: true }); mkdirSync(tmpRoot, { recursive: true });
const dir = mkdtempSync(join(tmpRoot, 'repo-')); const dir = mkdtempSync(join(tmpRoot, 'repo-'));
const git = (...args) => const git = gitIn(dir);
execFileSync('git', args, {
cwd: dir,
encoding: 'utf8',
env: { ...process.env, GIT_CONFIG_GLOBAL: '/dev/null', GIT_CONFIG_SYSTEM: '/dev/null' },
});
git('init', '-q', '-b', 'master'); git('init', '-q', '-b', 'master');
git('config', 'user.name', 'tester'); seed(git, dir, 'tester', 'tester@example.com');
git('config', 'user.email', 'tester@example.com');
writeFileSync(join(dir, 'README.md'), '# temp\n');
git('add', '-A');
git('commit', '-qm', 'initial');
return { dir, cleanup: () => rmSync(dir, { recursive: true, force: true }) }; return { dir, cleanup: () => rmSync(dir, { recursive: true, force: true }) };
} }
/**
* 綁在某個目錄上的 git 執行器。
* 全域與系統設定一律切掉:測試的結果不該隨開發者機器上的 git 設定而變。
*/
function gitIn(cwd) {
return (...args) =>
execFileSync('git', args, {
cwd,
encoding: 'utf8',
env: { ...process.env, GIT_CONFIG_GLOBAL: '/dev/null', GIT_CONFIG_SYSTEM: '/dev/null' },
}).trim();
}
/** 設好身分並放一顆起始 commit,讓 repo 有東西可以長分支 */
function seed(git, dir, name, email) {
git('config', 'user.name', name);
git('config', 'user.email', email);
writeFileSync(join(dir, 'README.md'), '# temp\n');
git('add', '-A');
git('commit', '-qm', 'initial');
}
/**
* 開一個「有遠端」的臨時 repo:一個 bare 當 origin,一個工作用的 clone。
*
* branch-prep 的重點行為都繞著遠端打轉——來源分支在遠端已存在時要 pull 而不是重建、
* 目標分支已存在時不能覆蓋他人進度。這些事沒有遠端就驗不出來。
* git 不做 mock:在臨時 repo 上跑真的 git 比假的 git 可信,成本也低。
*
* @returns {{dir: string, origin: string, git: Function, pushFromElsewhere: Function, cleanup: Function}}
* dir 為工作用 clone,origin 為 bare repo,pushFromElsewhere 用來模擬別人推東西上去
*/
export function makeTempRepoWithRemote() {
mkdirSync(tmpRoot, { recursive: true });
const base = mkdtempSync(join(tmpRoot, 'remote-'));
const origin = join(base, 'origin.git');
const dir = join(base, 'work');
execFileSync('git', ['init', '-q', '--bare', '-b', 'master', origin]);
execFileSync('git', ['clone', '-q', origin, dir]);
const git = gitIn(dir);
seed(git, dir, 'tester', 'tester@example.com');
git('push', '-q', 'origin', 'master');
return {
dir,
origin,
git,
/** 模擬「別人推了東西上去」:在 origin 上直接造一顆 commit */
pushFromElsewhere: (branch, filename, content) => {
const other = join(base, `other-${filename}`);
execFileSync('git', ['clone', '-q', origin, other]);
const otherGit = gitIn(other);
otherGit('config', 'user.name', 'someone-else');
otherGit('config', 'user.email', 'other@example.com');
otherGit('checkout', '-q', '-B', branch);
writeFileSync(join(other, filename), content);
otherGit('add', '-A');
otherGit('commit', '-qm', `別人的進度:${filename}`);
otherGit('push', '-q', 'origin', branch);
rmSync(other, { recursive: true, force: true });
},
cleanup: () => rmSync(base, { recursive: true, force: true }),
};
}