Что происходит с потерянными коммитами?

У меня есть репо с четырьмя коммитами:

$ 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

Что случилось с коммитами C3 и C4? Я не удалял их, поэтому я предполагаю, что они все еще там, родителем C3 все еще является C2.


person BanksySan    schedule 06.05.2015    source источник


Ответы (3)


Краткий ответ: коммиты C3 и C4 останутся в базе данных объектов Git до тех пор, пока они не будут удалены сборщиком мусора.

Длинный ответ: сборка мусора будет происходить автоматически с помощью различных команд Git фарфора или при явной сборке мусора. Существует много сценариев, которые могут вызвать автоматическую сборку мусора; взгляните на gc.* параметры конфигурации, чтобы получить представление. Вы можете явно собирать мусор с помощью git gc встроенной команды. Давайте посмотрим на пример, чтобы увидеть, что происходит.

Во-первых, давайте настроим нашу среду (я использую Linux; внесите необходимые изменения для вашей среды), чтобы мы, надеюсь, получили одинаковые хэши объектов в разных репозиториях Git.

export GIT_AUTHOR_NAME='Wile E. Coyote'
export [email protected]
export GIT_AUTHOR_DATE=2015-01-01T12:00:00
export GIT_COMMITTER_NAME='Roadrunner'
export [email protected]
export GIT_COMMITTER_DATE=2015-01-01T12:00:00

Поскольку хэши объектов коммитов генерируются с использованием этой информации, если мы используем одни и те же значения автора и коммиттера, мы все должны получить одинаковые хэши.

Теперь давайте инициализируем функцию для регистрации информации об объекте, используя git log, git reflog, git count-objects, git rev-list и 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: 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 <[email protected]>
[master 10bfa58] C2
 Author: Wile E. Coyote <[email protected]>
[master 8aa22b5] C3
 Author: Wile E. Coyote <[email protected]>
[master 1abb34f] C4
 Author: Wile E. Coyote <[email protected]>
[master d1efc10] C5
 Author: Wile E. Coyote <[email protected]>
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 есть ссылки на ветки, теги и/или журналы ссылок на все пять объектов коммитов. Пока 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

Теперь мы видим, что у нас есть только три объекта: два коммита и одно пустое дерево.

person Dan Cruz    schedule 07.05.2015
comment
Этот ответ потрясающий, но есть еще несколько вещей, которых я не знал, например --allow-empty. - person BanksySan; 07.05.2015

Запустите git show 6c35831, чтобы увидеть, что, например, C4 все еще существует. Запустите git reflog master, чтобы увидеть (много), что master использует для ссылки. Одна из записей (вероятнее всего master^{1}, но, возможно, и более старая, если вы внесли и другие изменения) должна соответствовать 6c35831, а git show master^{1} (или любая другая запись) должна показывать тот же результат, что и первая команда git show, о которой я упоминал.

person chepner    schedule 06.05.2015

Осиротевшие коммиты просто остаются там до тех пор, пока они не будут удалены сборщиком мусора путем явного запуска git gc.

person Mureinik    schedule 06.05.2015
comment
Тогда уточняйте вопрос. Я изменил историю или просто добавил в нее? - person BanksySan; 07.05.2015
comment
С точки зрения того, что в ветке (т.е. git log) - вы изменили историю. С точки зрения того, что произошло в репо (т. е. git reflog), вы добавили к нему. - person Mureinik; 07.05.2015
comment
Итак, если бы эти коммиты были опубликованы, было бы это «плохо»? - person BanksySan; 07.05.2015
comment
Вы обычно публикуете ветку - поэтому они просто не будут включены. Однако сброс общедоступной ветки обычно считается плохой практикой — это заставит всех, кто перебазируется поверх нее, принудительно обновить свои локальные ветки. - person Mureinik; 07.05.2015