Git知识rebase, merge, cherry-pick

本文详细解析了Git中Rebase与Merge的区别及应用场景,通过对比两种方式产生的历史记录,帮助开发者理解何时应选择Rebase,何时更适合使用Merge。同时,文章提供了具体的实例来说明这两种操作可能带来的不同结果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

如何优雅地使用 Git - 掘金
https://juejin.im/post/5a54386af265da3e3b7a6317

如何和产品妹子解释什么是变基
https://mp.weixin.qq.com/s?__biz=MzA3ODUxMjMyMw==&mid=2649753261&idx=1&sn=091ab93c0e15f02c7d8d45cf634a8820#rd

你会在 WebIDE 里变基么?
https://mp.weixin.qq.com/s?__biz=MzA3ODUxMjMyMw==&mid=2649753249&idx=1&sn=0e60ecb1d128696e6323552b082471ef&scene=21#wechat_redirect

git rebase vs git merge详解 - 世有因果知因索果 - 博客园
https://www.cnblogs.com/kidsitcn/p/5339382.html

git merge 和 git rebase 小结 - 优快云博客
http://blog.youkuaiyun.com/wh_19910525/article/details/7554489

在开发过程中使用git rebase还是git merge

https://www.zhihu.com/question/36509119

Git Rebase原理以及黄金准则详解 - 某熊的全栈之路 - SegmentFault 思否
https://segmentfault.com/a/1190000005937408

Elpie Kay

不管用什么风格的操作流程来整合你的本地分支,最终,这条分支是要和服务器上的目标分支做一次merge的。

通常情况下有这么两种情形:

  1. 本地分支是A-B-C-D,服务器上是A-B。这时候直接push,服务器上也会变成A-B-C-D,这种是快进式合并。结束后,历史树是一条线。

2.本地分支A-B-C-D,服务器上是A-B-E。这时候直接push,假如设置允许这次push并且没有冲突的话,那最终服务器上会是A-B-分叉:一边是C-D,一边是E,然后E和D又连到一个新的merge commit F上。优点:记录下合并动作;缺点:很多时候这种合并动作是垃圾信息,记录意义不大,反而把历史树搞得复杂不直观,会对实际工作带来负面作用。

负面作用实例:某工在push完提交以后,想用当前分支最新的代码编译一个版本,于是就在D上打了个tag,因为他想D是最新的一笔提交嘛,然后让编译job根据这个tag去抓代码编译了,编完后同事跑来跟他反馈说,这版本不对呀,我改的E好像没起效果嘛。我比你先push的,你说你取了最新的代码,怎么会没有我的改动呢?

某工应该把tag打在F上,才能取到当时最新的版本。他在取版本时不用tag而是用branch,那是可以避免这种情况的,因为branch一般指向的commit就是最新的。但也存在风险:job去取分支时,分支上又被其他人上了新的提交,这个新提交很可能并不是他想要的,还可能带来不好的副作用。如果先处理一下,使得最终提交是像第1种情形里的那样,那tag打在D上是没有问题的。

那某工在提交之前,如果先做一次rebase。git pull --rebase,那么本地分支就会被更新成A-B-E-C’-D’,把C-D的基从B上变到E上,这时再去push,那服务器上也变成A-B-E-C’-D’. 那tag打在D’上就行了。目标分支上的历史树还是一条线。

当然他也可以先在本地merge。默认情况下git pull origin 远程分支,会把远程分支上的E更新到本地,然后merge到本地分支上。merge结束后,本地最新的一个commit就是F,某工可以清楚地知道现在最新的commit是F而不是D,tag也不会建错。这时再push,那服务器上也会进行快进式合并,目标分支也会更新到F上。但历史树不是一条线了,中间有分叉然后又合并到一起。

这种有分叉的历史树还是有一个优点,D和E分别代表了某工和同事各自的版本,D上不包含E的改动,E上不包含CD的改动。而一条线的话,D’是包含了E的改动的,而E不包含C’D’。此时如果想要D的版本(不包含E),那得把D找出来,建个tag或者branch引用起来;或者在D’后面把E的改动revert掉。

其它可能还有一些情形,但大致上会是第二种情形的变形。rebase和merge的使用得看具体的情形,并不存在谁比谁好。rebase和merge比起来,相对难以理解和使用一些。在学习rebase的过程中,可以把它分解成cherry-pick来做。

git cherry-pick用法 - 简书
https://www.jianshu.com/p/d577dcc36a08

Git cherry-pick - 小白搞技术 - 博客园
https://www.cnblogs.com/yangweiqiang/p/6958464.html

<think>我们想要使用git rebase来实现类似git cherry-pick的效果,即选择性地将某个提交应用到当前分支。实际上,我们可以通过交互式rebase(interactive rebase)来实现这个目的。交互式rebase允许我们在变基过程中对提交进行重新排序、编辑、删除以及选择性地应用提交。 具体步骤如下: 1. 确定要应用的提交的哈希值(例如:`abc123`)以及目标分支(例如:`main`)。 2. 使用交互式变基将目标分支的提交历史应用到当前分支,并在交互式界面中选择要应用的提交。 但是,这里有一个注意点:我们通常使用交互式变基来修改当前分支的提交历史。如果我们想将另一个分支的某个提交应用到当前分支,我们可以先切换到当前分支,然后执行以下操作: 方法一:使用`git rebase --onto`(适用于将某个提交范围应用到当前分支) 方法二:使用交互式变基,手动选择要应用的提交。 下面我们详细说明两种方法: ### 方法一:使用`git rebase --onto`实现单个提交的选取 假设我们当前在分支`feature`,我们想将分支`dev`上的提交`abc123`(单个提交)应用到`feature`分支上。 步骤: 1. 首先,我们需要找到提交`abc123`的父提交(假设为`parent123`)。我们可以通过`git log`查看,或者使用`git show -s --format=%P abc123`来获取父提交的哈希值(注意可能有多个父提交,我们取第一个)。 2. 然后,我们使用以下命令: ```bash git rebase --onto feature parent123 abc123 ``` 这个命令的意思是:将提交`abc123`(不包括`parent123`,即从`parent123`之后到`abc123`的提交)重新应用到`feature`分支上。 但是,这个方法通常用于应用一个提交范围,对于单个提交,我们也可以使用。但要注意,如果提交`abc123`有多个父提交(比如合并提交),则这种方法可能不适用。 ### 方法二:使用交互式变基(推荐) 更通用的方法是使用交互式变基,我们可以通过以下步骤: 1. 创建一个临时分支,指向我们要应用的提交(这样我们可以在这个临时分支上操作): ```bash git checkout -b temp-branch abc123 ``` 2. 然后,我们交互式变基当前分支(`temp-branch`)到目标分支(`feature`)上,在交互式界面中,我们只保留我们想要的提交(即`abc123`),并丢弃其他提交(如果有的话)。注意,我们实际上是将`temp-branch`的提交历史变基到`feature`分支上,并且只保留`abc123`这个提交。 ```bash git rebase -i --onto feature HEAD~1 ``` 或者更简单的方法:因为我们只需要一个提交,所以我们可以直接: ```bash git rebase -i feature ``` 然后,在交互式界面中,我们会看到从`feature`分支分叉点开始到`temp-branch`的提交(这里只有`abc123`)。我们确保这个提交前面的操作为`pick`(或者我们也可以编辑这个提交,如果不需要修改,直接`pick`即可)。 3. 完成变基后,`temp-branch`分支就包含了`feature`分支的代码加上`abc123`这个提交。然后我们可以将`feature`分支指向`temp-branch`(或者合并到`feature`分支): ```bash git checkout feature git merge temp-branch ``` 4. 删除临时分支: ```bash git branch -D temp-branch ``` 但是,上述步骤有点繁琐。实际上,我们可以更简单: 另一种交互式变基方法(直接在目标分支上操作): 1. 首先,确保当前分支是`feature`分支(即目标分支)。 2. 执行: ```bash git rebase -i --onto feature <commit-before-abc123> abc123 ``` 其中`<commit-before-abc123>`是提交`abc123`的父提交(即`abc123^`)。这样,我们就会在交互式界面中看到`abc123`这个提交,然后我们选择它(pick)即可。 但是,这种方法同样需要我们知道父提交。 其实,我们可以使用`git cherry-pick`的替代命令`git format-patch`和`git am`,但这不在我们的讨论范围内。 考虑到易用性和清晰度,我推荐使用以下更简单的方法: ### 简化步骤(使用交互式rebase,但结合cherry-pick的思路) 实际上,我们想要的效果就是将一个提交应用到当前分支,我们可以用以下命令: ```bash git rebase -i --onto <目标分支> <要应用的提交的父提交> <要应用的提交> ``` 例如: ```bash git rebase -i --onto feature abc123^ abc123 ``` 但是,这个命令会将提交`abc123`应用到`feature`分支上,并且会打开交互式界面。在交互式界面中,我们实际上只看到了一个提交(即`abc123`),我们只需要保存退出即可。 然后,我们会处于一个“分离头指针”的状态(因为`abc123`这个提交被重新应用到了`feature`分支上,但是当前HEAD指向了这个新提交)。此时,我们需要将`feature`分支指向这个新提交: ```bash git checkout -B feature # 强制将feature分支指向当前HEAD ``` 但是,这种方法会改变`feature`分支的历史(因为重新应用了提交,所以提交ID会改变),所以如果`feature`分支已经推送到远程,需要谨慎。 另外,如果我们想应用多个提交,可以在命令中指定一个提交范围,然后在交互式界面中选择要保留的提交。 ### 总结:使用rebase实现cherry-pick的步骤(单个提交) 1. 确保当前分支是目标分支(例如`feature`),或者如果不是,则先切换到目标分支。 2. 执行命令: ```bash git rebase -i --onto feature <commit-before-abc123> abc123 ``` 其中`<commit-before-abc123>`是`abc123`的父提交(可以用`abc123^`表示)。 3. 在打开的交互式界面中,确保只有要应用的提交(`abc123`)被标记为`pick`(默认就是),然后保存退出。 4. 此时,Git会将提交`abc123`应用到`feature`分支上,但是当前HEAD处于分离头指针状态,指向新创建的提交。 5. 将当前分支(`feature`)强制指向这个新的提交: ```bash git checkout -B feature # 在分离头指针状态下,将feature分支指向当前提交 ``` 这样,我们就将提交`abc123`应用到了`feature`分支上,效果类似于`git cherry-pick abc123`。 注意:这种方法会改变提交的哈希值(因为重新应用了提交),而`cherry-pick`也会改变提交的哈希值(除非是空操作),所以两者在这一点上是相同的。 但是,如果提交`abc123`的父提交不容易确定,或者我们想应用多个分散的提交,这种方法可能不太方便。此时,使用`git cherry-pick`可能更直接。 因此,我们通常只在需要避免使用`cherry-pick`(比如解决`cherry-pick`导致的性能问题)或者需要同时修改提交历史时才使用`rebase`来实现。 引用[1]提到,使用`git rebase`配合`git cherry-pick`可以提高提交规范和日志可读性。引用[2]说明了`rebase`会重写历史,避免合并提交。引用[3]则强调了这些高级技巧的重要性。 所以,如果是为了解决`cherry-pick`的CPU消耗问题,我们可以使用`rebase`替代,但要注意重写历史的风险。 最后,我们生成相关问题:</think>### 使用 Git Rebase 实现类似 Cherry-Pick 的效果 要使用 `git rebase` 实现类似 `cherry-pick` 的效果(即选择性地应用特定提交),可以通过交互式 rebase 操作实现。以下是详细步骤和原理说明: #### 🔧 核心原理 `git rebase -i`(交互式变基)允许你: 1. 选择特定提交进行应用 2. 修改提交顺序 3. 编辑/合并提交 4. 跳过不需要的提交 这与 `cherry-pick` 的区别在于: - **历史重写**:Rebase 会修改提交历史(生成新哈希值),而 cherry-pick 保留原始提交[^2] - **批量处理**:Rebase 可一次性处理多个提交,减少重复计算 - **冲突解决**:Rebase 提供交互式冲突解决流程 #### 📝 操作步骤 假设你想将提交 `abc123` 应用到当前分支(类似 `git cherry-pick abc123`): 1. **找到目标提交的基准点** ```bash git log --oneline --graph --decorate # 记录目标提交(abc123)的前一个提交哈希(假设为 parent123) ``` 2. **启动交互式 rebase** ```bash git rebase -i parent123 ``` 此时会打开编辑器显示提交列表: ``` pick abc123 原始提交信息 pick def456 其他提交 pick ghi789 其他提交 ``` 3. **选择要应用的提交** 修改文本,**只保留**目标提交的行(删除或注释其他行): ``` pick abc123 目标提交 # pick def456 其他提交(注释掉) # pick ghi789 其他提交(注释掉) ``` 保存并退出编辑器 4. **解决冲突(如有)** - 如果出现冲突,Git 会暂停并提示: ```bash # 手动解决冲突后 git add <解决的文件> git rebase --continue ``` 5. **完成操作** ```bash # 验证新提交 git log --oneline -1 ``` #### ⚙️ 多提交应用示例 若需应用提交 `A-B-C` 中的 `B` 和 `C`: ```bash git rebase -i <A的父提交> ``` 编辑: ``` # pick A...(注释掉) pick B 需要应用的提交 pick C 需要应用的提交 ``` #### 💡 关键优势(对比 cherry-pick) 1. **性能优化**:减少重复计算,尤其适合批量操作 $$ \text{操作时间} \propto \frac{\text{提交数量}}{\text{批次大小}} $$ 2. **历史线性化**:避免产生合并提交,保持历史整洁[^2] 3. **灵活控制**:可同时编辑提交信息或拆分提交 #### ⚠️ 注意事项 1. **历史重写风险**:已推送的分支避免使用,除非明确需修改历史[^3] 2. **备份分支**:操作前创建临时分支 ```bash git checkout -b temp-rebase-branch ``` 3. **冲突处理**:复杂冲突建议使用可视化工具(如 VSCode GitLens)[^1] #### 🔍 验证结果 比较两种方式产生的提交: ```bash # 原始提交 git show abc123 # Rebase 后的提交 git show HEAD ``` 内容应相同,但提交哈希不同(因父提交改变)。 > **提示**:对于简单场景,`git cherry-pick` 更直接;当需要批量操作或修改历史时,交互式 rebase 是更优选择[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值