Как мне уменьшить размер раздутого репозитория Git, не интерактивно подавляя все коммиты, кроме самых последних?
В моем репозитории Git хранятся сотни гигабайт данных, например, резервные копии SQL, поэтому я пытаюсь удалить старые, устаревшие коммиты, потому что они делают все больше и медленнее. Мне нужно быстрое решение; чем быстрее тем лучше.
Как раздавить все коммиты, кроме самых последних, и сделать это без необходимости вручную раздавливать каждый из них в интерактивной перебазировке? В частности, я не хочу использовать
git rebase -i --root
Мой репо
У меня есть эти коммиты:
A .. B .. C ... ... H .. I .. J .. K .. L
Что я хочу, так это (раздавить все между A
а также H
в A
):
A .. H .. I .. J .. K .. L
Есть ответ о том, как раздавить все коммиты, но я хочу сохранить некоторые из последних коммитов. Я тоже не хочу раздавить последние коммиты. (Особенно мне нужно сохранить первые два коммита, считая сверху.)
3 ответа
Самое быстрое время выполнения подсчета почти наверняка будет с трансплантатами и ветвью фильтра, хотя вы могли бы добиться более быстрого выполнения с помощью управляемой последовательности дерева коммитов, работающей от вывода списка ревью.
Rebase построен, чтобы применить изменения к другому контенту. То, что вы делаете здесь, - это сохранение содержимого и преднамеренная потеря истории изменений, которая его произвела, так что почти вся утомительная и медленная работа rebase теряется.
Полезная нагрузка здесь, исходя из вашей картинки,
echo `git rev-parse H; git rev-parse A` > .git/info/grafts
git filter-branch -- --all
Документация для git rev-parse
а также git filter-branch
,
Фильтр-ветвь очень осторожен, чтобы его можно было восстановить после сбоя в любой момент, что, безусловно, является самым безопасным... но это действительно очень полезно, когда восстановление, просто повторив его, не будет быстрее и проще, если дела пойдут на юг. Отказы редки, а перезапуски, как правило, дешевы, нужно сделать не "безопасную", но очень быструю операцию, которая почти наверняка сработает. Для этого лучшим вариантом здесь является сделать это на tmpfs (ближайший аналог, который я знаю в Windows - это виртуальный диск, такой как ImDisk), который будет работать очень быстро и не затронет ваш основной репозиторий, пока вы не будете уверены, что " у нас есть результаты, которые вы хотите.
Так о Windows, скажем T:\wip
находится на виртуальном диске, и обратите внимание, что клон здесь ничего не копирует. А также чтение документов на git clone
"s --shared
вариант, изучите внутренности клона, чтобы увидеть реальный эффект, это очень просто.
# switch to a lightweight wip clone on a tmpfs
git clone --shared --no-checkout . /t/wip/filterwork
cd !$
# graft out the unwanted commits
echo `git rev-parse $L; git rev-parse $A` >.git/info/grafts
git filter-branch -- --all
# check that the repo history looks right
git log --graph --decorate --oneline --all
# all done with the splicing, filter-branch has integrated it
rm .git/info/grafts
# push the rewritten histories back
git push origin --all --force
Существует достаточно возможных вариантов того, что вы, возможно, захотите сделать, и того, что может быть в вашем репо, что может пригодиться практически любой из параметров этих команд. Выше тестируется и будет делать то, что говорит, но это может быть не совсем то, что вы хотите.
Оригинальные комментарии автора:
если мы сделаем снимок коммита 10004, удалим все коммиты до него и сделаем коммит 10004 корневым коммитом, я буду в порядке
Один из способов сделать это - предположить, что ваша текущая работа называется branchname
, Мне нравится использовать временный тег всякий раз, когда я делаю большую перебазировку, чтобы перепроверить, что изменений не было, и отметить точку, которую я могу reset
Вернемся к тому, если что-то пойдет не так (не уверен, стандартная ли это процедура или нет, но она работает для меня):
git tag temp
git checkout 10004
git checkout --orphan new_root
git commit -m "set new root 10004"
git rebase --onto new_root 10004 branchname
git diff temp # verification that it worked with no changes
git tag -d temp
git branch -D new_root
Чтобы избавиться от старой ветви, вам нужно удалить все теги и теги ветви на ней; затем
git prune
git gc
очистит его от вашего репо.
Обратите внимание, что у вас будет временно две копии всего, пока у вас не будет gc
', но это неизбежно; даже если вы делаете стандартный сквош и перебазируете, у вас все еще остается две копии всего, пока перебазирование не закончится.
Проблема XY
Обратите внимание, что у оригинального плаката есть проблема XY, где он пытается выяснить, как раздавить свои более старые коммиты (проблема Y), когда его настоящая проблема на самом деле пытается уменьшить размер своего репозитория Git (проблема X), так как Я упомянул в комментариях:
Наличие большого количества коммитов не обязательно увеличивает размер вашего репозитория Git. Git очень эффективен при сжатии текстовых файлов. Вы уверены, что количество коммитов является реальной проблемой, которая приводит к вашему большому размеру репо? Более вероятным кандидатом является то, что у вас слишком много версий двоичных ресурсов, которые Git не сжимает (или вообще) по сравнению с обычными текстовыми файлами.
Несмотря на это, для полноты картины я также добавлю альтернативное решение ответа Мэтта МакНабба на проблему Y.
Сокрушение (сотни или тысячи) старых комитетов
Как уже отмечалось в оригинальном постере, использование интерактивной перебазировки с --root
Флаг может быть непрактичным, когда есть много коммитов (нумерация исчисляется сотнями или тысячами), особенно потому, что интерактивное перебазирование не будет эффективно выполняться на таком большом количестве из них.
Как указал Мэтт Макнабб в своем ответе, одно из решений состоит в том, чтобы использовать ветвь-сироту в качестве нового (сжатого) корня, а затем перебазировать поверх этого. Другое решение состоит в том, чтобы использовать несколько различных сбросов ветви для достижения того же эффекта:
# Save the current state of the branch in a couple of other branches
git branch beforeReset
git branch verification
# Also mark where we want to start squashing commits
git branch oldBase <most_recent_commit_to_squash>
# Temporarily remove the most recent commits from the current branch,
# because we don't want to squash those:
git reset --hard oldBase
# Using a soft reset to the root commit will keep all of the changes
# staged in the index, so you just need to amend those changes to the
# root commit:
git reset --soft <root_commit>
git commit --amend
# Rebase onto the new amended root,
# starting from oldBase and going up to beforeReset
git rebase --onto master oldBase beforeReset
# Switch back to master and (fast-forward) merge it with beforeReset
git checkout master
git merge beforeReset
# Verify that master still contains the same state as before all of the resets
git diff verification
# Cleanup
git branch -D beforeReset oldBase verification
# As part of cleanup, since the original poster mentioned that
# he has a lot of commits that he wants to remove to reduce
# the size of his repo, garbage collect the old, dangling commits too
git gc --prune=all
--prune=all
возможность git gc
обеспечит сбор всех мусорных коммитов, а не только тех, которые старше 2 недель, что является настройкой по умолчанию для git gc
,