Примечание. Я спрашиваю, как сохранить историю файла замены.
Вам необходимо организовать удаление только оригинального файла. Как это сделать немного сложно.
Ключ к пониманию проблемы здесь прост: У Git нет истории файлов. Такой вещи нет! (См. Также Отсутствует удаление строк в истории файлов (git) )
История Git История коммитов . Точнее, коммиты являются историей: каждый коммит имеет обратную ссылку на свой родительский коммит. (Для коммитов слиянием обратная ссылка идет как минимум к двум родителям.) Каждый коммит содержит полный снимок всех файлов, поэтому Git просматривает историю commit и задает вопросы о том, какие файлы в каждом коммите вы можете сделать так, чтобы Git синтезировал притворную историю файлов. Но на самом деле это не там , а , вычисленное исходя из того, что есть там .
Все коммиты в репозитории Git полностью доступны только для чтения. Это включает их обратные ссылки: ссылка содержит «истинное имя» (хэш-идентификатор) родительского коммита, а истинное имя коммита зависит от его содержимого. Это делает невозможным изменение истории. Git's filter-branch
даже не пытается это сделать. Вместо этого он прост: он копирует каждый коммит (ну, каждый коммит, который вы говорите ему копировать), при этом применяя фильтры, которые вы указали. Для каждого существующего коммита Git:
- извлекает коммит во временную рабочую область;
- применяет ваши фильтры; и
- фиксирует результат, как новый коммит.
Если новый коммит на 100%, бит за битом идентичен оригинальному коммиту, вы получаете исходный коммит с его оригинальным идентификатором хеша. Однако, как только произойдут какие-либо изменения, вы получите другой коммит с другим хеш-идентификатором. Основная хитрость внутри ответвления фильтра заключается в том, что он определяет отображение от исходного идентификатора хеша (оригинального коммита) до нового идентификатора хэша (скопированного коммита), и при копировании он всегда заменяет родительский хеш-идентификаторов с их сопоставленными версиями.
Это означает, что вы можете взять хороший, простой график, например:
A <-B <-C <--master
(где каждая заглавная буква обозначает фактический хэш-идентификатор коммита, а стрелки - это сохраненные хэш-идентификаторы в каждом коммите или в имени master
) и фильтруют его. Если вы что-то измените в коммите A
, вы получите новый, другой коммит A'
, и копия B
будет указывать на A'
, а не на A
. Копия C'
укажет на B'
. Это верно, даже если вы также измените что-то во время копирования B
и C
. Результат:
A <-B <-C <--master
A' <-B' <-C'
Последнее, что делает фильтр-ветвь, - это отрывает имена от исходных цепочек коммитов и заставляет их указывать на новые цепочки:
A <-B <-C [refs/original/refs/heads/master]
A' <-B' <-C' <-- master
Запуск git log
, или что-то, что отображает коммиты - история - теперь начинается с C'
и работает в обратном направлении. Показанная или синтезированная история взята из новых скопированных коммитов.
В вашей первоначальной серии коммитов у вас есть некоторые коммиты, которые содержат файл с именем Makefile
, который вы не хотите, чтобы они содержали. Затем у вас есть ряд других коммитов, которые содержат файл с именем Makefile
, который вы do хотите, чтобы они содержали. Ваша задача в вашем фильтре - различать эти два набора коммитов. Вместо:
git filter-branch --index-filter 'git rm --cached --ignore-unmatch Makefile'
Вы хотите, например ::1084 *
git filter-branch --index-filter magic-script
(плюс любые другие опции, которые вам нравятся 1 ). Сложная часть - решить, что входит в этот magic-script
, потому что вы хотите: «если копируемый коммит имеет неправильный make-файл, удалите его, если нет, не делайте». Но как ты это проверишь?
Существует несколько ответов, в том числе с использованием --tree-filter
вместо --index-filter
: древовидный фильтр, который намного ( намного ) медленнее, буквально извлекает каждый коммит, так что вы можете просматривать файлы в нем и создает новый коммит из извлеченных файлов.
Фильтр индекса оставляет извлеченный коммит в индексе (это специальный временный индекс ветки фильтра, но вам, как правило, это не нужно). Вот почему вы использовали git rm --cached --ignore-unmatch Makefile
: он удаляет файл с именем Makefile
из индекса, после чего ветвь фильтра создает новый коммит из индекса. Операции с индексами, которые выполняются в специальном Git-файле, выполняются намного быстрее, чем обычные операции с файловой системой. Но они не позволяют вам проверять файл с именем Makefile
, чтобы принять решение об этом.
Однако есть и другой способ справиться с этим. Предположим, что в нашем идеализированном репозитории с тремя коммитами A-B-C
выше вы исправили Makefile в коммите C
, затем добавили еще несколько коммитов D-E-F-G
или что-то еще. В этом случае вы хотите использовать тест вида:
- Если текущим коммитом с фильтром является
B
или любой из его предков, например A
, удалите файл с именем Makefile
.
- В противном случае (текущий коммит
C
или выше), оставьте файл с именем Makefile
.
Это, как выясняется, возможно . сантехническая команда git merge-base --is-ancestor
выполняет этот родовой тест и может использоваться в сценарии оболочки if
test:
if git merge-base --is-ancestor $GIT_COMMIT <hash>; then
git rm --cached --ignore-unmatch Makefile;
fi
(тест «является предком» включает равенство, поэтому <hash>
здесь будет буквальным хеш-идентификатором commit B
). Поместите все это в одинарные кавычки с соответствующим идентификатором хеша, и у вас есть фильтр, который вы, вероятно, хотите.
(Где это может пойти не так, если есть несколько случаев, когда Makefile
следует или не следует удалять. Если у вас достаточно времени и / или файловой системы на основе ОЗУ с достаточным пространством, вы можете использовать --tree-filter
и изучите фактический Makefile
. Или вы можете получить очень причудливые и использовать сантехнические команды, чтобы исследовать объект Git, хэш-идентификатор которого хранится в индексе, и использовать --index-filter
, но это немного сложно.)
1 Возможно, вы все еще захотите -f
здесь, а также такие вещи, как --tag-name-filter cat
и -- --all
. Обратите внимание, что -f
существует, чтобы сообщить ответвлению фильтра, что если предыдущая ветвь фильтра оставила позади пространство имен refs/original/
, то можно уничтожить это. Всегда целесообразно запускать эти операции с копией хранилища (клон: возможно, сделанный с git clone --mirror
) на случай, если все закончится, и в случае с refs/original/
ненужное предостережение: Вы уже применили все необходимые меры предосторожности!