如何回滚代码?

由于不合预期的合并或提交导致代码内容被污染,希望能够回滚代码,此时应该如何操作呢?

通用Git命令介绍

Git 作为灵活的版本控制工具,提供了 revertreset 两种命令来进行恢复操作,两个操作的效果是不同的,请根据以下说明选择自己期望的方式进行代码回滚。

revert

回滚某个 commit 提交版本,除此版本的提交全部保留,使用此方法回滚是一个 merge 操作,不需要强制推送到远程。

reset

回滚到某个 commit 提交版本,自此版本的后续提交全部从提交历史上清除丢弃,需要强制推送到远程。

回退命令:

git reset --hard HEAD^ 回退到上个版本

git reset --hard HEAD^^ 回退到上上个版本

git reset --hard HEAD~3 回退到前3次提交之前,以此类推,回退到n次提交之前

git reset --hard <commitID> 退到/进到指定提交

然后强制推送到远程。

重要

强制推送后,远程将丢弃已被指定清除的提交历史,不支持数据恢复。

强制覆盖远程分支进行恢复的场景

在分支推送、合并中,可能会出现误操作的情况。比如:

  • 本地分支落后远程分支,错误使用强制推送将远程分支“回滚”;

  • 本地分支与远程分支出现分叉,错误使用强制推送将远程分支覆盖;

  • 误合并未经审核的代码评审,导致目标分支合入未经评审的提交。

此时,通常需要对目标分支进行强制覆盖的操作——即将目标分支恢复到历史的某个版本,同时丢弃误合入的全部提交。

重要

注意,下文介绍强制覆盖远程分支进行恢复的场景,会直接丢弃误合入的提交历史。如果对这些错误提交仍有保留的诉求,请使用上文所述revert命令。

前向强制覆盖

前向覆盖指远程的分支被误操作“回滚”到了较久之前的版本,需要将远程分支恢复到正确的版本上。

这种情况一般发生在某个用户本地的分支未与远程分支保持一致,本地分支落后于远程分支,但在提交其他分支的时候错误使用了全量推送+强制覆盖的命令,使得远程分支被强制“回滚”到该用户本地的落后版本,导致其他用户的提交丢失场景。

master 分支被污染,希望主干回退到正确版本的场景为例,操作如下:

步骤一:找到正确版本

通过动态找到最近一次更新的版本

登录访问目标代码库,通过左侧主菜单进入代码库动态页面,查看仓库的推送历史,可以通过动态查找需要恢复分支的最后一次推送。例如下图中的master分支:

1-2

动态中一般会标记出强制推送的记录。我们只需要找到这一条一般就能看到该分支被强制推送后变化。该例子中,就是被强制覆盖为add6fcd3。通过动态记录继续往回查找,可以找到最近一条正常推送的记录,这一条记录就是最新一次正常推送的版本,也就是我们期望恢复的版本79b769

步骤二:强制更正分支版本

通过步骤一中找到的期望恢复的提交版本,可开始分支的更正。

说明

如恢复的提交已为悬空提交,服务端会由于Git对悬空提交的回收机制而无法恢复,如遇此种情况,服务端已无记录,需要自行在本地找到该提交。

什么是悬空提交?

如果一个提交未关联任何引用,那么该提交即为悬空提交。例如ID为79b7691d3e3b6ca19bb8f4f6c76023443735362c仅在master上存在过,如果将master分支回退到该提交之前的祖先提交,那么该提交将不存在于任何引用上即为悬空提交。使用 git fsck --dangling --no-reflogs可以显示当前仓库下为关联引用的悬空提交。

为什么悬空提交会被回收?

当服务端存在大量松散对象时,git 提供 gc 策略来将提交进行压缩以减少存储和索引上性能开销。未关联任何引用的悬空提交被认为对仓库来说没有保留意义,因此在保存较长时间段后,服务端会删除这些悬空的提交。或者用户在仓库 gc 中自行选择强制 gc 也会触发对悬空提交的清理。

上述的例子中,我们需要将master分支的提交版本恢复到79b7691d3e3b6ca19bb8f4f6c76023443735362c

  • 如果用户本地保留了该提交记录,那么可以直接将该提交关联一个特定的恢复分支,然后再用这个分支覆盖掉要更正的远程分支。具体操作如下:

# 以 79b7691d3e3b6ca19bb8f4f6c76023443735362c 为最新提交,生成一个新的分支
git checkout 79b7691d3e3b6ca19bb8f4f6c76023443735362c -b master-recover

# 以master-recover分支的内容覆盖远端master,以此来修正远端master的提交版本
git push --force origin master-recover:master 
  • 如果用户本地已无该提交记录,而服务端还未对该对象进行 gc 回收的情况下,可以从服务端以这个提交新建一个分支然后恢复。具体操作如下:

  1. 在 Codeup 的分支页面,点击新建,自定义分支名,如:master-recover,来源设置为目标提交:79b7691d3e3b6ca19bb8f4f6c76023443735362c

1-2
  1. master-recover 拉取到本地,并强制覆盖远端 master 分支:

# 将远端的master-recover分支拉取到本地
git fetch origin master-recover:master-recover

# 本地检查master-recover是否为要恢复的版本
git log master-recover

# 以master-recover覆盖远程要恢复的master分之
git push --force origin master-reover:master

以上操作后,将用 master-recover 分支的内容强制覆盖 master 分支,让主干回到期望的提交状态,该提交之后的错误提交记录将被全部丢弃。

后向强制覆盖

需要向后覆盖分支,往往是远程分支被提前合入了非预期的内容。比如有开发人员本地直接在主干上开发,而仓库没有设置推送规则,导致主干被该人员提交覆盖。还有一种情况是未经严格审核的代码被误操作合并到目标分支,需要对目标分支进行“回退”。

这种情况相对简单,因为往往需要回滚掉的是新增内容,所以需要恢复的提交一定还关联在分支的提交树上,这种情况只需要找到期望恢复的版本即可。

查找要恢复的版本非常简单,可以是根据团队同学的记忆,也可以通过Codeup的动态页面,找到最后一次正确提交的 ID 即可。

例如,我们现在是要将master分支的内容回退到add6fcd3efb163349cba4b4cc8fdfefe49befb6d,那么可以通过以下操作实现:

# 直接在本地的分支上进行回退(这种情况仅限于,本地已同步远程被污染的提交)
git reset --hard add6fcd3efb163349cba4b4cc8fdfefe49befb6d

# 使用本地已修正的master分支,覆盖远程分支
git push --force origin master:master

# 如果本地的分支还自己的工作内容,则可以通过新建一个修复分支来操作
git checkout add6fcd3efb163349cba4b4cc8fdfefe49befb6d -b master-recover

# 然后用修复分支来覆盖远程分支
git push --force origin master-recover:master

恢复已删除分支

如果分支被不小心删除掉。恢复方案类似于强制覆盖远程分支中,找回正确版本的办法。

  1. 通过Codeup的动态页面,找到分支最后一次提交的版本,例如下图的dev分支被删除,删除前分支指向的提交版本为:eba83265a7096d656f582161e4e42010bb4b89f7

1-2
  1. 通过提交版本eba83265a7096d656f582161e4e42010bb4b89f7新建分支即可重建分支:

1-2 2