在实际开发会中经常会碰到这一场景,你想在commit A提交的版本上修改,你从代码仓拉下一份最新的代码,但是最新的代码以及有人提交了commit B、C、D好几次新的commit,这时你需要回退到commit A提交的版本,git reset命令用于将当前HEAD重置到指定状态。一般用于撤消之前的一些操作(如:git add,git commit等)。
首先了解一下“三棵树”的基本概念
树 | 作用 |
---|---|
HEAD | HEAD 是当前分支引用的指针,它总是指向该分支上的最后一次提交,下一次提交的父结点 |
Index | 预期的下一次提交 |
Working Directory | 工作区 |
HEAD 是当前分支引用的指针,它总是指向当前branch最顶端的一个commit。 这表示HEAD将是下一次提交的父结点。 通常,理解HEAD的最简方式,就是将它看做该分支上的最后一次提交的快照。
Index(索引)也就是使用git add添加后的文件,是一堆将在下一次commit中提交的文件,提交之后它就是当前HEAD的父节点。这个概念被引用为Git的“暂存区”。
Working Directory(工作区)即实际工作目录。
git reset一般常用的形式:git reset [<mode>] [<commit>]
这种形式将当前分支HEAD重设为<commit>
,并可能更新暂存区和工作区,具体取决于<mode>
。如果省略了<mode>
,默认为--mixed
。
<mode>
常用的有以下几种,<commit>
可以使用引用或者提交ID,如果省略<commit>
则相当于使用了HEAD的指向作为提交ID。
git reset --hard HEAD^
硬重置,此处<commit>
使用对HEAD的引用。HEAD,Index(暂存区),Working Directory(工作区)同时改变到要reset的那个commit(上一次提交的状态)。HEAD指向的提交历史被删除,HEAD重新指向上一次提交,并且被git管理的工作区以及暂存区未提交的内容都会被彻底丢弃(慎用)。
git reset --soft HEAD^
软重置,HEAD改变到要reset的那个commit(上一次提交commit),HEAD指向的提交历史被删除,HEAD重新指向上一次提交,Index(暂存区),Working Directory(工作区)不会改变。
git reset --mixed HEAD^
或者git reset HEAD^
( 缺省即为–mixed)
默认重置,HEAD,Index(暂存区)改变到要reset的那个commit,HEAD指向的提交历史被删除,HEAD重新指向上一次提交,Index(暂存区)被重置,Working Directory(工作区)不重置(即保留已修改的文件,但不标记为提交)并报告未更新的内容,当修改了一个文件,工作区不再和index和HEAD相同了,所以当文件有改动,git会标记这些文件。
git reset -- filename
或者git reset HEAD filename
仅将文件filename
撤出暂存区,暂存区中其他文件不改变。相当于对命令git add filename
的反向操作。
如果没有记下重置前master分支指向的提交ID,想要重置回原来的提交真的是一件麻烦的事情(去对象库中一个一个地找)。幸好Git提供了一个挽救机制,通过.git/logs
目录下日志文件记录了分支的变更。默认非裸版本库(带有工作区)都提供分支日志功能,这是因为带有工作区的版本库都有如下设置:
$ git config core.logallrefupdates
true
cat .git/logs/refs/heads/master
即可查看master分支的日志文件中的内容。
Git提供了一个git reflog
命令,对这个文件进行操作。使用show子命令可以显示此文件的内容。
$ git reflog show master | head -5
9e8a761 master@{0}: 9e8a761: updating HEAD
e695606 master@{1}: HEAD^: updating HEAD
4902dc3 master@{2}: commit: does master follow this new commit?
e695606 master@{3}: commit: which version checked in?
a0c641e master@{4}: commit (amend): who does commit?
使用:command:git reflog
的输出和直接查看日志文件最大的不同在于显示顺序的不同,即最新改变放在了最前面显示,而且只显示每次改变的最终的SHA1哈希值。还有个重要的区别在于使用git reflog
的输出中还提供一个方便易记的表达式:<refname>@{<n>}
。这个表达式的含义是引用之前第次改变时的SHA1哈希值。
那么将引用master切换到两次变更之前的值,可以使用下面的命令。
$ git reset --hard master@{2}
HEAD is now at 4902dc3 does master follow this new commit?
重置后工作区中文件又变回来了。提交历史也回来了。
此时如果再用git reflog
查看,会看到恢复master的操作也记录在日志中了。