前言

前面了解过,每次提交都会产生新的id,master引用分支总是会指向最新的提交id,其实,引用也可以指向历史id,将当前版本指向历史id,这就是重置。

引用就像是一个游标,可上可下。git提供了reset命令来指向任意一个id。

reset

git reset命令就是用来重置的命令,利用这个命令可以回退到想要的版本,下面可以试验一下

先创建一个新文件,并提交

1
2
3
4
5
6
7
8
$ touch newfile
$ git add newfile
$ git commit -m "does master follow this new commit?"
[master 6a26023] does master follow this new commit?
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 newfile
$ ls
hellogit newfile

查看master引用指向的id

1
2
$ cat .git/refs/heads/master 
6a2602307968a2fa13efc3974ab4e0f4e7abf49a

确实指向了新的提交id,查看一下日志

1
2
3
4
5
6
$ git log --oneline
6a26023 (HEAD -> master) does master follow this new commit?
6cffe14 2018.4.20 change1
5782fe2 third change
0789463 which version checked in?
ac01ff3 init hellogit file

现在想将版本重置到父提交

1
2
$ git reset --hard HEAD^
HEAD is now at 6cffe14 2018.4.20 change1

再查看master引用

1
2
$ cat .git/refs/heads/master 
6cffe145e4f83255fbdef7f68c4b0ed94acea8bc

确实指向了父提交了,再看下日志

1
2
3
4
5
$ git log --oneline
6cffe14 (HEAD -> master) 2018.4.20 change1
5782fe2 third change
0789463 which version checked in?
ac01ff3 init hellogit file

head和master引用都指向了父提交,再看一下文件是否存在

1
2
$ ls
hellogit

文件也删了

……

看了上面的日志,是完成了目的,回退到父提交了,但是,是不是有点太干净了,没有任何的回退记录,那如果是错误的回退怎么办,连日志都没了!!!这是一个危险的操作(特别是–hard参数,会破坏工作区未提交的改动),会彻底的丢弃历史,所以是不可能通过提交历史的办法来恢复,所以慎重!!!!

reflog

git作为一个这么🐂x的版本控制工具,岂会不考虑错误重置的这种情况。reflog就是来挽救错误的reset。

.git/logs目录下记录了分支的变更(必须是带有工作区的提供分支日志功能)。这是因为带有工作区的版本库都有以下设置

1
2
$ git config core.logallrefupdates
true

查看一下master分支的日志文件

1
2
3
4
5
6
$ tail -5 .git/logs/refs/heads/master 
ac01ff39a78fa388e1e628754fd55a76feb358d2 0789463b17be77ffe8da6fbc7e4b7777d065008c smxknife <2323937771@qq.com> 1522171550 +0800 commit: which version checked in?
0789463b17be77ffe8da6fbc7e4b7777d065008c 5782fe225003c2bc481d554ddeeab8fecd0dd19d smxknife <2323937771@qq.com> 1522171984 +0800 commit: third change
5782fe225003c2bc481d554ddeeab8fecd0dd19d 6cffe145e4f83255fbdef7f68c4b0ed94acea8bc smxknife <2323937771@qq.com> 1524190757 +0800 commit: 2018.4.20 change1
6cffe145e4f83255fbdef7f68c4b0ed94acea8bc 6a2602307968a2fa13efc3974ab4e0f4e7abf49a smxknife <2323937771@qq.com> 1524318936 +0800 commit: does master follow this new commit?
6a2602307968a2fa13efc3974ab4e0f4e7abf49a 6cffe145e4f83255fbdef7f68c4b0ed94acea8bc smxknife <2323937771@qq.com> 1524323213 +0800 reset: moving to HEAD^

在这里可以找到最后的操作日志,最后一条,reset:moving to HEAD^,提交id由6a26变为6cff

除了直接查看文件外,还可以通过reflog命令来查看

1
2
3
4
5
6
$ git reflog show master | head -5
6cffe14 master@{0}: reset: moving to HEAD^
6a26023 master@{1}: commit: does master follow this new commit?
6cffe14 master@{2}: commit: 2018.4.20 change1
5782fe2 master@{3}: commit: third change
0789463 master@{4}: commit: which version checked in?

可以看到同样的效果,只是对日志里面的信息进行精简和包装。在reflog命令中提供了一个方便易记的表达式:refname@{n},上面输出的就是master@{0}。这个表达式的意思是refname之前第n次改变时的id值。那么将master切换之前的值,可以使用下面的命令

1
2
$ git reset --hard master@{1}
HEAD is now at 6a26023 does master follow this new commit?

查看日志

1
2
3
4
5
6
$ git log --oneline
6a26023 (HEAD -> master) does master follow this new commit?
6cffe14 2018.4.20 change1
5782fe2 third change
0789463 which version checked in?
ac01ff3 init hellogit file

查看引用

1
2
$ cat .git/refs/heads/master 
6a2602307968a2fa13efc3974ab4e0f4e7abf49a

查看工作区文件

1
2
$ ls
hellogit newfile

用reflog命令查看日志

1
2
3
4
5
6
$ git reflog show master | head -5
6a26023 master@{0}: reset: moving to master@{1}
6cffe14 master@{1}: reset: moving to HEAD^
6a26023 master@{2}: commit: does master follow this new commit?
6cffe14 master@{3}: commit: 2018.4.20 change1
5782fe2 master@{4}: commit: third change

深入了解git reset命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
$ echo "this is new" >> newfile

$ git status -s
M newfile

$ git diff
diff --git a/newfile b/newfile
index e69de29..fff92cf 100644
--- a/newfile
+++ b/newfile
@@ -0,0 +1 @@
+this is new

$ git add newfile

$ git status -s
M newfile

$ git diff

$ git reset HEAD
Unstaged changes after reset:
M newfile

$ git status -s
M newfile

$ git diff
diff --git a/newfile b/newfile
index e69de29..fff92cf 100644
--- a/newfile
+++ b/newfile
@@ -0,0 +1 @@
+this is new

从上面的操作来看,git reset HEAD 并没有改变工作区。改变的只有暂存区文件。将git add newfile 重置为add之前的状态。如果get reset HEAD^^ 就会发现引用也被改变,暂存区也被改变,只有工作区没有变化

–hard

使用hard模式会执行以下三个操作:

  • 替换引用的指向。引用指向新的提交id
  • 替换暂存区。替换后,暂存区的内容和引用指向目录树一致
  • 提供工作区。替换后,工作区的内容变得和暂存区一致(引用、暂存区、工作区状态一致)

–soft

soft模式会执行一个操作:

  • 更改引用的指向,不改变暂存区和工作区

–mixed

mixed模式同上面的git reset HEAD~n

  • 将引用和暂存区回退到执行提交,工作区不变