українська мова ▾ Topics ▾ Latest version ▾ git-filter-branch last updated in 2.44.0

НАЗВА

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 для всіх файлів) або інформацію про кожну коміт. У решті випадків уся інформація (включно з оригінальними часами комітів та даними про злиття) буде збережена.

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

ПРИМІТКА: Ця команда враховує файл .git/info/grafts та посилання у просторі імен refs/replace/. Якщо у вас визначено будь-які grafts або посилання на заміну, виконання цієї команди зробить їх постійними.

УВАГА! У переписаній історії всі обʼєкти матимуть інші імена і не збігатимуться з оригінальною гілкою. Ви не зможете легко надіслати та розповсюдити переписану гілку поверх оригінальної. Будь ласка, не використовуйте цю команду, якщо ви не знаєте всіх наслідків, і уникайте її використання, якщо для вирішення вашої проблеми достатньо простого одиночного коміту. (Дивіться розділ «ВІДНОВЛЕННЯ З ВИСХІДНОГО ПЕРЕБАЗУВАННЯ» у 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, яка приймає аргумент «original sha1 id» та виводить «rewritten sha1 id», якщо коміт вже переписано, та «original sha1 id» в іншому випадку; функція map може повертати кілька ідентифікаторів в окремих рядках, якщо ваш фільтр комітів випустив кілька комітів.

ОПЦІЇ

--setup <command>

Це не справжній фільтр, який виконується для кожного коміту, а одноразове налаштування безпосередньо перед циклом. Тому жодних змінних, специфічних для коміту, ще не визначено. Функції або змінні, визначені тут, можна використовувати або змінювати в наступних кроках фільтра, окрім фільтра коміту, з технічних причин.

--subdirectory-filter <directory>

Дивіться лише на історію, яка стосується заданої теки. Результат міститиме цю теку (і тільки її) як кореневу теку проєкту. Мається на увазі Перенаправити до попередника.

--env-filter <command>

Цей фільтр можна використовувати, якщо вам потрібно лише змінити середовище, в якому буде виконано коміт. Зокрема, ви можете переписати змінні середовища автора/імʼя комітера/електронна пошта/час (див. git-commit-tree[1] для отримання детальнішої інформації).

--tree-filter <command>

Це фільтр для перезапису дерева та його вмісту. Аргумент обчислюється в оболонці, а робочу теку встановлено на корінь активного дерева. Нове дерево потім використовується як є (нові файли додаються автоматично, зниклі файли видаляються автоматично — ні файли .gitignore, ні будь-які інші правила ігнорування НЕ МАЮТЬ ЖОДНОГО ЕФЕКТУ!).

--index-filter <command>

Це фільтр для перезапису індексу. Він схожий на фільтр дерева, але не перевіряє дерево, що робить його набагато швидшим. Часто використовується з git rm --cached --ignore-unmatch ..., див. ПРИКЛАДИ нижче. Для складних випадків див. git-update-index[1].

--parent-filter <command>

Це фільтр для перезапису батьківського списку коміту. Він отримає батьківський рядок в stdin та виведе новий батьківський рядок в stdout. Батьківський рядок має формат, описаний у git-commit-tree[1]: порожній для початкового коміту, "-p parent" для звичайного коміту та "-p parent1 -p parent2 -p parent3 …​" для коміту злиття.

--msg-filter <command>

Це фільтр для перезапису повідомлень комітів. Аргумент обчислюється в оболонці з оригінальним повідомленням коміту на стандартному вводі; його стандартний вивід використовується як нове повідомлення коміту.

--commit-filter <command>

Це фільтр для виконання коміту. Якщо цей фільтр вказано, він буде викликаний замість команди git commit-tree з аргументами виду "<TREE_ID> [(-p <PARENT_COMMIT_ID>)…​]" та повідомленням журналу на stdin. Ідентифікатор коміту очікується на stdout.

Як спеціальне розширення, фільтр комітів може видавати кілька ідентифікаторів комітів; у такому випадку переписані дочірні обʼєкти оригінального коміту матимуть їх усі як батьківські.

Ви можете використовувати зручну функцію 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>

Ця опція призведе до того, що при запуску програма завантажить відповідність між старими та новими об’єктами з вказаної гілки, а при завершенні збереже її як новий коміт у цій гілці, що дозволить виконувати інкрементне оновлення великих дерев. Якщо <гілка> не існує, її буде створено.

<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, воно буде правильно поширене, і всі дочірні елементи злиття стануть комітами злиття з 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 намагається не втрачати ваші об’єкти, доки ви не дасте йому відповідну команду. Спочатку переконайтеся, що:

  • Ви справді видалили всі варіанти імені файлу, якщо блоб-обʼєкт було переміщено протягом його життя. git log --name-only --follow --all -- filename може допомогти вам знайти перейменування.

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

Тоді є два способи отримати менший репозиторій. Безпечніший спосіб — клонувати, це збереже оригінал недоторканим.

  • Клонуйте його за допомогою git clone file:///path/to/repo. Клон не міститиме видалених обʼєктів. Див. git-clone[1]. (Зверніть увагу, що клонування з простим шляхом лише жорстко звʼязує все!)

Якщо ви дійсно не хочете його клонувати з якихось причин, перевірте наступні пункти (у такому порядку). Це дуже руйнівний підхід, тому зробіть резервну копію або поверніться до клонування. Вас попередили.

  • Видаліть оригінальні посилання, резервні копії яких створено за допомогою git-filter-branch: напишіть git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d.

  • Протермінуйте всі reflogs за допомогою команди 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 унікальних блобів.

  • Якщо ви спробуєте схитрувати та змусити git-filter-branch працювати лише з файлами, зміненими в коміті, то відбудуться дві речі

    • Ви стикаєтеся з проблемами видалення щоразу, коли користувач просто намагається перейменувати файли (оскільки спроба видалити файли, яких не існує, виглядає як невдала операція; перепризначення видалення між перейменуваннями файлів вимагає певних хитрощів, коли перейменування відбувається через довільну оболонку, надану користувачем.)

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

  • Навіть якщо вам не потрібно редагувати файли, а лише, наприклад, перейменувати або видалити деякі, і таким чином уникнути перевірки кожного файлу (тобто ви можете використовувати --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 не був написаний мовою оболонки, то зручні функції (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, кумедні імена авторів або електронні адреси, недійсні часові пояси, наявність grafts або обʼєктів заміни тощо), що означає, що їм, можливо, доведеться довго чекати, натрапляти на помилку, а потім перезапускати. Продуктивність git-filter-branch настільки погана, що цей цикл є болісним, зменшуючи час, доступний для ретельної повторної перевірки (не кажучи вже про те, як це впливає на терпіння людини, яка виконує перезапис, навіть якщо технічно у неї є більше часу). Ця проблема ще більше ускладнюється тим, що помилки від несправних фільтрів можуть довго не відображатися та/або губитися в морі виводу. Ще гірше, несправні фільтри часто призводять лише до тихих неправильних перезаписів.

  • На додачу до всього, навіть коли користувачі нарешті знаходять робочі команди, вони, природно, хочуть ними поділитися. Але вони можуть не знати, що в їхньому репозиторії не було деяких особливих випадків, на відміну від чужого. Тож, коли хтось інший з іншого репозиторію виконує ті самі команди, він стикається з вищезазначеними проблемами. Або ж користувач просто виконує команди, які дійсно були перевірені для особливих випадків, але запускає їх на іншій ОС, де вони не працюють, як зазначалося вище.

GIT

Частина набору git[1]