"动"话 Git (2): 深入理解 git reset 和 git checkout 命令

  • 原创
  • Madman
  • /
  • /
  • 0
  • 3396 次阅读

动话Git.png

Synopsis: 在《"动"话 Git》系列的第二篇中,我们将继续通过生动的动态图片和精彩的图解,真正理解 git reset 和 git checkout 并恰当地运用它们

1、复习三棵树架构

我们先复习下第一篇中讲的 Git 三棵树(文件引用的集合)架构:

1.1 查看提交(快照)内容

一般情况(未分离头指针)下,HEAD(头指针) 指向当前分支上的最后一次提交的快照

[root@cscs-100-116-20-141 my_project]# cat .git/HEAD 
ref: refs/heads/master
[root@cscs-100-116-20-141 my_project]# git cat-file -t HEAD
commit
[root@cscs-100-116-20-141 my_project]# git cat-file -p HEAD
tree d73cc53e075d8d54f32ec4041795d5051e8bf46d
parent 8bd7143de5d6ce6dd83d742fbb0f85906347decd
author Madman <Madman@163.com> 1705474710 +0800
committer Madman <Madman@163.com> 1705479040 +0800

renamed: README -> README.md; rebuild soft link help.txt

# 显示了 HEAD 快照实际的目录列表,以及其中每个文件的 SHA-1 哈希值
# 查看指定提交或分支的快照内容: git ls-tree -r 8bd7143 和 git ls-tree -r bugFix
[root@cscs-100-116-20-141 my_project]# git ls-tree -r HEAD
100644 blob 5e1b2cf75e9102b1e84c26d4c7bf2155fe6db005    README.md
120000 blob 42061c01a1c70097d1e4579f29a5adf40abdec95    help.txt
100644 blob b1b716105590454bfc4c0247f193a04088f39c7f    init.txt
100644 blob a258e13b63ae84eda30d4d4fc940dc58159e32f7    utils/C1.txt
100644 blob 18b5e4c8c8a5568eb522f1599714946678c2d8bd    utils/C2.txt
# 可以继续查看快照里面文件版本内容
[root@cscs-100-116-20-141 my_project]# git cat-file -p 5e1b2cf
init
hello

1.2 查看暂存区内容

staging area/index(暂存区) 是预期的下一次提交的快照。当你 检出(checkout) 一个分支或标签时,它会移动 HEAD 指向该分支或标签,将指向的提交的内容复制到 暂存区工作区

[root@cscs-100-116-20-141 my_project]# git ls-files -s
100644 5e1b2cf75e9102b1e84c26d4c7bf2155fe6db005 0   README.md
120000 42061c01a1c70097d1e4579f29a5adf40abdec95 0   help.txt
100644 b1b716105590454bfc4c0247f193a04088f39c7f 0   init.txt
100644 a258e13b63ae84eda30d4d4fc940dc58159e32f7 0   utils/C1.txt
100644 18b5e4c8c8a5568eb522f1599714946678c2d8bd 0   utils/C2.txt
# 可以继续查看快照里面文件版本内容
[root@cscs-100-116-20-141 my_project]# git cat-file -p b1b7161
init

1.3 查看工作区内容

HEAD暂存区 这两棵树以一种高效但并不直观的方式,将它们的内容存储在 .git/ 目录中。项目目录下除了 .git 目录外就是你的 working directory(工作区) 了。在你将修改提交到暂存区并记录到历史版本库之前,可以随意更改

[root@cscs-100-116-20-141 my_project]# tree
.
├── help.txt -> README.md
├── init.txt
├── README.md
└── utils
    ├── C1.txt
    └── C2.txt

1 directory, 5 files

为了后面的讲解,我们先提交三次、涉及多个文件的新增、修改、删除操作:

第 1 次提交: 新增 a.txt 文件(v1 版本)

[root@cscs-100-116-20-141 my_project]# echo "version 1 of a.txt" > a.txt
[root@cscs-100-116-20-141 my_project]# git add a.txt
[root@cscs-100-116-20-141 my_project]# git commit -m "Add a.txt(v1)"
[master 90471f3] Add a.txt(v1)
 1 file changed, 1 insertion(+)
 create mode 100644 a.txt

第 2 次提交: 修改 a.txt 文件(v2 版本); 新增 b.txt 文件(v1 版本)

[root@cscs-100-116-20-141 my_project]# echo "version 2 of a.txt" > a.txt
[root@cscs-100-116-20-141 my_project]# echo "version 1 of b.txt" > b.txt
[root@cscs-100-116-20-141 my_project]# git add a.txt b.txt 
[root@cscs-100-116-20-141 my_project]# git commit -m "Modified a.txt(v2); add b.txt(v1)"
[master c32d77f] Modified a.txt(v2); add b.txt(v1)
 2 files changed, 2 insertions(+), 1 deletion(-)
 create mode 100644 b.txt

第 3 次提交: 修改 a.txt 文件(v3 版本); 删除 b.txt 文件; 新增 c.txt 文件(v1 版本)

[root@cscs-100-116-20-141 my_project]# echo "version 3 of a.txt" > a.txt
[root@cscs-100-116-20-141 my_project]# git rm b.txt
rm 'b.txt'
[root@cscs-100-116-20-141 my_project]# echo "version 1 of c.txt" > c.txt
[root@cscs-100-116-20-141 my_project]# git add a.txt c.txt
[root@cscs-100-116-20-141 my_project]# git commit -m "Modified a.txt(v3); del b.txt(v1); add c.txt(v1)"
[master c2da288] Modified a.txt(v3); del b.txt(v1); add c.txt(v1)
 3 files changed, 2 insertions(+), 2 deletions(-)
 delete mode 100644 b.txt
 create mode 100644 c.txt

[root@cscs-100-116-20-141 my_project]# git log --all --graph --pretty=oneline --abbrev-commit --decorate=short
* c2da288 (HEAD, master) Modified a.txt(v3); del b.txt(v1); add c
* c32d77f Modified a.txt(v2); add b.txt(v1)
* 90471f3 Add a.txt(v1)
* d9e659d (develop, bugFix3, bugFix) renamed: README -> README.md
* 8bd7143 Add C2.txt
| * 6c586a3 (bugFix2) bug fix 02
|/  
* 3174178 Add C1.txt; modify README
* 1d36d90 init repo

2、git reset

git-reset - Reset current HEAD to the specified state

在进行 git reset 实验前,我们再 基于 c2da288 提交 进行一些变更:修改 a.txt 文件(v4 版本)并添加到暂存区; 修改 c.txt 文件(v2 版本)不添加到暂存区; 新增 d.txt 文件(v1 版本)尚未通过 Git 追踪管理

[root@cscs-100-116-20-141 my_project]# echo "version 4 of a.txt" > a.txt
[root@cscs-100-116-20-141 my_project]# echo "version 2 of c.txt" > c.txt
[root@cscs-100-116-20-141 my_project]# echo "version 1 of d.txt" > d.txt
[root@cscs-100-116-20-141 my_project]# git status
# On branch master
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   a.txt
#   modified:   c.txt
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#   d.txt
no changes added to commit (use "git add" and/or "git commit -a")
[root@cscs-100-116-20-141 my_project]# git add a.txt
[root@cscs-100-116-20-141 my_project]# git diff
diff --git a/c.txt b/c.txt
index 69ef72f..c4f7d1f 100644
--- a/c.txt
+++ b/c.txt
@@ -1 +1 @@
-version 1 of c.txt
+version 2 of c.txt
[root@cscs-100-116-20-141 my_project]# git diff --cached
diff --git a/a.txt b/a.txt
index 8775fe8..e0b4d4b 100644
--- a/a.txt
+++ b/a.txt
@@ -1 +1 @@
-version 3 of a.txt
+version 4 of a.txt

2.1 提交级别的重置

说明: 下面的实验我都是指定了 HEAD^,实际使用时可以指定一个提交的 SHA-1 哈希值或分支名

(1) git reset --soft

执行 git reset --soft HEAD^ 后,Git 只是执行了 reset 的第一个步骤(同时移动 HEAD 与它指向的分支到前一个提交节点),而暂存区和工作区的内容不变,即它本质上是 撤销了上一次提交

[root@cscs-100-116-20-141 my_project]# git reset --soft HEAD^

# 1.HEAD 和 master 指向的提交节点变了
[root@cscs-100-116-20-141 my_project]# git log --all --graph --pretty=oneline --abbrev-commit --decorate=short
* c32d77f (HEAD, master) Modified a.txt(v2); add b.txt(v1)
* 90471f3 Add a.txt(v1)
* d9e659d (develop, bugFix3, bugFix) renamed: README -> README.md
* 8bd7143 Add C2.txt
| * 6c586a3 (bugFix2) bug fix 02
|/  
* 3174178 Add C1.txt; modify README
* 1d36d90 init repo

# 暂存区未被覆盖,且它与 HEAD 的差异,跟执行 reset --soft 前相比不一样,因为 HEAD 后退了一个提交
[root@cscs-100-116-20-141 my_project]# git diff --cached
diff --git a/a.txt b/a.txt
index 09b8dbb..e0b4d4b 100644
--- a/a.txt
+++ b/a.txt
@@ -1 +1 @@
-version 2 of a.txt
+version 4 of a.txt
diff --git a/b.txt b/b.txt
deleted file mode 100644
index 9fd018e..0000000
--- a/b.txt
+++ /dev/null
@@ -1 +0,0 @@
-version 1 of b.txt
diff --git a/c.txt b/c.txt
new file mode 100644
index 0000000..69ef72f
--- /dev/null
+++ b/c.txt
@@ -0,0 +1 @@
+version 1 of c.txt

# 工作区和暂存区都未被覆盖,所以它与暂存区的差异,跟执行 reset --soft 前相比一样
[root@cscs-100-116-20-141 my_project]# git diff
diff --git a/c.txt b/c.txt
index 69ef72f..c4f7d1f 100644
--- a/c.txt
+++ b/c.txt
@@ -1 +1 @@
-version 1 of c.txt
+version 2 of c.txt

[root@cscs-100-116-20-141 my_project]# git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   a.txt
#   deleted:    b.txt
#   new file:   c.txt
#
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   c.txt
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#   d.txt

用途:

  • 有时候我们提交完了才发现漏掉了几个文件没有添加,或者提交信息写错了。你可以选择再提交一次,但是这样的话提交历史不太干净(修改前后共 2 个提交节点)。还可以使用 git reset --soft HEAD^ 然后继续增删改文件,再次运行 git commit 即可(修改前后共 1 个提交节点,相当于用一个新的提交替换了旧的提交);或者使用 git commit --amend 效果也一样
  • 或者在开发一个功能时提交了太多次需要 压缩(squash ) 成一次(还可以继续增删改文件)提交,保持提交历史干净。可以使用 git reset --soft HEAD~<num> 然后继续增删改文件,再次运行 git commit 即可(相当于用一个新的提交替换了 num 个旧的提交))

(2) git reset [--mixed]

恢复到进行 git reset 实验前的状态!

执行 git reset HEAD^ 或者 git reset --mixed HEAD^ 后,Git 先执行了 reset 的第一个步骤(同时移动 HEAD 与它指向的分支到前一个提交节点),然后执行第二个步骤(用 HEAD 指向的当前快照的内容来 覆盖 到暂存区),而工作区的内容不变,即它本质上是 撤销了上一次提交,并取消 暂存区 的所有内容

[root@cscs-100-116-20-141 my_project]# git reset HEAD^
Unstaged changes after reset:
M   a.txt
D   b.txt

# 1.HEAD 和 master 指向的提交节点变了
[root@cscs-100-116-20-141 my_project]# git log --all --graph --pretty=oneline --abbrev-commit --decorate=short
* c32d77f (HEAD, master) Modified a.txt(v2); add b.txt(v1)
* 90471f3 Add a.txt(v1)
* d9e659d (develop, bugFix3, bugFix) renamed: README -> README.md
* 8bd7143 Add C2.txt
| * 6c586a3 (bugFix2) bug fix 02
|/  
* 3174178 Add C1.txt; modify README
* 1d36d90 init repo

# 2.暂存区被覆盖,它与 HEAD 现在没有差异
[root@cscs-100-116-20-141 my_project]# git diff --cached

# 工作区未被覆盖,但是暂存区的内容被覆盖了,所以工作区与暂存区的差异,跟执行 reset --soft 前相比不一样
[root@cscs-100-116-20-141 my_project]# git diff
diff --git a/a.txt b/a.txt
index 09b8dbb..e0b4d4b 100644
--- a/a.txt
+++ b/a.txt
@@ -1 +1 @@
-version 2 of a.txt
+version 4 of a.txt
diff --git a/b.txt b/b.txt
deleted file mode 100644
index 9fd018e..0000000
--- a/b.txt
+++ /dev/null
@@ -1 +0,0 @@
-version 1 of b.txt

[root@cscs-100-116-20-141 my_project]# git status
# On branch master
# Changes not staged for commit:
#   (use "git add/rm <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   a.txt
#   deleted:    b.txt
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#   c.txt
#   d.txt
no changes added to commit (use "git add" and/or "git commit -a")

(3) git reset --hard

恢复到进行 git reset 实验前的状态!

执行 git reset --hard HEAD^ 后,Git 先执行了 reset 的第一个步骤(同时移动 HEAD 与它指向的分支到前一个提交节点),然后执行第二个步骤(用 HEAD 指向的当前快照的内容来 覆盖 到暂存区),最后执行第三个步骤(用 HEAD 指向的当前快照的内容来 覆盖 到工作区中已被 Git 追踪管理的文件),即它本质上是 撤销了上一次提交,并取消 暂存区工作区 的所有内容

危险: 会丢失工作区中所有已被 Git 追踪管理但是新变更尚未提交(比如 a.txt 的 v4 版本永久丢失)的内容!

[root@cscs-100-116-20-141 my_project]# git reset --hard HEAD^
HEAD is now at c32d77f Modified a.txt(v2); add b.txt(v1)

# 1.HEAD 和 master 指向的提交节点变了
[root@cscs-100-116-20-141 my_project]# git log --all --graph --pretty=oneline --abbrev-commit --decorate=short
* c32d77f (HEAD, master) Modified a.txt(v2); add b.txt(v1)
* 90471f3 Add a.txt(v1)
* d9e659d (develop, bugFix3, bugFix) renamed: README -> README.md
* 8bd7143 Add C2.txt
| * 6c586a3 (bugFix2) bug fix 02
|/  
* 3174178 Add C1.txt; modify README
* 1d36d90 init repo

# 2.暂存区被覆盖,它与 HEAD 现在没有差异
[root@cscs-100-116-20-141 my_project]# git diff --cached

# 3.工作区被覆盖,它与暂存区现在没有差异
[root@cscs-100-116-20-141 my_project]# git diff

# d.txt 这种 Untracked files 没有影响
[root@cscs-100-116-20-141 my_project]# git status
# On branch master
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#   d.txt
nothing added to commit but untracked files present (use "git add" to track)

2.2 文件级别的重置

我们可以提供 路径(paths) 来重置指定文件,跟提交级别的重置不同的是,若指定了一个路径,将会跳过 reset 步骤 1(不会移动 HEAD 与它指向的分支),并且将它的作用范围限定为指定的文件或文件集合。因为 HEAD 只是一个指针,你无法让它同时指向两个提交中各自的一部分。不过暂存区和工作区可以部分更新,所以会继续进行 reset 步骤 2/reset 步骤 3

注意: Cannot do soft reset with paths. / Cannot do hard reset with paths.

(1) 取消暂存文件

# 将一个或多个文件从暂存区中移除,用 HEAD 指向的快照中的对应文件覆盖到暂存区;而工作区的内容不变。本质上只是将 <file>... 从 HEAD 复制到暂存区中
# 它是 git reset --mixed HEAD <file>... 的简写形式,因为你既没有指定一个提交的 SHA-1 或分支名
# 还可以简写为 git reset <file>...
git reset HEAD <file>...

# 将一个或多个文件从暂存区中移除,通过具体指定一个提交(90471f3)或分支来拉取该文件的对应版本;而工作区的内容不变
git reset 90471f3 <file>...
git reset master <file>...

恢复到进行 git reset 实验前的状态!

我们执行 git reset 90471f3 a.txt 来取消暂存区中 a.txt,并替换为提交(90471f3)中的版本:

# 重置 a.txt 前,HEAD 指向的快照中 a.txt 内容为 version 3 of a.txt
[root@cscs-100-116-20-141 my_project]# git ls-tree -r HEAD
100644 blob 5e1b2cf75e9102b1e84c26d4c7bf2155fe6db005    README.md
100644 blob 8775fe8be49c3a4b67b8106fea615eb5ab50591a    a.txt
100644 blob 69ef72f1f278f34d8a8e9c469ccf2196d8451bab    c.txt
120000 blob 42061c01a1c70097d1e4579f29a5adf40abdec95    help.txt
100644 blob b1b716105590454bfc4c0247f193a04088f39c7f    init.txt
100644 blob a258e13b63ae84eda30d4d4fc940dc58159e32f7    utils/C1.txt
100644 blob 18b5e4c8c8a5568eb522f1599714946678c2d8bd    utils/C2.txt
[root@cscs-100-116-20-141 my_project]# git cat-file -p 8775fe8
version 3 of a.txt

# 重置 a.txt 前,暂存区中 a.txt 内容为 version 4 of a.txt,因为工作区中修改了 a.txt 并执行 git add 了
[root@cscs-100-116-20-141 my_project]# git ls-files -s
100644 5e1b2cf75e9102b1e84c26d4c7bf2155fe6db005 0   README.md
100644 e0b4d4bdbc6e45d092a2df4942f6863c781a0527 0   a.txt
100644 69ef72f1f278f34d8a8e9c469ccf2196d8451bab 0   c.txt
120000 42061c01a1c70097d1e4579f29a5adf40abdec95 0   help.txt
100644 b1b716105590454bfc4c0247f193a04088f39c7f 0   init.txt
100644 a258e13b63ae84eda30d4d4fc940dc58159e32f7 0   utils/C1.txt
100644 18b5e4c8c8a5568eb522f1599714946678c2d8bd 0   utils/C2.txt
[root@cscs-100-116-20-141 my_project]# git cat-file -p e0b4d4b
version 4 of a.txt
[root@cscs-100-116-20-141 my_project]# cat a.txt 
version 4 of a.txt

# 从提交(90471f3)中拉取 a.txt 的对应版本,覆盖到暂存区
[root@cscs-100-116-20-141 my_project]# git reset 90471f3 a.txt
Unstaged changes after reset:
M   a.txt
M   c.txt

# 重置 a.txt 后,暂存区中 a.txt 内容为 version 1 of a.txt,将 90471f3 提交中的 a.txt 复制到了暂存区。c.txt 还存在,说明只单独恢复了 a.txt,而不是将 90471f3 整个提交覆盖到暂存区
[root@cscs-100-116-20-141 my_project]# git ls-files -s
100644 5e1b2cf75e9102b1e84c26d4c7bf2155fe6db005 0   README.md
100644 0fdd823d5eae387324c523ade942d5e3b19f2993 0   a.txt
100644 69ef72f1f278f34d8a8e9c469ccf2196d8451bab 0   c.txt
120000 42061c01a1c70097d1e4579f29a5adf40abdec95 0   help.txt
100644 b1b716105590454bfc4c0247f193a04088f39c7f 0   init.txt
100644 a258e13b63ae84eda30d4d4fc940dc58159e32f7 0   utils/C1.txt
100644 18b5e4c8c8a5568eb522f1599714946678c2d8bd 0   utils/C2.txt
[root@cscs-100-116-20-141 my_project]# git cat-file -p 0fdd823
version 1 of a.txt

# 重置 a.txt 后,工作区的 a.txt 内容不受影响
[root@cscs-100-116-20-141 my_project]# cat a.txt 
version 4 of a.txt

# 重置 a.txt 后,HEAD 指向的快照中 a.txt 内容还是 version 3 of a.txt,即没有移动 HEAD 与它指向的分支
[root@cscs-100-116-20-141 my_project]# git ls-tree -r HEAD
100644 blob 5e1b2cf75e9102b1e84c26d4c7bf2155fe6db005    README.md
100644 blob 8775fe8be49c3a4b67b8106fea615eb5ab50591a    a.txt
100644 blob 69ef72f1f278f34d8a8e9c469ccf2196d8451bab    c.txt
120000 blob 42061c01a1c70097d1e4579f29a5adf40abdec95    help.txt
100644 blob b1b716105590454bfc4c0247f193a04088f39c7f    init.txt
100644 blob a258e13b63ae84eda30d4d4fc940dc58159e32f7    utils/C1.txt
100644 blob 18b5e4c8c8a5568eb522f1599714946678c2d8bd    utils/C2.txt
[root@cscs-100-116-20-141 my_project]# git cat-file -p 8775fe8
version 3 of a.txt

3、git checkout

3.1 提交级别的检出

运行 git checkout <branch>/<commit> 与运行 git reset --hard <branch>/<commit> 非常相似,它会更新所有三棵树使其看起来像 <branch>/<commit>,不过有两点重要的区别:

  • git checkout 只会移动 HEAD 本身、不移动原分支。git reset --hard 会同时移动 HEAD 与它指向的分支
  • git checkout 对工作目录是安全的,如果检查到工作区有修改内容还未提交,Git 会阻止检出。而 git reset --hard 则会不做检查就全面覆盖所有已追踪的文件

恢复到进行 git reset 实验前的状态!

因为工作区有修改内容未提交,所以 Git 会阻止检出:

[root@cscs-100-116-20-141 my_project]# git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   a.txt
#
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   c.txt
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#   d.txt
[root@cscs-100-116-20-141 my_project]# git checkout 90471f3
error: Your local changes to the following files would be overwritten by checkout:
    a.txt
Please, commit your changes or stash them before you can switch branches.
error: Your local changes to the following files would be overwritten by checkout:
    c.txt
Please, commit your changes or stash them before you can switch branches.
Aborting

执行 git stash 命令将工作区变更内容存起来后,可以检出到 90471f3:

[root@cscs-100-116-20-141 my_project]# git checkout 90471f3
Note: checking out '90471f3'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b new_branch_name

HEAD is now at 90471f3... Add a.txt(v1)

[root@cscs-100-116-20-141 my_project]# git status
# HEAD detached at 90471f3
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#   d.txt
nothing added to commit but untracked files present (use "git add" to track)

[root@cscs-100-116-20-141 my_project]# git diff
[root@cscs-100-116-20-141 my_project]# git diff --cached

[root@cscs-100-116-20-141 my_project]# tree
.
├── a.txt
├── d.txt
├── help.txt -> README.md
├── init.txt
├── README.md
└── utils
    ├── C1.txt
    └── C2.txt

1 directory, 7 files
[root@cscs-100-116-20-141 my_project]# cat a.txt 
version 1 of a.txt

3.2 文件级别的检出

(1) 撤消对文件的修改

运行 git checkout HEAD/<branch>/<commit> <file>... 会像 git reset HEAD/<branch>/<commit> <file>... 那样用对应提交中的那个文件来更新暂存区,同时它也会覆盖工作区中对应的文件,所以该命令对工作区并不安全。另外,它也不会移动 HEAD

危险: 会丢失工作区中所有已被 Git 追踪管理但是新变更尚未提交(比如 a.txt 的 v4 版本永久丢失)的内容!

恢复到进行 git reset 实验前的状态!

# 检出 a.txt 前,HEAD 指向的快照中 a.txt 内容为 version 3 of a.txt
[root@cscs-100-116-20-141 my_project]# git ls-tree -r HEAD
100644 blob 5e1b2cf75e9102b1e84c26d4c7bf2155fe6db005    README.md
100644 blob 8775fe8be49c3a4b67b8106fea615eb5ab50591a    a.txt
100644 blob 69ef72f1f278f34d8a8e9c469ccf2196d8451bab    c.txt
120000 blob 42061c01a1c70097d1e4579f29a5adf40abdec95    help.txt
100644 blob b1b716105590454bfc4c0247f193a04088f39c7f    init.txt
100644 blob a258e13b63ae84eda30d4d4fc940dc58159e32f7    utils/C1.txt
100644 blob 18b5e4c8c8a5568eb522f1599714946678c2d8bd    utils/C2.txt
[root@cscs-100-116-20-141 my_project]# git cat-file -p 8775fe8
version 3 of a.txt

# 检出 a.txt 前,暂存区中 a.txt 内容为 version 4 of a.txt,因为工作区中修改了 a.txt 并执行 git add 了
[root@cscs-100-116-20-141 my_project]# git ls-files -s
100644 5e1b2cf75e9102b1e84c26d4c7bf2155fe6db005 0   README.md
100644 e0b4d4bdbc6e45d092a2df4942f6863c781a0527 0   a.txt
100644 69ef72f1f278f34d8a8e9c469ccf2196d8451bab 0   c.txt
120000 42061c01a1c70097d1e4579f29a5adf40abdec95 0   help.txt
100644 b1b716105590454bfc4c0247f193a04088f39c7f 0   init.txt
100644 a258e13b63ae84eda30d4d4fc940dc58159e32f7 0   utils/C1.txt
100644 18b5e4c8c8a5568eb522f1599714946678c2d8bd 0   utils/C2.txt
[root@cscs-100-116-20-141 my_project]# git cat-file -p e0b4d4b
version 4 of a.txt
[root@cscs-100-116-20-141 my_project]# cat a.txt 
version 4 of a.txt

# 从提交(90471f3)中拉取 a.txt 的对应版本,覆盖到暂存区、工作区
[root@cscs-100-116-20-141 my_project]# git checkout 90471f3 a.txt

# 检出 a.txt 后,暂存区中 a.txt 内容为 version 1 of a.txt,将 90471f3 提交中的 a.txt 复制到了暂存区。c.txt 还存在,说明只单独恢复了 a.txt,而不是将 90471f3 整个提交覆盖到暂存区
[root@cscs-100-116-20-141 my_project]# git ls-files -s
100644 5e1b2cf75e9102b1e84c26d4c7bf2155fe6db005 0   README.md
100644 0fdd823d5eae387324c523ade942d5e3b19f2993 0   a.txt
100644 69ef72f1f278f34d8a8e9c469ccf2196d8451bab 0   c.txt
120000 42061c01a1c70097d1e4579f29a5adf40abdec95 0   help.txt
100644 b1b716105590454bfc4c0247f193a04088f39c7f 0   init.txt
100644 a258e13b63ae84eda30d4d4fc940dc58159e32f7 0   utils/C1.txt
100644 18b5e4c8c8a5568eb522f1599714946678c2d8bd 0   utils/C2.txt
[root@cscs-100-116-20-141 my_project]# git cat-file -p 0fdd823
version 1 of a.txt
[root@cscs-100-116-20-141 my_project]# cat c.txt 
version 2 of c.txt

# 检出 a.txt 后,工作区的 a.txt 内容也被覆盖
[root@cscs-100-116-20-141 my_project]# cat a.txt 
version 1 of a.txt

# 检出 a.txt 后,HEAD 指向的快照中 a.txt 内容还是 version 3 of a.txt,即没有移动 HEAD 与它指向的分支
[root@cscs-100-116-20-141 my_project]# git ls-tree -r HEAD
100644 blob 5e1b2cf75e9102b1e84c26d4c7bf2155fe6db005    README.md
100644 blob 8775fe8be49c3a4b67b8106fea615eb5ab50591a    a.txt
100644 blob 69ef72f1f278f34d8a8e9c469ccf2196d8451bab    c.txt
120000 blob 42061c01a1c70097d1e4579f29a5adf40abdec95    help.txt
100644 blob b1b716105590454bfc4c0247f193a04088f39c7f    init.txt
100644 blob a258e13b63ae84eda30d4d4fc940dc58159e32f7    utils/C1.txt
100644 blob 18b5e4c8c8a5568eb522f1599714946678c2d8bd    utils/C2.txt
[root@cscs-100-116-20-141 my_project]# git cat-file -p 8775fe8
version 3 of a.txt

4. 总结

是否移动 HEAD 或分支 是否复制到暂存区 是否复制到工作区 工作区是否安全
提交级别
git reset --soft <commit> HEAD+分支
git reset [--mixed] <commit> HEAD+分支
git reset --hard <commit> HEAD+分支
git checkout <commit> HEAD
文件级别
git reset <commit> <file>...
git checkout <commit> <file>...
附件: my_project.tar.gz
分类: 杂记
标签: Git
未经允许不得转载: LIFE & SHARE - 王颜公子 » "动"话 Git (2): 深入理解 git reset 和 git checkout 命令

分享

作者

作者头像

Madman

如需 Linux / Python 相关问题付费解答,请按如下方式联系我

0 条评论

暂时还没有评论.

专题系列

文章目录