【问题标题】:What happens to orphaned commits?孤立的提交会发生什么?
【发布时间】:2015-07-17 07:05:42
【问题描述】:

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

$ 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

【问题讨论】:

    标签: git git-commit git-reset


    【解决方案1】:

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

    长答案:垃圾收集将由不同的 Git 瓷器命令自动发生,或者在显式垃圾收集时发生。有很多场景可以触发自动垃圾收集;看看gc.* configuration settings 了解一下。您可以使用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 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://stackoverflow.com/a/7350019/649852
        {
            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 引用一个对象,该对象就不会被垃圾回收。显式运行 gabage 集合将导致不会从存储库中删除任何对象。 (我将把验证这个作为一个练习留给你完成。)

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

    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
    

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

    【讨论】:

    • 这个答案太棒了,里面还有一些我不知道的东西,比如--allow-empty
    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      孤立的提交会一直保留在那里,直到通过显式运行 git gc 将它们收集起来。

      【讨论】:

      • 然后跟进问题。我是否更改了历史记录或只是添加了历史记录?
      • 就分支中的内容(即git log)而言 - 你已经改变了历史。就回购中发生的事情(即git reflog)而言,您添加了它。
      • 那么,如果这些提交被发布,这会是一件“坏事”吗?
      • 您通常会发布一个分支 - 所以它们不会被包含在内。但是,重置公共分支通常被认为是一种不好的做法 - 它会迫使所有基于它重新建立基础的人强制更新其本地分支。
      【解决方案4】:

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

      OP 所描述的实际上称为 unreachable/dangling 提交。请参阅官方词汇表中的相应条目:dangling objectunreachable object

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-01-01
        • 2011-01-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-06-15
        • 2015-02-22
        相关资源
        最近更新 更多