Community编程与开发github.com

Git 冲突解决

正确解决 Git 合并、变基、拣选、还原或贮藏弹出冲突 - 理解每一方的意图,合并它们而非选择其一,检查结果能构建,并继续操作。

Git 冲突解决 是什么?

这个技能专门用于正确解决 Git 冲突,无论是合并、变基、拣选(cherry-pick)、还原(revert)还是贮藏弹出(stash pop)操作。它解释了 `ours` 和 `theirs` 在不同操作中的含义变化,尤其是在变基时,`ours` 实际上是上游分支,而 `theirs` 是被重放的用户自己的提交,这与合并时相反。该技能提供了详细的步骤:了解当前操作类型、查看完整冲突(包括共同祖先)、通过理解双方意图来合并而非机械选择、验证结果(检查冲突标记、构建并测试)以及继续操作。它还提供了如何应对重复冲突(使用 `rerere`)、何时应停下来询问用户的指导,以及关键的安全护栏(如绝不单方面丢弃一方、不盲目推送等)。支持所有主流的 Git 冲突场景。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/roselletabuena/skills/tree/HEAD/plugins/git-workflows/skills/git-conflict-resolution

Installed? Explore more 编程与开发 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

解决冲突

冲突意味着 Git 发现同一个地方有两个更改,并且拒绝猜测。解决冲突是一项关于意图的决定,安全的默认做法是保留双方的意图。通过删除一方来解决冲突只会让标记消失,但问题变得更糟:构建仍然通过,而同事的更改却消失了。

1. 了解你处于哪个操作中 - ours 和 theirs 取决于它

git status                  # "rebase in progress" / "You have unmerged paths" / "cherry-pick in progress"
操作--ours / HEAD 方--theirs 方
merge你当前所在的分支被合并进来的分支
rebase你变基到的分支(上游)你正在被重放的提交
cherry-pick,revert,stash pop你当前所在的分支正在被应用的提交

变基行是一个陷阱。在执行 git rebase main 时,“ours”是 main,而“theirs”是用户自己的工作,这与合并相反。在变基中运行 git checkout --theirs file 会保留你的更改并丢弃上游的更改。在使用这两个标志之前,请重新阅读此表。

2. 查看整个冲突,而不仅仅是标记

git diff --name-only --diff-filter=U        # 未合并的文件
git status                                   # 类型:both modified / deleted by us / both added
git config merge.conflictStyle zdiff3        # 也显示共同祖先(Git 2.35+;旧版本使用 diff3)
git checkout --conflict=zdiff3 -- <file>     # 重新显示一个文件的冲突,带基版本(会丢弃已对该文件所做的编辑)

当基版本可见时,标记有三个部分:你的方、原始版本、他们的方。当你能看到每一方从什么更改而来时,大多数表面上的冲突就会变得显而易见。

要了解每一方更改的原因:

git log --merge -p -- <file>                 # 来自双方修改该文件的提交(合并或变基进行中)
git show REBASE_HEAD                          # 在变基期间:正在被重放的提交
git show :1:<file>  :2:<file>  :3:<file>     # 基版本、"ours"、"theirs" 作为完整文件

阅读提交信息。一个你无法用一句话解释每一方意图的解决方案就是猜测。

3. 通过合并来解决

  • 双方因不同原因编辑了相同的行 - 编写同时执行两者的版本。例如,一方面添加了 tax_rate 参数,另一方面添加了 discount 参数:已解决的函数同时接受两者并应用两者。
  • 一方移动或重命名了,另一方编辑了 - 在当前位置应用编辑。不要同时保留旧文件和新文件。
  • 一方删除了,另一方修改了 - 这是一个真正的决定,而不是机械的决定。找出被删除的原因;如果修改仍然重要,必须保留,否则删除占上风。如果不清楚,询问用户。
  • 生成文件和锁文件(package-lock.json、poetry.lock、go.sum、快照、编译输出)- 不要手动合并。选择一方,然后用工具重新生成(npm install、poetry lock、go mod tidy、快照命令)。
  • 二进制文件 - 无法合并;有目的地选择一方(注意上表)并说明。

文本可以干净地合并但仍然错误:如果一方重命名了一个函数,而另一方添加了对旧名称的调用,Git 不会检测到冲突。这就是为什么第 4 步包含构建。

全局标志隐藏决定。 git checkout --ours .、-X ours / -X theirs(将每个冲突区块朝一方解决)和 -s ours(完全丢弃另一个分支)都会在没有检查的情况下丢弃工作。仅当用户明确要求这些操作并知道正在丢弃什么时才使用它们。

4. 验证,然后继续

git diff --check                              # 检查残留冲突标记和空白错误
grep -rnE '^(<<<<<<<|=======|>>>>>>>)' .      # 双重检查,在 git diff-check 不检查的文件中
# 构建并运行最快的相关测试
git add <已解决的文件>

使用匹配的命令继续。添加 GIT_EDITOR=true 以便在编辑器会打开的地方使用,因为编辑器会在非交互式 shell 中挂起:

git merge --continue            # 或者:git commit --no-edit
GIT_EDITOR=true git rebase --continue
GIT_EDITOR=true git cherry-pick --continue

变基会逐个重放提交,因此它可能会在下一个提交处再次停止。重复此循环。在最早出现冲突的提交处解决;后面的提交可能是同一个冲突的重放。

如果在解决后测试失败,失败信息是关于解决结果的信息。不要越过它继续,也不要在知道测试保护的是哪一方的行为之前通过编辑测试来“修复”它。

5. 何时停止并询问

在以下情况下停止而非猜测:

  • 双方以不兼容的方式更改了业务逻辑(两种不同的税收规则,两种不同的重试策略);
  • 你无法用一句话说出每一方做出更改的原因;
  • 解决方案导致测试失败且原因不明确。

说明双方的意图,说出你会选择什么以及原因,然后让用户决定。在 --continue 之前的所有操作都是可逆的:git merge --abort、git rebase --abort、git cherry-pick --abort 会将树恢复到起始状态。

重复冲突

对于同一冲突反复出现的长时间变基,或反复合并和放弃的分支:

git config rerere.enabled true      # 记录每个解决方案,下次重放
git rerere diff                      # 它记录了什么
git rerere forget-path <file>        # 丢弃一个错误的已记录解决方案

rerere 会不询问地重新应用解决方案,因此错误的解决方案会传播。在 rerere 解决的冲突之后,仍然要检查结果。

当分支寿命很长且远远落后时,一次性合并主干通常意味着一次冲突回合;变基会按提交重放冲突。这个选择由用户决定。

安全护栏

  • 永远不要通过单方面选择一方来让冲突消失。 另一方是某人的工作。
  • 不要仅仅因为无聊就中止用户要求你完成的变基。 只有当出现错误时才中止,并说明原因。
  • 除非被要求,否则不要在解决后推送。 变基需要强制推送;请遵循 git-safe-history-rewrite。
  • 报告每个冲突文件:双方做了什么,你保留了什么,以及任何你无法验证的内容。

Individual skills in this repo

This repo contains 2 individual skills — each has its own dedicated page.

相关技能