Будет ли удаление файла из истории Git также удалить его замену? - PullRequest
0 голосов
/ 28 августа 2018

Я узнал, как удалить файл из истории git, но когда я попытался удалить файл (root Makefile), который был заменен другим (Makefile в подкаталоге перемещен в root), метод, который я использовал также удалил замену.

Вот точная команда, которую я использовал:

git filter-branch -f --index-filter 'git rm --cached --ignore-unmatch Makefile'

Есть ли способ сделать это без удаления замены? Если да, то как?

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

Ответы [ 2 ]

0 голосов
/ 29 августа 2018

Для этого сначала нужно определить последние коммиты, которые содержат старый файл (который вы хотите удалить), и самые ранние коммиты, которые содержат новый файл, который вы хотите сохранить. Затем вы можете применить свой фильтр только к более старым коммитам.

Например, если у вас есть

o -- o -- o -- o -- A -- x -- x -- x <--(master)

, где старый файл присутствует в коммитах, помеченных o, но коммит A переместил новый файл (который вы хотите сохранить) в корень: затем вы хотите оставить A и коммиты x нетронутый.

Чтобы сделать это с filter-branch, либо вам нужен фильтр, который «знает», какой коммит он редактирует, либо вам нужно применить фильтр только к коммитам o. Последнее проще, но в этом случае вы получите разделенную историю

o -- o -- o -- o -- A -- x -- x -- x <--(master)

o' -- o' -- o' -- o'

и вам нужно будет выполнить «перевоспитание» A до последнего o' коммита. Это также может быть сделано с filter-branch (с использованием --parent-filter), но это все еще имеет дело только с одной строкой истории - или, по крайней мере, только с одним "переходным" коммитом, где вы переключали файлы. Если у вас есть несколько коммитов, которые «вводят» изменения между файлами (то есть, потому что изменения распространяются по веткам через слияния), то эта процедура быстро усложняется.

Лучшим решением было бы рассмотреть BFG Repo Cleaner. Он специализируется на удалении нежелательной истории, поэтому (1) это быстрее и (2) часто проще. Он может быть настроен так, чтобы «защищать» некоторые коммиты и редактировать только другие. Пожалуйста, смотрите страницу проекта и документы для более подробной информации (https://rtyley.github.io/bfg-repo-cleaner/)

0 голосов
/ 28 августа 2018

Примечание. Я спрашиваю, как сохранить историю файла замены.

Вам необходимо организовать удаление только оригинального файла. Как это сделать немного сложно.

Ключ к пониманию проблемы здесь прост: У 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/ ненужное предостережение: Вы уже применили все необходимые меры предосторожности!

...