Как удалить все коммиты до указанной даты в истории Git?
Учитывая репозиторий, я хочу удалить все коммиты, которые были до определенного фиксации, или дату в истории.
У меня в моем репозитории около 10000 коммитов, и я хочу сохранить только 1000 или около того и удалить остальные. В основном, что я хочу сделать, это сказать перенести первый фиксатор вперед на X.
Сначала я подумал, что могу просто переустановить и вырезать все эти коммиты в один, но это вызывает много конфликтов слияния во время rebase. Если бы был способ выполнить сквош, чтобы версия после сквоша была последней фиксацией, это тоже сработало бы.
Ответы
Ответ 1
Предупреждение: следующее опасно, поскольку оно перезаписывает историю. Всегда убедитесь, что у вас есть резервная копия вашего репо, прежде чем делать какие-либо крупные перезаписи истории, как это.
Замените хэш в следующем: хеш родителя комманды, который вы хотите иметь в качестве новой первой фиксации.
git filter-branch --parent-filter '
read parent
if [ "$parent" = "-p 5bdd44e5919cb0a95a9924817529cd7c980f88b5" ]
then
echo
else
echo "$parent"
fi'
Это перезаписывает родителей каждой фиксации; для большинства коммитов он оставляет их одинаковыми, но тот, который имеет родительский элемент, соответствующий данному хешу, заменяется пустым родителем, то есть теперь он станет фиксацией без родителя. Это отделит всю вашу старую историю.
Обратите внимание, что если вы хотите, чтобы ваша первая фиксация была фиксацией слияния, вам нужно сопоставить что-то вроде -p parent1 -p parent2 -p parent3
для каждого из родителей слияния в правильном порядке.
Если вы хотите применить это ко всем ветвям и тегам вместо текущей ветки, пройдите в --all
в конце команды (после script).
После того, как вы это сделали, и проверите, чтобы он работал правильно, вы можете удалить исходную ветвь и запустить gc
для очистки неавторизованных коммитов:
git update-ref -d refs/original/refs/heads/master
Обратите внимание, что поскольку git
стремится попытаться сохранить данные, чтобы фактически освободить пространство, вам также придется удалить коммиты из вашего рефлога, а затем запустите gc
, чтобы очистить его.
git reflog expire --expire-unreachable=all --all
git gc --prune=all
Если вы не делаете этого, чтобы сэкономить место или уничтожить старые фиксации, вы можете сохранить старую историю в ветке, например git branch old-master refs/original/refs/heads/master
; вы можете даже "практически повторно подключить" его с помощью git replace
, после чего у вас будет две несвязанные истории (поэтому, когда вы нажимаете на дистанционное репо, вы будете нажмите на укороченную историю), но когда вы просматриваете историю в своем местном репо, вы увидите полную историю.
Ответ 2
Для меня проще использовать git replace
(изменить: успешно протестировано!).
Сначала раздайте все, что хотите, в одном:
(мы будем называть sha последней фиксации, которую вы хотите сквош
и sha самого первого фиксации, поэтому ваш корневой фиксатор)
git checkout -b big_squash <LastSha>
git reset --soft <RootSha>
git commit --amend -m "My new root"
Теперь у вас должна быть ваша ветка big_squash
, указывающая на новый корень (называемый здесь <NewRootSha>
. Мы здесь заинтересованы только sha1, и ветка может быть удалена в конце после успешного завершения операции).
Тогда у вас есть 2 возможности:
- Делает ли
git rebase --onto
более позднего совершает, если это легко сделать (что предпочтительное решение книги git, но после успешного тестирования другого решения, это не мое;))
- Используйте
git replace
для скрывать старую историю (история все еще находится в репозитории! Но мы сделаем ее постоянной с git filter-branch
)
Чтобы заменить последнюю фиксацию, которую вы хотите сквозировать с помощью вновь созданной фиксации:
git replace <RootSha> <NewRootSha>
Теперь вы можете сделать git filter-branch
после git replace
, чтобы сделать его постоянным!
После замены выполните следующие действия:
git filter-branch master, <put here the name of all your branches>
Если результат вам подходит, а затем удалите папку .git/refs/original
(которая содержит все сохраненные ссылки перед git filter-branch
) и папку .git/refs/replace
(которая содержит замену, в которой вам больше не нужна).
Это решение имеет то преимущество, что оно простое и обратимое (за исключением последнего шага после удаления папок;))
Это сделано!
Здесь вы можете найти документацию:
Ответ 3
Вы можете использовать мелкий клон через git clone --depth 1000
. Неглубокий клон все еще имеет полную силу фиксации, см. https://github.com/git/git/commit/82fba2b9d39163a0c9b7a3a2f35964cbc039e1a
Вы даже можете сохранить старое дерево на случай, если оно вам еще понадобится, и оно полностью совместимо, не нужно менять историю.
Ответ 4
Вы не можете получить то, что хотите, потому что вы не можете удалить что-либо из репозитория, вы можете добавлять к нему новые вещи.
Чтобы пересчитать, но с графическим графиком фиксации, у вас есть (упрощено):
<jumble of commits> - K - L - M - etc ... <-- master
\ / (merges) <-- etc
(branches)
и то, что вы хотите (упрощенно):
K - L - M - etc ... <-- master
\ / (merges) <-- etc
(branches)
так что K
теперь является фиксацией корня.
Вы не можете это получить, но вы можете получить новый корневой коммит, который почти точно такой же, как K
, с двумя большими отличиями: другим SHA-1 и без родительских идентификаторов (-ов). Конец будет иметь то же дерево и все те же файлы, что и commit K
.
Скопировав K
в K'
, вы можете скопировать L
в L'
и т.д., так что вы получите новый граф фиксации, который имеет одинаковую форму и те же файлы и так далее, просто со всеми новыми идентификаторами SHA-1.
Объект git, который делает это, filter-branch
.
Существует как минимум два способа достижения этого с помощью filter-branch
. Один из них - иметь фильтр фиксации, который:
- пропускает все коммиты, пока не появится фиксация
K
, затем
- копирует все коммиты (включая
K
)
(а затем добавьте обычный --tag-name-filter cat
и т.д.). Это немного болезненно, поскольку фильтр фиксации не является eval
-ed, поэтому вам нужно "запомнить" состояние пропуска/сохранения извне (например, в файле).
Другим методом является использование --parent-filter
как уже описано Брайаном Кэмпбеллом.
Разница между ними заключается в том, что метод --parent-filter
проще, но копирует все "pre-K
", так что вы завершаете с двумя независимыми графами в своей копии. Возможно, вы захотите этого или нет; и если после очистки пространства имен refs/original
нет ссылок на "pre- K'
", они будут собирать мусор, как обычно, так что разница будет отсутствовать.