Переписать файл в истории GIT? - PullRequest
0 голосов
/ 26 июня 2018

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

$ diff README.md README.md.new
28a29,32
> ## Contributors
>   - ehime     [Jd Daniel]
>   - thatgguy  [Someone Else]

Поэтому обычно я полностью удаляю файл из всей истории, используя filter-branch:

git filter-branch --force --index-filter     \
'git rm --cached --ignore-unmatch README.md' \  
--prune-empty --tag-name-filter cat -- --all

Это, очевидно, уничтожило бы всю историю моего файла, включая теги и т. Д., Затем mv README.md.new README.md и мои обычные git push вещи ... хорошо, нет, нет, у меня есть теги и коммиты, у которых больше нет этого файла ...

Итак, как мне переписать эту строку через всю историю файла? Я думаю, что мы также можем избежать защиты от сбоев, но я не уверен ...

Я не верю, что BFG может это сделать, поскольку он специально предназначен для очистки, а не для переписывания?

Это чрезвычайно удобно, когда у нас есть, например, изменяющаяся конечная точка API или статический / эластичный ip, который необходимо будет отражать во всех точках истории для тестирования и т. Д.

1 Ответ

0 голосов
/ 26 июня 2018

Вы можете (возможно) добиться того, чего хотите, но давайте начнем с этого:

как мне переписать эту строку через всю историю файла?

Рассмотрим этот философский вопрос: предположим, я говорю вам, что вместо 1993-2000 гг. = Президентство Клинтона, 2001-2008 гг. = Президентство Г.В.Буша, фактически это был 1993-2001 гг. = Клинтон, 2002-2008 гг. = Буш. Предположим далее, что я как-то загипнотизирую вас, чтобы поверить в это. Я действительно изменил историю, или вы просто используете неправильную историю? Что, если каким-то образом все тоже поверит в это?

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

Итак: что если мы скопируем каждый , фиксируем новый, немного другой, приводящий к новому, другому хранилищу, а затем каким-то образом убедим всех, что этот новый, другой хранилище является реальным хранилище и все они раньше использовали фальшивую историю?

Вот что делает git filter-branch: он копирует каждый коммит 1 в новый, применяя фильтры, которые вы указали в первую очередь. Новый коммит имеет новый, отличный хеш-идентификатор, если что-либо в нем даже отличается от оригинала на один бит - и, конечно, в качестве копий ветки фильтра, если он внес изменение в предыдущий коммит, следующий коммит, который будет скопирован, должен измените родительский хэш-идентификатор, чтобы он использовал скопированный коммит вместо оригинала. Так что, как только где-нибудь произойдут какие-либо изменения, это будет распространяться на всю историю.

В результате ваш репозиторий теперь содержит два набора коммитов - две полные истории - и если вы изменили самую первую копию коммита, эти два набора коммитов не пересекаются. (Если вы оставили этот самый первый коммит один, два набора - две истории - объединяются в начале, а затем расходятся везде, где вы сделали свое первое изменение.) Фильтр-ветвь перезаписывает имена ветвей и, возможно, имена тегов (--tag-name-filter), чтобы они ссылались на эту новую историю. Ваш репозиторий теперь верит в новую историю, а не в старую. (Имена refs/original/ запоминают исходную историю; после их удаления исходные коммиты становятся уязвимыми для сборщика мусора.)

Теперь вы должны убедить всех переключиться. Это большой флаг дня .


1 Точнее говоря, каждый достижимый коммит основывается на аргументах стиля git rev-list, которые вы передаете git filter-branch. Кроме того, некоторые фильтры специально пропускают некоторые коммиты, так что в скопированной истории зафиксировано меньше коммитов, чем в оригинале.


Фильтрующие механизмы

Самый простой фильтр - --tree-filter. Этот фильтр работает, буквально извлекая каждый коммит во временный каталог, а затем выполняя любые команды, которые вы предоставляете, в этом временном каталоге. Чтобы вы могли использовать:

--tree-filter /tmp/edit-readme.sh

где /tmp/edit-readme.sh - ваша (исполняемая) программа, которая редактирует README.md на месте. Обратите внимание, что мы используем здесь абсолютный путь (/tmp/edit-readme.sh), поскольку Git находится в каком-то таинственном неизвестном временном каталоге, когда запускает фильтр дерева. (Даже если вы используете -d, Git создает подкаталоги внутри любого аргумента, который вы использовали.)

Недостаток этого фильтра в том, что он чрезвычайно медленный. Извлечение, изменение и перестроение каждого коммита занимает много времени. Вы можете использовать опцию filter-branch -d, чтобы использовать файловую систему в памяти, что значительно ускорит ее, но вам потребуется достаточно памяти для распаковки и восстановления каждого коммита.

Theбыстрый фильтр для этого --index-filter, но его сложно использовать: фильтр-ветвь копирует каждый коммит только в индекс. 2 Ваша задача состоит в том, чтобы изменить индекс . Вы можете проверить индекс на наличие файла README.md, и, если он существует, извлечь его, изменить его и поместить новый обратно в индекс. Git делает новые коммиты из того, что вы оставляете в индексе, после фильтра индекса.

Я оставляю вам право написать скрипт редактирования на основе индекса или дерева. Теперь у вас есть инструменты, чтобы переписать ваш репозиторий в новый, несовместимый репозиторий. Можете ли вы заставить всех остальных отказаться от своих существующих репозиториев в пользу этого нового, чтобы они поверили в фальшивую историю, - это совсем другой вопрос.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...