Как сделать git rebase и сохранить метку фиксации?

Я хочу сделать rebase, чтобы удалить определенную фиксацию из моей истории. Я знаю, как это сделать. Однако, если я это сделаю, отметка фиксации устанавливается в тот момент, когда я завершил перезагрузку. Я хочу, чтобы коммиты сохраняли метку времени.

Я увидел последний ответ здесь: qaru.site/info/3997/..., однако это не сработало.

Последняя важная команда только что показала новую строку с

>

Итак, я открываю новый вопрос.

Ответ 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

Что все!

Ответ 2

Итак, вот утомительный способ сделать это (в зависимости от того, сколько коммитов вам нужно перебалансировать), но я попробовал, и он работает. Когда вы делаете интерактивную перезагрузку, отметьте каждую фиксацию "e", чтобы вы могли ее отредактировать. Это приведет к остановке git после каждой фиксации. При каждой паузе вы можете указать, какую дату использовать, и продолжить следующую фиксацию с помощью:

GIT_COMMITTER_DATE="Wed Feb 16 14:00 2011 +0100" git commit --amend   
git rebase --continue

Это, конечно, большая боль в тылу, и вы должны знать все даты фиксации перед рукой, но если вы не можете сделать это каким-либо другим способом, это, по крайней мере, должно работать.