Гілки та хірургія

Видалення файлу з історії

git rm не дає файлу з’являтися в нових комітах. Зі старими він не робить нічого: блоб і далі в базі об’єктів, і далі в кожному клоні, і далі за один git show від вас.

Це важливо у двох випадках — коли файл був секретом і коли він важив 400 МБ.

⌘K → Видалити файл з історії, або правий клік на файлі — у дереві проєкту, у списку файлів коміту чи в редакторі повідомлення коміту. Саме коміт, який видалив файл, зазвичай і є місцем, де хтось усвідомлює, що файл досі в історії, тож вихід є і в тому меню теж.

Як знайти шлях

Два входи, бо вони відповідають на різні запитання.

Введіть його — відносно кореня репозиторію, без початкової скісної риски — коли ви вже знаєте, що прийшли видалити.

Перегляньте історію, коли не знаєте. Вона перелічує кожен шлях, який будь-коли комітили, найважчі згори, з кількістю версій кожного і позначкою, чи він досі відстежується. Видалені шляхи позначені окремо, і зазвичай саме вони вам і потрібні: файл, якого немає в робочому дереві, але який є в кожному клоні, — це рівно той випадок, якого звичайний діалог вибору файлу показати не може, бо файлу там просто немає.

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

Кожен шлях, який будь-коли комітили, найважчі згори, з позначеними видаленими

Виміряйте, перш ніж погоджуватися

Натисніть Виміряти (або виберіть рядок). Поки що нічого не записано. Ви отримуєте:

Переписано комітів Кожен коміт, починаючи з першого, який містив файл
Гілки / теги Посилання, які зрушать з місця
Займають його блоби Байти, які ці версії реально займають
Перший коміт Звідки починається переписування — усе після нього отримає новий хеш

Вимірювання: переписані коміти, зачеплені посилання, зайняті байти і попередження все одно змінити секрет

Якщо лічильник показує нуль — шлях неправильний. Зазвичай це помилка в написанні або пропущений префікс каталогу, а не відсутність файлу.

Що насправді робить переписування

Gitcito копіює кожну гілку й тег у refs/gitcito/pre-purge/<timestamp>/…, а потім запускає:

git filter-branch --force \
  --index-filter 'git rm --cached --ignore-unmatch -- <path>' \
  --prune-empty --tag-name-filter cat -- --branches --tags

--index-filter переписує індекс напряму, замість того щоб робити чекаут кожного коміту, — це різниця між хвилинами й годинами. --branches --tags замість --all тут навмисне: --all включив би й резервні посилання, і переписування з’їло б власну страхувальну сітку.

Коміти, які не містили нічого, крім видаленого файлу, відкидаються (--prune-empty). Теги перенаправляються на свої переписані коміти.

Резервна копія і чому місце ще не повертається

Чистку можна скасувати, і ціна цього в тому, що дисковий простір не звільняється, доки ви цього не скажете. Поки резервна копія існує, старі коміти все ще досяжні, тож git їх не збере.

Дія Наслідок
Відновити Кожна гілка й тег повертаються до коміту, що був до чистки; файл повертається разом з ними
Видалити резервну копію Видаляє резервні посилання, очищує рефлог, запускає git gc --prune=now — місце повернуто, чистка тепер остаточна

Два кроки, а не один, бо перший — це та половина, яку можна відновити, а другий — ні.

Змініть секрет у будь-якому разі

Якщо секрет колись потрапив у пуш, переписування вашої історії не робить витік не-витоком. Хтось міг його зафетчити; сервери форджів тримають нереференсовані об’єкти; лог CI міг його надрукувати. Переписування зупиняє подальше поширення — воно не скасовує розкриття.

Змініть ключ. А тоді робіть чистку, щоб наступний, хто зробить клон, його там не знайшов.

Чого воно не робитиме

  • Воно не робитиме пуш. Переписування — локальне. Опублікувати результат означає force push у кожну зачеплену гілку, і всім іншим доведеться переклонувати або зробити hard reset — рішення про це живе у захисті від force push.
  • Воно відмовляється працювати з брудним робочим деревом чи посеред злиття або ребейзу. Переписування багато разів рухає HEAD, і робити це навколо незакомічених змін — найкоротший шлях їх втратити.
  • Воно переписує за шляхом, а не за вмістом. Видалення секрету, який вставили у файл із кодом, а не тримали в окремому файлі, потребує фільтра за вмістом — це територія git filter-repo --replace-text, і Gitcito її не обгортає.
  • filter-branch повільний на дуже великих історіях. Але саме він іде в комплекті з git усюди, тому саме його Gitcito й використовує. На репозиторії з десятками тисяч комітів git filter-repo у терміналі — швидший інструмент.
  • Чужі клони — це не ваш репозиторій. Вони зберігають стару історію, доки їх не переклонують.

Покращити цей переклад на GitHub