Переписывание истории - git filter-branch

17 декабря 2025
Автор

Олег Марков

Введение

Переписывание истории в Git звучит опасно и немного пугающе, особенно если вы работаете в команде и репозиторий уже живет своей жизнью. Но иногда без этого не обойтись. Например, если:

  • в репозиторий случайно попали пароли, токены или другие секреты;
  • нужно удалить тяжелые бинарные файлы, распухший node_modules или логи;
  • необходимо массово изменить авторов коммитов (например, из-за смены домена почты);
  • вы хотите переорганизовать структуру проекта, переместив код в подпапку или объединив несколько проектов;
  • требуется сделать историю более аккуратной перед открытием кода в open source.

Здесь и помогает git filter-branch. Этот инструмент позволяет переписать уже существующую историю репозитория на уровне коммитов: менять содержимое, авторов, сообщения, структуру папок. По сути, вы создаете новую ветку истории, которая логически похожа на прежнюю, но очищена или изменена в нужных местах.

В этой статье я покажу вам, как работает git filter-branch, какие у него возможности, как правильно готовиться к переписыванию истории и как минимизировать риски. Мы разберем практические сценарии, с которыми чаще всего сталкиваются разработчики, и посмотрим, как именно выглядит переписывание истории в коде команд.

Важно: git filter-branch считается устаревшим инструментом, и во многих случаях Git рекомендует использовать git filter-repo. Но filter-branch по-прежнему широко применяется, особенно в legacy-проектах и старых инструкциях. Поэтому полезно понимать, как он работает и что делает “под капотом”.

Теперь давайте перейдем к базам и реальным примерам.

Что такое git filter-branch и как он работает

Общая идея переписывания истории

Когда вы запускаете git filter-branch, Git не “правит” существующие коммиты. Коммиты в Git неизменяемы: каждый имеет свой хэш, зависящий от содержимого, родителей, автора, даты и сообщения. Если что-то меняется — это уже новый коммит.

git filter-branch создает новую версию истории:

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

В итоге вы получаете “параллельную” историю, логически похожую на исходную, но с нужными изменениями. Все старые коммиты физически остаются в репозитории, пока не будет выполнена очистка (garbage collection), но ссылки на них (ветки, теги) больше не ведут на старую историю.

Базовый синтаксис git filter-branch

Чаще всего команда выглядит так:

git filter-branch [опции] [фильтры] -- [диапазон_веток_или_коммитов]

Простейший пример, который вы часто увидите:

git filter-branch --tree-filter 'rm -f secrets.txt' -- --all
# --tree-filter - фильтр, который работает с деревом файлов
# 'rm -f secrets.txt' - команда, которую Git выполнит на каждом коммите
# --all - переписать историю всех веток

Смотрите, что здесь происходит:

  1. Git берет каждый коммит во всех ветках (--all).
  2. Временная рабочая директория приводится к состоянию этого коммита.
  3. Выполняется команда rm -f secrets.txt.
  4. Получившееся дерево файлов записывается как новый коммит (с учетом остальных фильтров, если они есть).
  5. Ветки перемещаются на новые коммиты.

Предупреждение от Git и статус устаревания

Если вы сейчас попробуете запустить git filter-branch в свежей версии Git, скорее всего увидите предупреждение:

WARNING: git-filter-branch has a glut of gotchas generating mangled history
         rewrites.  Hit Ctrl-C before proceeding to abort, then use an
         alternative filtering tool such as 'git filter-repo'

Git официально предупреждает, что инструмент сложный и легко “сломать” историю. Но он по-прежнему работает и, к сожалению (или к счастью), встречается во множестве статей и скриптов. Наша цель — понять, как использовать его осознанно и аккуратно.

Подготовка к переписыванию истории

Обязательная резервная копия

Перед тем как что-либо переписывать, стоит сделать бэкап. Это может быть:

# Создаем временную резервную ветку от текущего состояния
git branch backup-before-filter

# Или клонируем репозиторий в другое место
git clone --mirror /path/to/repo /path/to/repo-backup

Комментарии к примеру:

git branch backup-before-filter
# Создаем дополнительную ветку, которая будет указывать на текущую историю
# Если что-то пойдет не так, вы сможете вернуться к ней

git clone --mirror /path/to/repo /path/to/repo-backup
# Создаем зеркальный клон, который содержит все ветки, теги и ссылки
# Это хороший вариант для бэкапа перед агрессивными операциями

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

Работа в отдельном клоне

Очень полезный прием — делать все переписывание истории в отдельном локальном клоне:

git clone --mirror git@github.com:org/project.git project-filtered.git
cd project-filtered.git
  • --mirror копирует все ссылки, в том числе refs/notes, refs/pull, refs/original.
  • В этом клоне вы можете экспериментировать с фильтрами, не задевая основной рабочий репозиторий разработчиков.

После успешного переписывания истории и проверки вы сможете переопубликовать эту версию репозитория на сервере.

Что изменится для коллег

Важно помнить: переписывание истории — это изменение хэшей коммитов. Если вы пушите такие изменения в общий репозиторий, то:

  • всем коллегам придется заново синхронизировать свои ветки;
  • старые локальные ветки и коммиты станут “висячими” относительно сервера;
  • нужно будет делать git fetch и, как правило, git reset --hard или пересоздавать локальные клоны.

Поэтому перед использованием git filter-branch в командном репозитории обязательно согласуйте изменения и заранее предупредите всех участников.

Основные типы фильтров в git filter-branch

git filter-branch предоставляет несколько типов фильтров, которые вы можете комбинировать. Давайте разберем основные:

  • --tree-filter
  • --index-filter
  • --env-filter
  • --msg-filter
  • --commit-filter
  • --subdirectory-filter
  • --tag-name-filter

Фильтр tree-filter — изменение содержимого файлового дерева

--tree-filter позволяет вам выполнять произвольную команду над рабочей директорией для каждого коммита. Это удобно, но довольно медленно, потому что для каждого коммита Git:

  • разворачивает файлы в рабочую директорию;
  • запускает вашу команду;
  • считывает измененное состояние обратно.

Пример: удалить файл secrets.txt из всех коммитов:

git filter-branch --tree-filter 'rm -f secrets.txt' -- --all
# rm -f secrets.txt - команда, которая вызывается для каждого коммита
# --all - применить фильтр ко всем веткам и тегам

Как видите, мы просто используем обычную команду оболочки. Комментарий:

# Если файла нет, rm -f не выдаст ошибку
# Это важно, потому что в некоторых коммитах файла может не быть

Еще пример: удалить директорию logs из всех коммитов:

git filter-branch --tree-filter 'rm -rf logs' -- --all
# rm -rf logs - рекурсивно удаляет каталог logs, если он есть

Фильтр index-filter — быстрые манипуляции с индексом

--index-filter работает быстрее, чем --tree-filter, потому что он изменяет не рабочую директорию, а индекс (staging area). Git может сделать это без полного разворачивания файлов.

Смотрите пример: удалить файл secrets.txt из индекса всех коммитов:

git filter-branch --index-filter 'git rm --cached --ignore-unmatch secrets.txt' -- --all
# git rm --cached - удаляет файл только из индекса, не трогая рабочую директорию
# --ignore-unmatch - не выдает ошибку, если файл отсутствует в текущем коммите

Комментарии:

# В отличие от --tree-filter, здесь файлы физически не удаляются с диска
# Меняется только содержимое коммита - файл больше не будет входить в его дерево

Если секретный файл уже попал в удаленный репозиторий, это именно то, что вам нужно для очистки истории.

Фильтр env-filter — изменение автора, коммитера и временных меток

--env-filter позволяет менять переменные окружения, которые Git использует для формирования метаданных коммита: GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, GIT_COMMITTER_NAME, GIT_COMMITTER_EMAIL, а также даты.

Давайте разберемся на примере. Допустим, вы сделали часть коммитов с неправильной почтой old@example.com и хотите заменить ее на new@example.com.

git filter-branch --env-filter '
if [ "$GIT_AUTHOR_EMAIL" = "old@example.com" ]
then
    GIT_AUTHOR_EMAIL="new@example.com"
    GIT_COMMITTER_EMAIL="new@example.com"
fi
export GIT_AUTHOR_EMAIL
export GIT_COMMITTER_EMAIL
' -- --all

Что здесь происходит, шаг за шагом:

# 1. Для каждого коммита Git запускает этот скрипт оболочки
# 2. Мы проверяем, совпадает ли e-mail автора с нужным
# 3. Если да, то меняем e-mail и автора, и коммитера
# 4. Экспортируем переменные, чтобы Git использовал новое значение при создании нового коммита

Аналогично можно изменить имя автора:

git filter-branch --env-filter '
if [ "$GIT_AUTHOR_EMAIL" = "old@example.com" ]
then
    GIT_AUTHOR_NAME="John Doe"
    GIT_COMMITTER_NAME="John Doe"
fi
export GIT_AUTHOR_NAME
export GIT_COMMITTER_NAME
' -- --all

Вы можете комбинировать изменения имени, почты и даже даты (через GIT_AUTHOR_DATE, GIT_COMMITTER_DATE).

Фильтр msg-filter — изменение сообщений коммитов

С помощью --msg-filter вы можете массово исправить сообщения коммитов. Фильтр получает на вход старое сообщение на стандартный ввод, а на выход должен выдать новое сообщение.

Простой пример: заменить все вхождения слова TODO на FIXME в сообщениях:

git filter-branch --msg-filter '
sed "s/TODO/FIXME/g"
' -- --all
# sed - выполняет построчную замену в тексте сообщения
# g - заменяет все вхождения в каждой строке

Вы можете использовать более сложную логику. Например, добавить префикс к каждому сообщению:

git filter-branch --msg-filter '
while IFS= read -r line
do
    # Добавляем префикс к каждой строке сообщения
    echo "[LEGACY] $line"
done
' -- --all

Комментарии к примеру:

# IFS= read -r line - читаем сообщение построчно, сохраняя пробелы
# echo "[LEGACY] $line" - добавляем префикс к строке
# В результате все сообщения будут начинаться с [LEGACY]

Фильтр commit-filter — полное управление созданием коммитов

--commit-filter дает вам максимальный контроль: вы сами решаете, создавать новый коммит или нет, какие у него будут родители и т. д. Используется реже, но бывает полезен для сложных преобразований.

Классический прием — пропустить некоторые коммиты (например, удалить “плохие” коммиты, сохранив изменения через объединение).

Скелет фильтра выглядит примерно так:

git filter-branch --commit-filter '
if [ "$GIT_AUTHOR_EMAIL" = "bad@example.com" ]
then
    # Пропускаем этот коммит, не создавая нового
    skip_commit "$@"
else
    # Создаем обычный коммит с текущими параметрами
    git commit-tree "$@"
fi
' -- --all

Комментарии:

# "$@" - набор параметров, которые Git передает фильтру
# skip_commit - специальная функция, которую предоставляет git filter-branch
# git commit-tree - низкоуровневая команда, создающая новый объект-коммит

Этот фильтр уже ближе к “внутренностям” Git, и с ним стоит работать только тогда, когда вы уверенно понимаете, что делаете.

Фильтр subdirectory-filter — работа с подкаталогами

--subdirectory-filter выбирает только файлы из определенной директории и делает их корнем проекта.

Давайте разберемся на примере. Допустим, у вас был монорепозиторий, и вы хотите вытащить историю только директории frontend:

git filter-branch --subdirectory-filter frontend -- --all
# frontend - путь к директории, с которой хотим работать

Что делает эта команда:

  • в каждом коммите оставляет только файлы из папки frontend;
  • “поднимает” содержимое frontend в корень;
  • удаляет коммиты, в которых после фильтрации не осталось файлов.

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

Фильтр tag-name-filter — переименование тегов

Теги после переписывания истории могут указывать на старые коммиты. Иногда вы хотите массово переименовать их или изменить формат.

Пример: добавить префикс legacy- ко всем тегам:

git filter-branch --tag-name-filter 'sed "s/^/legacy-/"' -- --all
# sed "s/^/legacy-/" - добавляет строке префикс legacy-
# В данном случае строка - это имя тега

Обратите внимание: это не меняет содержимое коммитов, только переименовывает сами теги.

Практические сценарии использования git filter-branch

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

1. Удаление случайно закоммиченных секретов

Ситуация: вы случайно закоммитили config.json с паролями или .env с токенами.

Этапы решения:

  1. Удалить файл из текущего состояния.
  2. Переписать историю и убрать файл из всех прошлых коммитов.
  3. Переписать историю на удаленном репозитории.
  4. Поменять реальные секреты (так как они уже могли утечь).

Сначала удалим файл из текущей версии:

git rm config.json
# Удаляем файл из индекса и рабочей директории

git commit -m "Remove config.json with secrets"
# Фиксируем удаление файла

Теперь переписываем историю, используя index-filter:

git filter-branch --index-filter 'git rm --cached --ignore-unmatch config.json' -- --all
# Удаляем config.json из индекса на каждом коммите
# --ignore-unmatch защищает от ошибок, если файла нет в конкретном коммите

После этого все новые коммиты больше не будут содержать config.json. Старые версии все еще физически есть в объектной базе Git, но на них уже не ведут ветки и теги.

Теперь надо пересоздать ссылки на удаленном репозитории:

git push --force --all
# Перезаписываем все ветки на сервере

git push --force --tags
# Перезаписываем теги

Комментарии:

# Обязательно предупредите команду, что была выполнена перепись истории
# Коллегам нужно будет вручную синхронизировать свои локальные копии

И самое важное: поменять секреты (ключи, токены) в реальных сервисах, потому что факт пребывания в истории уже считается компрометацией.

2. Очистка репозитория от тяжелых файлов

Допустим, вы по ошибке коммитили большие бинарные файлы (видео, архивы, скомпилированные билды). Репозиторий стал “толстым” и долго клонируется.

Сначала имеет смысл выяснить, какие файлы больше всего занимают места. Для этого есть утилиты вроде git rev-list + git ls-tree или сторонние инструменты (например, git-sizer), но мы сосредоточимся на фильтрации.

Допустим, вы решили удалить все .zip файлы из истории:

git filter-branch --prune-empty --index-filter '
git ls-files -s | grep "\.zip$" | awk "{print \$4}" | xargs -r git rm --cached --ignore-unmatch
' -- --all

Пояснения:

git ls-files -s
# Показывает содержимое индекса с указанием путей файлов

grep "\.zip$"
# Отбираем только файлы с расширением .zip

awk "{print \$4}"
# Получаем только имя файла (4-й столбец вывода)

xargs -r git rm --cached --ignore-unmatch
# Передаем имена файлов в git rm, удаляя их из индекса
# -r - не запускать команду, если список пуст

Опция --prune-empty удаляет “пустые” коммиты, в которых после удаления файлов не осталось изменений. Это помогает чуть упростить историю.

3. Перемещение проекта в подпапку или из подпапки

Иногда нужно “обернуть” весь проект в подпапку. Например, вы хотите объединить два репозитория в один монорепозиторий.

Перенести историю в подпапку

Допустим, вы хотите, чтобы все файлы проекта оказались в app/:

  1. Используем --tree-filter, чтобы переместить файлы в подпапку.
  2. Применяем это ко всем коммитам.
git filter-branch --tree-filter '
mkdir -p app
# Создаем папку app, если ее нет

# Перемещаем все файлы и каталоги, кроме .git, в app
for entry in * .[^.] .??*; do
  # Пропускаем служебный каталог .git
  if [ "$entry" = ".git" ]; then
    continue
  fi

  # Перемещаем все остальные элементы внутрь app
  mv "$entry" app/ 2>/dev/null || true
done
' -- --all

Что здесь важно:

  • мы явно исключаем .git, чтобы не сломать репозиторий;
  • используем маски, чтобы захватить скрытые файлы (например, .gitignore);
  • перенаправляем ошибки mv в /dev/null, чтобы не пугаться, если каких-то файлов нет.

Извлечь историю из подпапки в корень

Обратная задача: история проекта жила в подпапке app/, а вы хотите сделать ее корнем. Здесь идеально подходит --subdirectory-filter:

git filter-branch --subdirectory-filter app -- --all
# Остаются только файлы из app
# Они становятся корнем проекта в каждом коммите

4. Массовая правка авторов и доменов

Допустим, компания сменила домен почты: было @oldcorp.com, стало @newcorp.com. А вы хотите обновить коммиты так, чтобы они отображали корректные e-mail.

Вот как можно это сделать:

git filter-branch --env-filter '
old_domain="@oldcorp.com"
new_domain="@newcorp.com"

update_email() {
  local email="$1"
  local new_email

  # Заменяем домен, если он совпадает с old_domain
  new_email=$(echo "$email" | sed "s/$old_domain$/$new_domain/")
  echo "$new_email"
}

GIT_AUTHOR_EMAIL=$(update_email "$GIT_AUTHOR_EMAIL")
GIT_COMMITTER_EMAIL=$(update_email "$GIT_COMMITTER_EMAIL")

export GIT_AUTHOR_EMAIL
export GIT_COMMITTER_EMAIL
' -- --all

Комментарии:

# update_email - небольшая функция, которая меняет домен e-mail
# sed "s/$old_domain$/$new_domain/" - ищет домен в конце строки и заменяет его
# Мы обновляем и автора, и коммитера, чтобы все метаданные были согласованы

После выполнения вы получите новую историю, в которой все e-mail с старым доменом заменены на новый.

5. Массовая правка сообщений коммитов

Допустим, вы хотите:

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

Давайте разберемся на примере исправления опечатки:

git filter-branch --msg-filter '
# Заменяем "teh" на "the" во всем сообщении коммита
sed "s/\bteh\b/the/g"
' -- --all

Еще пример: добавим суффикс [reviewed] к сообщениям старше определенной даты не будем сейчас усложнять анализ дат, просто добавим суффикс ко всем:

git filter-branch --msg-filter '
while IFS= read -r line
do
  # Добавляем суффикс только к первой строке сообщения (заголовку)
  if [ -z "$processed" ]
  then
    echo "$line [reviewed]"
    processed=1
  else
    echo "$line"
  fi
done
' -- --all

Комментарии:

# processed - флаг, который помогает понять, обработана ли уже первая строка
# В Git первая строка сообщения - заголовок, остальные - тело

Важные нюансы и подводные камни

Переписанная история несовместима со старой

После git filter-branch у вас:

  • новые хэши коммитов;
  • ветки указывают на новые коммиты;
  • старые коммиты становятся “висячими” (на них никто не ссылается).

Если кто-то продолжает работать с репозиторием на основе старой истории, при попытке пуша он столкнется с конфликтами и отказом сервера принимать изменения.

Поэтому после переписывания истории обычно действуют так:

  1. Делают форс-пуш на сервер:

    git push --force --all
    git push --force --tags
    
  2. Все разработчики выполняют в своих локальных репозиториях:

    git fetch origin
    git reset --hard origin/main
    # И аналогично для других веток
    

    Или просто заново клонируют репозиторий, если так удобнее.

refs/original и как откатить filter-branch

Один из полезных механизмов безопасности: при запуске git filter-branch Git создает резервные ссылки refs/original/…, которые указывают на старые версии веток.

Посмотрите на них:

git show-ref | grep refs/original
# Показывает все резервные ссылки, созданные filter-branch

Если вы хотите откатить переписывание истории, можно:

git reset --hard refs/original/refs/heads/main
# Возвращаем ветку main к исходному состоянию до filter-branch

Или просто переключиться на старую ветку и создать от нее новую:

git branch restored-main refs/original/refs/heads/main
# Создаем новую ветку restored-main, указывающую на старую историю

Только после того, как вы убедились, что новая история вас устраивает, имеет смысл удалять refs/original:

git update-ref -d refs/original/refs/heads/main
# Удаляем резервную ссылку для ветки main

Очистка объекта базы после переписывания

После git filter-branch старые объекты все еще занимают место в .git/objects. Чтобы их удалить, нужно:

  1. Убедиться, что вы удалили ненужные резервные ссылки и теги, ведущие на старую историю.
  2. Запустить сборку мусора:

    git gc --prune=now --aggressive
    # --prune=now - удалить все объекты, на которые больше нет ссылок
    # --aggressive - выполнить более тщательную оптимизацию (медленнее, но эффективнее)
    

Комментарии:

# Эту команду стоит запускать только после того как вы точно больше не планируете возвращаться к старой истории
# Иначе вы потеряете возможность восстановить старые коммиты

Производительность: зачем избегать тяжелых фильтров

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

  • для каждого коммита разворачивает файлы;
  • выполняет команды;
  • пересобирает индексы.

Чтобы ускорить работу:

  • по возможности используйте --index-filter вместо --tree-filter;
  • ограничивайте диапазон коммитов (например, не --all, а конкретную ветку или диапазон main~100..main);
  • избегайте сложных скриптов в фильтрах, если можно заменить их простыми командами Git.

Комментарии к ограничению диапазона:

git filter-branch --index-filter 'git rm --cached --ignore-unmatch secrets.txt' -- main~100..main
# Применяем фильтр только к последним 100 коммитам ветки main

Почему git filter-repo обычно лучше

Хотя статья посвящена git filter-branch, важно понимать, что на его замену пришел инструмент git filter-repo:

  • он быстрее;
  • проще в настройке;
  • безопаснее по умолчанию;
  • лучше документирован;
  • не требует сложных shell-скриптов для многих типовых задач.

Но на практике вы все равно будете встречать git filter-branch в старых инструкциях, документации и скриптах CI. Зная, как он работает, вы:

  • сможете понять такие скрипты;
  • аккуратно применить их на уже существующем репозитории;
  • при необходимости переписать логику на git filter-repo.

Заключение

git filter-branch — это мощный и довольно низкоуровневый инструмент для переписывания истории в Git. Он позволяет:

  • удалить файлы и секреты из всех прошлых коммитов;
  • изменить авторов и e-mail;
  • переписать сообщения коммитов;
  • извлечь или поместить проект в подпапку;
  • переименовать теги и управлять сложными сценариями миграции.

При этом у него есть важные особенности:

  • он переписывает хэши коммитов и делает старую историю несовместимой с новой;
  • ошибки могут привести к потере ссылок на нужные коммиты;
  • фильтры с --tree-filter могут работать очень медленно на больших историях.

Если вы используете git filter-branch:

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

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

Частозадаваемые технические вопросы

1. Как переписать историю только одной ветки, не затрагивая остальные?

Если вы хотите изменить только конкретную ветку, укажите ее явно вместо --all:

git filter-branch --index-filter 'git rm --cached --ignore-unmatch secrets.txt' -- main
# Фильтр будет применен только к ветке main

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

git push --force origin main

2. Как пропустить конкретный коммит при переписывании истории?

Можно использовать --commit-filter и встроенную функцию skip_commit:

git filter-branch --commit-filter '
if [ "$GIT_COMMIT" = "ABCDEF123456..." ]
then
    skip_commit "$@"
else
    git commit-tree "$@"
fi
' -- --all

Так вы удалите коммит с указанным хэшем из истории, “склеив” его родителя и потомков.

3. Как переписать только файлы в определенной директории, не трогая остальное?

Используйте --index-filter с фильтрацией путей:

git filter-branch --index-filter '
git ls-files -s dir/ | awk "{print \$4}" | xargs -r git rm --cached --ignore-unmatch
' -- --all

Эта команда удалит из всех коммитов только файлы внутри dir/, остальные файлы останутся без изменений.

4. Что делать, если после filter-branch появились странные конфликты при merge?

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

git fetch origin
git checkout main
git reset --hard origin/main

Повторите это для всех активных веток. После этого слияния будут использовать переписанную историю как общую базу.

5. Можно ли применить несколько фильтров за один проход?

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

git filter-branch \
  --index-filter 'git rm --cached --ignore-unmatch secrets.txt' \
  --env-filter '
    if [ "$GIT_AUTHOR_EMAIL" = "old@example.com" ]; then
      GIT_AUTHOR_EMAIL="new@example.com"
      GIT_COMMITTER_EMAIL="new@example.com"
    fi
    export GIT_AUTHOR_EMAIL
    export GIT_COMMITTER_EMAIL
  ' \
  -- --all

Так вы одновременно удалите файл из истории и перепишете e-mail автора, не проходя по истории дважды.

Стрелочка влевоХуки Git git hooksИзвлечение изменений - git cherry-pickСтрелочка вправо

Постройте личный план изучения Git до уровня Middle — бесплатно!

Git — часть карты развития Frontend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Бесплатные лекции

Все гайды по Git

Открыть базу знаний

Лучшие курсы по теме

изображение курса

Основы Git

Антон Ларичев
AI-тренажеры
Гарантия
Бонусы
иконка звёздочки рейтинга4.9
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий