Spec-Zone.ru › Git

git-filter-branch

Имя

git-filter-branch - Переписать ветви

Синтаксис

git filter-branch [--setup <command>] [--subdirectory-filter <directory>]
        [--env-filter <command>] [--tree-filter <command>]
        [--index-filter <command>] [--parent-filter <command>]
        [--msg-filter <command>] [--commit-filter <command>]
        [--tag-name-filter <command>] [--prune-empty]
        [--original <namespace>] [-d <directory>] [-f | --force]
        [--state-branch <branch>] [--] [<rev-list-options>…​]

Предупреждение

git filter-branch имеет множество ловушек, которые могут привести к неявным искажениям предполагаемой переработки истории (и могут оставить вам мало времени на расследование таких проблем, так как производительность ужасная). Эти проблемы с безопасностью и производительностью не могут быть исправлены обратной совместимостью, и поэтому их использование не рекомендуется. Пожалуйста, используйте альтернативный инструмент фильтрации истории, такой как git filter-repo. Если вам всё же необходимо использовать git filter-branch, пожалуйста, внимательно прочитайте раздел БЕЗОПАСНОСТЬ (и ПРОИЗВОДИТЕЛЬНОСТЬ), чтобы узнать о ловушках filter-branch, и затем старайтесь по возможности избегать как можно большего количества описанных там опасностей.

Описание

Позволяет переписать историю ревизий Git, переписывая ветви, упомянутые в <rev-list-options>, применяя пользовательские фильтры к каждой ревизии. Эти фильтры могут изменять каждый дерево (например, удаляя файл или выполняя переписывание всех файлов с помощью Perl) или информацию о каждом коммите. В противном случае вся информация (включая исходные временные метки коммитов или информацию о слиянии) сохраняется.

Команда перепишет только positive ссылки, упомянутые в командной строке (например, если вы передадите a..b, будет переписана только b). Если вы не укажете фильтры, коммиты будут перекоммитированы без изменений, что обычно не повлияет на работу. Тем не менее, это может быть полезно в будущем для компенсации некоторых ошибок Git или подобного, поэтому такое использование разрешено.

ПРИМЕЧАНИЕ: Эта команда учитывает .git/info/grafts файл и ссылки в refs/replace/ пространстве имён. Если у вас есть какие-либо прививки или заменённые ссылки, выполнение этой команды сделает их постоянными.

ПРЕДУПРЕЖДЕНИЕ! Переписанная история будет иметь разные имена объектов для всех объектов и не будет совпадать с исходной ветвью. Вы не сможете легко отправить и распространить переписанную ветвь поверх исходной ветви. Пожалуйста, не используйте эту команду, если вы не понимаете всех последствий, и во избежание этого вообще, если для решения вашей проблемы достаточно простого единственного коммита. (См. раздел «ВОССТАНОВЛЕНИЕ ИЗ РЕБАЙЗА УПСТРИМА» в git-rebase[1] для получения дополнительной информации о переписывании опубликованной истории).

Всегда проверяйте правильность переписанной версии: исходные ссылки, если они отличаются от переписанных, будут храниться в пространстве имён refs/original/.

Поскольку эта операция очень затратна по вводу-выводу, возможно, целесообразно перенаправить временную директорию вне диска с помощью -d параметра, например, на tmpfs. Сообщается, что ускорение заметно.

Фильтры

Фильтры применяются в порядке, указанном ниже. Аргумент <command> всегда вычисляется в контексте оболочки с использованием eval команды (с заметным исключением фильтра коммита по техническим причинам). Перед этим переменная окружения $GIT_COMMIT будет установлена в идентификатор переписываемого коммита. Кроме того, GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, GIT_AUTHOR_DATE, GIT_COMMITTER_NAME, GIT_COMMITTER_EMAIL и GIT_COMMITTER_DATE берутся из текущего коммита и экспортируются в среду, чтобы повлиять на идентификаторы автора и коммитера заменяемого коммита, созданного командой git-commit-tree[1] после выполнения фильтров.

Если любое вычисление <command> возвращает ненулевой код возврата, вся операция будет прервана.

Доступна функция map, которая принимает аргумент «исходный идентификатор sha1» и выводит «переписанный идентификатор sha1», если коммит уже переписан, и «исходный идентификатор sha1» в противном случае; функция map может возвращать несколько идентификаторов в отдельных строках, если ваш фильтр коммитов вывел несколько коммитов.

Параметры

--setup <command>

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

--subdirectory-filter <directory>

Рассматривается только история, затрагивающая указанную поддиректорию. Результат будет содержать эту директорию (и только её) в качестве корня проекта. Подразумевает переназначение на предка.

--env-filter <command>

Этот фильтр можно использовать, если требуется только изменить среду, в которой будет выполнен коммит. В частности, вы можете переписать переменные среды имени/e-mail автора/автора коммита/времени (см. git-commit-tree[1] для подробностей).

--tree-filter <command>

Это фильтр для переписывания дерева и его содержимого. Аргумент оценивается в оболочке с рабочей директорией, установленной в корень проверочного дерева. Новое дерево затем используется как есть (новые файлы автоматически добавляются, исчезнувшие файлы автоматически удаляются — ни файлы .gitignore, ни другие правила игнорирования НЕ ОКАЗЫВАЮТ НИКАКОГО ВЛИЯНИЯ!).

--index-filter <command>

Это фильтр для переписывания индекса. Он похож на фильтр дерева, но не проверяет дерево, что делает его намного быстрее. Часто используется с git rm --cached --ignore-unmatch ..., см. ПРИМЕРЫ ниже. Для сложных случаев см. git-update-index[1].

--parent-filter <command>

Это фильтр для переписывания списка предков коммита. Он получит строку предков на стандартном входе и должен вывести новую строку предков на стандартном выходе. Строка предков имеет формат, описанный в git-commit-tree[1]: пустая для начального коммита, «-p parent» для обычного коммита и «-p parent1 -p parent2 -p parent3 …​» для коммита слияния.

--msg-filter <command>

Это фильтр для переписывания сообщений коммитов. Аргумент оценивается в оболочке с исходным сообщением коммита на стандартном входе; его стандартный вывод используется в качестве нового сообщения коммита.

--commit-filter <command>

Это фильтр для выполнения коммита. Если этот фильтр указан, он будет вызван вместо команды git commit-tree, с аргументами вида «<ID_ДЕРЕВА> [( -p <ID_ПРЕДКОВОГО_КОММИТА>)…​]» и сообщением журнала на стандартном входе. ID коммита ожидается на стандартном выходе.

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

Вы можете использовать удобную функцию map в этом фильтре, а также другие удобные функции. Например, вызов skip_commit "$@" пропустит текущий коммит (но не его изменения! Если вам нужно это, используйте git rebase вместо этого).

Вы также можете использовать git_commit_non_empty_tree "$@" вместо git commit-tree "$@", если не хотите сохранять коммиты с единственным родителем и не производящие изменений в дереве.

--tag-name-filter <command>

Это фильтр для переписывания имён тегов. При его передаче он будет вызван для каждого указателя тега, указывающего на переписанный объект (или на объект тега, который указывает на переписанный объект). Исходное имя тега передаётся через стандартный ввод, а новое имя тега ожидается на стандартном выходе.

Исходные теги не удаляются, но могут быть перезаписаны; используйте «--tag-name-filter cat», чтобы просто обновить теги. В этом случае будьте очень осторожны и убедитесь, что у вас есть резервная копия старых тегов на случай, если преобразование завершилось неудачно.

Поддерживается почти корректное переписывание объектов тегов. Если к тегу прикреплено сообщение, будет создан новый объект тега с тем же сообщением, автором и меткой времени. Если к тегу прикреплена подпись, подпись будет удалена. Удаление подписей — неизбежно. Причина, по которой это «почти» корректное переписывание, заключается в том, что в идеале, если тег не изменился (указывает на тот же объект, имеет то же имя и т. д.), он должен сохранить любую подпись. Это не так, подписи всегда будут удалены, пользователь несёт ответственность. Также нет поддержки изменения автора или метки времени (или сообщения тега). Теги, которые указывают на другие теги, будут переписаны для указания на лежащий в основе коммит.

--prune-empty

Некоторые фильтры создают пустые коммиты, которые не изменяют дерево. Этот параметр сообщает git-filter-branch об удалении таких коммитов, если у них ровно один или ноль непрореженных родителей; коммиты слияния останутся неизменными. Этот параметр нельзя использовать вместе с --commit-filter, хотя тот же эффект можно достичь, используя предоставленную функцию git_commit_non_empty_tree в фильтре коммитов.

--original <namespace>

Используйте этот параметр для установки пространства имён, где будут храниться исходные коммиты. Значение по умолчанию — refs/original.

-d <directory>

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

-f
--force

git filter-branch отказывается запускаться с существующей временной директорией или при наличии уже существующих ссылок, начинающихся с refs/original/, если не принудительно.

--state-branch <branch>

Этот параметр приведет к загрузке отображения от старых к новым объектам из указанной ветви при запуске и сохранению как нового коммита в эту ветвь при выходе, что позволит выполнять инкрементальную обработку больших деревьев. Если <branch> не существует, она будет создана.

<rev-list options>…​

Аргументы для git rev-list. Все положительные ссылки, включённые этими параметрами, переписываются. Вы также можете указать параметры, такие как --all, но вы должны использовать -- для разделения их от параметров git filter-branch. Подразумевает переназначение на предка.

Переназначение на предка

Используя аргументы git-rev-list[1], например, ограничители путей, вы можете ограничить набор ревизий, которые будут переписаны. Однако положительные ссылки в командной строке отличаются: мы не позволяем им исключаться такими ограничителями. Для этой цели они вместо этого переназначаются на ближайшего предка, который не был исключён.

Код возврата

При успешном выполнении код возврата — 0. Если фильтр не найдёт коммитов для переписывания, код возврата — 2. При любой другой ошибке код возврата может быть любым ненулевым значением.

Примеры

Предположим, вы хотите удалить файл (содержащий конфиденциальную информацию или нарушение авторских прав) из всех коммитов:

git filter-branch --tree-filter 'rm filename' HEAD

Однако, если файл отсутствует в дереве некоторых коммитов, простое rm filename потерпит неудачу для этого дерева и коммита. Таким образом, вы можете вместо этого использовать rm -f filename в качестве скрипта.

Использование --index-filter с git rm даёт существенно более быструю версию. Как и при использовании rm filename, git rm --cached filename потерпит неудачу, если файл отсутствует в дереве коммита. Если вы хотите «полностью забыть» файл, не важно, когда он попал в историю, поэтому мы также добавляем --ignore-unmatch:

git filter-branch --index-filter 'git rm --cached --ignore-unmatch filename' HEAD

Теперь вы получите переписанную историю, сохранённую в HEAD.

Чтобы переписать репозиторий так, как будто foodir/ был корнем проекта, и отбросить всю остальную историю:

git filter-branch --subdirectory-filter foodir -- --all

Таким образом, вы можете, например, превратить поддиректорию библиотеки в собственный репозиторий. Обратите внимание на -- , который отделяет опции filter-branch от опций ревизии, и на --all для переписывания всех ветвей и тегов.

Чтобы установить коммит (который обычно находится в конце другой истории) в качестве родителя текущего начального коммита, чтобы вставить другую историю позади текущей истории:

git filter-branch --parent-filter 'sed "s/^\$/-p <graft-id>/"' HEAD

(если строка родителя пустая — что происходит, когда мы имеем дело с начальным коммитом — добавьте graftcommit в качестве родителя). Обратите внимание, что это предполагает историю с одним корнем (то есть не произошло слияние без общих предков). Если это не так, используйте:

git filter-branch --parent-filter \
        'test $GIT_COMMIT = <commit-id> && echo "-p <graft-id>" || cat' HEAD

или даже проще:

git replace --graft $commit-id $graft-id
git filter-branch $graft-id..HEAD

Чтобы удалить коммиты, автором которых является «Darl McBribe», из истории:

git filter-branch --commit-filter '
        if [ "$GIT_AUTHOR_NAME" = "Darl McBribe" ];
        then
                skip_commit "$@";
        else
                git commit-tree "$@";
        fi' HEAD

Функция skip_commit определена следующим образом:

skip_commit()
{
        shift;
        while [ -n "$1" ];
        do
                shift;
                map "$1";
                shift;
        done;
}

Магия сдвига сначала отбрасывает идентификатор дерева, а затем параметры -p. Обратите внимание, что это правильно обрабатывает слияния! В случае, если Darl сделал коммит слияния между P1 и P2, он будет правильно проpropagated, и все дети слияния станут коммитами слияния с P1,P2 в качестве их родителей вместо коммита слияния.

ПРИМЕЧАНИЕ изменения, внесённые коммитами и которые не отменяются последующими коммитами, всё ещё будут в переписанной ветви. Если вы хотите отбросить changes вместе с коммитами, вы должны использовать интерактивный режим git rebase.

Вы можете переписать сообщения коммитов с помощью --msg-filter. Например, строки git svn-id в репозитории, созданном с помощью git svn, можно удалить таким образом:

git filter-branch --msg-filter '
        sed -e "/^git-svn-id:/d"
'

Если вам нужно добавить Acked-by строк, скажем, к последним 10 коммитам (ни один из которых не является слиянием), используйте эту команду:

git filter-branch --msg-filter '
        cat &&
        echo "Acked-by: Bugs Bunny <bunny@bugzilla.org>"
' HEAD~10..HEAD

Опция --env-filter может быть использована для изменения идентификатора коммитатора и/или автора. Например, если вы обнаружили, что ваши коммиты имеют неправильный идентификатор из-за неправильно настроенного user.email, вы можете внести исправление перед публикацией проекта следующим образом:

git filter-branch --env-filter '
        if test "$GIT_AUTHOR_EMAIL" = "root@localhost"
        then
                GIT_AUTHOR_EMAIL=john@example.com
        fi
        if test "$GIT_COMMITTER_EMAIL" = "root@localhost"
        then
                GIT_COMMITTER_EMAIL=john@example.com
        fi
' -- --all

Чтобы ограничить переписывание только частью истории, укажите диапазон ревизий в дополнение к новому имени ветви. Новое имя ветви укажет на самую верхнюю ревизию, которую выведет git rev-list этого диапазона.

Рассмотрим эту историю:

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

Чтобы переписать только коммиты D,E,F,G,H, но оставить A, B и C, используйте:

git filter-branch ... C..H

Чтобы переписать коммиты E,F,G,H, используйте одну из этих команд:

git filter-branch ... C..H --not D
git filter-branch ... D..H --not C

Чтобы переместить всё дерево в поддиректорию или удалить его оттуда:

git filter-branch --index-filter \
        'git ls-files -s | sed "s-\t\"*-&newsubdir/-" |
                GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
                        git update-index --index-info &&
         mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"' HEAD

Список проверок для уменьшения размера репозитория

git-filter-branch может быть использован для удаления подмножества файлов, обычно с некоторым сочетанием --index-filter и --subdirectory-filter. Ожидается, что полученный репозиторий будет меньше исходного, но для его фактического уменьшения необходимы дополнительные шаги, потому что Git старается не терять ваши объекты, пока вы не скажете ему об этом. Сначала убедитесь, что:

  • Вы действительно удалили все варианты имени файла, если blob был перемещён в течение его жизненного цикла. git log --name-only --follow --all -- filename поможет вам найти переименования.

  • Вы действительно отфильтровали все refs: используйте --tag-name-filter cat -- --all при вызове git-filter-branch.

Затем есть два способа получить меньший репозиторий. Более безопасный способ — клонирование, которое сохраняет ваш оригинальный репозиторий.

  • Склонируйте его с помощью git clone file:///path/to/repo. Клон не будет содержать удалённых объектов. См. git-clone[1]. (Обратите внимание, что клонирование с простым путем просто создаёт жёсткие ссылки на всё!)

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

  • Удалите исходные refs, резервированные git-filter-branch: скажем git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d.

  • Выведите все reflog с помощью git reflog expire --expire=now --all.

  • Соберите все неиспользуемые объекты с помощью git gc --prune=now (или, если ваш git-gc недостаточно новый, чтобы поддерживать аргументы для --prune, используйте git repack -ad; git prune вместо этого).

Производительность

Производительность git-filter-branch ужасающе низкая; его дизайн делает невозможным создание обратной совместимой реализации, которая когда-либо будет быстрой:

  • При редактировании файлов, git-filter-branch по своему дизайну проверяет каждый коммит так, как он существовал в исходном репозитории. Если ваш репозиторий содержит 10^5 файлов и 10^5 коммитов, но каждый коммит изменяет только пять файлов, тогда git-filter-branch заставит вас выполнить 10^10 модификаций, несмотря на то, что у вас (в лучшем случае) 5*10^5 уникальных blob.

  • Если вы попытаетесь обмануть и заставить git-filter-branch работать только с файлами, изменёнными в коммите, тогда произойдут две вещи

    • вы столкнётесь с проблемами с удалениями всякий раз, когда пользователь просто пытается переименовать файлы (поскольку попытка удалить несуществующие файлы выглядит как бессмысленная операция; для переназначения удалений в ходе переименований файлов, когда переименования происходят через произвольные пользовательские оболочки, требуется определённая хитрость)

    • даже если вам удастся добиться хитрости с переназначением удалений при переименованиях, вы всё ещё технически нарушаете обратную совместимость, потому что пользователям разрешается фильтровать файлы таким образом, который зависит от топологии коммитов, вместо фильтрации исключительно на основе содержимого или имён файлов (хотя в дикой природе это не наблюдалось).

  • Даже если вам не нужно редактировать файлы, а только переименовывать или удалять некоторые из них, и тем самым вы можете избежать проверки каждого файла (т. е. вы можете использовать --index-filter), вы всё равно передаёте фрагменты оболочки для ваших фильтров. Это означает, что для каждого коммита вам нужен подготовленный репозиторий Git, где эти фильтры можно запустить. Это существенная подготовка.

  • Кроме того, git-filter-branch создаёт или обновляет несколько дополнительных файлов на каждый коммит. Некоторые из них предназначены для поддержки удобных функций, предоставляемых git-filter-branch (например, map()), а другие — для отслеживания внутреннего состояния (но к ним также могли получить доступ пользовательские фильтры; один из регрессионных тестов git-filter-branch это делает). Это фактически сводится к использованию файловой системы как механизма IPC между git-filter-branch и пользовательскими фильтрами. Диски имеют тенденцию быть медленным механизмом IPC, а запись этих файлов также эффективно представляет собой принудительную точку синхронизации между отдельными процессами, на которую мы сталкиваемся с каждым коммитом.

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

  • Сам git-filter-branch написан на оболочке, что довольно медленно. Это единственная проблема производительности, которую можно было бы обратной совместимостью исправить, но по сравнению с вышеуказанными проблемами, которые являются неотъемлемой частью дизайна git-filter-branch, язык самого инструмента является относительно незначительной проблемой.

    • Примечание: К сожалению, люди склонны зацикливаться на аспекте написания на оболочке и периодически спрашивают, можно ли переписать git-filter-branch на другом языке, чтобы исправить проблемы с производительностью. Это не только игнорирует более крупные неотъемлемые проблемы с дизайном, но и поможет меньше, чем вы ожидаете: если бы git-filter-branch не был на оболочке, то удобные функции (map(), skip_commit() и т. д.) и аргумент --setup больше не могли бы быть выполнены один раз в начале программы, а вместо этого должны были бы быть добавлены к каждому пользовательскому фильтру (и, таким образом, повторно выполнялись бы с каждым коммитом).

Инструмент git filter-repo — альтернатива git-filter-branch, которая не страдает от этих проблем с производительностью или проблем с безопасностью (описанных ниже). Для тех, чьи существующие инструменты полагаются на git-filter-branch, git filter-repo также предоставляет filter-lamely, замену git-filter-branch (с некоторыми оговорками). Хотя filter-lamely страдает от тех же проблем безопасности, что и git-filter-branch, он по крайней мере несколько улучшает производительность.

Безопасность

git-filter-branch полон ловушек, которые могут привести к различным способам лёгкого повреждения репозитория или к результату, худшему, чем исходное состояние:

  • У кого-то может быть набор «рабочих и проверенных фильтров», которые они документируют или предоставляют коллеге, который затем применяет их на другой ОС, где те же команды не работают/не протестированы (некоторые примеры в справке git-filter-branch также подвержены этому). Различия между BSD и GNU пользовательскими пространствами могут сильно сказаться. В лучшем случае будут выведены сообщения об ошибках. Но с равной вероятностью команды либо не выполняют запрошенное фильтрование, либо молча повреждают репозиторий, внеся нежелательные изменения. Нежелательные изменения могут затрагивать только несколько коммитов, поэтому это не всегда очевидно. (Тот факт, что проблемы не обязательно будут очевидными, означает, что они могут остаться незамеченными до тех пор, пока переписанная история не будет использоваться довольно долго, после чего очень трудно оправдать ещё один «день флагов» для новой переработки.)

  • Имена файлов с пробелами часто обрабатываются фрагментами оболочки неправильно, так как они создают проблемы для конвейеров оболочки. Не все знакомы с find -print0, xargs -0, git-ls-files -z и т. д. Даже те, кто знаком с ними, могут предположить, что такие флаги не нужны, поскольку кто-то переименовал все такие файлы в своём репозитории до того, как человек, выполняющий фильтрование, присоединился к проекту. И зачастую даже те, кто знаком с обработкой аргументов с пробелами, не делают этого просто потому, что не находятся в режиме мысли о том, что может пойти не так.

  • Имена файлов без ASCII-символов могут быть молча удалены, несмотря на то, что они находятся в нужном каталоге. Сохранение только нужных путей часто выполняется с помощью конвейеров, таких как git ls-files | grep -v ^WANTED_DIR/ | xargs git rm. ls-files будет цитировать имена файлов только при необходимости, поэтому люди могут не заметить, что один из файлов не соответствует регулярному выражению (по крайней мере, не до тех пор, пока не станет слишком поздно). Да, кто знает про core.quotePath, может этого избежать (если у них нет других специальных символов, таких как \t, \n или "), и люди, использующие ls-files -z с чем-то, кроме grep, могут этого избежать, но это не означает, что они это сделают.

  • Аналогично, при перемещении файлов можно обнаружить, что имена файлов с символами, не являющимися ASCII, или специальными символами, оказываются в другом каталоге, включающем символ двойной кавычки. (Это технически та же проблема, что и выше, с цитированием, но, возможно, интересный другой способ, которым она могла и проявлялась как проблема.)

  • Очень легко случайно смешать старую и новую историю. Это возможно с любым инструментом, но git-filter-branch практически приглашает к этому. В лучшем случае единственным недостатком является разочарование пользователей, которые не знают, как уменьшить свой репозиторий и удалить старые данные. В худшем случае они объединяют старую и новую историю и получают несколько «копий» каждого коммита, некоторые из которых содержат нежелательные или конфиденциальные файлы, а другие — нет. Это происходит по нескольким причинам:

    • по умолчанию выполняется только частичная переработка истории (--all не является значением по умолчанию, и мало примеров демонстрируют это)

    • отсутствие автоматической очистки после выполнения

    • то, что --tag-name-filter (при использовании для переименования тегов) не удаляет старые теги, а просто добавляет новые с новым именем

    • то, что предоставляется мало обучающей информации, чтобы проинформировать пользователей о последствиях переработки и о том, как избежать смешивания старой и новой истории. Например, в этой справке обсуждается, как пользователям необходимо понять, что им нужно перебазировать свои изменения для всех своих ветвей поверх новой истории (или удалить и переклонировать), но это лишь одна из нескольких проблем, которые нужно учитывать. Более подробную информацию см. в разделе «ОБСУЖДЕНИЕ» справки git filter-repo.

  • Аннотированные теги могут быть случайно преобразованы в лёгкие теги из-за одной из двух проблем:

    • Кто-то может выполнить переработку истории, понять, что он ошибся, восстановить из резервных копий в refs/original/ и затем повторно выполнить команду git-filter-branch. (Резервная копия в refs/original/ не является реальной резервной копией; она сначала обрабатывает теги.)

    • Запуск git-filter-branch с --tags или --all в вашем <rev-list-options>. Для сохранения аннотированных тегов как аннотированных, необходимо использовать --tag-name-filter (и не следует восстанавливать из refs/original/ при ранее неудачной переработке).

  • Любые сообщения коммитов, указывающие кодировку, будут повреждены при переработке; git-filter-branch игнорирует кодировку, берёт исходные байты и отправляет их в commit-tree, не сообщая ему правильную кодировку. (Это происходит независимо от того, используется ли --msg-filter.)

  • Сообщения коммитов (даже если все они UTF-8) по умолчанию повреждаются из-за того, что не обновляются — любые ссылки на другие хэши коммитов в сообщениях коммитов теперь будут ссылаться на несуществующие коммиты.

  • Нет средств, помогающих пользователям найти нежелательные ненужные данные, которые они должны удалить, что означает, что у них гораздо больше шансов иметь неполную или частичную очистку, которая иногда приводит к путанице и трате времени людьми, пытающимися понять. (Например, люди склонны искать только большие файлы для удаления, а не большие каталоги или расширения, и после этого, возможно, в будущем люди, использующие новый репозиторий, просматривая историю, могут заметить каталог артефактов сборки, в котором есть некоторые файлы, но не все, или кэш зависимостей (node_modules или аналогичный), который никогда не мог быть функциональным, так как некоторые файлы отсутствуют.)

  • Если --prune-empty не указано, то процесс фильтрации может создать множество запутанных пустых коммитов

  • Если --prune-empty указано, то намеренно размещённые пустые коммиты до операции фильтрации также обрезаются, а не только коммиты, которые стали пустыми из-за правил фильтрации.

  • Если --prune-empty указано, иногда пустые коммиты пропускаются и остаются (это довольно редкая ошибка, но она случается…)

  • Незначительная проблема, но пользователей, которые стремятся обновить все имена и электронные адреса в репозитории, могут привести к --env-filter, который будет обновлять только авторов и коммитов, пропуская теггеров.

  • Если пользователь предоставляет --tag-name-filter, который сопоставляет несколько тегов одному имени, не выдаётся предупреждение или сообщение об ошибке; git-filter-branch просто перезаписывает каждый тег в не документированном заранее определённом порядке, что приводит к одному тегу в итоге. (Тест регрессии git-filter-branch требует этого удивительного поведения.)

Кроме того, низкая производительность git-filter-branch часто приводит к проблемам безопасности:

  • Составление правильного фрагмента оболочки для выполнения желаемого фильтрации бывает трудно, если вы не выполняете тривиальные изменения, такие как удаление нескольких файлов. К сожалению, люди часто узнают, правильно ли фрагмент или нет, проверяя его, но правильность или неправильность может меняться в зависимости от особых обстоятельств (пробелы в именах файлов, имена файлов без ASCII-символов, забавные имена авторов или электронные адреса, недействительные часовые пояса, наличие графтов или заменяемых объектов и т. д.), что означает, что они могут долго ждать, столкнуться с ошибкой, а затем перезапустить процесс. Производительность git-filter-branch настолько низка, что этот цикл утомителен, сокращая время, доступное для тщательной проверки (не говоря уже о том, что он делает с терпением человека, выполняющего переработку, даже если у него технически больше времени). Эта проблема ещё больше усугубляется тем, что ошибки от неисправных фильтров могут долго не показываться и/или теряться в море вывода. Ещё хуже то, что неисправные фильтры часто приводят к молчаливой неверной переработке.

  • В довершение ко всему, даже когда пользователи, наконец, находят работающие команды, они естественно хотят поделиться ими. Но они могут не знать, что их репозиторий не содержал особых случаев, которые есть у кого-то ещё. Таким образом, когда кто-то другой с другим репозиторием выполняет те же команды, на них влияют описанные выше проблемы. Или же пользователь просто выполняет команды, которые действительно были проверены на наличие особых случаев, но он выполняет их на другой ОС, где они не работают, как указано выше.

filter-branch

© 2005–2026 Linus Torvalds and others
Licensed under the GNU General Public License version 2.
https://git-scm.com/docs/git-filter-branch

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API