Git —— merge, cherry-pick, rebase

本文深入探讨了Git中的合并(merge)、挑选(cherry-pick)及重新应用(rebase)操作,并通过实际案例展示了这些操作在冲突解决过程中的应用,以及它们之间的区别。

1. merge explore

  You may read my article Git inside before. I hope it’ll help you to know Git in essence.

  (1) test case 1: merge without conflict

~ $ mkdir beta
~ $ cd beta
~ $ git init
~ $ printf '1' > number.txt
~ $ printf 'a' > letter.txt
~ $ git add number.txt letter.txt
~ $ git commit -m 'a1'
~ $ git checkout -b dev
...... 
make some commit in dev branch
......
~ $ printf 'c' > letter.txt
~ $ git add letter.txt
~ $ git commit -m 'c1'
// switch back to master branch
~ $ git checkout master
......
make some commit in master commit
......
~ $ printf '4' > number.txt
~ $ git add number.txt
~ $ git commit -m 'a4'
// switch to dev branch
~ $ git merge master

  In branch master, we changed a file content (number.txt) .
  In branch dev , we changed another different file (letter.txt).
  Then after some commits, we do git merge master. dev as receiver, master as giver. The GIt get a blob object (bf) from master HEAD commit, and get another blob object (34) from dev HEAD commit, and merge a new commit without conflict.
  Of course, you can add more commit in each branch as you want. And you also can change both file in any commit. But at this time, you should make sure the last commit version will not change both file. Because Git just compare three version commit : the common parent (base), master HEAD commit, dev HEAD commit. And take the change blob objects. So if you change a file in both branch, then Git confuse what to pick, thus conflict, ask you for help. As show in Fig 1.


这里写图片描述
Fig 1. Merge without conflict

  (2) test case 2 : merge conflict

~ $ git checkout dev
~ $ printf 'd' > letter.txt
~ $ git add letter.txt
~ $ git commmit -m 'd4'
// checkout master branch, modify letter.txt also
~ $ git checkout master
~ $ printf 'e' > letter.txt
~ $ git add letter.txt
~ $ git commit -m 'e4'
~ $ git checkout dev
~ $ git merge master // merge conflict in letter.txt
// resolve conflict
~ $ printf 'de' > letter.txt
~ $ git add letter.txt
~ $ git commit -m 'de4'

  when merge conflict, the index looks like this.

100644 2e65efe2a145dda7ee51d1741299f848e5bf752e 1   letter.txt
100644 c59d9b6344f1af00e504ba698129f07a34bbed8d 2   letter.txt
100644 9cbe6ea56f225388ae614c419249bfc6d734cc30 3   letter.txt
100644 bf0d87ab1b2b0ec1a11a3973d2845b42413d9767 0   number.txt
  • The entry for number.txt at stage 0 is the same as it was before the merge. (no conflict)
  • The entry for stage 1 has the hash of the base letter.txt content.
  • The entry for stage 2 has the hash of the receiver (dev) letter.txt content.
  • The entry for stage 3 has the hash of the giver (master) letter.txt content.
  • The presence of these three entries tells Git that letter.txt is in conflict.

  we modify file letter.txt content in both branch. so it make conflict. we resolve conflict by print ‘de’ content to letter.txt. of course, you can resolve conflict as you will. show in Fig 2.


这里写图片描述
Fig 2. Merge conflict

2. cherry-pick explore

  I’ve already mentioned (back on the page about Garbage Collection) that a Git commit’s ID is a hash of both its contents and its history. So, even if you have two commits that introduce the exact same change, if they point to different parent commits, they’ll have different IDs.
  What git cherry-pick does, basically, is take a commit from somewhere else, and “play it back” wherever you are right now. Because this introduces the same change with a different parent, Git builds a new commit with a different ID. [3] [ 3 ]


  As noted above, both for a cherry-pick and for a rebase, BASE is the parent (C^) of the the commit C being pulled in. In the general case C^ isn’t a common ancestor, so why call it BASE? (In a normal merge BASE is a common ancestor. And part of git’s successes in merging are due to its ability to find a good common ancestor.)
  Essentially, one does this as a way to implement “patch” functionality via the normal three-way merge algorithm. In particular you get these “patchy” properties: [4] [ 4 ]

(1) test case 1 : cherry-pick without conflict

~ $ mkdir testcherry
~ $ git init
~ $ printf '0' > number.txt
~ $ git add number.txt
~ $ git commit -m '0'
~ $ printf '1' > number.txt
..... do some commit 
~ $ git checkout 487a6b3 -b dev //from commit 0 checkout dev
~ $ printf '2' > number.txt
~ $ git add number.txt
~ $ git commit -m 'dev-2'
~ $ printf '3' > number.txt
~ $ git add number.txt
~ $ git commit -m 'dev-3'

  if we do git merge master, obviously, it’ll cause merge conflict. the index looks like this.

100644 c227083464fb9af8955c90d2924774ee50abb547 1   number.txt
100644 e440e5c842586965a7fb77deda2eca68612b1f53 2   number.txt
100644 d8263ee9860594d2806b0dfd1bfd17528b0ba2a4 3   number.txt

   we do git cherry hash

~ $ git checkout master 
~ $ git cherry-pick af052b8

  As it show in Fig 3. after cherry-pick, get a new commit (5f). unlike merge, the BASE is different, and new commit has only one parent (83).


这里写图片描述
Fig 3. Git cherry without conflict

(2) test case 2 : cherry-pick conflict

~ $ checkout dev
~ $ printf '4' > number.txt
~ $ git add number.txt
~ $ git commit -m 'dev-4'
~ $ git checkout master
~ $ git cherry-pick a09c286  // dev-4

  because conflict, so cherry-pick stopped with error: could not apply a09c286… dev-4. the index looks like this.

100644 e440e5c842586965a7fb77deda2eca68612b1f53 1   number.txt
100644 d8263ee9860594d2806b0dfd1bfd17528b0ba2a4 2   number.txt
100644 bf0d87ab1b2b0ec1a11a3973d2845b42413d9767 3   number.txt

  we simply do printf '24' > number.txt; git add number.txt; git commit -m '24' to resolve conflict. As show in Fig 4.


这里写图片描述
FIg 4. cherry-pick conflict

3. rebase explore

  With the rebase command, you can take all the changes that were committed on one branch and replay them on another one.
  It works by going to the common ancestor of the two branches (the one you’re on and the one you’re rebasing onto), getting the diff introduced by each commit of the branch you’re on, saving those diffs to temporary files, resetting the current branch to the same commit as the branch you are rebasing onto, and finally applying each change in turn. [5] [ 5 ]

  in above Fig 4, if we do git rebase master instead cherry-pick, then we got the commit graph similar to Fig 5.


这里写图片描述
Fig 5. git rebase

  we can use git cherry-pick to simulate git rebase. first, cherry-pick c0, BASE is 48, no change. second, cherry-pick af, BASE is c0, apply change, last, cherry-pick a0, BASE is af, apply change.

4. summary

  • (a) Perhaps merge is better than cherry-pick or rebase. (refs:no-cherry-picking  argue-for-merge)
  • (b) cherry-pick BASE is the parent of cherrying id, not common ancestor.
  • (c) git compare the receiver to BASE, compare the giver to BASE, and then decide what to be changed.

Reference

[1] merge_vs_rebase_why_not_cherrypick
[2] git-cherry-pick-lvs-rebase
[3] think-like-a-git
[4] what-is-cherry-pick-ancestor
[5] Git-branching-rebasing

<think> 我们有两个独立的问题需要解决: 1. git rebase提示已有rebase-merge目录 2. git cherry-pick提示前一次操作空提交 下面分别给出解决方案。 问题1:git rebase提示已有rebase-merge目录 根据之前的讨论,我们已知这是因为存在未完成的rebase操作。解决方案如下: 步骤: 1. 确认当前状态:运行`git status`查看是否有未完成的rebase。 2. 根据提示,我们可以选择继续、跳过或中止rebase- 如果希望继续完成之前的rebase,使用:`git rebase --continue` - 如果希望跳过导致冲突的提交,使用:`git rebase --skip` - 如果希望完全放弃之前的rebase,回到rebase之前的状态,使用:`git rebase --abort` 3. 如果上述命令不起作用(例如因为状态已经损坏),我们可以手动删除rebase的残留目录: 在Unix/Linux/Mac系统下: rm -rf .git/rebase-merge 在Windows系统下(在Git Bash或命令提示符中): rmdir /s /q .git\rebase-merge 或者使用资源管理器删除该目录。 注意:手动删除目录前,请确保你不需要之前rebase的任何中间状态(因为可能会丢失进度)。 问题2:git cherry-pick提示前一次操作空提交 当我们执行`git cherry-pick`时,如果选择的提交是一个空提交(即该提交没有引入任何代码变更),则会遇到此问题。空提交通常是由于以下原因产生: - 两个分支已经同步了该提交的变更(通过合并或其他方式) - 提交本身确实没有修改任何文件(例如只修改了注释然后又撤销了) 解决方案: 1. 检查该提交是否为空:使用`git show <commit-id>`查看该提交的变更。如果输出中没有diff内容,说明是空提交。 2. 如果确认是空提交,我们可以选择跳过该提交: git cherry-pick --skip 3. 或者,如果我们正在执行一连串的cherry-pick,我们可以使用`--keep-redundant-commits`选项(但这通常不用于空提交): git cherry-pick --keep-redundant-commits <commit-id> 但注意:空提交会被应用,但不会改变工作目录。 4. 如果我们不想应用空提交,可以在cherry-pick命令中排除它。 另外,如果我们遇到cherry-pick过程中有未解决的空提交而导致状态停留在中间,我们可以: - 继续:`git cherry-pick --continue`(如果我们解决了冲突并添加了文件,但这里空提交通常不需要解决冲突) - 中止:`git cherry-pick --abort`(放弃整个cherry-pick操作序列) 注意:cherry-pick操作也会在.git目录下留下状态(在rebase-apply或sequencer目录中),如果操作中断,我们可能需要清理(类似于rebase的清理)或使用`--abort`。 结合上述两个问题,我们给出系统化的解决方案: 步骤总结: 一、解决rebase-merge目录问题(问题1): 1. 尝试使用`git rebase --abort`来终止之前的rebase操作(推荐首选)。 2. 如果失败,再手动删除`.git/rebase-merge`目录。 二、解决cherry-pick空提交问题(问题2): 1. 使用`git cherry-pick --skip`跳过当前空提交。 2. 或者,如果整个cherry-pick操作序列需要中止,则使用`git cherry-pick --abort`。 注意:在解决第二个问题之前,确保第一个问题已经解决,即没有残留的rebase操作目录。 下面给出具体操作命令: 步骤1:清除rebase残留状态 在命令行中执行以下命令之一: git rebase --abort # 尝试中止rebase 或者(如果上一步无效) rm -rf .git/rebase-merge # Linux/Mac/Windows Git Bash 或者(Windows命令提示符) rmdir /s /q .git\rebase-merge 步骤2:处理cherry-pick空提交 执行: git cherry-pick --skip # 跳过当前空提交 或者,如果我们不想继续当前的cherry-pick操作序列: git cherry-pick --abort # 中止整个cherry-pick序列 预防措施: - 在执行rebasecherry-pick之前,使用`git status`检查当前状态,确保没有未完成的操作。 -cherry-pick之前,使用`git log <commit-id> -p`检查要挑选的提交是否为空提交,避免将其加入操作序列。 引用说明: - 关于rebasecherry-pick的基本操作和区别,请参考引用[1][^1]和引用[4][^4]。 - 关于cherry-pick的应用场景,引用[3][^3]指出它适合选择性引入特定提交,而不会重写历史或引入合并节点。 生成相关问题: §§相关问题§§ 1. 如何避免在Git操作中出现残留状态目录? 2. 在什么情况下会产生空提交?如何避免? 3. 如何在执行`git rebase`或`git cherry-pick`之前检查提交是否为空? 4. 如何处理在`git cherry-pick`多个提交时遇到空提交的情况? </think> ### 🔧 解决 Git 操作中的两大常见错误 #### ❌ 错误一:Git Rebase 提示存在 rebase-merge 目录 **原因分析** 当 Git 检测到 `.git/rebase-merge` 或 `.git/rebase-apply` 目录时,表明上次 rebase 操作未完成或被中断[^2]。这通常由: 1. 强制关闭终端或 IDE 2. 系统崩溃/断电 3. 手动终止 rebase 进程 4. 冲突未解决导致操作停滞 **解决方案** ```bash # 1. 尝试官方终止指令(首选) git rebase --abort # 2. 若失效则手动清理残留 rm -rf .git/rebase-* # Linux/Mac rd /s /q .git\rebase-* # Windows CMD ``` #### ❌ 错误二:Git Cherry-pick 提示空提交 **原因分析** 当 cherry-pick 的提交满足以下条件时会报空提交错误: 1. 提交内容已被当前分支包含 2. 提交仅修改已忽略的文件 3. 提交本身无实质性变更(如仅空白字符修改) 4. Cherry-pick 序列中存在冗余提交[^3] **解决方案** ```bash # 1. 验证提交是否真正为空 git show <commit-hash> # 2. 跳过特定空提交 git cherry-pick --skip # 3. 强制保留空提交(特殊场景) git cherry-pick --keep-redundant-commits <commit-hash> # 4. 中止整个操作序列 git cherry-pick --abort ``` ### ️ 联合操作的完整流程 当两个错误同时出现时,建议按顺序处理: ```mermaid sequenceDiagram 用户->>+Git: 发起 cherry-pick/rebase Git-->>-用户: 报错(空提交/rebase残留) 用户->>+Gitgit cherry-pick --abort 用户->>+Gitgit rebase --abort 用户->>+Git: rm -rf .git/rebase-* 用户->>+Gitgit show <commit> 验证提交 用户->>+Git: 重新执行目标操作 Git-->>-用户: 操作成功 ``` ### 🔒 预防措施 1. **操作前检查状态** ```bash git status # 确认无未完成操作 git log --oneline origin/main..HEAD # 检查待提交内容 ``` 2. **使用事务型操作** ```bash # 使用 -n 预演 cherry-pick git cherry-pick -n <commit> && git diff --cached ``` 3. **配置自动清理** ```bash # 设置 30 天自动清理残留 git config --global gc.reflogexpire '30 days' ``` > **技术原理**:Git 通过 `.git/rebase-*` 目录保存操作状态,其存在表明状态不一致: > $$ \text{ValidState} = \begin{cases} > 0 & \text{if } \exists\, \text{.git/rebase-*} \\ > 1 & \text{otherwise} > \end{cases} $$ ### 📚 补充说明 - Rebase 通过重建提交历史实现线性化,适合分支更新[^4] - Cherry-pick 精准提取特定提交,避免合并冗余变更[^3] - 两者操作中断时都会生成状态目录,需显式清理[^2]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值