Git 如何遗弃已经 Push 的提交

做者:咕咚移动技术团队-nanchengit

题目看起来很像是提供解决方案的文章,但实际上我并不会给你们直接提供解决方案,咱们追求的历来不该该是答案,而是探索的过程。固然,若是你只想查看答案的话,请直接拉到文章最底部。vim

写在前面

相信你们都知道,Git 相比于 SVN,优点不言而喻,以至于如今大多数公司的项目都在采用 Git 进行管理。做为一个开发人员,对 Git 的使用天然应该是驾轻就熟。post

若是你还不会使用 Git 的话,那我劝你仍是不要声张,好好的去学习一番,再本身弄个实验项目走一下流程,以避免遭到同事的鄙视。学习

每一个公司都会有本身不同的 Git 分支管理规范,特别是在开发人员较多的公司,Git 的分支管理规范就显得更加剧要。前面比较出名的 Git Flow 分支管理策略相信很多人都已经了解了,不熟悉的固然也能够去看看:nvie.com/posts/a-suc…code

分支管理

Git Flow 管理方式把项目分为 5 条线,一般会是下面的管理方式。cdn

  • Master:做为稳定主分支,长期有效。不能够在此分支进行任何提交,只能接受从 Hotfix 分支或者 Release 分支发起的 merge request,该分支上的每个提交都对应一个 Tag。
  • Develop:开发主分支,长期有效。不能够在此分支上作任何提交,只接受从 Feature 分支发起的 merge request。全部的 Alpha Release 都应该在这个分支发布。
  • Feature:功能分支,生命周期为产品迭代周期,每一个分支对应一期的需求。只能够从 Develop 分支进行 Kick Off。能够 merge Release 分支的代码,生命周期结束后,须要 merge 回 Develop 分支。方式须要采用 merge request。
  • Release:发布分支,声明周期重新需求的预发布到正式发布,每个分支对应一个新版本的版本号。只能够从 Develop 分支 Kick Off。声明周期结束后,须要 Merge 回 Master 及 Develop 分支,方式一样须要采用 merge request。全部的 Beta Release 均须要在该分支发布。
  • Hotfix:热修复分支,生命周期对应一个或者多个须要紧急修复并上线的 Bug,每个分支对应一个小版本号。只能够从 Master 分支进行 Kick Off。声明周期结束后,须要 merge 回 Master 分支和 Develop 分支,方式固然也是采用 merge request。

实际上,若是你熟悉 Git 的话,你会很快发现上面的管理方式会存在历史提交很是混乱的缺点,但以为不失为一个 Git 分支管理的经典。实际上,咱们能够用 rebase 去替换 merge 让 commit 看起来更加清晰。对 rebase 和 merge 的优劣对比这里暂不作讲解,感兴趣的能够直接 Google 搜索。blog

下面就给你们分享一下发生在咕咚项目的一次坑爹的 Git 体验。生命周期

从 git revert 提及

咕咚项目组并无对开发者限制 Develop 分支和 Master 分支的权限,咱们暂时并无一个专门作代码 Review 和 PR 的角色,其实必定意义上也提现了团队对每一个人的信任。开发

咱们依然会基于 Develop 作开发主线,每一个需求迭代期,团队成员会从 Develop 拉取本身的分支,并命名于 feture/XX,而后各自在本身的分支上进行开发。get

因为你们开发业务上的不一样,因此在需求开发完毕,整合代码到 Develop 分支的时候,通常不会出现太多冲突的状况。

而我这边交接一个需求时,采用 merge 的时候出现了一个奇怪的问题,咱们姑且来重现一下事故现场。

首先使用 git branch 查看一下当前咱们的本地分支。

查看分支

这里先简单提一下咱们要作的操做。

"feature8.28_buyGifts" 是咱们同事的分支,基于 "release8.27.0" 拉取,而 "feature8.29.0_nanchen" 是个人分支,基于 "release8.28.0" 分支拉取,因此我这边的分支包含了最新的代码。

如今因为某些缘由,我须要把同事的 "feature8.28_buyGifs" 分支代码合并到个人分支上,直接接手他的代码进行开发。

就不要吐槽为啥不按照功能搞分支开发了,缘由是由于他那边代码基本已经完成,如今只须要少许修改。

因此咱们就采用 git merge <branch> 命令进行 merge 操做。

merge

咱们用 git status 更容易看明白冲突了什么。

能够看到,上面冲突的文件全是和同事开发的需求出现的冲突,因此出现这个冲突其实使人很是懊恼,由于是不可能有其余同事改动到这些文件的。

为了验证本身的想法,咱们随意打开一个文件查看。这里就采用 vim <filename> 查看第一个文件。

正如咱们所想,确实和同事编写的需求 Presents 类有关系,但看冲突内容就更一脸懵逼了,由于看起来,这应该是一个不会冲突的 merge。

因而赶忙使用 git merge --abort 撤销此次 merge。再在 "origin/feature8.29.0_nanchen" 查看咱们刚刚的文件提交历史。

能够很清晰的看到,确实是最近没有任何的修改记录。

一个 7 个月都没人动的文件,竟然 merge 的时候发生了冲突!这让我一脸懵逼。(手动黑人问号)

使用 git lg 查看一下该分支的提交历史,咱们但愿从中能获得某些思路。

注意其中红框中的 commit,咱们这位同事以前想往 "release8.28.0" 合并他分支的代码,后面又由于某些缘由,但愿撤销此次提交,他采用了 revert 进行处理。虽然 revert 对文件没有提交记录,但 Git 却认为咱们在当前分支更改了这些文件,因此在咱们 git merge 的时候,Git 认为这是一次冲突,并选择了告知咱们。

如若如咱们所想,那咱们只须要撤销此次 revert 操做便可。

咱们固然知道,能够经过 reset 命令放弃此次提交,但这里后面已经有了很是多的 commit,显然咱们这样是不行的,咱们须要另辟蹊径。

解决方案?

最容易想到的大概就是直接在 merge 的时候解决冲突了,但经过一系列查看之后,咱们发现文件改动量很是大,直接解决冲突并不是易事。因此咱们仍是得 想办法取消掉此次 revert 的 commit,再进行 merge

咱们知道,代码回滚有三种方式:reset、checkout,还有咱们的 revert。直观感觉,咱们应该在 reset 上想办法。

咱们来看看 reset 有些怎样的操做方法。

主要想给你们讲讲:--soft 和 --hard 的区别。

咱们常常会用到 git reset --hard <commit> 作「毁尸灭迹」的操做,经常爽到不能自已,由于这不只能够回退到咱们想要的版本,并且还「直接丢弃」了后面提交的代码,真正的「毁尸灭迹」级别的操做。

而另一个 --soft 处理,实际上还具有点人性,虽然一样能够回退到咱们想要的版本,但目标版本后面的提交都还会存放在 stage 区域中,以便后面找出证据。

说到这,彷佛咱们已经有了思路。

  1. 使用 git reset --soft <revert 操做的 commit ID> 回退到 revert 操做的版本;
  2. 使用 git reset --hard <revert 操做的前一个 commit> 干掉那次 revert 提交;
  3. 最后再把 stage 区域的全部改动汇聚成一个新的提交 commit 到咱们的项目仓库中。

固然,细心的你必定会发现,在第 1 步操做后,咱们还必须执行 git stash 命令把全部的改动存到暂存区,再在第 2 步操做后使用 git stash pop 命令取出来,直接进行第 2 步操做确定仍是会毁灭证据的。

咱们后面的提交不见了。

这样彷佛能够解决咱们的问题,不过有个弊端:咱们后面那么多的提交被合并成一个提交了,之后咱们就没办法看到了,万一...

很多小伙伴会想到进阶方案:

  1. 对 "feature8.29.0_nanchen" 的最新代码 checkout -b 一个分支 feature_copy;
  2. 而后使用 git checkout feature8.29.0_nanchen 回到咱们的分支;
  3. 而后直接对当前分支 reset 到 revert 的前一个 commit 后,咱们采用 cherry-pick 方式进行傻瓜式改写即可以把历史重写了。(谁说的咱们不能改写历史?)

改写历史?

改写历史?等等,好像还有一个操做:rebase。

rebase 是 Git 的一个神奇的命令,前面我也说了,总会有人不喜欢 merge 以后历史的分叉,这种分叉再汇合后会让结构看起来很是混乱,以至于没法管理。若是你不喜欢 commit 历史出现分叉,那 rebase 绝对是你的救星。

改写历史是 rebase 与生俱来的能力。咱们能够用 git rebase -i <commit> 进行历史的改写。

咱们试试看在咱们的项目中直接使用 git rebase -i <commit> 会怎样。

咱们会拿到分支后面的提交历史,而且前面还有一个 Commands。咱们能够从提示中看到,上面全写的 pick 就是表明保持这个提交的意思,edit 表明编辑这次提交...

咱们但愿删除这次 revert 此次提交,那固然咱们最关心的就是 drop 了,甚至咱们能够更加简单粗暴:直接删掉这一行

而后咱们便开始处理了。

过程当中可能会出现冲突,咱们只须要解决就好。

解决掉冲突后,再使用 git add <filename> 把它们 merge 进去。

oh,咱们看到咱们已经 rebase 成功了。咱们再使用 git lg 查看一下提交历史。

咱们成功改写了历史!

历史改写结束,咱们还要作咱们最开始想作的事情,进行 merge 操做。

能够看到,此次咱们 merge 确实如咱们预期的再也不发生冲突,方案亲测有效!

写在最后

写了这么多,想必你们对解决方案也算比较清楚了。咱们主要即是采用 git rebase -i <> 操做进入到 commit 历史编辑页面,而后进行历史改写处理!

相关文章
相关标签/搜索