Команда Git Filter-Branch All

На данный момент я использую команду "git filter-branch --subdirectory-filter MY_DIRECTORY -- --all", чтобы захватить определенный каталог из всех 30 ветвей в этом репозитории git. Перед выполнением этой команды фильтрации веток я обязательно проверяю каждую ветку, чтобы убедиться, что команды --all работают правильно.

Мой вопрос: нужно ли мне проверять каждую ветку, прежде чем я сделаю git-filter all, или все git-filter все еще будет работать без необходимости проверять все 30 веток, на которые я смотрю? Сейчас размер каждой ветки составляет почти 3 ГБ, поэтому весь процесс проверки занимает очень много времени. Любое разъяснение было бы здорово!


person JWill23    schedule 21.06.2018    source источник


Ответы (1)


Прежде чем мы начнем

Прежде чем я углублюсь в сам ответ, обратите внимание, что если вы хотите иметь имя локальной ветки для каждого из ваших имен удаленного отслеживания, вы можете просто создать это имя локальной ветки без использования git checkout:

git branch -t develop origin/develop
git branch -t feature/X origin/feature/X
git branch -t foo origin/foo

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

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

Короткий и длинный ответ

Короткий ответ заключается в том, что вам не нужно извлекать (или создавать новые) имена веток. Но чтобы хорошо использовать Git (включая эту конкретную операцию git filter-branch), вам нужно понимать больше, чем это.

Начнем с этого: --all здесь означает все ссылки. Но что такое «ссылка» тогда?

Любое название ветки является ссылкой. Но то же самое относится и к любому названию тега. Специальное имя refs/stash, используемое git stash, является ссылкой. Имена удаленного отслеживания являются ссылками. Примечания refs (из git notes) являются ссылками. Подробнее об этом и других терминах Git см. гитглоссарий (обратите внимание, что эта конкретная запись находится под ref, а не reference).

Когда вы впервые используете git clone для клонирования репозитория, вы говорите своему собственному Git: создайте новую, независимую копию некоторого существующего репозитория по указанному вами URL-адресу, чтобы я мог выполнять свою работу, а затем поделиться ею. или не так, как мне нравится. Но их репозиторий — кем бы ни были «они» по URL-адресу — имеет свои собственные имена ветвей. У них есть их master, которые не всегда совпадают с вашими master. Итак, ваш Git переименовывает их имена: их master становится вашими origin/master и так далее. Эти имена удаленного отслеживания являются ссылками.

После того, как git clone завершит копирование в ваш репозиторий всех своих коммитов и переименует все их имена в ваши имена для удаленного отслеживания, последним шагом git clone будет получение ветки. Но у вас пока нет нет филиалов. Вот тут-то и появляется особый трюк, который делает git checkout: если вы просите Git проверить по имени ветку, которая не существует, Git просматривает все ваши имена для удаленного отслеживания. Если один из них совпадает, Git создает имя локальной ветки — новую ссылку — которая указывает на тот же коммит, что и это имя для удаленного отслеживания.

Следовательно, в вашем репозитории есть несколько серий коммитов, все из которых связаны друг с другом обратным образом:

first  <--next ... <--almost-last  <--last

(если они все линейные, что почти никогда не бывает), которые мы можем нарисовать как:

A--B--...--H--I

где каждая заглавная буква представляет фиксацию. Набор коммитов с некоторой «ветвистостью» (ветвистостью?) может выглядеть так:

     C--D
    /
A--B
    \
     E--F--G

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

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

...--H--I   <-- origin/master

Говорят, что имя origin/master указывает на фиксацию I. Когда ваш Git создает свой собственный master, ваш master теперь также указывает на I:

...--H--I   <-- master, origin/master

Если вы создадите свой собственный новый коммит на master, произойдет следующее:

...--H--I   <-- origin/master
         \
          J   <-- master

Git создает новый идентификатор для нового коммита — это какой-то явно случайный большой уродливый хеш-идентификатор, но здесь мы просто называем его J — а затем меняет ваше имя master, чтобы оно указывало на этот новый коммит.

Если вы запустите git fetch и внесете новые коммиты из origin, и они обновят своего мастера, вы получите:

...--H--I--K   <-- origin/master
         \
          J   <-- master

а теперь ваш master и их origin/master разошлись.

Эти имена, master и origin/master, имеют важный эффект, делая их коммиты доступными. То есть, следуя по стрелке от каждого имени, Git может найти коммиты J и K. Затем, используя стрелку назад — фактически идентификатор хэша parent коммита — от J до I или от K до I, Git может найти коммит I. Используя стрелку назад от самого I, Git может найти H и так далее, вплоть до самой первой фиксации, где действие останавливается.

Все недостижимые коммиты — те, которые не были найдены путем запуска во всех этих начальных (конечных?) точках и обхода в обратном направлении — в какой-то момент будут удалены, поэтому они фактически не существуют. Для большинства команд Git, которые проходят через граф, это также верно. (Есть несколько специальных приемов восстановления, которые позволяют вернуть удаленные коммиты на 30 дней, но filter-branch их не учитывает.)

Что все это значит для filter-branch

Работа git filter-branch заключается в копировании коммитов. Он проходит по графу, используя начальные (конечные?) точки, которые вы ему даете, чтобы найти все достижимые коммиты. Он сохраняет их хэш-идентификаторы во временном файле. Затем, двигаясь в обратном направлении — т. е. вперед во времени вместо обычного Git назад — он извлекает каждый из этих коммитов. То есть он проверяет его, чтобы все файлы в этом снимке были доступны. Затем filter-branch применяет фильтр(ы), а затем делает новую фиксацию из полученных файлов. Таким образом, если ваш фильтр вносит простое изменение, результатом будет копия исходного графика:

A--B--C------G--H   <-- master, origin/master
    \       /
     D--E--F

становится:

A'-B'-C'-----G'-H'  <-- master, origin/master
    \       /
     D'-E'-F'

Что происходит с исходными коммитами? Ну, они все еще там: что filter-branch делает с именами, которые их нашли, так это переименовывает их, используя refs/original/ перед их внутренними полными именами:

A--B--C------G--H   <-- refs/original/refs/heads/master, refs/original/refs/remotes/origin/master
    \       /
     D--E--F

Одна из причин, по которой filter-branch имеет так много параметров фильтрации, заключается в том, что этот процесс ужасно медленный. Извлечение каждого файла во временный каталог занимает много времени. Таким образом, некоторые фильтры могут работать вообще без извлечения файлов, что происходит намного (намного!) быстрее.

Другая причина заключается в том, что иногда мы не хотим копировать каждую фиксацию, мы хотим копировать только некоторые фиксации, соответствующие определенным критериям. Это относится к --subdirectory-filter: он копирует коммит только в том случае, если он изменяет файлы (по отношению к его родительскому коммиту (ам)), которые включают рассматриваемый подкаталог. Таким образом, в некоторых случаях он может пропустить извлечение большого количества коммитов. Конечно, фильтр подкаталога также переименовывает файлы по мере извлечения и повторной фиксации, чтобы удалить путь к подкаталогу. В результате больший граф фиксации копируется в новый, меньший:

A--B--C------G--H   <-- master
    \       /
     D--E--F

может стать:

B'--G'--H'   <-- master
 \ /
  E'

Сохраненный refs/original/refs/heads/master по-прежнему будет указывать на коммит H, а переписанный refs/heads/master будет указывать на скопированный коммит H'. Обратите внимание, что первая фиксация в новом графике — B', а не A', так как A' не имеет рассматриваемого подкаталога.

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

Команда перезапишет только положительные ссылки, указанные в командной строке (например, если вы передадите a..b, будут перезаписаны только b) .

Поскольку вы используете --all, это приведет к перезаписи всех имен удаленного отслеживания origin/*. (--all считается положительным упоминанием каждой ссылки здесь. С тегами есть некоторая хитрость: если вы хотите переписать свои теги, добавьте --tag-name-filter cat в качестве фильтра.)

Резюме

После операции filter-branch у вас есть ряд refs/original/* имен, которые указывают на исходные (предварительно фильтрующие) коммиты, переименованные из их исходных полных имен. У вас есть ряд новых обновленных ссылок, включая все имена ваших веток (refs/heads/*) и имена удаленного отслеживания (refs/remotes/*), указывающие на последний из скопированных коммитов.

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

Кроме того, вы можете просто оставить скопированный репозиторий на месте после удаления всех имен пространств имен refs/original/. Затем вы можете git checkout develop создать свой собственный refs/heads/develop на основе вашего (отфильтрованного) refs/remotes/origin/develop и так далее. Все, что вы делаете, это создаете новые имена — сами коммиты — это то, о чем действительно заботится Git, и на них ссылаются переписанные имена удаленного отслеживания, — а затем проверяете этот конкретный коммит, чтобы он был в вашем индексе и рабочем дереве. . (Команды git branch -t, которые мы показали в начале, создали имена без копирования коммитов в index-and-work-tree.)

person torek    schedule 21.06.2018
comment
Это фантастика, я очень ценю ваше руководство и объяснение! Я прекрасно понимаю, что мне сейчас нужно делать. - person JWill23; 25.06.2018