孤立的提交会怎样?

What happens to orphaned commits?

我有一个包含四个提交的回购协议:

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

reset -- softC2 提交,现在我有一个像这样的回购:

$ 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

孤立的提交只会留在那里,直到它们被显式 运行 git gc.

垃圾回收

运行 git show 6c35831 以查看 C4,例如,仍然存在。 运行 git reflog master 查看(很多)master 使用 参考的内容。其中一个条目(master^{1} 最有可能,但如果您还进行了其他更改,则可能是一个更老的条目)应该对应于 6c35831,并且 git show master^{1}(或任何条目)应该显示我提到的第一个 git show 命令的相同输出。

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

长答案:垃圾收集将通过不同的 Git 瓷器命令自动发生,或者在明确垃圾收集时发生。有很多场景可以触发自动垃圾收集;看看 gc.* configuration settings to get an idea. You can explicitly gabage collect using the git gc builtin command。让我们看一个例子,看看会发生什么。

首先,让我们设置我们的环境(我正在使用 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 log, git reflog, git count-objects, git rev-list and git 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: 
    {
        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 有分支,标记 and/or reflog 对所有五个提交对象的引用。只要 Git 引用了一个对象,该对象就不会被垃圾回收。明确地 运行 垃圾收集将导致没有对象从存储库中删除。 (我将验证这个作为练习让你完成。)

现在让我们删除 Git 对 C3C4C5 提交的引用。

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 or look at it with 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

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

很棒的问答帖子。这里只是提醒一下准确的措辞。

OP 描述的内容实际上称为 unreachable/dangling 提交。参见官方词汇表中的相应条目:dangling object and unreachable object.

虽然 orphan,在 Git 的上下文中,修改由 git init 创建的 分支 git checkout --orphan,因为这些分支上的第一次提交没有父项。