本文主要介绍通过reset如何将 HEAD重置到特定的状态

常见工作流程

三个区域

HEAD:存储在.git目录,上一次提交对象,下一次提交对象的父提交对象
Index:存储在.git目录,暂存区域,用于下一次提交
Working Directory:实际的文件

Working Directory->Index->HEAD

v1 : touch file.txt

git init后尚未有commit,此时master指向不明确

1
2
3
4
5
6
7
$ git init

$ touch file.txt

$ gst -sb
## Initial commit on master
?? file.txt

v1 : git add

1
2
3
4
5
$ git add file.txt

$ gst -sb
## Initial commit on master
A file.txt

v1 : git commit

1
2
3
4
5
6
7
8
9
10
11
$ git commit -m 'file.txt v1'
[master (root-commit) a5c8857] file.txt v1
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 file.txt

$ gst
On branch master
nothing to commit, working directory clean

$ git branch -vv
* master a5c8857 file.txt v1

v2 : edit file.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ echo 'v2' > file.txt

$ gst -sb
## master
M file.txt

$ git diff # changes between Index and working directory
diff --git a/file.txt b/file.txt
Index e69de29..8c1384d 100644
--- a/file.txt
+++ b/file.txt
@@ -0,0 +1 @@
+v2

$ git diff --cacheed # changes between the Index and your last commit , same for now

v2 : git add

1
2
3
4
5
$ git add file.txt

$ gst -sb
## master
M file.txt

v2 : git commit

1
2
3
4
5
6
7
8
9
10
$ git commit -m 'file.txt v2'
[master 7806e5f] file.txt v2
1 file changed, 1 insertion(+)

$ gst -sb
On branch master
nothing to commit, working directory clean

$ git branch -vv
* master 7806e5f file.txt v2

HEAD->Index->Working Directory

git checkout

  1. checkout的**本质是checkout提交对象**,将HEAD指向分支引用,分支引用再指向该提交对象
  2. HEAD的内容填充Index
  3. Index的内容填充Working Directory

git clone

  1. origin/master建立分支master,将HEAD指向master(一般情况下)
  2. HEAD的内容填充Index
  3. Index的内容填充Working Directory

reset

reset可以直接操纵HEADIndexWorking Directory的状态

提交历史

1
2
3
4
$ git log --oneline --decorate --graph --all
* 5ed53e1 (HEAD -> master) file.txt v3
* 7806e5f file.txt v2
* a5c8857 file.txt v1

git reset --soft

--soft:仅移动HEAD的指向,不会改变IndexWorking Directory的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ git reset --soft HEAD~

$ gst -sb
## master
M file.txt

$ git diff # Index on 5ed53e1 , Working Directory on 5ed53e1 , nothing printed

$ git diff --cached # HEAD on 7806e5f , Index on 5ed53e1
diff --git a/file.txt b/file.txt
index 8c1384d..29ef827 100644
--- a/file.txt
+++ b/file.txt
@@ -1 +1 @@
-v2
+v3

git reset --mixed

--mixed:是reset的默认行为,移动HEAD的指向,改变Index的内容,但不会改变Working Directory的内容
reset == reset --mixed

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
$ git branch -vv
* master 7806e5f file.txt v2

$ git reflog
7806e5f HEAD@{0}: reset: moving to HEAD~
5ed53e1 HEAD@{1}: commit: file.txt v3
7806e5f HEAD@{2}: commit: file.txt v2
a5c8857 HEAD@{3}: commit (initial): file.txt v1

$ git reset --hard HEAD@{1}
HEAD is now at 5ed53e1 file.txt v3

$ git reset HEAD~
Unstaged changes after reset:
M file.txt

$ git diff # Index on 7806e5f , Working Directory on 5ed53e1
diff --git a/file.txt b/file.txt
index 8c1384d..29ef827 100644
--- a/file.txt
+++ b/file.txt
@@ -1 +1 @@
-v2
+v3

$ git diff --cached # HEAD on 7806e5f , Index on 7806e5f , nothing printed

git reset --hard

--hard直接覆盖未提交的修改,谨慎使用,可以先stash起来(Stash的内容请参照「Git++ - Stash」)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ git branch -vv
* master 7806e5f file.txt v2

$ git reflog
7806e5f HEAD@{0}: reset: moving to HEAD~
5ed53e1 HEAD@{1}: reset: moving to HEAD@{1}
7806e5f HEAD@{2}: reset: moving to HEAD~
5ed53e1 HEAD@{3}: commit: file.txt v3
7806e5f HEAD@{4}: commit: file.txt v2
a5c8857 HEAD@{5}: commit (initial): file.txt v1

$ git reset --hard HEAD@{3}
HEAD is now at 5ed53e1 file.txt v3

# git reset --hard HEAD~
HEAD is now at 7806e5f file.txt v2

$ gst
On branch master
nothing to commit, working directory clean

$ git diff # Index on 7806e5f , Working Directory on 7806e5f , nothing printed

$ git diff --cached # HEAD on 7806e5f , Index on 7806e5f , nothing printed

git reset file

git reset $ref $file:不移动HEAD,只更新Index
等效于:get reset $ref -- $file

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
$ git branch -vv
* master 7806e5f file.txt v2

$ git reset --hard 5ed53e1
HEAD is now at 5ed53e1 file.txt v3

$ git reset HEAD~ file.txt # just update Index
Unstaged changes after reset:
M file.txt

$ gst -sb
## master
MM file.txt

$ git diff # Index on 7806e5f , Working Directory on 5ed53e1
diff --git a/file.txt b/file.txt
index 8c1384d..29ef827 100644
--- a/file.txt
+++ b/file.txt
@@ -1 +1 @@
-v2
+v3

$ git diff --cached # HEAD on 5ed53e1 , Index on 7806e5f
diff --git a/file.txt b/file.txt
index 29ef827..8c1384d 100644
--- a/file.txt
+++ b/file.txt
@@ -1 +1 @@
-v3
+v2

压缩提交

交互式rebase也能压缩提交,相关内容请参照「Git++ - 重写提交历史」,如果压缩的提交数量较大,选择reset --soft更便捷

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
$ git reset --hard 5ed53e1
HEAD is now at 5ed53e1 file.txt v3

$ git reset --soft HEAD~2

$ git branch -v
* master a5c8857 file.txt v1

$ gst -sb
## master
M file.txt

$ git diff --cached
diff --git a/file.txt b/file.txt
index e69de29..29ef827 100644
--- a/file.txt
+++ b/file.txt
@@ -0,0 +1 @@
+v3

$ git commit -m 'file.txt v2+v3'
[master 0896cad] file.txt v2+v3
1 file changed, 1 insertion(+)

$ git log --oneline --decorate --graph --all
* 0896cad (HEAD -> master) file.txt v2+v3
* a5c8857 file.txt v1

checkout vs reset

checkout更安全

checkout会进行冲突检查尝试简单合并reset --hard直接全面替换

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
$ git branch -vv
* branchA 3c38e92 b.txt
branchB 3c38e92 b.txt

$ gst -sb
## branchA
A a1.txt
?? a2.txt

$ git checkout branchB # a1.txt is staged , auto merge
A a1.txt
Switched to branch 'branchB'

$ gst -sb
## branchB
A a1.txt
?? a2.txt

$ git checkout branchA
A a1.txt
Switched to branch 'branchA'

$ git reset --hard branchB # update HEAD , Index , Working Directory
HEAD is now at 3c38e92 b.txt

$ gst -sb # a1.txt is lost!
## branchA
?? a2.txt
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
$ git log --oneline --decorate --graph --all
* a823c74 (dev) dev
* 50a172e (HEAD -> master) init commit

$ echo 'master' > file

$ git diff dev # conflict!!
diff --git a/file b/file
index 38f8e88..1f7391f 100644
--- a/file
+++ b/file
@@ -1 +1 @@
-dev
+master

$ git checkout dev # check conflict
error: Your local changes to the following files would be overwritten by checkout:
file
Please, commit your changes or stash them before you can switch branches.
Aborting

$ git reset --hard dev # no conflict check
HEAD is now at a823c74 dev

$ cat file
dev

checkout不影响原先分支的指向

checkout只是移动HEAD,并尝试修改IndexWorking Directory的内容,但不会影响原先分支的指向
reset实际上不会移动HEAD的指向(HEAD->分支引用->提交对象,为了行文方便,上文将提分支指向的变动简单地归结为HEAD的移动),但会使得分支指向不同的提交对象

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
$ git log --oneline --decorate --graph --all
* 7f73c2a (HEAD -> dev) echo dev > file
* af4c251 (master) init commit

$ cat .git/HEAD
ref: refs/heads/dev

$ git branch -vv
* dev 7f73c2a echo dev > file
master af4c251 init commit

$ git checkout master
Switched to branch 'master'

$ cat .git/HEAD
ref: refs/heads/master

$ git branch -vv
dev 7f73c2a echo dev > file
* master af4c251 init commit # nothing changed

-------------------------------
$ git reset dev
Unstaged changes after reset:
M file

$ cat .git/HEAD
ref: refs/heads/master

$ git branch -vv
dev 7f73c2a echo dev > file
* master 7f73c2a echo dev > file # change to 7f73c2a(dev)

ckeout file会更新Working Directory

checkout $ref $file:不会移动HEAD分支指向,会更新IndexWorking Directory
reset $ref $file:不会移动HEAD分支指向,也不会更新Working Directory,只会会更新Index

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
35
36
37
$ git branch -vv
dev 6bdc578 b.txt
* master 61779da a.txt

$ git checkout dev b.txt # HEAD not changed , update Index and Working Directory

$ gst -sb
## master
A b.txt

$ git branch -vv
dev 6bdc578 b.txt
* master 61779da a.txt

$ git reset --hard master
On branch master
nothing to commit, working directory clean

$ git reset dev b.txt # HEAD and Working Directory not changed , update Index

$ git branch -vv
dev 6bdc578 b.txt
* master 61779da a.txt

$ gst -sb
## master
AD b.txt

$ git diff # Index has b.txt , Working has no b.txt -> delete file
diff --git a/b.txt b/b.txt
deleted file mode 100644
index e69de29..0000000

$ git diff --cached # HEAD has no b.txt , Index has b.txt -> new file
diff --git a/b.txt b/b.txt
new file mode 100644
index 0000000..e69de29