背景 链接到标题
本文首先对常用的 Revert,Reset 回退代码命令进行了讲解。希望可以讲明白它们的弊端。最后介绍如何使用 Rebase 优雅回退代码。
一. Revert 回退代码 链接到标题
1.1. 命令描述 链接到标题
使用命令 git revert commit_id 能产生一个 与 commit_id 完全相反的提交,即在 log 中会看到一条新的提交 new_commit_id, revert 提交就是删除 commit_id 的提交。
1.2. 命令使用 链接到标题
# 查看提交记录
$ git log -p
# 回退某个提交
$ git revert commit_id
# 取消执行回退
$ git revert --abort
# revert merge commit
# 一般来说,如果在 master 上 merge a_branch,那么 parent 1 就是 master,parent 2就是 a_branch。
# git revert merge_commit_id -m parent
$ git revert b7b7b87d5d05a22ad1e7779484bcf82e31041a72 -m 1
1.3. 使用场景分析 链接到标题
- 场景1:提交次数多,中途还有几次从其他分支的 merge 操作。
要完成从 C 版本到 N 版本的 revert,需要倒序执行 revert 操作几十次,如果其中顺序错了一次,最终结果可能就是不对的。
- 场景2:在进行代码 merge 时,也会把 merge 信息产生一次新的提交,而 revert 这次 merge commit 时需要指定 m 参数,以指定
mainline。
这个 mainline 是主线,也是我们要保留代码的主分支,从 feature 分支往 develop 分支合并,或由 develop 分支合并到 master 的提交还好确定,但 feature 分支互相合并时,难以区分主线。

二. Reset 回退代码 链接到标题
2.1. 命令描述 链接到标题
reset 也能使代码回到某次提交,但跟 revert 不同的是, reset 是将提交的 HEAD 指针指到某次提交,之后的提交记录会消失,就像从没有过这么一次提交。
2.2. 命令使用 链接到标题
# 重置暂存区的指定文件,与上一次commit保持一致,但工作区不变
$ git reset [file]
# 重置暂存区与工作区,与上一次commit保持一致
$ git reset --hard
# 重置当前分支的指针为指定commit,同时重置暂存区,但工作区不变
$ git reset [commit]
# 重置当前分支的HEAD为指定commit,同时重置暂存区和工作区,与指定commit一致
$ git reset --hard [commit]
# 重置当前HEAD为指定commit,但保持暂存区和工作区不变
$ git reset --keep [commit]
2.3. 使用场景分析 链接到标题
- 场景1:使用 feature 分支开发,在 feature 分支上将代码回退到某次提交后。将其合并到 develop 分支时却被提示报错。
这是因为 feature 分支回退提交后,在 git 的 workflow 里,feature 分支是落后于 develop 分支。
- 场景2:在 master 分支上执行 reset,使用 –hard 选项完全抛弃这些旧代码,reset 后再强制推到远端
master 分支在 gitlab 里是被保护的,不能使用 force push,毕竟风险挺大
git reset 前

git reset 后

三. Rebase 回退代码 链接到标题
rebase把多个提交合并成一个提交,再使用revert产生一次反提交,这种方法的思路非常清晰,把revert和rebase两个命令搭配得很好,相当于使用revert回退的升级版,其实现更优雅,便捷,强大。
3.1. 命令描述 链接到标题
rebase 是“变基”的意思,这里的“基”,指[多次] commit 形成的 git workflow,使用 rebase,我们可以改变这些历史提交,修改 commit 信息,将多个 commit 进行组合。后续写一篇文章详细介绍 rebase。
3.2. 命令使用 链接到标题
# 执行变基
$ git rebase -i commit_n
# 终止变更
$ git rebase --abort
3.2. 回退代码步骤 链接到标题
1). 切出一个新分支 rebase-rollback
首先,切出一个新分支 rebase-rollback,使用 git log 查询一下要回退到的 commit 版本 commit_n。如下图回退到蓝框中的版本。

2). 执行命令 git rebase -i commit_n
-i 指定交互模式后,会打开 git rebase 编辑界面。
$ git rebase -i fa1b56d920e636914b6ef27988358ff122279261
编辑界面看到形如下述的 commit:
pick 6fa5869 commit1
pick 0b84ee7 commit2
pick 986c6c8 commit3
pick 91a0dcc commit4
3). 合并 commit2 ~ commitN 到最旧的 commit1 上 在合并 commit 时,我们可以选择 pick(p) 最旧的 commit1,然后在后续的 commit_xxx 前添加 squash(s) 命令,将这些 commits 都合并到最旧的 commit1 上。
编辑界面修改后形如下述的 commit:
pick 6fa5869 commit1
squash 0b84ee7 commit2
squash 986c6c8 commit3
squash 91a0dcc commit4

4). 保存 rebase 结果后,再编辑 commit 信息,使这次 rebase 失效 git 会将之前的这些 commit 都删除,并将其更改合并为一个新的 commit5。如果出错了,也可以使用 git rebase –abort/–continue/–edit-todo 对之前的编辑进行撤销、继续编辑
- 冲突处理
出现冲突时,需处理冲突后,再执行 –continue。处理冲突时一般选择
Accept Current Change
# fix conflicts and then run "git rebase --continue"
$ git rebase --continue
# 执行成功提示语
Successfully rebased and updated refs/heads/rebase-rollback.


5). 检查 rebase 执行成功
- 主干提交记录 执行下述命令,查看主干的提交记录:
# 切回master
$ git checkout master
# 查看提交记录
$ git log
master 提交记录 commits 形如:
commit 4
commit 3
commit 2
commit 1
# 回退代码的版本号
commit_n

- rebase-rollback 分支提交记录 执行下述命令,查看 rebase-rollback 分支的提交记录:
# 切回rebase-rollback
$ git checkout rebase-rollback
# 查看提交记录
$ git log
master 提交记录 commits 形如:
# 执行rebase 生成的新记录
commit 5
# 回退代码的版本号
commit_n

6). 合并 master 到 rebase-rollback
由于 rebase-rollback 分支落后与 master 分支,因此需要执行 git merge master 将主分支向 rebase-rollback 分支合并,合并后 git 会发现 commit1 到 commit4 提交的内容和 rebase-rollback 分支上 commit5 的修改内容是完全相同的,会自动进行合并,内容不变,但多了一个 commit5。

7). 在 rebase-rollback 执行 revert 反提交
在 rebase-rollback 分支上对 commit5 进行一次 revert 反提交,就实现了把 commit1 到 commit4 的提交全部回退。即撤回步骤6中的合并。
$ git revert 5c6123b94705ea605d404fc03aad90b23ab156bd

8). 合并 rebase-rollback 到 master
# 切回到 master
$ git checkout master
# 合并 rebase-rollback
$ git merge rebase-rollback