1. Git三棵树

语法

commit模式

1
git reset (<branch_name>|HEAD|<tree-ish>|<tag_name>) [--mixed | --soft | --hard | --merge | --keep]

file模式

1
git reset (<branch_name>|HEAD|<tree-ish>|<tag_name>) [--] <file_path>

2. 正向流程

git的核心工作就是管理这三棵树。git add就是把你工作目录(Working Directory)的修改提交到暂存区(Index),git commit就是把暂存区的内容同步到仓库里作为一个快照,并移动HEAD指向新快照;

三棵树从右向左看 ⬅️
提交历史从左到右看➡️

2.1. git init

  • 让我们来可视化这个过程:假设进入到一个新目录,其中有一个文件,称其为该文件的 v1 版本,将它标记为蓝色,现在运行 git init,这会创建一个 Git 仓库,其中的 HEAD 引用指向未创建的 master 分支。

2.2. git add

  • 此时,只有工作目录有内容。现在想要提交这个文件,所以用 git add 来获取工作目录中的内容,并将其复制到索引中。

2.3. git commit

  • 接着运行 git commit,它会取得索引中的内容并将它保存为一个永久的快照,然后创建一个指向该快照的提交对象,最后更新 master 来指向本次提交。

2.4. edit file

  • 此时如果我们运行 git status,会发现没有任何改动,因为现在三棵树完全相同。现在想要对文件进行修改然后提交它,将会经历同样的过程;首先在工作目录中修改文件,称其为该文件的 v2 版本,并将它标记为红色:

2.5. git add

  • 如果现在运行 git status,将会看到文件显示在 “Changes not staged for commit” 下面并被标记为红色,这是因为该条目在索引与工作目录之间存在不同。接着运行 git add 来将它暂存到索引中:

2.6. git commit

  • 此时,由于索引和 HEAD 不同,若运行 git status 的话就会看到 “Changes to be committed” 下的该文件变为绿色,也就是说,现在预期的下一次提交与上一次提交不同。最后,运行 git commit 来完成提交:

3. 逆向流程

3.1. 精简理解

显然,git reset就是对上述行为的反向操作。

reset的本质其实有2个动作:

  1. 移动HEAD指针指向某一个快照。
  2. 通过指定参数(3选1,不写则默认mixed),来递进的控制,在哪几颗树上进行覆盖。
  • --soft——如图示中序号1所示情况,只改变指针指向的快照,即只覆盖本地版本库中的文件
  • --mixed——如图示中序号2所示情况,覆盖本地版本库中的文件的同时,也把快照内容同步到暂存区;
  • --hard——如图示中序号3所示情况,HEAD、Index改变的同时,本地工作区也被覆盖。三棵树全同步为指针指向的快照;

可以结合命令git diffgit diff --cachedgit ls-files -sgit show或者git cat-file -p进行验证理解
git diff请见Git进阶--13、Git比对各区差异-git diff

三棵树从左向右 ➡️
提交历史从右向左⬅️

  • 假设再次修改了 file.txt 文件并第三次提交它,现在的历史看起来是这样的:
  • 跟着 reset 看看它都做了什么,它以一种简单可预见的方式直接操纵这三棵树。

3.2. 深入解析

3.2.1. 移动 HEAD(–soft)

1
git reset --soft 9e5e
  • reset 做的第一件事是移动 HEAD 的指向,这与改变 HEAD 自身不同(checkout 所做的);reset 移动 HEAD 指向的分支。这意味着如果 HEAD 设置为 master 分支(例如正在 master 分支上),运行 git reset 9e5e6a4 将会使 master 指向 9e5e6a4:
  • 无论调用了何种形式的带有一个提交的 reset,它首先都会尝试这样做,使用 reset –soft,它将仅仅停在那儿。
  • 现在看一眼上图,理解一下发生的事情:它本质上是撤销了上一次 git commit 命令。当在运行 git commit 时,Git 会创建一个新的提交,并移动 HEAD 所指向的分支来使其指向该提交;当将它 reset 回 HEAD~(HEAD 的父结点)时,其实就是把该分支移动回原来的位置,而不会改变索引和工作目录。现在可以更新索引(或者只更新提交备注信息)之后再次运行 git commit。这与 Git进阶 — 5、Git-后悔药-重写历史-git commit--amend 所要做的事情是等同的。

3.2.2. 更新索引(–mixed)

  • 注意,如果现在运行 git status 的话,就会看到新的 HEAD 和以绿色标出的它和索引之间的区别。接下来,reset 会用 HEAD 指向的当前快照的内容来更新索引:
  • 如果指定 –mixed 选项,reset 将会在暂存区Index这里停止,这也是默认行为,所以如果没有指定任何选项(例子中只是 git reset HEAD~),这就是命令将会停止的地方。
  • 现在再看一眼上图,理解一下发生的事情:它依然会撤销一上次提交,但还会取消暂存所有的东西,于是,我们回滚到了所有 git add 和 git commit 的命令执行之前

3.2.3. 更新工作目录(–hard)

  • reset 要做的的第三件事情就是让工作目录看起来像索引。如果使用 –hard 选项,它将会继续这一步:

  • 现在来回想一下刚才发生的事情:撤销了最后的提交、git add 和 git commit 命令以及工作目录中的所有工作。

  • 必须注意,–hard 标记是 reset 命令唯一的危险用法,它也是 Git 会真正地销毁数据的仅有的几个操作之一。其他任何形式的 reset 调用都可以轻松撤消,但是 –hard 选项不能,因为它强制覆盖了工作目录中的文件。在这种特殊情况下,Git 数据库中的一个提交内还留有该文件的 v3 版本,可以通过 reflog 来找回它,但是若该文件还未提交,Git 仍会覆盖它从而导致无法轻松恢复。若想恢复则需要其他命令,请见Git进阶 — 6、Git找回丢失代码 git fsck --lost-found
    WD不安全操作,请见Git进阶--8、Git-后悔药-回退撤销-大总结

  • reset 命令会以特定的顺序重写这三棵树,在指定以下选项时停止:

    • 移动 HEAD 分支的指向 (若指定了 –soft,则到此停止);
    • 使索引看起来像 HEAD (若未指定 –hard,则到此停止);
    • 使工作目录看起来像索引。
3.2.3.1. 演示示例

📢 由于引用的是网络动图,所以演示示例跟动图中的SHA-1值不一致,请注意辨别理解

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
cd ~

rm -rf recovery

mkdir recovery;cd recovery

git init .

echo "hello v1" > file.txt

git status

git add .

git commit -m "v1"

echo "hello v2" >> file.txt

git add .

git commit -m "v2"

echo "hello v3" >> file.txt

git add .

git commit -m "v3"

git log

git reflog查看引用历史

1
2
3
4
5
6
7
d4e752c (HEAD -> master) HEAD@{0}: commit: v3

e32d991 HEAD@{1}: commit: v2

c3daf0c HEAD@{2}: commit (initial): v1

(END)

git log

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
commit d4e752c4d65dddd8a45962c7bb1937256534cd48 (HEAD -> master)

Author: luoweile <luoweile2008@126.com>

Date:   Thu Sep 15 12:39:51 2022 +0800



    v3



commit e32d991ec050e7b8d5183c1c9c02206d5b59982f

Author: luoweile <luoweile2008@126.com>

Date:   Thu Sep 15 12:39:51 2022 +0800



    v2



commit c3daf0c0af95d50ec25af70f48741bb1e3ff27ca

Author: luoweile <luoweile2008@126.com>

Date:   Thu Sep 15 12:39:51 2022 +0800



    v1

(END)

使用–soft回到v2 commit之后,git log如下

1
git reset --soft e32d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
commit e32d991ec050e7b8d5183c1c9c02206d5b59982f (HEAD -> master)

Author: luoweile <luoweile2008@126.com>

Date:   Thu Sep 15 12:39:51 2022 +0800



    v2



commit c3daf0c0af95d50ec25af70f48741bb1e3ff27ca

Author: luoweile <luoweile2008@126.com>

Date:   Thu Sep 15 12:39:51 2022 +0800



    v1

(END)

git reflog如下:

1
2
3
4
5
6
7
8
9
e32d991 (HEAD -> master) HEAD@{0}: reset: moving to e32d

d4e752c HEAD@{1}: commit: v3

e32d991 (HEAD -> master) HEAD@{2}: commit: v2

c3daf0c HEAD@{3}: commit (initial): v1

(END)

git status如下:

1
2
3
4
5
6
7
8
9
➜  recovery git:(master) ✗ git status

位于分支 master

要提交的变更:

  (使用 "git restore --staged <文件>..." 以取消暂存)

修改:     file.txt

我们可以看到–soft只是回退了commit到本地版本库的操作,文件为绿色,表示已经add到了暂存区

mixed和hard,不做演示

3.3. 通过路径来重置

理清了 reset 基本形式的行为,不过我们还可以给它提供一个作用路径:若指定了一个路径,reset 将会跳过第① 步(移动 HEAD),即只要加了路径,HEAD是不动的,并且将它的作用范围限定为指定的文件或文件集合。
这样做自然有它的道理,因为 HEAD 只是一个指针,无法让它同时指向两个提交中各自的一部分,不过索引和工作目录可以部分更新,所以重置可以继续进行第 ②步(更新索引) 和第 ③ 步(更新工作目录)

📢 值得注意的是,加了路径就不能再加–soft或者–hard

因为git规定加了路径,HEAD就不会动,所以加–soft也不会起作用
硬重置的话,有git checkout HEAD –path,所以为了简化reset和减少错误发生的目的,–hard也是不允许的,否则会报 fatal: 不能带路径进行软性/硬性重置

3.3.1. 不加分支名或commit的SHA-1

3.3.1.1. 作用原理
  • 现在,假如运行 git reset file.txt (这其实是 git reset –mixed HEAD file.txt 的简写形式,因为既没有指定一个提交的 SHA-1 或分支,也没有指定 –soft 或 –hard),它会:
    • 移动 HEAD 分支的指向 (已跳过),使用当前HEAD指向的commit
    • 让索引看起来像 HEAD (到此处停止),拿HEAD中的覆盖Index索引区;
  • 所以它本质上只是将 file.txt 从 HEAD 覆盖到索引中。即保留工作区成果,回退add和commit

实际应用场景
取消暂存文件
如果查看该命令的示意图,然后再想想 git add 所做的事,就会发现它们正好相反。

3.3.1.2. 演示示例

我们做到v2commit之前这一步,然后执行git reset file.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
cd ~

rm -rf recovery

mkdir recovery;cd recovery

git init .

echo "hello v1" > file.txt

git status

git add .

git commit -m "v1"

echo "hello v2" >> file.txt

git add .

git status`

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
➜  recovery git:(master) ✗ git status   

位于分支 master

要提交的变更:

  (使用 "git restore --staged <文件>..." 以取消暂存)

修改:     file.txt



➜  recovery git:(master) ✗ cat file.txt

hello v1

hello v2

➜  recovery git:(master) ✗

git reflog如下:

1
f7138cf (HEAD -> master) HEAD@{0}: commit (initial): v1

git reset file.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
27
➜  recovery git:(master) ✗ git reset file.txt

重置后取消暂存的变更:

M file.txt

➜  recovery git:(master) ✗ cat file.txt

hello v1

hello v2

➜  recovery git:(master) ✗ git status        

位于分支 master

尚未暂存以备提交的变更:

  (使用 "git add <文件>..." 更新要提交的内容)

  (使用 "git restore <文件>..." 丢弃工作区的改动)

修改:     file.txt



修改尚未加入提交(使用 "git add" 和/或 "git commit -a"

我们可以发现,暂存区的文件被清空了,工作区的文件内容没有变化

3.3.2. 加分支名或commit的SHA-1

3.3.2.1. 作用原理

我们可以不让 Git 从 HEAD 拉取数据,而是通过具体指定一个提交来拉取该文件的对应版本,只需运行类似于 git reset eb43bf file.txt 的命令即可:

  • 如果现在运行 git commit,它就会记录一条“将该文件恢复到 v1 版本”的更改,尽管我们并未在工作目录中真正地再次拥有它。
  • 还有一点同 git add 一样,就是 reset 命令也可以接受一个 –patch 选项来一块一块地取消暂存的内容,这样就可以根据选择来取消暂存或恢复内容。
  • 那工作区能不能强制覆盖呢,即git reset SHA-1 --hard file.txt,答案是no,会提示
    fatal: 不能带路径进行硬性重置。那–soft呢,聪明如你,可以猜到会得到 fatal: 不能带路径进行软性重置。原因前面已经讲过。
  • so,带路径带分支名或者commit的只有默认mixed参数,作用可以理解为拿某个文件或路径的某个commit的内容仅仅覆盖暂存区

当检测到文件路径时,git reset 将缓存区同步到你指定的那个提交。比如,下面这个命令会将倒数第二个提交中的foo.py加入到缓存区中,供下一个提交使用。

git reset HEAD~2 foo.py
和提交层面的git reset一样,通常我们使用HEAD而不是某个特定的提交。运行git reset HEAD foo.py 会将当前的foo.py从缓存区中移除出去,而不会影响工作目录中对foo.py的更改。
–soft、–mixed和–hard对文件层面的git reset毫无作用,因为缓存区中的文件一定会变化,而工作目录中的文件一定不变。

3.3.2.2. 演示示例
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
cd ~

rm -rf recovery

mkdir recovery;cd recovery

git init .

echo "hello v1" > file.txt

git status

git add .

git commit -m "v1"

echo "hello v2" >> file.txt

git add .

git commit -m "v2"

echo "hello v3" >> file.txt

git add .

git commit -m "v3"

git log

git reflog如下:

1
2
3
4
5
6
7
ae08e71 (HEAD -> master) HEAD@{0}: commit: v3

ec6224c HEAD@{1}: commit: v2

1c8edce HEAD@{2}: commit (initial): v1

(END)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
➜  recovery git:(master) git status

位于分支 master

无文件要提交,干净的工作区

➜  recovery git:(master) cat file.txt

hello v1

hello v2

hello v3

➜  recovery git:(master)

我们照例用v1的commit进行回退
git reset 1c8e file.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
27
28
29
30
31
32
33
34
35
➜  recovery git:(master) git reset 1c8e file.txt

重置后取消暂存的变更:

M file.txt

➜  recovery git:(master) ✗ git status             

位于分支 master

要提交的变更:

  (使用 "git restore --staged <文件>..." 以取消暂存)

修改:     file.txt



尚未暂存以备提交的变更:

  (使用 "git add <文件>..." 更新要提交的内容)

  (使用 "git restore <文件>..." 丢弃工作区的改动)

修改:     file.txt



➜  recovery git:(master) ✗ cat file.txt

hello v1

hello v2

hello v3

我们可以看到暂存区加入了文件,查看内容,发现暂存区确实变为了v1版本

1
2
3
4
5
6
7
8
9
➜  recovery git:(master) ✗ git ls-files -s

100644 d21fe316a0e9578aea75decc8c60e2a899534708 0 file.txt

➜  recovery git:(master) ✗ git show d21f

hello v1

(END)

工作区文件为红色,表示被修改,虽然默认是mixed覆盖到暂存区,但工作区相当于做了覆盖,然后又改成了v3版本,所以是红色

1
2
3
4
5
6
7
➜  recovery git:(master) ✗ cat file.txt

hello v1

hello v2

hello v3

此时我们git commit一下

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
➜  recovery git:(master) ✗ git commit -m "commit after reset"

[master 08d4efd] commit after reset

 1 file changed, 2 deletions(-)

➜  recovery git:(master) ✗ git status                        

位于分支 master

尚未暂存以备提交的变更:

  (使用 "git add <文件>..." 更新要提交的内容)

  (使用 "git restore <文件>..." 丢弃工作区的改动)

修改:     file.txt



修改尚未加入提交(使用 "git add" 和/或 "git commit -a")

➜  recovery git:(master) ✗ git log        



)

Author: luoweile <luoweile2008@126.com>

Date:   Thu Sep 15 15:01:53 2022 +0800



    commit after reset



commit ae08e7126daa0b323fc8bcf55a273ddd0f18ccb2

Author: luoweile <luoweile2008@126.com>

Date:   Thu Sep 15 14:41:03 2022 +0800



    v3



commit ec6224cc5549e06ac0b8b65a00dd8472ac30128e

Author: luoweile <luoweile2008@126.com>

Date:   Thu Sep 15 14:41:03 2022 +0800



    v2



commit 1c8edced86209178b3ff52ed782afbeecfe02420

Author: luoweile <luoweile2008@126.com>

Date:   Thu Sep 15 14:41:03 2022 +0800



    v1

git reflog

1
2
3
4
5
6
7
8
9
08d4efd (HEAD -> master) HEAD@{0}: commit: commit after reset

ae08e71 HEAD@{1}: commit: v3

ec6224c HEAD@{2}: commit: v2

1c8edce HEAD@{3}: commit (initial): v1

(END)

可以看到多了一次提交, git show 08d4

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
commit 08d4efd3c3910ac07b0c19f47f754d797f4c2e98 (HEAD -> master)

Author: luoweile <luoweile2008@126.com>

Date:   Thu Sep 15 15:01:53 2022 +0800



    commit after reset



diff --git a/file.txt b/file.txt

index 56dc9b1..d21fe31 100644

--- a/file.txt

+++ b/file.txt

@@ -1,3 +1 @@

 hello v1

-hello v2

-hello v3

(END)

可以看到回退到了v1版本
但此时工作区还是v3的版本,想也回退到v1,可以使用git reset --hard或者git checkout file.txt

3.4. 压缩提交

3.4.1. 作用原理

假设一系列提交信息中有 “oops.”“WIP” 和 “forgot this file”, 就能使用 reset 来轻松快速地将它们压缩成单个提交。假设有一个项目,第一次提交中有一个文件,第二次提交增加了一个新的文件并修改了第一个文件,第三次提交再次修改了第一个文件,由于第二次提交是一个未完成的工作,因此需要压缩它。

  • 那么可以运行 git reset –soft HEAD~2 来将 HEAD 分支移动到一个旧一点的提交上(即想要保留的最近的提交):

  • 然后只需再次运行 git commit:
  • 现在可以查看可到达的历史,即将会推送的历史,现在看起来有个 v1 版 file-a.txt 的提交,接着第二个提交将 file-a.txt 修改成了 v3 版并增加了 file-b.txt,包含 v2 版本的文件已经不在历史中。
3.4.2. 演示示例
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
cd ~

rm -rf recovery

mkdir recovery;cd recovery

git init .

echo "init file-a.txt v1" > file-a.txt

git status

git add .

git commit -m "init file-a.txt v1"



echo "update file-a.txt v2" >> file-a.txt

echo "init file-b.txt v1" >> file-b.txt

git add .

git commit -m "update file-a.txt v2"



echo "finish fix file-a.txt v3" >> file-a.txt

git add .

git commit -m "finish fix file-a.txt v3"

git log

git log

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
commit de9545c70d3005351b1724d6c87e8610db4d0611 (HEAD -> master)

Author: luoweile <luoweile2008@126.com>

Date:   Fri Sep 16 14:27:11 2022 +0800



    finish fix file-a.txt v3



commit cf4753faaf9cb1ac03eeacebb725e59408420b9a

Author: luoweile <luoweile2008@126.com>

Date:   Fri Sep 16 14:27:11 2022 +0800



    update file-a.txt v2



commit cfb20be06b826848f1330426b06ae795cd09a9a3

Author: luoweile <luoweile2008@126.com>

Date:   Fri Sep 16 14:27:11 2022 +0800



    init file-a.txt v1

(END)

git reflog

1
2
3
4
5
6
7
de9545c (HEAD -> master) HEAD@{0}: commit: finish fix file-a.txt v3

cf4753f HEAD@{1}: commit: update file-a.txt v2

cfb20be HEAD@{2}: commit (initial): init file-a.txt v1

(END)

git reset --soft HEAD~2

git log

1
2
3
4
5
6
7
8
9
10
11
commit cfb20be06b826848f1330426b06ae795cd09a9a3 (HEAD -> master)

Author: luoweile <luoweile2008@126.com>

Date:   Fri Sep 16 14:27:11 2022 +0800



    init file-a.txt v1

(END)

git reflog

1
2
3
4
5
6
7
8
9
cfb20be (HEAD -> master) HEAD@{0}: reset: moving to HEAD~2

de9545c HEAD@{1}: commit: finish fix file-a.txt v3

cf4753f HEAD@{2}: commit: update file-a.txt v2

cfb20be (HEAD -> master) HEAD@{3}: commit (initial): init file-a.txt v1

(END)

可以看出来,此时HEAD指向了第一次的commit cfb2,相当于把commit回退了

git status

1
2
3
4
5
6
7
8
9
10
11
➜  recovery git:(master) ✗ git status

位于分支 master

要提交的变更:

  (使用 "git restore --staged <文件>..." 以取消暂存)

修改:     file-a.txt

新文件:   file-b.txt

我们再次执行git commit

1
2
3
4
5
6
7
➜  recovery git:(master) ✗ git commit -m "compress commit test"

[master 798ae56] compress commit test

 2 files changed, 3 insertions(+)

 create mode 100644 file-b.txt

git log

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
commit 798ae5608429362e4390b1eac74746e2fc78bb12 (HEAD -> master)

Author: luoweile <luoweile2008@126.com>

Date:   Fri Sep 16 14:32:17 2022 +0800



    compress commit test



commit cfb20be06b826848f1330426b06ae795cd09a9a3

Author: luoweile <luoweile2008@126.com>

Date:   Fri Sep 16 14:27:11 2022 +0800



    init file-a.txt v1

(END)

git log 只看到了2次commit信息

git reflog

1
2
3
4
5
6
7
8
9
10
11
798ae56 (HEAD -> master) HEAD@{0}: commit: compress commit test

cfb20be HEAD@{1}: reset: moving to HEAD~2

de9545c HEAD@{2}: commit: finish fix file-a.txt v3

cf4753f HEAD@{3}: commit: update file-a.txt v2

cfb20be HEAD@{4}: commit (initial): init file-a.txt v1

(END)

不过git reflog还是看得很清楚

4. 对commit历史的影响

命令logreflog
revert新增可跟踪
checkout无变化无变化
reset丢失可跟踪
restore无变化无变化
git amend改变改变

5. 其他后悔药

5.1. revert

Git进阶 — 2、Git-后悔药-回退撤销-revert

5.2. checkout

Git进阶 — 3、Git-后悔药-回退撤销-checkout

5.3. restore

Git进阶 — 4、Git-后悔药-回退撤销-restore

5.4. git commit –amend

Git进阶 — 5、Git-后悔药-重写历史-gitcommit--amend

6. 延长保存期限

我们前面说到在Git上做的所有操作都被保存到记录里,一般是从你本地Git库执行clone开始的所有操作都保存了下来,所以不用担心很久之前的一些Commit log找不到,你或许期望去为已删除的提交设置一个更长的保存周期。例如:
$ git config gc.pruneexpire "30 days" 默认2周
意思是一个被删除的提交会在删除30天后,且运行 git gc 以后,被永久丢弃。
你或许还想关掉 git gc 的自动运行:
$ git config gc.auto 0
在这种情况下提交将只在你手工运行 git gc 的情况下才永久删除。

7. 参考

https://bbs.huaweicloud.com/blogs/331355

https://www.jianshu.com/p/8b4c95677ee0

https://static.kancloud.cn/apachecn/git-doc-zh/1945496

https://wikinote.gitbook.io/git-learning/git-ji-ben-ming-ling/ti-jiao-che-xiao-yu-la-qu/git-reset