Ответ 1
Настройка
Скажем, это история вокруг фиксации, которую вы хотите удалить.
... o - o - o - o ... ... o
^ ^ ^ ^
| | +- next |
| +- bad +-- master (HEAD)
start
где:
-
bad
- это фиксация, которую вы хотите удалить; -
start
является родителем комманды, которую вы хотите удалить; -
next
- следующая фиксация послеbad
; это хорошо, вы хотите сохранить его и все временные рамки после него; он заменитbad
после rebase.
Необходимые условия
Чтобы иметь возможность безопасно удалить bad
, важно, чтобы никакая другая ветвь, существующая в момент создания bad
, не была объединена с основной временной шкалой после bad
. То есть удалив bad
, и его соединения со своим родителем и дочерним элементом берутся из графика истории, вы получите две отключенные фрагменты временной шкалы.
Вероятно, можно удалить bad
, даже если другая существующая ветка была объединена после bad
. Я не проверял эту ситуацию, но я ожидаю некоторых препятствий из-за фиксации слияния.
Идея
Каждая фиксация git
идентифицируется хешем, который вычисляется с использованием свойств commit: содержимого, сообщения, автора и даты коммиттера и электронной почты.
Бабаза всегда меняет дату коммиттера. Он также может изменять сообщение коммиттера, комментировать сообщение и содержимое.
Чтобы восстановить исходные даты коммиттера после перезагрузки, нам нужно сохранить их вместе с некоторой информацией, которая может идентифицировать каждую фиксацию после перезагрузки.
Поскольку вы хотите изменить фиксацию, содержимое фиксации изменяется во время переустановки. Добавление или удаление файлов или коммитов изменяет содержимое, которое все в будущем фиксирует.
Это оставляет нас без свойства, которое однозначно идентифицирует коммиты и не изменяет во время желаемой перезагрузки. Мы можем попытаться использовать два или более свойства, которые не изменяются во время rebase.
Письма (автор и коммиттер) практически бесполезны. Если в проекте работает один человек, они одинаковы для всех коммитов и не могут использоваться. Свойства, которые остаются (отличаются от большинства коммитов, не зависят от rebase), являются датой автора и сообщением фиксации (первая строка).
Если пара (дата автора, сообщение фиксации) предоставляет уникальные значения для всех коммитов, затронутых rebase, тогда мы можем восстановить даты фиксации после ошибок без ошибок.
Убедитесь, что это можно сделать безопасно
Существует простой способ проверить, являются ли пары (авторская дата, фиксация сообщения) уникальными для затронутых коммитов.
Выполните следующие две команды:
$ git log --format="%aI %s" start...master | uniq | wc -l
$ git log --oneline start...master | wc -l
Если они отображают один и тот же номер, вам повезет: пара (дата автора, сообщение фиксации) может использоваться для однозначной идентификации коммитов. Читайте дальше.
Если числа разные (первая команда всегда будет выдавать число, меньшее или равное числу, полученному второй командой), вам не повезло.
Извлечь информацию, необходимую для фиксации дат фиксации после rebase
Эта команда
$ git log --format="%H %cI %aI %s" start...master > /tmp/hashlist
извлекает хеш фиксации, дату коммиттера (полезную нагрузку), дату автора и сообщение фиксации (ключ) для всех коммитов, начинающихся с start
, и сохраняет их в файле.
Резервное копирование текущего мастера
Хотя распространенное заблуждение, что git
"перезаписывает историю", на самом деле он просто генерирует альтернативную линию истории и решает, что это правильная история. Он не изменяет или не удаляет "перезаписанные" коммиты; они все еще присутствуют в своей базе данных в течение некоторого времени и могут быть восстановлены в случае сбоя операции.
Мы можем проактивно создать резервную копию текущей строки истории, чтобы ее легко восстановить, если это необходимо. Все, что нам нужно сделать, это создать новую ветвь, указывающую на master
. Таким образом, когда git rebase
перемещает master
на новую временную шкалу, старый все еще доступен с использованием новой ветки.
$ git branch old_master
В приведенной выше команде создается ветвь с именем old_master
, которая удерживает текущую временную шкалу в фокусе, пока мы не выполним все изменения и не удовлетворены новым мировым порядком.
Сделайте rebase
Удаление фиксации bad
из истории так же просто, как:
$ git rebase --preserve-merges --onto start bad
Исправить даты фиксации
Следующая команда "перезаписывает" историю и изменяет дату коммиттера, используя ранее сохраненные значения:
$ git filter-branch --env-filter 'export GIT_COMMITTER_DATE=$(fgrep -m 1 "$(git log -1 --format="%aI %s" $GIT_COMMIT)" /tmp/hashlist | cut -d" " -f2)' -f start...master
Как это работает:
git
просматривает историю между коммитами, отмеченными start
и master
, и для каждой фиксации она выполняет команду, предоставленную как аргумент --env-filter
, перед переписыванием фиксации. Он устанавливает переменную окружения GIT_COMMIT
, когда хэш переписываемого коммита перезаписывается.
Поскольку мы уже сделали rebase
, который модифицировал хэши всех коммитов, мы не можем использовать $GIT_COMMIT
непосредственно для идентификации исходной даты фиксации фиксации (потому что $GIT_COMMIT
является фиксацией, сгенерированной git rebase
, и мы не интересуются датами их коммиттера).
Команда, которую мы предоставляем --env-filter
export GIT_COMMITTER_DATE=$(fgrep -m 1 "$(git log -1 --format="%aI %s" $GIT_COMMIT)" /tmp/hashlist | cut -d" " -f2)
запускает git log -1 --format="%aI %s" $GIT_COMMIT
, чтобы сгенерировать пару ключей (дата автора, сообщение фиксации), рассмотренное выше. Его вывод передается в качестве аргумента команде fgrep -m 1 "..." /tmp/hashlist | cut -d" " -f2
, которая находит пару в списке ранее сохраненных хэшей (fgrep
) и извлекает исходную дату фиксации из сохраненной строки (cut
). Наконец, значение даты фиксации сохраняется в переменной среды GIT_COMMITTER_DATE
, которая используется git
для перезаписи фиксации.
Проверка
С помощью команды git log
$ git log --format="%cI %aI %s" start...master
вы можете проверить, что переписанная история соответствует исходной истории. Если вы используете графический клиент git
, вы можете легко проверить результаты с помощью визуального контроля. В ветке old_master
сохраняется старая строка истории на клиенте, и вы можете легко сравнить даты каждой фиксации ветки old_master
с соответствующей ветвью master
.
Если что-то не пошло хорошо или вам нужно изменить процедуру, вы можете легко начать с нее:
$ git reset --hard old_master
Cleanup
Когда вы удовлетворены результатом, вы можете удалить ветвь резервного копирования и файл, используемый для хранения исходных дат фиксации:
$ git branch -D old_master
$ rm /tmp/hashlist
Что все!