2. 重置 HEAD 指针

在使用 Git 进行版本控制时,每次提交后 HEAD 和当前的分支指针都指向最后一次提交的提交对象。下一次提交会将此刚提交的提交对象作为下一此提交的父对象。如果你希望丢弃本地提交。你的提交从当前的父提交重新创建一个新的提交,你可以使用 get reset 命令重置 HEAD 指针的位置。

之前我们已经完成了 my_project 的分支合并。最终仓库结构如下:

                                             HEAD
                                              |
                                          .--------.
                                          | master |
                                          `--------`
                                             |
+-------+     +-------+     +-------+     +-------+
|8cb34d1| <-- |5dcccb0| <-- |9d3b213| <-- |11caedf|
+-------+     +-------+     +-------+     +-------+
                  ^                         |
                  |         +-------+  <----+
                  `-------- |8e7f7c7|
                            +-------+
                               |
                            .--------.
                            |  myb1  |
                            `--------`

如果我们在主分支上再次进行提交则提交对象会放在 11caedf 这个提交对象之后。新创建的提交对象会指向 11caedf 让其作为父对象。如果我们希望丢弃 11caedf 这次提交,那我们可以将 HEAD 指针指向 9d3b213 这个提交对象,然后再次提交则新创建的提交对象会以 9d3b213 作为父对象。这这样 master 分支就丢弃了 11caedf, 此对象成为了悬空对象。此悬空对象在一定时间后会被 Git 的垃圾回收机制回收,从而达到丢弃 11caedf 这次提交的目的。

git reset 命令

作用: 移动当前分支的 HEAD 指针到指定的提交对象。并可以选择是否重置暂存区和工作区。

命令格式

git reset [选项] 提交对象位置

注意:这是一个比较危险的命令,请谨慎使用。

git reset 的常用选项

选项
说明
--mixed
移动 HEAD 指针,重置暂存区,不修改工作区。(默认)
--soft
移动 HEAD 指针,不修改暂存区和工作区。
--hard
移动 HEAD 指针,重置暂存区和工作区(未提交的修改会丢失,危险!)。
--merge
移动 HEAD 指针,重置暂存区,保留工作区中未提交的更改与重置提交之间的冲突。
--keep
如果工作区的可能更改,则终止执行当前命令。否则移动 HEAD 指针,重置暂存区。
其它选项
--no-refresh
移动 HEAD 指针后,暂存区不变。
-- 路径
重置指定路径的文件

示例:

1、 使用 git reset --hard 将之前工作区和暂存区的内容都清空,HEAD 指针位置不变,等同于 git restore --staged --worktree .,如:

weimingze@mzstudio:~/my_project$ git log --graph --all --oneline
*   11caedf (HEAD -> master) 将功能1合并到 master分支
|\
| * 8e7f7c7 (myb1) 完成了功能1
* | 9d3b213 完成了功能2
|/
* 5dcccb0 (origin/master) 魏明择在当前项目中修改了 README.md 文件,并添加了 website.txt 文件
* 8cb34d1 魏明择在当前项目中添加了 README.md文件
weimingze@mzstudio:~/my_project$ git status
On branch master
Your branch is ahead of 'origin/master' by 3 commits.
  (use "git push" to publish your local commits)

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
    modified:   website.txt

weimingze@mzstudio:~/my_project$ git reset --hard
HEAD is now at 11caedf 将功能1合并到 master分支
weimingze@mzstudio:~/my_project$ git log --graph --all --oneline
*   11caedf (HEAD -> master) 将功能1合并到 master分支
|\
| * 8e7f7c7 (myb1) 完成了功能1
* | 9d3b213 完成了功能2
|/
* 5dcccb0 (origin/master) 魏明择在当前项目中修改了 README.md 文件,并添加了 website.txt 文件
* 8cb34d1 魏明择在当前项目中添加了 README.md文件
weimingze@mzstudio:~/my_project$ git status
On branch master
Your branch is ahead of 'origin/master' by 3 commits.
  (use "git push" to publish your local commits)

nothing to commit, working tree clean

2、 使用 git reset --hard HEAD~1 将 当前指针重置到 9d3b213,如:

weimingze@mzstudio:~/my_project$ git reset --hard HEAD~1
HEAD is now at 9d3b213 完成了功能2
weimingze@mzstudio:~/my_project$ ls
README.md  function2.txt  website.txt
weimingze@mzstudio:~/my_project$ git log --graph --all --oneline
* 9d3b213 (HEAD -> master) 完成了功能2
| * 8e7f7c7 (myb1) 完成了功能1
|/
* 5dcccb0 (origin/master) 魏明择在当前项目中修改了 README.md 文件,并添加了 website.txt 文件
* 8cb34d1 魏明择在当前项目中添加了 README.md文件

可见当前 HEAD 指针指向了 9d3b213 提交对象。此时使用 git log 命令已经看不到最后一次合并后的节点 11caedf。目前的仓库状态如下所示:

                               HEAD
                                |
                            .--------.
                            | master |
                            `--------`
                                |
+-------+     +-------+     +-------+     +-------+
|8cb34d1| <-- |5dcccb0| <-- |9d3b213| <-- |11caedf|
+-------+     +-------+     +-------+     +-------+
                  ^                         |
                  |         +-------+  <----+
                  `-------- |8e7f7c7|
                            +-------+
                               |
                            .--------.
                            |  myb1  |
                            `--------`

假如此时在当前分支 master 上创建新的提交 6666666,则此提交对象的父节点则是 9d3b213。如下图所示

                                                           HEAD
                                                            |
                                                        .--------.
                                +--------------------+  | master |
                                |                    |  `--------`
                                V                    |      |
+-------+     +-------+     +-------+     +-------+  |  +-------+
|8cb34d1| <-- |5dcccb0| <-- |9d3b213| <-- |11caedf|  +- |6666666|
+-------+     +-------+     +-------+     +-------+     +-------+
                  ^                         |
                  |         +-------+  <----+
                  `-------- |8e7f7c7|
                            +-------+
                               |
                            .--------.
                            |  myb1  |
                            `--------`

如果我们需要将 HEAD 指针再次重定位到之前合并后的节点 11caedf, 则必须使用 git reset 11caedf 命令来实现。因为此时已经无法通过 HEAD 找到它的位置了。

引用日志

在上述示例中,在移动HEAD 指针后,就再也无法通过 git log 等命令看到合并后的提交对象 11caedf,也无法恢复本次提交后的内容。那我们如何查找到我们之前创建过的提交对象的历史记录呢。如果你有这样的需求,那你需要使用 git reflog 命令来查看引用日志了。

引用日志(简称 "reflogs")记录了分支头指针及其它引用在本地仓库中更新的时间点。它记录了仓库中 HEAD 指针的所有移动历史,可以帮助你找回 丢失 的提交、分支或更改。

git reflog 命令

作用: 用于查看和管理引用日志中的记录信息。

命令格式

git reflog [选项] [引用信息]

示例:

查看 git reset --hard HEAD~1 后的引用日志

weimingze@mzstudio:~/my_project$ git reflog
9d3b213 (HEAD -> master) HEAD@{0}: reset: moving to HEAD~1
11caedf HEAD@{1}: commit (merge): 将功能1合并到 master分支
9d3b213 (HEAD -> master) HEAD@{2}: commit (amend): 完成了功能2
2d700a1 HEAD@{3}: commit: 完成了功能1
5dcccb0 (origin/master) HEAD@{4}: checkout: moving from myb1 to master
8e7f7c7 (myb1) HEAD@{5}: commit: 完成了功能1
5dcccb0 (origin/master) HEAD@{6}: checkout: moving from master to myb1
5dcccb0 (origin/master) HEAD@{7}: checkout: moving from myb1 to master
...

上述日志中,第一列是提交对象的 哈希值,如 9d3b213。括号中是分支信息。HEAD@{n} 表示距离当前操作的第n次 HEAD指针的变动。冒号后面是具体的操作信息。

如果我们希望将 HEAD 再次指向 11caedf HEAD@{1}: commit (merge): 将功能1合并到 master分支 这次操作的位置。则我们可以使用 git reset 11caedfgit reset HEAD@{1} 来实现此操作。

实验:

使用 git reset 命令任意改动 HEAD 指针的位置,然后通过 git reflog 查看引用日志。最后通过引用日志中的信息,使用 git reset 重新将 HEAD 指针定位到你去到某个位置之前的位置。