Setup and Config
Getting and Creating Projects
Basic Snapshotting
Branching and Merging
Sharing and Updating Projects
Inspection and Comparison
Patching
Debugging
External Systems
Server Admin
Guides
- gitattributes
- Command-line interface conventions
- Everyday Git
- Frequently Asked Questions (FAQ)
- Glossary
- Hooks
- gitignore
- gitmodules
- Revisions
- Submodules
- Tutorial
- Workflows
- All guides...
Administration
Plumbing Commands
- 2.44.1 → 2.54.0 no changes
-
2.44.0
2024-02-23
- 2.29.3 → 2.43.7 no changes
-
2.29.2
2020-10-29
- 2.25.2 → 2.29.1 no changes
-
2.25.1
2020-02-17
-
2.25.0
2020-01-13
- 2.24.1 → 2.24.4 no changes
-
2.24.0
2019-11-04
- 2.22.1 → 2.23.4 no changes
-
2.22.0
2019-06-07
- 2.18.1 → 2.21.4 no changes
-
2.18.0
2018-06-21
- 2.16.6 → 2.17.6 no changes
-
2.15.4
2019-12-06
-
2.14.6
2019-12-06
-
2.13.7
2018-05-22
-
2.12.5
2017-09-22
- 2.10.5 → 2.11.4 no changes
-
2.9.5
2017-07-30
-
2.8.6
2017-07-30
- 2.3.10 → 2.7.6 no changes
-
2.2.3
2015-09-04
- 2.1.4 no changes
-
2.0.5
2014-12-17
ОБЗОР
git filter-branch [--setup <команда>] [--subdirectory-filter <каталог>] [--env-filter <команда>] [--tree-filter <команда>] [--index-filter <команда>] [--parent-filter <команда>] [--msg-filter <команда>] [--commit-filter <команда>] [--tag-name-filter <команда>] [--prune-empty] [--original <пространство-имён>] [-d <каталог>] [-f | --force] [--state-branch <ветка>] [--] [<параметры-rev-list>…]
ПРЕДУПРЕЖДЕНИЕ
git filter-branch имеет множество подводных камней, которые могут привести к неочевидным искажениям предполагаемого переписывания истории (и могут оставить вам мало времени для исследования таких проблем из-за его ужасной производительности). Эти проблемы безопасности и производительности не могут быть исправлены с сохранением обратной совместимости, и поэтому его использование не рекомендуется. Пожалуйста, используйте альтернативный инструмент фильтрации истории, такой как git filter-repo. Если вам всё ещё нужно использовать git filter-branch, пожалуйста, внимательно прочитайте БЕЗОПАСНОСТЬ (и ПРОИЗВОДИТЕЛЬНОСТЬ), чтобы узнать о минных полях filter-branch, а затем бдительно избегайте как можно большего количества перечисленных там опасностей.
ОПИСАНИЕ
Позволяет переписать историю редакций Git, переписывая ветки, упомянутые в <параметрах-rev-list>, применяя пользовательские фильтры к каждой редакции. Эти фильтры могут изменять каждое дерево (например, удалять файл или выполнять переписывание на Perl для всех файлов) или информацию о каждом коммите. В остальном вся информация (включая исходные времена коммитов или информацию о слиянии) будет сохранена.
Команда перепишет только положительные ссылки, упомянутые в командной строке (например, если вы передадите a..b, будет переписана только b). Если вы не укажете фильтры, коммиты будут заново зафиксированы без каких-либо изменений, что обычно не имеет эффекта. Тем не менее, это может быть полезно в будущем для компенсации некоторых ошибок Git или подобного, поэтому такое использование разрешено.
ПРИМЕЧАНИЕ: Эта команда учитывает файл .git/info/grafts и ссылки в пространстве имён refs/replace/. Если у вас определены какие-либо grafts или заменяющие ссылки, выполнение этой команды сделает их постоянными.
ПРЕДУПРЕЖДЕНИЕ! Переписанная история будет иметь разные имена объектов для всех объектов и не будет сходиться с исходной веткой. Вы не сможете легко отправить и распространить переписанную ветку поверх исходной ветки. Пожалуйста, не используйте эту команду, если вы не знаете всех последствий, и в любом случае избегайте её использования, если для исправления вашей проблемы достаточно одного простого коммита. (Дополнительную информацию о переписывании опубликованной истории см. в разделе "ВОССТАНОВЛЕНИЕ ПОСЛЕ ПЕРЕМЕЩЕНИЯ ВЫШЕСТОЯЩЕЙ ВЕТКИ" в git-rebase[1].)
Всегда проверяйте, что переписанная версия верна: исходные ссылки, если они отличаются от переписанных, будут сохранены в пространстве имён refs/original/.
Обратите внимание, что поскольку эта операция очень затратна по вводу-выводу, возможно, будет хорошей идеей перенаправить временный каталог с диска с помощью параметра -d, например, на tmpfs. Сообщается, что ускорение очень заметно.
Фильтры
Фильтры применяются в порядке, указанном ниже. Аргумент <команда> всегда вычисляется в контексте оболочки с использованием команды 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] после выполнения фильтров.
Если любое вычисление <команды> возвращает ненулевой статус выхода, вся операция будет прервана.
Доступна функция map, которая принимает аргумент "исходный идентификатор sha1" и выводит "переписанный идентификатор sha1", если коммит уже был переписан, и «исходный идентификатор sha1» в противном случае; функция map может возвращать несколько идентификаторов на отдельных строках, если ваш фильтр коммитов выдал несколько коммитов.
ПАРАМЕТРЫ
- --setup <команда>
-
Это не реальный фильтр, выполняемый для каждого коммита, а одноразовая настройка непосредственно перед циклом. Поэтому переменные, специфичные для коммита, ещё не определены. Функции или переменные, определённые здесь, могут быть использованы или изменены в следующих шагах фильтрации, за исключением фильтра коммитов по техническим причинам.
- --subdirectory-filter <каталог>
-
Рассматривать только историю, которая затрагивает указанный подкаталог. Результат будет содержать этот каталог (и только его) в качестве корня проекта. Подразумевает Пересопоставление на предка.
- --env-filter <команда>
-
Этот фильтр может использоваться, если вам нужно только изменить среду, в которой будет выполняться коммит. В частности, вы можете захотеть переписать переменные среды имя/адрес электронной почты/время автора/коммиттера (подробности см. в git-commit-tree[1]).
- --tree-filter <команда>
-
Это фильтр для переписывания дерева и его содержимого. Аргумент вычисляется в оболочке с рабочим каталогом, установленным в корень переключённого дерева. Затем новое дерево используется как есть (новые файлы автоматически добавляются, исчезнувшие файлы автоматически удаляются — ни файлы .gitignore, ни какие-либо другие правила игнорирования НЕ ИМЕЮТ НИКАКОГО ЭФФЕКТА!).
- --index-filter <команда>
-
Это фильтр для переписывания индекса. Он похож на фильтр дерева, но не переключает дерево, что делает его намного быстрее. Часто используется с
gitrm--cached--ignore-unmatch..., см. ПРИМЕРЫ ниже. Для сложных случаев см. git-update-index[1]. - --parent-filter <команда>
-
Это фильтр для переписывания списка родителей коммита. Он получит строку родителей в stdin и должен вывести новую строку родителей в stdout. Строка родителей имеет формат, описанный в git-commit-tree[1]: пустая для начального коммита, "-p родитель" для обычного коммита и "-p родитель1 -p родитель2 -p родитель3 …" для коммита слияния.
- --msg-filter <команда>
-
Это фильтр для переписывания сообщений коммитов. Аргумент вычисляется в оболочке с исходным сообщением коммита в стандартном вводе; его стандартный вывод используется как новое сообщение коммита.
- --commit-filter <команда>
-
Это фильтр для выполнения коммита. Если этот фильтр указан, он будет вызван вместо команды git commit-tree с аргументами вида «<ID_ДЕРЕВА> [(-p <PARENT_COMMIT_ID>)…]» и сообщением журнала в stdin. Идентификатор коммита ожидается в stdout.
В качестве специального расширения фильтр коммитов может выдавать несколько идентификаторов коммитов; в этом случае переписанные потомки исходного коммита будут иметь всех их в качестве родителей.
Вы можете использовать удобную функцию map в этом фильтре, а также другие удобные функции. Например, вызов skip_commit "$@" пропустит текущий коммит (но не его изменения! Если вы хотите этого, используйте вместо этого git rebase).
Вы также можете использовать
git_commit_non_empty_tree"$@"вместоgitcommit-tree"$@", если вы не хотите сохранять коммиты с одним родителем, которые не вносят изменений в дерево. - --tag-name-filter <команда>
-
Это фильтр для переписывания имён меток. При передаче он будет вызываться для каждой ссылки метки, которая указывает на переписанный объект (или на объект метки, который указывает на переписанный объект). Исходное имя метки передаётся через стандартный ввод, а новое имя метки ожидается в стандартном выводе.
Исходные метки не удаляются, но могут быть перезаписаны; используйте "--tag-name-filter cat", чтобы просто обновить метки. В этом случае будьте очень осторожны и убедитесь, что у вас есть резервные копии старых меток на случай, если преобразование пойдёт не так.
Поддерживается почти правильное переписывание объектов меток. Если у метки есть прикреплённое сообщение, будет создан новый объект метки с тем же сообщением, автором и временной меткой. Если у метки есть прикреплённая подпись, подпись будет удалена. По определению невозможно сохранить подписи. Причина, по которой это "почти" правильно, заключается в том, что в идеале, если метка не изменилась (указывает на тот же объект, имеет то же имя и т.д.), она должна сохранять любую подпись. Это не так, подписи всегда будут удалены, так что имейте это в виду. Также нет поддержки изменения автора или временной метки (или сообщения метки, если на то пошло). Метки, которые указывают на другие метки, будут переписаны так, чтобы указывать на базовый коммит.
- --prune-empty
-
Некоторые фильтры будут создавать пустые коммиты, которые не изменяют дерево. Этот параметр указывает git-filter-branch удалять такие коммиты, если у них ровно один или ноль необрезанных родителей; коммиты слияния поэтому останутся нетронутыми. Этот параметр нельзя использовать вместе с
--commit-filter, хотя тот же эффект может быть достигнут использованием предоставленной функцииgit_commit_non_empty_treeв фильтре коммитов. - --original <пространство-имён>
-
Используйте этот параметр, чтобы установить пространство имён, где будут храниться исходные коммиты. Значение по умолчанию — refs/original.
- -d <каталог>
-
Используйте этот параметр, чтобы установить путь к временному каталогу, используемому для переписывания. При применении фильтра дерева команде необходимо временно переключить дерево в какой-либо каталог, что может потребовать значительного места в случае больших проектов. По умолчанию она делает это в каталоге
.git-rewrite/, но вы можете изменить этот выбор с помощью этого параметра. - -f
- --force
-
git filter-branch отказывается запускаться, если существует временный каталог или уже есть ссылки, начинающиеся с refs/original/, если только не использовано принудительное выполнение.
- --state-branch <ветка>
-
Этот параметр приведёт к загрузке сопоставления старых объектов с новыми из именованной ветки при запуске и сохранению в виде нового коммита в эту ветку при выходе, обеспечивая инкрементальность для больших деревьев. Если <ветка> не существует, она будет создана.
- <параметры rev-list>…
-
Аргументы для git rev-list. Все положительные ссылки, включённые этими параметрами, переписываются. Вы также можете указывать параметры, такие как
--all, но вы должны использовать--, чтобы отделить их от параметров git filter-branch. Подразумевает Пересопоставление на предка.
Пересопоставление на предка
Используя аргументы git-rev-list[1], например, ограничители путей, вы можете ограничить набор переписываемых редакций. Однако положительные ссылки в командной строке выделяются: мы не позволяем им быть исключёнными такими ограничителями. Для этой цели они вместо этого переписываются так, чтобы указывать на ближайшего предка, который не был исключён.
КОД ЗАВЕРШЕНИЯ
В случае успеха статус выхода равен 0. Если фильтр не может найти ни одного коммита для переписывания, статус выхода равен 2. При любой другой ошибке статус выхода может быть любым другим ненулевым значением.
ПРИМЕРЫ
Предположим, вы хотите удалить файл (содержащий конфиденциальную информацию или нарушающий авторские права) из всех коммитов:
git filter-branch --tree-filter 'rm имя-файла' HEAD
Однако, если файл отсутствует в дереве какого-либо коммита, простой rm имя-файла завершится ошибкой для этого дерева и коммита. Поэтому вместо этого вы можете захотеть использовать rm -f имя-файла в качестве сценария.
Использование --index-filter с git rm даёт значительно более быструю версию. Как и при использовании rm имя-файла, git rm --cached имя-файла завершится ошибкой, если файл отсутствует в дереве коммита. Если вы хотите "полностью забыть" файл, не имеет значения, когда он попал в историю, поэтому мы также добавляем --ignore-unmatch:
git filter-branch --index-filter 'git rm --cached --ignore-unmatch имя-файла' HEAD
Теперь вы получите переписанную историю, сохранённую в HEAD.
Чтобы переписать репозиторий так, чтобы он выглядел, как если бы foodir/ был его корнем проекта, и отбросить всю остальную историю:
git filter-branch --subdirectory-filter foodir -- --all
Таким образом, вы можете, например, превратить подкаталог библиотеки в собственный репозиторий. Обратите внимание на --, который отделяет параметры filter-branch от параметров редакций, и на --all, чтобы переписать все ветки и метки.
Чтобы установить коммит (который обычно находится на верхушке другой истории) в качестве родителя текущего начального коммита, чтобы вставить другую историю за текущую историю:
git filter-branch --parent-filter 'sed "s/^\$/-p <id-прививки>/"' HEAD
(если строка родителей пуста — что происходит, когда мы имеем дело с начальным коммитом — добавьте graftcommit в качестве родителя). Обратите внимание, что это предполагает историю с одним корнем (то есть не было слияния без общих предков). Если это не так, используйте:
git filter-branch --parent-filter \ 'test $GIT_COMMIT = <id-коммита> && echo "-p <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;
}
Магия shift сначала отбрасывает идентификатор дерева, а затем параметры -p. Обратите внимание, что это правильно обрабатывает слияния! Если Дарл совершил слияние между P1 и P2, оно будет правильно распространено, и все потомки слияния станут коммитами слияния с P1 и P2 в качестве родителей вместо коммита слияния.
ПРИМЕЧАНИЕ изменения, внесённые коммитами и не отменённые последующими коммитами, всё равно будут в переписанной ветке. Если вы хотите отбросить изменения вместе с коммитами, вам следует использовать интерактивный режим 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\"*-&новыйподкаталог/-" | 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 -- имя-файла может помочь вам найти переименования.
-
Вы действительно отфильтровали все ссылки: используйте
--tag-name-filtercat----allпри вызове git-filter-branch.
Затем есть два способа получить меньший репозиторий. Более безопасный способ — клонирование, которое сохраняет ваш оригинал нетронутым.
-
Клонируйте его с помощью git clone file:///путь/к/репозиторию. В клоне не будет удалённых объектов. См. git-clone[1]. (Обратите внимание, что клонирование с обычным путём просто создаёт жёсткие ссылки на всё!)
Если вы по какой-либо причине действительно не хотите его клонировать, проверьте вместо этого следующие пункты (в этом порядке). Это очень разрушительный подход, поэтому сделайте резервную копию или вернитесь к клонированию. Вы были предупреждены.
-
Удалите исходные ссылки, сохранённые git-filter-branch: выполните
gitfor-each-ref--format="%(refname)"refs/original/|xargs-n1gitupdate-ref-d. -
Истечь срок действия всех журналов ссылок с помощью
gitreflogexpire--expire=now--all. -
Соберите мусор всех недостижимых объектов с помощью
gitgc--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 написан на shell, что довольно медленно. Это одна из проблем производительности, которая могла бы быть исправлена с сохранением обратной совместимости, но по сравнению с вышеуказанными проблемами, присущими дизайну git-filter-branch, язык самого инструмента является относительно незначительной проблемой.
-
Примечание: К сожалению, люди склонны зацикливаться на аспекте написания на shell и периодически спрашивать, может ли git-filter-branch быть переписан на другом языке для решения проблем с производительностью. Это не только игнорирует более серьёзные внутренние проблемы дизайна, но и поможет меньше, чем вы ожидаете: если бы сам git-filter-branch не был написан на shell, то удобные функции (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 изобилует подводными камнями, приводящими к различным способам легко повредить репозитории или получить беспорядок хуже, чем то, с чего вы начинали:
-
Кто-то может иметь набор «рабочих и протестированных фильтров», которые он документирует или передаёт коллеге, который затем запускает их на другой операционной системе, где те же команды не работают/не протестированы (некоторые примеры в man-странице git-filter-branch также подвержены этому). Различия между BSD и GNU userland могут сильно укусить. Если повезёт, будут выведены сообщения об ошибках. Но с равной вероятностью команды либо не выполняют запрошенную фильтрацию, либо молча повреждают, внося нежелательные изменения. Нежелательное изменение может затронуть только несколько коммитов, поэтому оно также не обязательно очевидно. (Тот факт, что проблемы не обязательно очевидны, означает, что они, вероятно, останутся незамеченными, пока переписанная история не будет использоваться в течение довольно долгого времени, после чего будет действительно трудно оправдать ещё один день заморозки для другого переписывания.)
-
Имена файлов с пробелами часто неправильно обрабатываются фрагментами оболочки, поскольку они создают проблемы для конвейеров оболочки. Не все знакомы с find -print0, xargs -0, git-ls-files -z и т.д. Даже люди, знакомые с ними, могут предположить, что такие флаги не имеют значения, потому что кто-то другой переименовал любые такие файлы в своём репозитории ещё до того, как человек, выполняющий фильтрацию, присоединился к проекту. И часто даже те, кто знаком с обработкой аргументов с пробелами, могут не делать этого просто потому, что они не настроены думать обо всём, что может пойти не так.
-
Не-ASCII имена файлов могут быть молча удалены, несмотря на то, что находятся в желаемом каталоге. Сохранение только нужных путей часто выполняется с помощью конвейеров, таких как git ls-files | grep -v ^ЖЕЛАЕМЫЙ_КАТАЛОГ/ | xargs git rm. ls-files будет заключать имена файлов в кавычки только при необходимости, поэтому люди могут не заметить, что один из файлов не соответствует регулярному выражению (по крайней мере, пока не станет слишком поздно). Да, тот, кто знает о core.quotePath, может избежать этого (если у него нет других специальных символов, таких как \t, \n или "), и люди, которые используют ls-files -z с чем-то другим, кроме grep, могут избежать этого, но это не значит, что они будут.
-
Аналогично, при перемещении файлов можно обнаружить, что имена файлов с не-ASCII или специальными символами попадают в другой каталог, который включает символ двойной кавычки. (Технически это та же проблема, что и выше с кавычками, но, возможно, интересный другой способ, которым она может и проявлялась как проблема.)
-
Слишком легко случайно перепутать старую и новую историю. Это возможно с любым инструментом, но git-filter-branch почти приглашает к этому. Если повезёт, единственным недостатком будет разочарование пользователей, которые не знают, как уменьшить свой репозиторий и удалить старые вещи. Если не повезёт, они сливают старую и новую историю и получают несколько "копий" каждого коммита, некоторые из которых содержат нежелательные или конфиденциальные файлы, а другие — нет. Это происходит несколькими разными способами:
-
по умолчанию выполняется только частичное переписывание истории (--all не является значением по умолчанию, и немногие примеры показывают его)
-
отсутствие автоматической очистки после выполнения
-
тот факт, что --tag-name-filter (при использовании для переименования меток) не удаляет старые метки, а просто добавляет новые с новым именем
-
тот факт, что предоставляется мало образовательной информации, чтобы проинформировать пользователей о последствиях переписывания и о том, как избежать смешивания старой и новой истории. Например, эта man-страница обсуждает, как пользователям необходимо понимать, что им нужно переместить свои изменения для всех своих веток поверх новой истории (или удалить и клонировать заново), но это только одна из множества проблем, которые необходимо учитывать. Более подробную информацию см. в разделе "ОБСУЖДЕНИЕ" man-страницы git filter-repo.
-
-
Аннотированные метки могут быть случайно преобразованы в легковесные метки из-за одной из двух проблем:
-
Кто-то может выполнить переписывание истории, понять, что напортачил, восстановить из резервных копий в refs/original/, а затем повторить свою команду git-filter-branch. (Резервная копия в refs/original/ не является настоящей резервной копией; она сначала разыменовывает метки.)
-
Запуск git-filter-branch с --tags или --all в ваших <параметрах rev-list>. Чтобы сохранить аннотированные метки как аннотированные, вы должны использовать --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 имена файлов, странные имена авторов или адреса электронной почты, недопустимые часовые пояса, наличие grafts или заменяющих объектов и т.д.), что означает, что им, возможно, придётся долго ждать, получить ошибку, а затем перезапустить. Производительность git-filter-branch настолько плоха, что этот цикл является болезненным, сокращая время, доступное для тщательной повторной проверки (не говоря уже о том, что это делает с терпением человека, выполняющего переписывание, даже если у него технически есть больше времени). Эта проблема усугубляется тем, что ошибки от сломанных фильтров могут не отображаться в течение длительного времени и/или теряться в море вывода. Что ещё хуже, сломанные фильтры часто просто приводят к молчаливому неправильному переписыванию.
-
В довершение всего, даже когда пользователи наконец находят работающие команды, они, естественно, хотят поделиться ими. Но они могут не знать, что в их репозитории не было некоторых особых случаев, которые есть в репозитории кого-то другого. Таким образом, когда кто-то другой с другим репозиторием запускает те же команды, он сталкивается с вышеуказанными проблемами. Или пользователь просто запускает команды, которые действительно были проверены на особые случаи, но запускает их на другой операционной системе, где они не работают, как отмечалось выше.
GIT
Является частью пакета git[1]