Установка
Допустим, это история коммита, который вы хотите удалить.
... o - o - o - o ... ... o
^ ^ ^ ^
| | +- next |
| +- bad +-- master (HEAD)
start
где:
bad
- это коммит, который вы хотите удалить;
start
- родитель коммита, который вы хотите удалить;
next
- следующая фиксация после bad
; это хорошо, вы хотите сохранить его и всю временную шкалу после него; он заменит bad
после перебазирования.
Предпосылки
Чтобы иметь возможность безопасно удалить bad
, важно, чтобы никакая другая ветвь, существовавшая на момент создания bad
, не была объединена с основной временной шкалой после bad
. Т.е. удалив bad
и его связи с его родительскими и дочерними коммитами из графа истории, вы получите две несвязанные части шкалы времени.
Вероятно, можно удалить bad
, даже если после bad
была объединена другая существующая ветвь. Я не проверял эту ситуацию, но ожидаю некоторых препятствий из-за фиксации слияния.
Идея
Каждая git
фиксация идентифицируется хешем, который вычисляется с использованием свойств фиксации: содержимого, сообщения, автора и даты фиксации, а также электронной почты.
Перебазирование всегда изменяет дату коммиттера. Он также может изменять адрес электронной почты коммиттера, сообщение фиксации и контент.
Чтобы восстановить исходные даты коммиттеров после перебазирования, нам нужно сохранить их вместе с некоторой информацией, которая может идентифицировать каждую фиксацию после перебазирования.
Поскольку вы хотите изменить фиксацию, содержимое фиксации изменяется во время перебазирования. Добавление или удаление файлов или коммитов изменяет содержимое всех будущих коммитов.
Это оставляет нас без свойства, которое однозначно идентифицирует коммиты и не изменяется во время желаемой перебазировки. Мы можем попробовать использовать два или более свойства, которые не меняются во время перебазирования.
Письма (автор и коммиттер) практически бесполезны. Если над проектом работал один человек, они одинаковы для всех коммитов и не могут быть использованы. Остающиеся свойства (разные для большинства коммитов, на них не влияет перебазирование) - это дата автора и сообщение о фиксации (первая строка).
Если пара (дата автора, сообщение о фиксации) предоставляет уникальные значения для всех коммитов, затронутых перебазированием, тогда мы сможем восстановить даты фиксации впоследствии без ошибок.
Убедитесь, что это можно сделать безопасно
Существует простой способ проверить, являются ли пары (дата автора, сообщение о фиксации) уникальными для затронутых коммитов.
Выполните следующие две команды:
$ git log --format="%aI %s" start...master | uniq | wc -l
$ git log --oneline start...master | wc -l
Если они отображают одно и то же число, то вам повезло: пара (дата автора, сообщение о фиксации) может использоваться для однозначной идентификации коммитов. Читать дальше.
Если числа разные (первая команда всегда будет давать число меньше или равное числу, полученному второй командой), то вам не повезло.
Извлеките информацию, необходимую для исправления дат фиксации после перебазирования.
Эта команда
$ git log --format="%H %cI %aI %s" start...master > /tmp/hashlist
извлекает хеш фиксации, дату коммиттера (полезную нагрузку), дату автора и сообщение фиксации (ключ) для всех коммитов, начиная с start
, и сохраняет их в файле.
Сделайте резервную копию текущего мастера
Хотя это распространенное заблуждение, что git
«переписывает историю», на самом деле это просто генерирует альтернативную строку истории и решает, что это правильная история. Он не изменяет и не удаляет «переписанные» коммиты; они еще некоторое время присутствуют в его базе данных и могут быть восстановлены в случае сбоя операции.
Мы можем заранее создать резервную копию текущей строки истории, чтобы при необходимости легко восстановить ее. Все, что нам нужно сделать, это создать новую ветку, указывающую на master
. Таким образом, когда git rebase
перемещается master
на новую временную шкалу, старая временная шкала все еще доступна с использованием новой ветки.
$ git branch old_master
Приведенная выше команда создает ветку с именем old_master
, которая сохраняет текущую временную шкалу в фокусе, пока мы не завершим все изменения и не будем удовлетворены новым мировым порядком.
Сделайте перебазирование
Удалить фиксацию 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
Очистка
Когда вы будете удовлетворены результатом, вы можете удалить резервную копию и файл, используемый для хранения исходных дат фиксации:
$ git branch -D old_master
$ rm /tmp/hashlist
Это все!
person
axiac
schedule
13.06.2015
--committer-date-is-author-date
- person Vogel612   schedule 11.06.2015