孤立的提交会发生什么?

20

我有一个包含四个提交的代码库:

$ git log --oneline --decorate
6c35831 (HEAD, master) C4
974073b C3
e27b22c C2
9f2d694 C1

我使用reset -- soft命令到C2提交,现在我的仓库看起来是这样的:

$ git reset e27b22c --soft

$ git log --oneline --decorate
e27b22c (HEAD, master) C2
9f2d694 C1

现在我添加了一个额外的提交,因此日志看起来像这样:

$ git log --oneline --decorate
545fa99 (HEAD, master) C5
e27b22c C2
9f2d694 C1

提交记录 C3C4 发生了什么?我没有删除它们,所以我假设它们仍然存在,C3 的父提交记录仍然是 C2

4个回答

21
< p>简短回答:提交 C3C4 将一直保留在 Git 对象数据库中,直到它们被垃圾回收。

长回答:垃圾回收将通过不同的Git工具命令自动进行或明确进行垃圾回收。有许多情况可能会触发自动垃圾回收;请查看gc.* 配置设置以了解更多信息。您可以使用 git gc 内置命令明确地进行垃圾回收。 让我们看一个示例,了解发生了什么。

首先,让我们设置我们的环境(我正在使用Linux;根据您的环境进行更改),以便我们在不同的Git存储库中获得相同的对象哈希。

export GIT_AUTHOR_NAME='Wile E. Coyote'
export GIT_AUTHOR_EMAIL=coyote@acme.com
export GIT_AUTHOR_DATE=2015-01-01T12:00:00
export GIT_COMMITTER_NAME='Roadrunner'
export GIT_COMMITTER_EMAIL=roadrunner@acme.com
export GIT_COMMITTER_DATE=2015-01-01T12:00:00

由于提交对象哈希是使用此信息生成的,如果我们使用相同的作者和提交者值,则现在应该都会得到相同的哈希。

现在让我们初始化一个函数,使用git loggit refloggit count-objectsgit rev-listgit fsck来记录对象信息。

function git_log_objects () {
    echo 'Log ...'
    git log --oneline --decorate
    echo 'Reflog ...'
    git reflog show --all
    echo 'Count ...'
    git count-objects -v
    echo 'Hashes ...'
    # See: https://dev59.com/OWs05IYBdhLWcg3wQvuq#7350019
    {
        git rev-list --objects --all --reflog
        git rev-list --objects -g --no-walk --all
        git rev-list --objects --no-walk $(
            git fsck --unreachable 2>/dev/null \
                | grep '^unreachable commit' \
                | cut -d' ' -f3
        )
    } | sort | uniq
}

现在让我们初始化一个Git仓库。

git --version
git init
git_log_objects

对我来说,输出结果为:

git version 2.4.0
Initialized empty Git repository in /tmp/test/.git/
Log ...
fatal: bad default revision 'HEAD'
Reflog ...
fatal: bad default revision 'HEAD'
Count ...
count: 0
size: 0
in-pack: 0
packs: 0
size-pack: 0
prune-packable: 0
garbage: 0
size-garbage: 0
Hashes ...

正如预期的那样,我们有一个初始化的仓库,其中没有任何对象。让我们进行一些提交并查看这些对象。

git commit --allow-empty -m C1
git commit --allow-empty -m C2
git tag T1
git commit --allow-empty -m C3
git commit --allow-empty -m C4
git commit --allow-empty -m C5
git_log_objects

这给我以下输出:

[master (root-commit) c11e156] C1
 Author: Wile E. Coyote <coyote@acme.com>
[master 10bfa58] C2
 Author: Wile E. Coyote <coyote@acme.com>
[master 8aa22b5] C3
 Author: Wile E. Coyote <coyote@acme.com>
[master 1abb34f] C4
 Author: Wile E. Coyote <coyote@acme.com>
[master d1efc10] C5
 Author: Wile E. Coyote <coyote@acme.com>
Log ...
d1efc10 (HEAD -> master) C5
1abb34f C4
8aa22b5 C3
10bfa58 (tag: T1) C2
c11e156 C1
Reflog ...
d1efc10 refs/heads/master@{0}: commit: C5
1abb34f refs/heads/master@{1}: commit: C4
8aa22b5 refs/heads/master@{2}: commit: C3
10bfa58 refs/heads/master@{3}: commit: C2
c11e156 refs/heads/master@{4}: commit (initial): C1
Count ...
count: 6
size: 24
in-pack: 0
packs: 0
size-pack: 0
prune-packable: 0
garbage: 0
size-garbage: 0
Hashes ...
10bfa58a7bcbadfc6c9af616da89e4139c15fbb9
1abb34f82523039920fc629a68d3f82bc79acbd0
4b825dc642cb6eb9a060e54bf8d69288fbee4904 
8aa22b5f0fed338dd13c16537c1c54b3496e3224
c11e1562835fe1e9c25bf293279bff0cf778b6e0
d1efc109115b00bac9d4e3d374a05a3df9754551

现在我们在仓库中有六个对象:五个提交和一个空树。我们可以看到Git有分支、标签和/或reflog引用到所有五个提交对象。只要Git引用了一个对象,该对象就不会被垃圾回收。显式运行垃圾回收将导致没有对象从仓库中删除。(我会留下验证这一点的练习给你完成。)

现在让我们删除Git对C3、C4和C5提交的引用。

git reset --soft T1
git reflog expire --expire=all --all
git_log_objects

输出结果如下:

Log ...
10bfa58 (HEAD -> master, tag: T1) C2
c11e156 C1
Reflog ...
Count ...
count: 6
size: 24
in-pack: 0
packs: 0
size-pack: 0
prune-packable: 0
garbage: 0
size-garbage: 0
Hashes ...
10bfa58a7bcbadfc6c9af616da89e4139c15fbb9
1abb34f82523039920fc629a68d3f82bc79acbd0
4b825dc642cb6eb9a060e54bf8d69288fbee4904 
8aa22b5f0fed338dd13c16537c1c54b3496e3224
c11e1562835fe1e9c25bf293279bff0cf778b6e0
d1efc109115b00bac9d4e3d374a05a3df9754551

现在我们只看到 Git 引用了两次提交。然而,所有六个对象仍然存在于存储库中。它们会一直保留在存储库中,直到被自动或显式地垃圾回收。例如,您甚至可以使用 git cherry-pick 来恢复未引用的提交,或者使用 git show 查看它。不过,让我们明确地垃圾回收未引用的对象,并看看 Git 在幕后做了什么。

GIT_TRACE=1 git gc --aggressive --prune=now

这将输出一些信息。

11:03:03.123194 git.c:348               trace: built-in: git 'gc' '--aggressive' '--prune=now'
11:03:03.123625 run-command.c:347       trace: run_command: 'pack-refs' '--all' '--prune'
11:03:03.124038 exec_cmd.c:129          trace: exec: 'git' 'pack-refs' '--all' '--prune'
11:03:03.126895 git.c:348               trace: built-in: git 'pack-refs' '--all' '--prune'
11:03:03.128298 run-command.c:347       trace: run_command: 'reflog' 'expire' '--all'
11:03:03.128635 exec_cmd.c:129          trace: exec: 'git' 'reflog' 'expire' '--all'
11:03:03.131322 git.c:348               trace: built-in: git 'reflog' 'expire' '--all'
11:03:03.133179 run-command.c:347       trace: run_command: 'repack' '-d' '-l' '-f' '--depth=250' '--window=250' '-a'
11:03:03.133522 exec_cmd.c:129          trace: exec: 'git' 'repack' '-d' '-l' '-f' '--depth=250' '--window=250' '-a'
11:03:03.136915 git.c:348               trace: built-in: git 'repack' '-d' '-l' '-f' '--depth=250' '--window=250' '-a'
11:03:03.137179 run-command.c:347       trace: run_command: 'pack-objects' '--keep-true-parents' '--honor-pack-keep' '--non-empty' '--all' '--reflog' '--indexed-objects' '--window=250' '--depth=250' '--no-reuse-delta' '--local' '--delta-base-offset' '.git/objects/pack/.tmp-8973-pack'
11:03:03.137686 exec_cmd.c:129          trace: exec: 'git' 'pack-objects' '--keep-true-parents' '--honor-pack-keep' '--non-empty' '--all' '--reflog' '--indexed-objects' '--window=250' '--depth=250' '--no-reuse-delta' '--local' '--delta-base-offset' '.git/objects/pack/.tmp-8973-pack'
11:03:03.140367 git.c:348               trace: built-in: git 'pack-objects' '--keep-true-parents' '--honor-pack-keep' '--non-empty' '--all' '--reflog' '--indexed-objects' '--window=250' '--depth=250' '--no-reuse-delta' '--local' '--delta-base-offset' '.git/objects/pack/.tmp-8973-pack'
Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), done.
Total 3 (delta 1), reused 0 (delta 0)
11:03:03.153843 run-command.c:347       trace: run_command: 'prune' '--expire' 'now'
11:03:03.154255 exec_cmd.c:129          trace: exec: 'git' 'prune' '--expire' 'now'
11:03:03.156744 git.c:348               trace: built-in: git 'prune' '--expire' 'now'
11:03:03.159210 run-command.c:347       trace: run_command: 'rerere' 'gc'
11:03:03.159527 exec_cmd.c:129          trace: exec: 'git' 'rerere' 'gc'
11:03:03.161807 git.c:348               trace: built-in: git 'rerere' 'gc'

最后,让我们来看看这些对象。

git_log_objects

输出为:

Log ...
10bfa58 (HEAD -> master, tag: T1) C2
c11e156 C1
Reflog ...
Count ...
count: 0
size: 0
in-pack: 3
packs: 1
size-pack: 1
prune-packable: 0
garbage: 0
size-garbage: 0
Hashes ...
10bfa58a7bcbadfc6c9af616da89e4139c15fbb9
4b825dc642cb6eb9a060e54bf8d69288fbee4904 
c11e1562835fe1e9c25bf293279bff0cf778b6e0

现在我们只看到了三个对象:两个提交和一个空树。


1
那个答案太棒了,里面还有一些我不知道的东西,比如 --allow-empty - BanksySan

6

运行git show 6c35831命令以查看C4是否仍然存在。 运行git reflog master命令以查看(众多的)master曾经引用的内容。其中一个条目(大多数情况下是master^{1},但如果您还进行其他更改,则可能是较旧的一个)应该对应于6c35831,并且git show master^{1}(或任何其他相应的条目)应显示我提到的第一个git show命令的相同输出。


4
孤立的提交只会留在那里,直到通过显式运行 git gc 进行垃圾回收。

1
那么接下来的问题是,我改变了历史还是只是添加了一些内容? - BanksySan
就分支中的内容而言(即git log)- 您已更改历史记录。 就存储库中发生的事情而言(即git reflog),您已添加到其中。 - Mureinik
那么,如果这些提交被发布了,这会是一个“坏事”吗? - BanksySan
通常情况下,您会发布一个分支 - 这样它们就不会被包含在内。然而,重置公共分支通常被认为是一种不好的做法 - 它将强制所有基于其之上进行变基的人强制更新他们的本地分支。 - Mureinik

1

非常好的问题和答案帖子。这里只是提醒一下精确术语。

OP所描述的实际上被称为不可访问/悬挂提交。请参阅官方词汇表中对应的条目:dangling objectunreachable object

在Git的上下文中,孤儿指的是由git initgit checkout --orphan创建的分支,因为这些分支上的第一个提交没有父节点。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接