fix(gitrepo): 補抓淺層 checkout 的 base 歷史
node-actions/template: CI / BUILD (pull_request) Successful in 4s
CI / TEST (Codex) (pull_request) Successful in 31s
CI / TEST (Claude) (pull_request) Successful in 30s
CI / TEST (Antigravity) (pull_request) Failing after 33s

This commit is contained in:
Jeffery
2026-07-17 19:02:42 +08:00
parent 3fe643c003
commit c4c23d4531
2 changed files with 46 additions and 14 deletions
+40 -8
View File
@@ -44,6 +44,25 @@ function gitTrim(cwd, ...args) {
return git(cwd, ...args).trim();
}
/**
* 嘗試同步執行 git 指令,失敗時回傳 false,成功時回傳 true。
*
* @param {string} cwd - git 工作目錄(repo 的 checkout 路徑)。
* @param {...string} args - 傳給 git 的參數。
* @returns {boolean} git 指令是否成功結束。
* @remarks
* 使用情境:修復淺層 checkout 的歷史不足時,部分 fetch 策略可能因 runner
* 或遠端版本不同而失敗;呼叫端可依序嘗試多種策略,不讓第一個失敗中斷流程。
*/
function tryGit(cwd, ...args) {
try {
git(cwd, ...args);
return true;
} catch {
return false;
}
}
/**
* 取得目前 HEAD 最新一筆 commit 的訊息標題(commit message 第一行)。
*
@@ -64,14 +83,14 @@ function latestCommitSubject(cwd) {
/**
* 解析 PR base 分支與目前 HEAD 的 merge-base commit SHA。
*
* 先嘗試 `git fetch origin <baseRef>` 更新 base 分支資料(失敗時靜默忽略,
* 因 fetch-depth: 0 的 checkout 通常已含 base 分支,可直接沿用本地資料),
* 再以 `git merge-base origin/<baseRef> HEAD` 取得共同祖先
* 先以 refspec 明確更新 `origin/<baseRef>`,再以 `git merge-base origin/<baseRef> HEAD`
* 取得共同祖先。若 checkout 是淺層歷史而導致 merge-base 失敗,會補抓完整或更深的
* base/head 歷史後重試,避免 PR workflow 因 checkout 預設深度不足而中斷
*
* @param {string} cwd - git 工作目錄(repo 的 checkout 路徑)。
* @param {string} baseRef - PR 目標(base)分支名稱,例如 'master' 或 'develop';不含 'origin/' 前綴。
* @returns {string} merge-base 的 commit SHA40 碼十六進位字串)。
* @throws {Error} 本地不存在 origin/<baseRef>、或兩者無共同祖先時,`git merge-base` 失敗並拋出(fetch 失敗不會拋出)
* @throws {Error} 補抓歷史後仍無法取得共同祖先時,丟出含 baseRef 的明確錯誤
* @remarks
* 使用情境:AI code review 以此結果作為 diff 比較基準——
* 先 `resolveMergeBase(cwd, pr.base.ref)` 取得基準 SHA
@@ -79,12 +98,25 @@ function latestCommitSubject(cwd) {
* 避免把 base 分支後續演進誤算進 diff。
*/
function resolveMergeBase(cwd, baseRef) {
const remoteBase = `origin/${baseRef}`;
tryGit(cwd, 'fetch', '--no-tags', 'origin', `+refs/heads/${baseRef}:refs/remotes/${remoteBase}`);
try {
git(cwd, 'fetch', 'origin', baseRef);
} catch {
// fetch-depth: 0 的 checkout 通常已含 base 分支,抓不到時直接沿用本地資料。
return gitTrim(cwd, 'merge-base', remoteBase, 'HEAD');
} catch (firstError) {
const isShallow = gitTrim(cwd, 'rev-parse', '--is-shallow-repository') === 'true';
if (isShallow) {
tryGit(cwd, 'fetch', '--no-tags', '--unshallow', 'origin');
}
tryGit(cwd, 'fetch', '--no-tags', '--deepen=1000', 'origin', `+refs/heads/${baseRef}:refs/remotes/${remoteBase}`);
tryGit(cwd, 'fetch', '--no-tags', '--deepen=1000', 'origin', 'HEAD');
try {
return gitTrim(cwd, 'merge-base', remoteBase, 'HEAD');
} catch {
const error = new Error(`無法解析 origin/${baseRef} 與 HEAD 的 merge-base;請確認 checkout 有足夠歷史,或設定 checkout fetch-depth: 0。`);
error.cause = firstError;
throw error;
}
}
return gitTrim(cwd, 'merge-base', `origin/${baseRef}`, 'HEAD');
}
/**