修复: merge-tree 预检参数序与判定收紧
- 参数序改为共同祖先,原序致分支零差异预检失效 - 冲突判定只认真实标记,等号行不再误撞
This commit is contained in:
@@ -167,24 +167,35 @@ pub fn merge_branch(
|
|||||||
plan_wt_path: &Path,
|
plan_wt_path: &Path,
|
||||||
subtask_branch: &str,
|
subtask_branch: &str,
|
||||||
) -> Result<MergeResult, WorktreeError> {
|
) -> Result<MergeResult, WorktreeError> {
|
||||||
// 用旧版兼容的 merge-tree(base branch → 输出冲突树)
|
// 预检用旧版三参形态 merge-tree <共同祖先> <ours> <theirs>
|
||||||
// --write-tree 需 Git 2.38+,改用三参数 merge-tree <base> <branch1> <branch2>
|
// (--write-tree 需 Git 2.38+,为兼容旧 git 不采用)
|
||||||
// 成功(exit 0)= 无冲突可自动合并;非0且有输出 = 冲突
|
// 第 1 参必须是 merge-base:此前误传 HEAD,branch2(HEAD) 相对 base 零差异,
|
||||||
let base = "HEAD";
|
// trivial merge 恒判无冲突(实测真冲突场景输出为空 exit 0),预检形同虚设
|
||||||
let precheck = Command::new("git")
|
let mb = Command::new("git")
|
||||||
.args(["merge-tree", base, subtask_branch, base])
|
.args(["merge-base", "HEAD", subtask_branch])
|
||||||
.current_dir(plan_wt_path)
|
.current_dir(plan_wt_path)
|
||||||
.output()
|
.output()
|
||||||
.map_err(|e| WorktreeError::GitFailed(format!("git merge-tree 失败: {}", e)))?;
|
.map_err(|e| WorktreeError::GitFailed(format!("git merge-base 失败: {}", e)))?;
|
||||||
|
|
||||||
// merge-tree 输出含 conflict marker(<<<<<<<)时表示有冲突
|
// 旧式形态冲突时 exit 仍为 0,只能看 stdout 判定;只认 <<<<<<< 标记,
|
||||||
let precheck_stdout = String::from_utf8_lossy(&precheck.stdout);
|
// 不查 =======:干净合并的 diff 上下文会带出文件内容,文档里的等号分隔行会误撞
|
||||||
if precheck_stdout.contains("<<<<<<<") || precheck_stdout.contains("=======") {
|
if mb.status.success() {
|
||||||
return Ok(MergeResult::Conflict {
|
let merge_base = String::from_utf8_lossy(&mb.stdout).trim().to_string();
|
||||||
branch: subtask_branch.to_string(),
|
let precheck = Command::new("git")
|
||||||
details: precheck_stdout.to_string(),
|
.args(["merge-tree", merge_base.as_str(), "HEAD", subtask_branch])
|
||||||
});
|
.current_dir(plan_wt_path)
|
||||||
|
.output()
|
||||||
|
.map_err(|e| WorktreeError::GitFailed(format!("git merge-tree 失败: {}", e)))?;
|
||||||
|
|
||||||
|
let precheck_stdout = String::from_utf8_lossy(&precheck.stdout);
|
||||||
|
if precheck_stdout.contains("<<<<<<<") {
|
||||||
|
return Ok(MergeResult::Conflict {
|
||||||
|
branch: subtask_branch.to_string(),
|
||||||
|
details: precheck_stdout.to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// merge-base 失败 = 两分支无共同祖先,无法预检,交由下方实际 merge 裁决
|
||||||
|
|
||||||
// 无冲突,执行实际 merge
|
// 无冲突,执行实际 merge
|
||||||
let merge = Command::new("git")
|
let merge = Command::new("git")
|
||||||
@@ -539,4 +550,91 @@ mod tests {
|
|||||||
// plan wt 目录也应被清理
|
// plan wt 目录也应被清理
|
||||||
assert!(!plan_wt.0.exists(), "plan worktree 目录应已清理");
|
assert!(!plan_wt.0.exists(), "plan worktree 目录应已清理");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn wt_08_precheck_hits_conflict_before_real_merge() {
|
||||||
|
let repo = setup_repo();
|
||||||
|
let root = repo.path();
|
||||||
|
|
||||||
|
std::fs::write(root.join("shared.rs"), "line1\nline2\n").unwrap();
|
||||||
|
Command::new("git").args(["add", "-A"]).current_dir(root).output().unwrap();
|
||||||
|
Command::new("git").args(["commit", "-m", "add shared"]).current_dir(root).output().unwrap();
|
||||||
|
|
||||||
|
let plan_wt = create_plan_worktree(root, "plan-prechk").unwrap();
|
||||||
|
let plan_wt_path = plan_wt.0.clone();
|
||||||
|
let plan_branch = plan_wt.1;
|
||||||
|
|
||||||
|
// A 和 B 改同一文件同一行
|
||||||
|
let a = create_worktree(root, "plan-prechk", "A", &plan_branch).unwrap();
|
||||||
|
std::fs::write(a.path.join("shared.rs"), "A_version\n").unwrap();
|
||||||
|
commit_worktree(&a.path, "A change shared").unwrap();
|
||||||
|
|
||||||
|
let b = create_worktree(root, "plan-prechk", "B", &plan_branch).unwrap();
|
||||||
|
std::fs::write(b.path.join("shared.rs"), "B_version\n").unwrap();
|
||||||
|
commit_worktree(&b.path, "B change shared").unwrap();
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
merge_branch(&plan_wt_path, &a.branch).unwrap(),
|
||||||
|
MergeResult::Clean
|
||||||
|
));
|
||||||
|
|
||||||
|
// 冲突必须由 merge-tree 预检命中(details 含标记),而非实际 merge 失败兜底
|
||||||
|
// (兜底 details 来自 merge stderr,只有 CONFLICT 文案无标记)
|
||||||
|
match merge_branch(&plan_wt_path, &b.branch).unwrap() {
|
||||||
|
MergeResult::Conflict { details, .. } => {
|
||||||
|
assert!(
|
||||||
|
details.contains("<<<<<<<"),
|
||||||
|
"冲突详情应含 merge-tree 预检输出的标记,而非 merge 兜底文案: {}",
|
||||||
|
details
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => panic!("同文件同行改应冲突"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 预检命中即返回,不应留下合并中状态
|
||||||
|
let merging = Command::new("git")
|
||||||
|
.args(["rev-parse", "-q", "--verify", "MERGE_HEAD"])
|
||||||
|
.current_dir(&plan_wt_path)
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
assert!(!merging.status.success(), "预检命中不应留下 MERGE_HEAD");
|
||||||
|
|
||||||
|
remove_plan_worktrees(root, "plan-prechk").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn wt_09_clean_merge_with_eq_marker_content() {
|
||||||
|
let repo = setup_repo();
|
||||||
|
let root = repo.path();
|
||||||
|
|
||||||
|
// 文件内容含等号分隔行(md 标题下划线常见),A/B 隔行修改,实际可干净合并
|
||||||
|
std::fs::write(root.join("doc.md"), "t0\n=======\nl3\nl4\nl5\n").unwrap();
|
||||||
|
Command::new("git").args(["add", "-A"]).current_dir(root).output().unwrap();
|
||||||
|
Command::new("git").args(["commit", "-m", "add doc"]).current_dir(root).output().unwrap();
|
||||||
|
|
||||||
|
let plan_wt = create_plan_worktree(root, "plan-eqmark").unwrap();
|
||||||
|
let plan_wt_path = plan_wt.0.clone();
|
||||||
|
let plan_branch = plan_wt.1;
|
||||||
|
|
||||||
|
let a = create_worktree(root, "plan-eqmark", "A", &plan_branch).unwrap();
|
||||||
|
std::fs::write(a.path.join("doc.md"), "A0\n=======\nl3\nl4\nl5\n").unwrap();
|
||||||
|
commit_worktree(&a.path, "A chg line1").unwrap();
|
||||||
|
|
||||||
|
let b = create_worktree(root, "plan-eqmark", "B", &plan_branch).unwrap();
|
||||||
|
std::fs::write(b.path.join("doc.md"), "t0\n=======\nl3\nl4\nB5\n").unwrap();
|
||||||
|
commit_worktree(&b.path, "B chg line5").unwrap();
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
merge_branch(&plan_wt_path, &a.branch).unwrap(),
|
||||||
|
MergeResult::Clean
|
||||||
|
));
|
||||||
|
|
||||||
|
// B 隔行修改应 Clean:预检不得因 diff 上下文中的等号行误判冲突
|
||||||
|
assert!(matches!(
|
||||||
|
merge_branch(&plan_wt_path, &b.branch).unwrap(),
|
||||||
|
MergeResult::Clean
|
||||||
|
));
|
||||||
|
|
||||||
|
remove_plan_worktrees(root, "plan-eqmark").unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user