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

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

Я видел здесь последний ответ: https://stackoverflow.com/a/19522951/3995351, но этого не произошло. Работа.

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

>

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


person TheWatcher    schedule 11.06.2015    source источник
comment
не сработало, ответ ... пожалуйста, дайте ссылку на ответ и, возможно, объясните немного подробнее ..   -  person Vogel612    schedule 11.06.2015
comment
FWIW, дата автора должна быть сохранена по умолчанию. Изменяется только дата фиксации.   -  person David Deutsch    schedule 11.06.2015
comment
Вы также пробовали другие ответы на этот вопрос ?? дайте нам больше информации   -  person Vogel612    schedule 11.06.2015
comment
@ Vogel612 проблема в том, что вопрос был про дату автора. Я не хочу менять дату фиксации. Ответ, о котором я говорю, единственный, который подходит моим потребностям.   -  person TheWatcher    schedule 11.06.2015
comment
@DavidDeutsch да, и я хочу, чтобы дату фиксации не меняли   -  person TheWatcher    schedule 11.06.2015
comment
Ваша проблема (как вы ее описываете) решена в этом ответе на этот самый вопрос ... пробовали ли вы это в некоторых репо песочницы?   -  person Vogel612    schedule 11.06.2015
comment
возможный дубликат git rebase без изменения временных меток фиксации   -  person Vogel612    schedule 11.06.2015
comment
@ Vogel612 может быть дубликатом, но использование даты автора в качестве даты фиксации не является для меня решением, я хочу, чтобы старые даты фиксации сохранялись   -  person TheWatcher    schedule 11.06.2015
comment
старые даты фиксации являются датами автора, учитывая, что вы еще не выполнили перебазирование, изменив временные метки фиксации, потому что вы не использовали --committer-date-is-author-date   -  person Vogel612    schedule 11.06.2015
comment
@ Vogel612 Нет, у меня в репозитории много вещей, выбранных из вишен, поэтому даты фиксации могут сильно отличаться от дат авторов   -  person TheWatcher    schedule 11.06.2015
comment
Проверьте эту статью: axiac.ro/blog/2014/11/merging-git -repositories Необходимая вам часть объяснена в разделах о резервном копировании и восстановлении дат коммиттера. Ответ, на который вы указываете, был очень полезным для начала, но, к сожалению, объясненная здесь команда неверна (неполна?), Как вы уже выяснили.   -  person axiac    schedule 12.06.2015
comment
@axiac только что попробовал это, может я слишком плох .. У меня не работает :( Не могли бы вы дать мне шаг за шагом?: D Я думаю, мне не нужно все, что там сделано   -  person TheWatcher    schedule 12.06.2015
comment
Я перечитываю ваш вопрос сейчас. Действительно, процедура, описанная в этой статье, не работает в вашем случае. У меня это сработало, потому что я не менял содержимое перемещенных коммитов. Иная ситуация, когда вы хотите удалить коммит. Проблема заключается в сложности (или невозможности) найти некоторую часть информации, которая однозначно идентифицирует каждую фиксацию до и после перебазирования.   -  person axiac    schedule 12.06.2015
comment
@axiac, возможно, мы сможем как-то автоматизировать ответ Дэвида Дойча. Я действительно не хотел бы делать это по одному ..: D   -  person TheWatcher    schedule 12.06.2015
comment
Я написал ответ, который предоставляет весь рабочий процесс, необходимый для удаления фиксации из истории и восстановления исходных дат фиксации. Его цель - сделать процедуру максимально безопасной. Надеюсь, его можно без проблем применить в вашем случае.   -  person axiac    schedule 13.06.2015


Ответы (2)


Установка

Допустим, это история коммита, который вы хотите удалить.

... 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
comment
Большое тебе спасибо! Выглядит многообещающе :) попробую сегодня. - person TheWatcher; 14.06.2015
comment
Это дает мне ошибку при первой команде: git log --format% aI% s c318373c22214f91fb189619e093be298aa6c0e0 ... леденец | uniq | wc -l fatal: неоднозначный аргумент '% aI% s': неизвестная ревизия или путь не в рабочем дереве. Используйте '-', чтобы отделить пути от ревизий, например: 'git ‹command› [‹revision› ...] - [‹file› ...]' - person TheWatcher; 14.06.2015
comment
Знак равенства (=) отсутствовал. Правильная команда - git log --format="%aI %s" .... Обновил ответ. - person axiac; 14.06.2015
comment
Привет, все сработало, но на последнем реальном шаге говорится: (1/219) фатальный: неверный формат даты:% c Я не могу записать перезаписанный коммит - person TheWatcher; 17.06.2015
comment
Вы здесь? Вы знаете, в чем может быть проблема? - person TheWatcher; 20.06.2015
comment
Какое точное сообщение об ошибке и какая команда его генерирует? Ваш предыдущий комментарий неоднозначен: %cI используется с git log, но часть не может записать перезаписанный коммит выглядит как ошибка, созданная git filter-branch (и той командой, которая не использует %cI). - person axiac; 20.06.2015
comment
Отлично! Спасибо большое! Одна маленькая вещь, на которую я только что наткнулся: кажется, вы всегда должны использовать sort перед использованием uniq. Минимальный пример: printf "hello\nworld\nhello\nworld" | uniq возвращает четыре строки, а printf "hello\nworld\nhello\nworld" | sort | uniq возвращает две (как и ожидалось). Или напрямую используйте sort -u (или sort --unique), что делает трубопровод до uniq устаревшим. - person msa; 16.05.2019
comment
Не знаю почему, но я продолжаю получать grep: ./commit-list: No such file or directory, мой список хэшей называется commit-list и находится в том же каталоге (я изначально поместил его в родительский каталог, но у ../commit-list была та же проблема). - person Zarepheth; 20.12.2019
comment
В конце концов я заменил относительный путь абсолютным от корня /c/.../commit-list, и это сработало. :п - person Zarepheth; 20.12.2019
comment
Я просто пробовал это при перебазировании поверх другой ветки с слиянием, которое произошло после моего стартового коммита. Команда git filter-branch сбрасывает все даты фиксации между моей начальной точкой и слиянием на текущую дату и время - возможно, потому, что я не включил эту ветвь в файл hashlist. - person Zarepheth; 08.01.2020

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

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

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

person David Deutsch    schedule 11.06.2015
comment
Хм, у меня около 300 коммитов ... Так что это должно быть очень сложно сделать вручную, но, может быть, мы сможем автоматизировать этот набор команд? - person TheWatcher; 11.06.2015
comment
Вау, 300 - это определенно слишком много для моего метода, если только у вас нет много свободного времени. Единственное, что я могу предложить, - это взглянуть на последний ответ на этот вопрос . - person David Deutsch; 11.06.2015
comment
Ах, извините за это. - person David Deutsch; 12.06.2015
comment
может быть, вы могли бы помочь мне решить проблему с помощью того ответа, который мне не подходит .. - person TheWatcher; 12.06.2015