变基与解冲突
目标
通过哈希确认 rebase 不是移动 commit,而是重写 commit;亲手完成冲突解决、中止,以及交互式 rebase 的 squash/drop。产物放在 /root/gitx3/ 下。
为什么重要
commit 对象中包含父 commit 哈希。父节点变化,commit 哈希必然变化,其余结论都源自这一点。如果其他人已在该 commit 上继续工作,重写会破坏对方历史。因此黄金法则的准确说法不是“推送后不要做”,而是“不要重写已有他人工作建立其上的 commit”。冲突反复出现也来自同一结构:merge 只比较三个点,而 rebase 会逐个重新应用 commit,每次都执行三方合并,所以rebase 不是一次合并,而是 N 次合并。
步骤
**没有全局 git 身份。**每个新仓库都要配置本地 user.email / user.name。
- 在
/root/gitx3/repo创建仓库。在main上提交 3 个 commit,从那里创建topic分支并添加 2 个 commit;再回到main添加 1 个 commit,形成分叉状态(topic总 commit 数至少 4)。在/root/gitx3/diverged.txt写三行:main_ahead=1、topic_ahead=2、merge_base=<두 브랜치의 공통 조상 해시>。 - 将
topicrebase 到main上。rebase 之前记录topic尾部哈希,并在/root/gitx3/rebased.txt写两行:before=<리베이스 전 해시>、after=<리베이스 후 해시>。两值必须不同,after与当前topic尾部一致,且不得残留进行中的 rebase。 - 在
main上以快进方式合并topic。结果要求:main的 merge commit 为 0 个,main的总 commit 至少 6 个,main与topic指向同一 commit。 - 在
/root/gitx3/conflict创建新仓库。main中放置shared.txt;在conflict-topic分支修改该文件并提交,使其包含from-topic;随后在main同一位置修改并提交,使其包含from-main。将conflict-topicrebase 到main会产生冲突,请保留双方变更。完成条件:rebase 已结束;索引无未解决路径;文件中无<<<<<<<等冲突标记;shared.txt同时含from-main和from-topic;工作树干净。 - 在
/root/gitx3/abort创建新仓库,让risky分支和main分别以不同方式修改同一文件并提交。rebase 开始前将risky尾部哈希写入/root/gitx3/abort-before.txt(仅哈希字符串)。启动 rebase,遇到冲突后中止。完成条件:无进行中的 rebase;risky尾部与记录哈希完全一致;reflog 中有 rebase 记录。 - 在
/root/gitx3/squash创建新仓库,在feature/three分支堆叠 3 个 commit(分别添加s1.txt、s2.txt、s3.txt)。使用交互式 rebase 将3 个合为 1 个。完成条件:main..feature/three只有 1 个 commit;标题包含feat: combined;s1.txt/s2.txt/s3.txt三个文件都存在。 - 在
/root/gitx3/drop创建新仓库,在feature/four分支堆叠 4 个 commit:d1.txt、d2.txt、一个标题为debug: temp log且添加debug.txt的 commit,最后是d3.txt。使用交互式 rebase 只丢弃调试 commit。完成条件:main..feature/four有 3 个 commit;没有debug: temp logcommit;没有debug.txt;d1/d2/d3.txt均存在。 - 编写
/root/gitx3/rebase.md。必须包含:对--force-with-lease的说明及其与--force的区别;何时不能 rebase(包含共享、推送、其他人中任一表述);为什么哈希变化(包含哈希、重写中任一表述);并在merge_commits_on_main=行实际统计并写入/root/gitx3/repo的mainmerge commit 数量。
参考
- 统计领先数量:
git rev-list --count topic..main(main 领先数)、git rev-list --count main..topic(topic 领先数);共同祖先用git merge-base main topic。 - 无编辑器环境中的交互式 rebase:列表可用
GIT_SEQUENCE_EDITOR='sed -i "2,3s/^pick/fixup/"' git rebase -i main,commit 消息编辑可用GIT_EDITOR=true绕过。也可用 fixup 合并后运行git commit --amend -m "feat: combined"定标题。 - drop 可把相应行的
pick改为drop,或删除该行。 - 冲突解决后执行
git add <파일>,再执行git rebase --continue。若打开编辑器,在前面加GIT_EDITOR=true。 - 常见错误 1:用
--ours或--theirs只保留一方,导致另一方工作悄然消失。而且 rebase 期间被重放的 commit 属于 theirs,语义会与直觉相反。 - 常见错误 2:第 2 步先 rebase,之后才找
before哈希。rebase 后只能查git reflog,所以开始前先保存git rev-parse topic。
创建两个分叉分支
在 /root/gitx3/repo 创建仓库。在 main 上提交 3 个 commit,从那里创建 topic 分支并添加 2 个 commit;再回到 main 添加 1 个 commit,形成分叉状态(topic 总 commit 数至少 4)。在 /root/gitx3/diverged.txt 写三行:main_ahead=1、topic_ahead=2、merge_base=<두 브랜치의 공통 조상 해시>。
在 /root/gitx3/repo 中让 main 和 topic 各自拥有不同 commit。分支后两边都必须有 commit 才算分叉。可用 git rev-list --count A..B 统计领先数。
把 topic 重写到 main 上
将 topic rebase 到 main 上。rebase 之前记录 topic 尾部哈希,并在 /root/gitx3/rebased.txt 写两行:before=<리베이스 전 해시>、after=<리베이스 후 해시>。两值必须不同,after 与当前 topic 尾部一致,且不得残留进行中的 rebase。
必须在 rebase 前记录 topic 尾部哈希才能比较。完成后共同祖先变为 main 尾部;由于父节点改变,哈希也会改变。
通过快进形成线性历史
在 main 上以快进方式合并 topic。结果要求:main 的 merge commit 为 0 个,main 的总 commit 至少 6 个,main 与 topic 指向同一 commit。
rebase 后 main 是 topic 的祖先,因此无需 merge commit 即可合并。使用 --ff-only 可在条件不满足时拒绝操作,更安全。
保留双方变更解决冲突
在 /root/gitx3/conflict 创建新仓库。main 中放置 shared.txt;在 conflict-topic 分支修改该文件并提交,使其包含 from-topic;随后在 main 同一位置修改并提交,使其包含 from-main。将 conflict-topic rebase 到 main 会产生冲突,请保留双方变更。完成条件:rebase 已结束;索引无未解决路径;文件中无 <<<<<<< 等冲突标记;shared.txt 同时含 from-main 和 from-topic;工作树干净。
在 /root/gitx3/conflict 中让双方修改同一文件同一位置,再 rebase。解决时不是选择一方,而是保留双方变更,并删除冲突标记。
中止 rebase 并恢复原状
在 /root/gitx3/abort 创建新仓库,让 risky 分支和 main 分别以不同方式修改同一文件并提交。rebase 开始前将 risky 尾部哈希写入 /root/gitx3/abort-before.txt(仅哈希字符串)。启动 rebase,遇到冲突后中止。完成条件:无进行中的 rebase;risky 尾部与记录哈希完全一致;reflog 中有 rebase 记录。
在 /root/gitx3/abort 中先把分支尾部哈希写入文件,再启动会冲突的 rebase 并中止。中止会精确恢复到开始前状态。
把 3 个 commit 合为 1 个
在 /root/gitx3/squash 创建新仓库,在 feature/three 分支堆叠 3 个 commit(分别添加 s1.txt、s2.txt、s3.txt)。使用交互式 rebase 将3 个合为 1 个。完成条件:main..feature/three 只有 1 个 commit;标题包含 feat: combined;s1.txt/s2.txt/s3.txt 三个文件都存在。
在 /root/gitx3/squash 中以 main 为基准交互式 rebase feature/three。合并 commit 不应丢失修改,三个文件都必须保留。若难以打开编辑器,可用 GIT_SEQUENCE_EDITOR 修改列表。
丢弃一个 commit
在 /root/gitx3/drop 创建新仓库,在 feature/four 分支堆叠 4 个 commit:d1.txt、d2.txt、一个标题为 debug: temp log 且添加 debug.txt 的 commit,最后是 d3.txt。使用交互式 rebase 只丢弃调试 commit。完成条件:main..feature/four 有 3 个 commit;没有 debug: temp log commit;没有 debug.txt;d1/d2/d3.txt 均存在。
在 /root/gitx3/drop 的 feature/four 中只删除调试 commit。drop commit 时,该 commit 添加的文件也会消失,其余三个 commit 的文件必须保留。
总结黄金法则
编写 /root/gitx3/rebase.md。必须包含:对 --force-with-lease 的说明及其与 --force 的区别;何时不能 rebase(包含共享、推送、其他人中任一表述);为什么哈希变化(包含哈希、重写中任一表述);并在 merge_commits_on_main= 行实际统计并写入 /root/gitx3/repo 的 main merge commit 数量。
在 /root/gitx3/rebase.md 中写明 --force-with-lease 与 --force 的区别、何时不应 rebase、哈希为何变化,并在 merge_commits_on_main= 行实际统计第 3 步结果。