由一个业务问题简析git revert/checkout/reset操作

近期在工作中遇到一个比较有意思的问题,可以抽象为下面这种情况:
有某分支:commit1 <- commit2 <- commit3 <- commit4(<-HEAD),现需要将代码恢复到commit1提交后的状态,并且保持commit2, 3, 4的存在,即需要将分支变成:commit1 <- commit2 <- commit3 <- commit4 <- commit1’(<-HEAD),其中commit1’的代码与commit1相同。在要求只通过git操作来完成的前提下,由对解决此问题的git方法展开一些分析和思考。

git reset 方法(推荐):

1
2
3
$ git reset --hard commit1
$ git reset --soft @{1} # 或者 git reset --soft commit4
$ git commit -m "commit message"

git reset - -[param] <commit> 操作会将当前分支的 HEAD 从最新的本地 commit 移动到 <commit>,而在移动的同时,会通过 - - 所携带的参数处理工作区(Working Directory)和暂存区(Stage/Index)保存的文件,参数具体作用为:

  • - -mixed: 重置暂存区的修改到工作区,保留工作区的修改(此操作为默认操作);
  • - -hard: 清空所有工作区和暂存区的所有修改;
  • - -soft: 保留工作区和暂存区的修改,并且把 <commit> 时文件修改前的状态添加到暂存区;
  • - -merge: 清空暂存区,恢复工作区到 <commit> 时的状态,如果与reset前的工作区状态发生冲突,则终止reset;
  • - -keep: 清空暂存区,保留工作区在 <commit> 时的修改,如果与reset前的工作区状态发生冲突,则终止reset。

其中 merge 和 keep 参数不常用,- -merge 操作按照官方文档的解释,如果需要,一般是用于在撤销 merge 或者 pull 操作时,恢复 merge 或 pull 前的工作区

$ git pull 
Auto-merging nitfol
Merge made by recursive.
 nitfol                |   20 +++++----
 ...
$ git reset --merge ORIG_HEAD

在上面的示例中,git pull 操作之前,工作区存在被修改的文件,要 reset 回 git pull 之前的状态,如果是 git reset –hard ORIG_HEAD,工作区的变动也会被清空,此时 git reset –merge 就会保留工作区中的变动。

git revert 方法(推荐):

1
2
$ git revert -n master~3..master~1 #假设为master分支
$ git commit -m "commit message"

reset 操作用于恢复提交历史中的某些 commit ,可以恢复修改前的文件到工作区和暂存区,也可以在当前 commit 基础上,添加新的 commit,常用的句法为:

git revert [--[no-]edit] [-n] [-m parent-number] <commit>…
  • –[no-]edit: 恢复并提交后(不)进行编辑 commit 信息,默认为进入编辑 commit 信息;
  • -n: 将所有恢复后的文件状态保存在工作区,不进行提交;
  • [-m parent-number]: 需要恢复的 commit 的起始和结束的序号,当前 HEAD 所在 commit 计为 1;
  • <commit>: 需要恢复的 commit;

在此方法中,master~3..master~1 有很多种简写形式,如 master~3.. , HEAD~3..HEAD。此外,关于是否有 -n 参数,在此示例中有一个很明显的区别是,如果不带有 -n 参数,会连续进行三次 revert 的 commit 提交,反之则将所有 revert 后变动的文件状态一并保存在工作区,最后只需一次 commit 即可。
revert 操作不会保留 revert 之前 commit 时新增的文件。

git checkout 方法:

1
2
$ git checkout -f commit1 -- .
$ git commit -m "commit message"

checkout操作用于从历史提交(或者暂存区)中拷贝文件到工作区,也可用于切换分支.此项方法是用checkout把 - - 所指定的文件(paths)恢复到 commit1 状态并保存在暂存区,也就是说,commit1之后的所有提交中如果有 - - 指定的文件,文件所有改动会被撤销并保存在暂存区。

特别要注意的是,此方法会保留在commit2,commit3,commit4提交时新增的文件,因此属于不干净操作。

  • -f (force): 清空所有工作区和暂存区的所有修改。

其中 - - 不能省略,省略- -时,git会有如下提示:

1
2
3
4
5
$ git checkout commit1
filename git:(35850b2) git status
HEAD detached at 35850b2
nothing to commit, working tree clean
filename git:(35850b2)

省略 - - 实际上是checkout的另外一种用法:

git checkout [-q] [-f] [-m] [--detach] [<branch>]

此方法会将HEAD与master分离(–detach 为默认操作),HEAD会指向 commit1 ,并且之后的提交操作会在 commit1 基础上创建新的 commit 点(并不是新分支),如果需要产生新分支,使用 git checkout -b branchname

$ git checkout b
$ edit; git add; git commit (生成 e)

     HEAD (refers to commit 'e')
      |
      v
      e
     /
a---b---c---d  branch 'master' (refers to commit 'd')

 $ git checkout -b f

        HEAD (refers to commit 'f')
          |
          v
      e---f(new branch)
     /
a---b---c---d  branch 'master' (refers to commit 'd')

需要注意的是,如果在 git checkout -b branchname 之前切换到了其他分支,那么从 b 生成的新分支 e—f 将不会被保留。


参考链接:
starkoverflow: Revert multiple git commits    git-checkout    git-revert    git-reset

热评文章