українська мова ▾ 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.

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

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

GIT

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