Как безопасно разрешать конфликты слияния с ИИ

Разрешение конфликтов слияния с ИИ без потери работы: zdiff3, промпт для агента, тесты до git add, rerere, rebase или merge и профилактика конфликтов в Git.

Solviera Teknoloji 10 мин чтения

Чтобы безопасно разрешать конфликты слияния с ИИ, сначала сделайте конфликт читаемым, затем дайте агенту для кода цели обеих сторон и чёткие правила, а результат принимайте только после того, как Git подтвердит, что маркеров конфликта не осталось, и тесты пройдут. Агенты вроде Claude Code и Codex здесь действительно полезны: они читают обе версии, общего предка и окружающий код быстрее вас и умеют совмещать два изменения, а не выбирать одно. Их типичная ошибка так же очевидна: если цель одной стороны неясна, они оставляют ту сторону, которая компилируется, и молча выбрасывают другую.

В этом руководстве — почему возникают конфликты (и почему параллельные агенты делают их чаще), пошаговый процесс от git status до git merge --continue, шаблон промпта, rebase против merge, git rerere, проверка до слияния и способы избежать большинства конфликтов заранее. Команды Git сверены с официальной документацией на git-scm.com по состоянию на октябрь 2026 года.

Откуда берутся конфликты слияния

Сливая две ветки, Git сравнивает обе с их общим предком. Изменения в разных местах файла объединяются автоматически. Если обе стороны изменили одно и то же место, Git не может выбрать: он останавливается и записывает в файл обе версии между маркерами конфликта. Это текстовый конфликт.

Самые частые виды:

  • Конфликт содержимого: обе ветки изменили одни и те же строки одного файла.
  • Изменение/удаление: одна ветка изменила файл, другая удалила или переместила его.
  • Генерируемые файлы: lock-файлы, снапшоты, результаты сборки или пакеты переводов, которые перегенерировали обе ветки.

Есть и конфликт, которого Git не видит, — семантический. Две ветки трогают разные файлы, сливаются без единого конфликта и всё равно ломают друг друга: например, одна меняет сигнатуру функции, а другая добавляет новый её вызов. Поймать такое могут только тесты на слитом результате; в статье об очереди слияния это разобрано на примере.

Почему с параллельными агентами конфликтов больше

Несколько агентов одновременно, каждый в своём git worktree, не перезаписывают файлы друг друга во время работы. Но конфликт от этого не исчезает — он переносится на момент слияния. Каждый агент работает от той базы, с которой начал, и не видит, что изменили остальные. Некоторые файлы притягивают конфликты снова и снова:

  • таблицы маршрутов, настройка внедрения зависимостей и другие реестры, куда каждая фича добавляет по строке,
  • barrel-файлы (index.ts) и общие типы,
  • package.json и его lock-файл,
  • файлы переводов, changelog и конфиги.

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

Шаг 1: остановитесь и посмотрите

Когда merge или rebase останавливается, сначала выясните, где вы находитесь, и только потом просите кого-либо, человека или агента, всё исправить:

git status                                  # какая операция, какие файлы "both modified"
git diff --name-only --diff-filter=U        # только неслитые файлы

--diff-filter=U выбирает неслитые пути, и вы получаете чистый список, который можно отдать агенту. Помните и о путях отступления. Если конфликт оказался больше, чем вы думали, ещё ничего не потеряно:

git merge --abort      # вернуться к состоянию до слияния
git rebase --abort     # вернуть ветку туда, где она была до rebase

Документация Git советует закоммитить или спрятать (stash) свои изменения перед git merge, потому что --abort не всегда может восстановить незакоммиченную работу. То же касается worktree агента: пусть он закоммитит работу, прежде чем вы что-то в него сольёте.

Шаг 2: переключитесь на zdiff3, чтобы видеть базу

По умолчанию Git показывает только две стороны:

<<<<<<< HEAD
const timeout = config.timeout ?? 30_000;
=======
const timeout = getTimeout(config);
>>>>>>> feature/retry

Отсюда не понять, какой была исходная строка, а значит, и чего хотела каждая сторона. Переключите стиль конфликтов на zdiff3 (появился в Git 2.35; в более старых версиях используйте diff3):

git config --global merge.conflictStyle zdiff3

Теперь после маркера ||||||| в конфликте виден и общий предок:

<<<<<<< HEAD
const timeout = config.timeout ?? 30_000;
||||||| base
const timeout = config.timeout;
=======
const timeout = getTimeout(config);
>>>>>>> feature/retry

Теперь всё читается: одна сторона добавила значение по умолчанию, другая вынесла чтение в функцию. Правильное решение, скорее всего, сохраняет оба изменения — например, значение по умолчанию переезжает внутрь getTimeout. По документации git-config, zdiff3 похож на diff3, но выносит совпадающие строки в начале и конце конфликта за его пределы, так что фрагменты остаются компактными.

Если слияние уже идёт, маркеры отдельного файла можно пересоздать в новом стиле:

git checkout --conflict=zdiff3 src/http/client.ts

Для агента это ещё важнее, чем для вас. Без базы модели приходится угадывать, какая сторона что изменила, а именно с угадывания начинается тихая потеря данных.

Шаг 3: дайте агенту нужный контекст

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

git log --merge -p src/http/client.ts   # сначала диффы со стороны HEAD, потом со стороны MERGE_HEAD
git show :1:src/http/client.ts          # общий предок
git show :2:src/http/client.ts          # HEAD (ours)
git show :3:src/http/client.ts          # MERGE_HEAD (theirs)

Добавьте человеческий контекст: описание задачи или pull request каждой ветки. Затем используйте примерно такой промпт:

Мы посреди git merge ветки feature/retry в main. В этих файлах конфликты
(merge.conflictStyle — zdiff3, база видна):
<вывод git diff --name-only --diff-filter=U>

Цель main: <одно-два предложения или описание PR>
Цель feature/retry: <одно-два предложения или описание PR>

Для каждого конфликтного файла:
1. Прочитай секции ours, base и theirs и одной строкой скажи, что каждая
   сторона изменила относительно базы.
2. Напиши решение, которое сохраняет обе цели. Если они действительно
   противоречат друг другу, не выбирай: остановись и спроси меня.
3. Правь только конфликтные фрагменты. Никакого переформатирования,
   переименований и посторонних исправлений.
4. Не меняй тесты, чтобы они прошли, и не удаляй код, который не понимаешь.
Для package-lock.json / pnpm-lock.yaml: разреши только package.json, затем
перегенерируй lock-файл пакетным менеджером.
В конце: никаких маркеров конфликта, запусти проверку типов и тесты и дай
сводку по каждому файлу — что ты оставил от каждой стороны. Не запускай
git add, git commit и git merge --continue.

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

Шаг 4: по одному файлу, по чётким правилам

Несколько правил отделяют чистое слияние от тихой регрессии:

  • Никаких массовых --ours и --theirs. git checkout --ours <путь> берёт одну сторону файла целиком и отбрасывает другую. Используйте это, только если точно знаете, что одна сторона должна победить, например для генерируемого файла, который вы всё равно перегенерируете.
  • Помните о подмене при rebase. Документация Git отмечает, что во время git rebase и git pull --rebase --ours — это ветка, на которую вы перебазируетесь, а --theirs — ваша собственная работа, применяемая заново. Агенты путаются в этом удивительно часто, так что называйте им ветки по именам.
  • Генерируемые файлы перегенерируйте, а не сливайте. Lock-файлы, снапшоты и результаты сборки должны создаваться своим инструментом уже после того, как разрешены исходники.
  • Изменение/удаление требует решения. Если одна ветка удалила файл, который изменила другая, агент должен сообщить об этом и спросить, а не восстанавливать или удалять его по своему усмотрению.
  • Большие конфликты делите. Если в одном файле десятки фрагментов, попросите агента разрешать и объяснять их по частям или отступите на шаг и пересмотрите порядок слияний.

Шаг 5: проверьте, прежде чем продолжать

Слова агента «разрешено» — это утверждение, а не доказательство. Проверьте:

git diff --check                 # предупреждает о забытых маркерах конфликта (и ошибках пробелов)
git diff                         # просмотреть решение
pnpm typecheck && pnpm test      # или проверка вашего проекта

git diff --check завершается с ненулевым кодом, если изменения вносят маркеры конфликта, поэтому это дешёвый барьер, который стоит запускать каждый раз. В свежих версиях Git git diff AUTO_MERGE показывает только изменения, сделанные на данный момент для разрешения текстовых конфликтов, — то есть ровно то, что написал агент.

Затем завершите операцию сами:

git add src/http/client.ts
git merge --continue     # или: git rebase --continue

Не принимайте вслепую: тревожные признаки

Тревожный признакЧто это обычно значит
Изменение одной из сторон просто пропалоАгент выбрал одну сторону вместо того, чтобы объединить их
Изменился тест или проверка в нёмРешение сломало поведение, а тест подогнали
Изменились файлы вне списка конфликтовРасползание задачи: переформатирование или «исправления», которым нужно отдельное ревью
Код повторяется дваждыОбе стороны оставлены как есть, без слияния
Маркеры или одинокий ======= в файле, который не является кодомНикто не запустил git diff --check

Если видите что-то из этого, попросите агента объяснить фрагмент или переделайте файл вручную. ИИ-ревью кода слитого результата — полезный второй взгляд, но запуск тестов оно не заменяет.

Rebase или merge, если конфликты вероятны

Работают оба; ломаются по-разному.

MergeRebase
Где вы разрешаетеОдин раз, в коммите слиянияПокоммитно, при повторном применении каждого
Тот же конфликт дваждыНетВозможно, в нескольких коммитах
--ours / --theirsВаша ветка / другая веткаНаоборот: целевая ветка / ваша работа
ИсторияОстаётся коммит слиянияЛинейная, коммиты ветки переписываются
Безопасно для общих ветокДаТолько если на этой ветке больше никто не строит работу

Для короткоживущих веток агентов, которыми пользуетесь только вы, частый rebase на main обычно удобнее: конфликты проявляются рано, пока они маленькие. Для долгоживущих или общих веток используйте merge. Во время rebase git rebase --skip полностью отбрасывает текущий коммит, поэтому используйте его, только если уверены, что этот коммит не нужен.

Пусть Git запомнит: rerere

Если вы снова и снова перебазируете одну и ту же ветку, один и тот же конфликт придётся разрешать не раз. git rerere («reuse recorded resolution») запоминает, как вы разрешили конфликт, и повторяет это решение, когда тот же конфликт возникает снова:

git config --global rerere.enabled true

После включения git merge и git commit вызывают его автоматически. Полезные команды:

git rerere status       # пути, решение для которых будет записано
git rerere diff         # как сейчас выглядит решение
git rerere remaining    # конфликты, которые rerere не смог разрешить
git rerere forget src/http/client.ts   # забыть неверно записанное решение

Две вещи стоит знать. rerere не трогает индекс: вы по-прежнему проверяете результат и делаете git add. И это хорошо, ведь неверное решение повторяется так же точно, как и верное. А старые записи удаляет git rerere gc: по умолчанию неразрешённые через 15 дней, разрешённые через 60.

Проверьте конфликты до слияния

Чтобы узнать, будет ли конфликт, не обязательно начинать слияние. Начиная с Git 2.38, git merge-tree --write-tree выполняет слияние, не трогая рабочее дерево и индекс:

git merge-tree --write-tree --name-only main feature/retry
echo $?    # 0 = чисто, 1 = есть конфликты

С --name-only раздел конфликтов содержит только имена файлов. Документация Git советует опираться на код возврата, а не на пустой список файлов. Это удобное первое задание для агента, прежде чем он что-то тронет: «проверь, сливается ли feature/retry в main без конфликтов, а если нет — перечисли конфликтные файлы и что изменила каждая сторона».

Профилактика: меньше конфликтов с параллельными агентами

Самый дешёвый конфликт — тот, которого не случилось:

  • Маленькие задачи. Агент, меняющий пять файлов, конфликтует реже, чем агент, меняющий пятьдесят. Делите крупные задачи по областям.
  • Один владелец на область. Решите, какой агент может менять роутер, схему, общие типы, и пропишите это в инструкциях каждого агента.
  • Часто сливайте или перебазируйте. Ветка, живущая час, конфликтует редко; ветка, живущая неделю, — почти всегда.
  • Никаких правок по пути. Скажите агентам не переформатировать, не переименовывать и не чинить ничего за пределами их задачи.
  • Одно изменение lock-файла за раз. Пусть зависимости добавляет только одна ветка, или перегенерируйте lock-файл после каждого слияния.
  • Очередь слияния. Ветки попадают в основную по одной, на самую свежую вершину, с тестами на слитом результате, поэтому ловятся и семантические конфликты.

О том, как запускать агентов в отдельных папках, читайте в статье Git worktree для параллельных агентов.

Чем помогает AgentVera

AgentVera — десктопное приложение, которое запускает CLI агентов для кода, например Claude Code и Codex, бок о бок, со встроенным Git. С описанным выше процессом связаны такие функции:

  • Worktree и Git-панель. Создайте worktree под каждую фичу и запустите в нём агента; индексируйте по файлам, коммитьте, делайте fetch, pull и push, читайте диффы в стиле GitHub, единым видом или в две колонки.
  • Ранние предупреждения. По умолчанию AgentVera каждые 5 минут выполняет git fetch в фоне. Когда в вашу ветку приходят новые коммиты, баннер предлагает fast-forward в один клик или rebase, если коммиты есть с обеих сторон. Когда два агента в одном проекте правят один и тот же файл, координация агентов предупреждает вас; для агентов в разных worktree она отмечает, что при слиянии возможен конфликт.
  • Владение файлами. Через владение файлами вы назначаете агенту пути или glob-шаблоны вроде src/api/**. Пока он работает, агенты Claude Code останавливаются до правки этих файлов и получают указание спросить владельца; Codex и другие CLI получают правила в своих инструкциях.
  • Параллельная команда. Параллельная команда (Pro) делит задачу на части, каждая из которых работает в своём worktree и сливается в интеграционную ветку. Если слияние даёт конфликт или финальная проверка падает, подключается агент интеграции.
  • Очередь слияния. Очередь слияния (Pro) сливает ветки по одной на свежую вершину целевой ветки. Конфликты разрешает агент Claude, проверка идёт на слитом результате с не более чем 2 раундами исправлений, а ветка, которую не удалось починить, возвращается. По умолчанию приземление ждёт вашего подтверждения.

Если вы работаете один и над одной веткой за раз, обычного Git с zdiff3 и rerere вполне достаточно. Эти инструменты окупаются, когда несколько агентов одновременно производят ветки.

Частые ошибки

  • Разрешать без базы. Стиль конфликтов по умолчанию скрывает, каким код был раньше; включите zdiff3.
  • «Исправь конфликты» как весь промпт. Не зная цели каждой ветки, агент выбирает сторону, которая выглядит полнее.
  • Позволять агенту запускать git add и --continue. Оставьте финальный шаг на после git diff --check и тестов.
  • Сливать lock-файлы вручную. Разрешите манифест, затем перегенерируйте.
  • Делать rebase общей ветки. Всем, кто строил работу на ней, придётся распутывать переписанные коммиты.

Итог

Разрешение конфликтов слияния с ИИ работает, когда агент видит всю картину, а доказательства остаются за вами: маркеры zdiff3 с базой, промпт, который называет обе цели и запрещает обходные пути, git diff --check и тесты до git add и rerere для повторяющихся конфликтов. Остальное делает профилактика: маленькие задачи, понятное владение файлами и ветки, которые приземляются по одной.

Чтобы попробовать это со своими агентами, скачайте AgentVera.

Вопросы

Может ли ИИ-агент разрешать конфликты слияния в Git?

Да, и часто хорошо, если он видит общего предка (стиль zdiff3), знает, чего добивалась каждая ветка, и есть набор тестов, который подтвердит результат. Без этого он склонен оставить одну сторону и молча выбросить другую.

Что такое стиль конфликтов zdiff3?

Значение merge.conflictStyle, которое, как и diff3, добавляет секцию ||||||| с исходным текстом общего предка, но выносит за пределы конфликта строки, одинаково изменённые обеими сторонами. Он появился в Git 2.35.

Почему при rebase ours и theirs меняются местами?

Документация Git поясняет: во время git rebase и git pull --rebase --ours означает ветку, на которую вы перебазируетесь, а --theirs — вашу собственную работу, которая применяется заново. Уже поэтому массовое --ours или --theirs рискованно.

Что делает git rerere?

При включённом rerere.enabled Git запоминает, как вы разрешили конфликт, и применяет это решение снова, когда тот же конфликт возникает повторно, например при очередном rebase. Индекс он не трогает, так что вы по-прежнему проверяете результат и делаете git add.

Как узнать о конфликтах до слияния?

Запустите git merge-tree --write-tree --name-only main feature. Команда выполняет слияние, не трогая рабочее дерево и индекс, при конфликтах завершается с кодом 1 и выводит список конфликтных файлов. Нужен Git 2.38 или новее.

Соберите своих агентов за одним столом.

Скачайте AgentVera бесплатно; ваши установленные CLI уже готовы к работе.

Другие статьи