Удалить первые x коммитов в истории git и удалить все ветви слияния из остальной истории
У меня есть история проектов GIT, в которой у меня есть около 400 коммитов. Я хочу удалить первые (самые ранние) 200 коммитов. Затем в оставшихся 200 коммитах я просто хочу удалить все коммиты слияния и сохранить все в порядке.
После того, как это будет сделано, я хочу просмотреть все оставшиеся коммиты и сменить одного конкретного автора по электронной почте.
Есть ли способ сделать это изящно?
2 ответа
Как уже говорили несколько человек, это редко хорошая идея по нескольким причинам, которые я не буду повторять. Я хочу добавить еще одну вещь, а затем показать, как вы можете сделать это с git filter-branch
,
Это не удаление, это новая копия: по сути, новый репо
Важно знать, что вы не можете удалить коммиты с передней или средней части коммитов. Причина проста: каждая запись коммитов, как часть своей идентичности, идентифицирует родительский коммит (ы). Техническим термином для этого является то, что граф коммитов образует дерево Меркля.
Точнее говоря, идентичность - "истинное имя", если хотите, - это его SHA-1. SHA-1 - это криптографический 1 хеш данных в коммите. Одним из фрагментов данных является parent
линия. Вот фактический коммит внутри самого источника git (минус @
знаки для предотвращения сбора спама по электронной почте):
tree 55c0d854767f92185f0399ec0b72062374f9ff12
parent 8413a79e67177d026d2d8e1ac66451b80bb25d62
author Junio C Hamano <gitster pobox.com> 1436563740 -0700
committer Junio C Hamano <gitster pobox.com> 1436563740 -0700
The last minute bits of fixes
Signed-off-by: Junio C Hamano <gitster pobox.com>
Если вы попытаетесь удалить родительский коммит в любом месте цепочки, вы получите новый, другой хэш-номер для дочернего коммита. Это означает, что все его дочерние элементы также должны измениться, чтобы включить новые SHA-1 по всей цепочке.
Для вас это означает, что вы можете получить что-нибудь, включая git filter-branch
Чтобы удалить некоторые коммиты, вы должны скопировать каждый коммит для сохранения в новый коммит с новым коммитом с другим идентификатором (который имеет то же дерево и сообщение и т. д., как и прежде, но другой parent
линия). 2
По сути, результат выполнения git filter-branch
создать новую копию репозитория, содержащую, по крайней мере, некоторые, а может и полностью, новые и разные коммиты. Это, в свою очередь, означает, что любой, кто работает со старым репозиторием, должен отказаться от своего старого репозитория и переключиться на новый.
git filter-branch
В то время как git filter-branch
есть много вариантов, его основная работа сводится к этому. За каждый коммит: 3
- раскройте исходное дерево коммита
- получить автора и коммиттера (имя, адрес электронной почты и отметки времени)
- применить все фильтры:
- внести любые необходимые изменения в дерево
- внести необходимые изменения в автора и коммиттера
- сохранить или пропустить этот конкретный коммит: если сохраняете этот коммит, сделайте новый коммит из того, что осталось
- добавить запись в файл сопоставления, "оригинальный SHA-1" в "новый SHA-1"
Указанный здесь маркированный список является этапом "копирования", после которого следует еще одна задача - "обновить ссылки". Чтобы правильно понять эту часть, вам нужно знать, как работают ссылки git, но вкратце, имена веток (и если вы добавляете --tag-filter
, имена тегов как крошечные) проверяются, чтобы увидеть, если они указывают на старый коммит, который был переписан. Если это так, они изменяются, чтобы указывать на новую копию или на ближайший коммит новой копии в случае пропущенных коммитов,
Чтобы достичь того, что вы хотите, вам нужно написать фильтр фиксации, который использует skip_commit
функция для пропуска коммитов, которые вы хотите удалить (первые 200 и слияния), и использует git commit-tree
на отдыхе. Увидеть git filter-branch
документация для более подробной информации.
(Одна причина git filter-branch
Есть так много вариантов, что расширение и повторное сжатие целых деревьев очень медленно. Сценарий пытается избежать этого, и если все ваши фильтры могут быть выполнены в индексе и коммит-графе - без расширения деревьев исходного кода - фильтр завершается гораздо быстрее.)
Пример реализации, основанный на новом корне фиксации:
Код ниже создаст новое репо, состоящее только из всех коммитов ниже указанного нового STARTCOMMIT. Ветки и теги сохраняются.
export STARTCOMMIT=.....
git filter-branch --tag-name-filter cat \
--commit-filter '
git merge-base --is-ancestor ${STARTCOMMIT} ${GIT_COMMIT};
if [ $? -eq 1 ];
then
skip_commit "$@";
else
git commit-tree "$@";
fi' \
-- --all
# remove original references
git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
# reduce repo size
git reflog expire --expire=now --all && git gc --aggressive --prune=all
1 Смысл "криптографического" прилагательного состоит в том, что вы не можете просто внести небольшое изменение в коммит, например, добавив текст в сообщение, чтобы создать тот же старый SHA-1, который у вас был раньше. Единственный способ сделать это в вычислительно выполнимое время - сломать шифрование.
2 В случаях менее интенсивных изменений, если вы делаете точную копию оригинального коммита, вы получаете тот же SHA-1, что и раньше. Например, если у вас есть операция ветвления фильтра, которая удаляет коммит с наивысшим приоритетом в цепочке, только коммит с самым верхним уровнем получает новый SHA-1. Однако в этом конкретном случае мы предлагаем удалить корневой коммит, который обязательно перенумеровывает каждый последующий коммит.
3 Коммиты для копирования получаются из аргументов стиля gitrevisions, которые вы предоставляете как часть операции filter-branch. Имена ветвей, которые нужно переписать, также взяты отсюда, используя "положительные ссылки".
Сначала подумайте дважды, если вы действительно хотите это сделать. (Изменение истории, особенно в публичном хранилище, обычно плохая идея.)
Ты можешь использовать git rebase -i
сделать это. Там вы можете использовать fixup
чтобы объединить два коммита в один, вы можете использовать edit
изменить коммит. (включая смену автора.)
Для автоматических изменений нескольких коммитов вы можете использовать git filter-branch
, Но используйте это, только если вы знаете, что делаете.