Олег Марков
Применение патчей в Git с помощью git am
Введение
Механизм применения патчей в Git часто ассоциируется с командой git apply, но в реальных командных процессах, особенно когда задействованы почтовые рассылки и code review по почте, гораздо важнее git am.
git am берет патчи в формате email-сообщений (mbox), извлекает из них и изменения, и метаданные (автора, дату, тему, сообщение коммита) и превращает каждый такой патч в полноценный коммит в вашей ветке.
Смотрите, в этой статье я покажу вам:
- чем
git amотличается отgit apply; - как подготовить и применить пачку патчей;
- как разруливать конфликты при
git am; - как «перепридумывать» патчи перед применением (опции
--committer-date-is-author-date,--signoff,--whitespaceи другие); - как отменить неудачный
git amи вернуть репозиторий в рабочее состояние.
Мы будем двигаться по шагам, с практическими примерами, чтобы вы могли использовать git am в реальной работе, а не только в теории.
Что такое git am и когда его использовать
Задача, которую решает git am
git am расшифровывается как "apply mailbox". Его основное назначение — взять набор email-писем с патчами и превратить каждое письмо в отдельный коммит, сохранив:
- автора (
From); - время создания;
- тему письма как заголовок коммита;
- тело письма как описание коммита;
- сам дифф как изменения в файлах.
Это особенно полезно в сценариях:
- вы получаете патчи по электронной почте (классический workflow opensource-проектов в стиле Linux kernel);
- у вас есть
mboxфайл (выгрузка из почты) с набором патчей; - коллега отправил вам пачку патчей, созданных через
git format-patch; - вы хотите сохранить оригинальных авторов коммитов при перенесении изменений между репозиториями.
Отличие git am от git apply и git cherry-pick
Давайте разберемся, чем git am отличается от похожих команд:
git apply- применяет только дифф (изменения в файлах);
- не создает коммит автоматически;
- не использует метаданные email-писем;
- после
git applyвы сами решаете, как и с каким сообщением делать коммит.
git cherry-pick- берет существующий коммит (по SHA) из другой ветки и переносит его к вам;
- не работает с файлами-патчами;
- сохраняет автора и сообщение коммита, но источник — не email, а другой коммит в том же репозитории (или в связанном через
remote).
git am- берет патчи из файлов или stdin;
- ожидает формат email-писем (обычно, результат
git format-patch); - создает новые коммиты, используя метаданные письма как данные коммита.
Проще всего запомнить:
git format-patch— делает письма из коммитов;git am— делает коммиты из писем.
Подготовка патчей для git am
Связка git format-patch и git am
В типичном процессе один разработчик создает патчи с помощью git format-patch, другой применяет их git am.
Покажу вам простую схему:
- Разработчик A делает несколько коммитов в ветке
feature. Он создает пачку патчей:
# Создаем патчи для всех коммитов, которые есть в feature, но нет в main git checkout feature git format-patch main// Команда создаст серию файлов вида: // 0001-Добавить-новую-функцию.patch // 0002-Поправить-ошибку.patch // и т.д.
Он отправляет эти
.patchфайлы разработчику B (по почте, в архиве, через issue-трекер).Разработчик B применяет их у себя:
git checkout main git am 000*.patch// Каждому patch-файлу соответствует один новый коммит // с сохранением автора, даты и сообщения
Форматы входа для git am
git am умеет читать патчи из:
- одного файла (один или несколько патчей в формате mbox);
- списка файлов;
- стандартного ввода (stdin), например через пайп из
curlилиcat.
Пример применения одного файла
# Применяем один patch-файл
git am 0001-fix-typo-in-readme.patch
// Git прочитает email-заголовки и дифф // и создаст один новый коммит в текущей ветке
Пример применения нескольких файлов
# Применяем сразу все патчи из текущей директории
git am 000*.patch
// Патчи будут применены по порядку // согласно лексикографическому имени файла (0001, 0002, ...)
Применение из mbox-файла
Иногда патчи собирают в один файл в формате mbox. Тогда:
git am patches.mbox
// Git пройдет по всем письмам в mbox и превратит каждое в отдельный коммит
Применение патчей из stdin
Это удобно, если вы скачиваете патч напрямую по URL. Давайте разберемся на примере:
curl -L https://example.com/patches/series.mbox | git am
// curl скачивает содержимое файла // и передает его на вход git am // git am читает патчи со стандартного ввода и применяет их
Базовое использование git am
Простейший сценарий шаг за шагом
Допустим, у вас есть репозиторий и вы хотите применить патчи, которые прислал коллега.
Убедитесь, что рабочее дерево чистое:
git status// Если есть незакоммиченные изменения, лучше их закоммитить или отложить // через git stash, чтобы они не мешали применению патчей
Переключитесь в нужную ветку:
git checkout mainПримените патчи:
git am /path/to/patches/000*.patch// Патчи будут применяться по одному // Если все прошло без конфликтов, появится серия новых коммитов
Посмотрите историю:
git log --oneline// Вы увидите новые коммиты, автором которых будут те, // кто изначально писал патчи
Как git am формирует коммиты
Когда git am применяет патч, он:
- Берет заголовки письма:
From→ автор коммита;Date→ дата автора;Subject→ заголовок коммита (первые строки);
- Берет тело письма как основное сообщение коммита (после пустой строки);
- Берет дифф (часть после
---и+++) как изменения в файлах.
Типичный патч от git format-patch выглядит так (упрощенно):
From 1234567890abcdef Mon Sep 17 00:00:00 2001
From: Иван Иванов <ivan@example.com>
Date: Mon, 2 Dec 2024 10:00:00 +0300
Subject: [PATCH 1/2] Добавить функцию суммирования
--- a/math.go
+++ b/math.go
@@ -1,3 +1,7 @@
+// Sum возвращает сумму двух чисел
+func Sum(a, b int) int {
+ return a + b
+}
Когда вы запускаете:
git am 0001-Add-sum-function.patch
Git создаст коммит:
- автор — Иван Иванов;
- дата — Mon, 2 Dec 2024 10:00:00 +0300;
- заголовок коммита —
[PATCH 1/2] Добавить функцию суммирования(по умолчанию, без обрезки префикса); - изменения — добавление функции
Sum.
Важные опции git am и их использование
Работа с датами и авторством
--committer-date-is-author-date
По умолчанию у коммита есть две даты:
- author date — когда автор сделал изменения;
- committer date — когда изменения попали в вашу ветку (вы применили патч).
Иногда хотят, чтобы обе даты совпадали. Для этого есть:
git am --committer-date-is-author-date 0001-*.patch
// В этом случае время применения патча не учитывается // и история выглядит так, как будто коммиты были сделаны сразу в этой ветке
--ignore-date
Если, наоборот, вы хотите использовать текущее время как дату автора, можно сделать так:
git am --ignore-date 0001-*.patch
// Тогда и author date, и committer date будут установлены в текущее время
Добавление sign-off к коммитам
Во многих проектах (например, в Linux kernel) требуется строка Signed-off-by в сообщении коммита.
Чтобы не добавлять ее вручную, используйте:
git am --signoff 0001-*.patch
// Git возьмет ваше имя и email из настройки user.name и user.email // и добавит строку в конец сообщения коммита: // // Signed-off-by: Ваше Имя you@example.com
Если в патче уже есть Signed-off-by автора, в итоге будет две строки: автора и ваша. Это нормально и ожидаемо в таких workflow.
Управление пробелами: --whitespace и --ignore-space-change
Патчи часто ломаются из-за пробелов и переносов строк. Чтобы уменьшить количество проблем, используйте параметры обработки пробелов.
Самая полезная опция при git am — --whitespace. Например:
git am --whitespace=fix 0001-*.patch
Основные значения:
--whitespace=nowarn— не предупреждать о проблемах с пробелами;--whitespace=fix— пытаться автоматически исправить проблемы (лишние пробелы в конце строк и т.п.);--whitespace=error— считать проблемы с пробелами ошибкой и прерывать применение патча.
Смотрите, если у вас много патчей с мелкими проблемами форматирования, опция fix помогает их «пригладить», не вводя вас в постоянные конфликты из-за пробелов.
Дополнительно можно использовать опции диффа через -C (context) или -pN, но чаще всего это идет из формата патча, а не из git am.
Пропуск и повторное применение патчей
Если какой-то патч уже был применен (например, изменения уже в вашей ветке), Git может это обнаружить и пропустить его.
--skip
Когда вы применяете серию патчей и один из них вызывает проблему, вы можете его пропустить:
git am --skip
// Команда пропустит текущий патч // и попытается применить следующий
Это полезно, если:
- патч уже был включен другим коммитом;
- патч устарел и больше не нужен;
- вы осознанно хотите пройти мимо него.
--continue
Когда вы сталкиваетесь с конфликтом, вы:
- Ручками правите файлы;
Отмечаете конфликт как решенный:
git add путь/к/файлу// Повторяете для всех конфликтных файлов
Продолжаете применение:
git am --continue
// Git завершит текущий коммит (который конфликтовал) // и перейдет к следующему патчу в очереди
--abort
Если вы понимаете, что применение патчей зашло в тупик, можно полностью откатить процесс:
git am --abort
// Git вернет репозиторий к состоянию, в котором он был до запуска git am // Все частично примененные патчи и промежуточное состояние будут сброшены
Разбор конфликта при git am
Как выглядит конфликт в процессе git am
Представьте, что вы применяете пачку:
git am 000*.patch
На каком-то патче git останавливается и пишет:
Applying: Fix typo in README
error: patch failed: README.md:10
error: README.md: patch does not apply
Patch failed at 0003 Fix typo in README
hint: Use 'git am --show-current-patch=diff' to see the failed patch
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip".
To restore the original branch and stop patching, run "git am --abort".
Смотрите, здесь важные моменты:
- Git указывает, что сломался на патче
0003 Fix typo in README; - подсказывает, что можно:
- посмотреть сам патч через
git am --show-current-patch; - продолжить после ручного решения конфликта (
--continue); - пропустить (
--skip); - отменить полностью (
--abort).
- посмотреть сам патч через
Как посмотреть текущий патч
Очень полезная команда:
git am --show-current-patch=diff
// Показывает, какие изменения хотел внести текущий патч // Это помогает при ручном разрешении конфликта
Разрешение конфликта: пошаговый пример
Пусть у вас конфликт в файле README.md.
Git отметит конфликтные места вот так:
<<<<<<< HEAD Текущая версия строки в вашей ветке ======= Версия строки из патча >>>>>>> Fix typo in README// Все, что выше = — ваша текущая версия // Все, что ниже = и до >>>>>>> — версия из патча
Вы открываете файл и приводите его к желаемому виду, удаляя конфликтные маркеры:
Окончательная версия строки которую вы выбрали// Здесь вы вручную решили, какой вариант оставить // (или объединили оба варианта по смыслу)
Отмечаете файл как исправленный:
git add README.mdПродолжаете применение патчей:
git am --continue// Git завершит коммит для текущего патча // и перейдет к следующему
Если же вы решили, что патч вообще не нужен:
git am --skip
А если хотите отменить все:
git am --abort
Переписывание сообщений и авторства при git am
Иногда бывает нужно изменить:
- автора коммита;
- тему/описание коммита;
- добавить дополнительные строки в сообщение коммита.
Режим интерактивного редактирования сообщений
Один из вариантов — применить патч и потом сделать git commit --amend, но когда патчей много, это неудобно. Для git am есть опция:
git am -i 0001-*.patch
Однако в современных Git обычно используют флаг --interactive или связку с --edit. Более простой рабочий прием — включить редактирование сообщения для каждого применяемого патча через:
git am --edit 0001-*.patch
// Для каждого патча откроется редактор сообщения коммита // Вы сможете поправить заголовок и тело коммита перед его созданием
Переопределение автора
Если вы хотите явно задать автора коммита (например, патч пришел без корректных заголовков), можно сделать так:
GIT_AUTHOR_NAME="Иван Иванов" \
GIT_AUTHOR_EMAIL="ivan@example.com" \
git am 0001-*.patch
// Переменные окружения переопределят автора // для создаваемых коммитов
Аналогично можно управлять GIT_COMMITTER_NAME и GIT_COMMITTER_EMAIL.
Практический пример применения серии патчей
Давайте разберемся на примере типичного рабочего процесса от начала до конца.
Сценарий
- Ветка
main— основная. - Разработчик A сделал новую функциональность в ветке
feature-x. Он создал четыре коммита и сгенерировал патчи:
git checkout feature-x git format-patch main// Появились файлы 0001-..., 0002-..., 0003-..., 0004-...
Эти файлы вы получили в папке
/tmp/patches.
Применяем патчи как интегратор изменений
Проверяем состояние:
git statusПереключаемся в
main:git checkout mainПрименяем патчи, добавляя свой sign-off:
git am --signoff /tmp/patches/000*.patch// Если все прошло гладко, получим 4 новых коммита // Каждый будет содержать: // - автора как у оригинального разработчика // - дату из оригинального коммита // - ваше Signed-off-by внизу сообщения
Проверяем историю:
git log --oneline --decorate -4// Здесь вы увидите последние 4 коммита из серии
Если на каком-то шаге возник конфликт, переходите к алгоритму из раздела про конфликты: правка файлов → git add → git am --continue.
Отличия git am от git apply на практике
Чтобы у вас не смешивались эти две команды, давайте посмотрим на них рядом.
git apply: ручной режим
Пример:
git apply 0001-fix-bug.patch
// Применяет изменения к рабочему дереву // НЕ создает коммит автоматически
После этого:
git status
// Увидите измененные файлы // Дальше вам нужно:
git add .
git commit -m "Fix bug"
git am: автоматический режим с метаданными
git am 0001-fix-bug.patch
// Сразу создает коммит // Использует автора, дату и сообщение из патча
Когда что использовать
git apply, если:- патч не в email-формате (нет заголовков From/Subject);
- вы хотите вручную написать сообщение коммита;
- патч — это временное изменение или эксперимент.
git am, если:- у вас патчи от
git format-patch; - важно сохранить оригинального автора и дату;
- вы интегрируете чужие изменения как полноценную историю.
- у вас патчи от
Советы по безопасной работе с git am
Всегда работайте в чистом дереве
Перед git am проверяйте:
git status
Если есть локальные незакоммиченные изменения, примените один из вариантов:
закоммитьте:
git add . git commit -m "WIP save"или отложите:
git stash push -m "Перед git am"
// Это снизит риск того, что ваши текущие изменения будут конфликтовать с патчами
Используйте отдельную ветку для теста патчей
Хорошая практика — не применять патчи сразу в main, а сначала в отдельную ветку.
git checkout -b review-feature-x main
git am /tmp/patches/000*.patch
// Здесь вы можете: // - посмотреть изменения // - прогнать тесты // - при необходимости поправить историю
Если все хорошо, можно потом смержить эту ветку в main.
Проверяйте результат после применения
После удачного git am полезно:
git log --oneline -n 5
git show HEAD
// Так вы убедитесь, что: // - сообщения коммитов корректные // - авторы правильные // - дифф выглядит так, как ожидалось
При необходимости последний коммит можно поправить:
git commit --amend
// Но помните, что это изменит хэш коммита // Лучше делать такое до того, как ветка уйдет на общий remote
Заключение
git am — это инструмент для автоматического применения патчей в формате email, который:
- преобразует каждый патч в самостоятельный коммит;
- сохраняет автора, дату и сообщение из исходного письма;
- поддерживает удобный workflow с
git format-patch; - умеет работать с конфликтами, пропуском патчей и отменой операции.
Если вам нужно интегрировать чужие изменения, присланные патчами, и сохранить аккуратную и честную историю, git am — предпочтительный выбор по сравнению с git apply.
Используя опции --signoff, --whitespace, --committer-date-is-author-date, а также команды --continue, --skip, --abort, вы можете гибко управлять процессом применения патчей и избегать типичных проблем.
Ключ к уверенной работе с git am — практиковаться на отдельных ветках, внимательно следить за состоянием репозитория и не бояться использовать git am --abort, когда что-то идет не так.
Частозадаваемые технические вопросы по теме
1. Как применить только часть патчей из серии, а не все сразу?
Если у вас есть, например, 10 патчей, но вы хотите применить только первые 3, сделайте так:
Посмотрите список файлов:
ls 000*.patchЯвно укажите нужные файлы:
git am 0001-*.patch 0002-*.patch 0003-*.patch
Если патчи в одном mbox, можно предварительно разделить его на несколько файлов с помощью git mailinfo или простой ручной нарезки, но на практике чаще управляют набором через git format-patch на стороне отправителя.
2. Что делать, если патч не в формате mbox и git am его не воспринимает?
Если патч — обычный unified diff без email-заголовков (From, Subject), используйте:
git apply файл.patch
git commit -m "Описание изменений"
Либо, если хотите приблизиться к формату git am, можно руками собрать mbox-подобный файл, но проще попросить отправителя сгенерировать патч через git format-patch.
3. Как понять, какие патчи уже применены, если у меня их много?
Если патч был создан через git format-patch, внутри него есть строка From <SHA>. Ее можно поискать в истории:
git log --grep='Original-commit-SHA' --all
Но удобнее использовать git patch-id:
git show <commit> | git patch-id
cat 0001-some.patch | git patch-id
Если patch-id совпадают, патч уже присутствует в истории в эквивалентном виде.
4. Как изменить кодировку сообщений коммитов при git am?
Если письма с патчами в другой кодировке (например, Windows-1251), можно предварительно переконвертировать файл:
iconv -f WINDOWS-1251 -t UTF-8 patches.mbox > patches-utf8.mbox
git am patches-utf8.mbox
Git ожидает, что содержимое патчей будет в UTF-8, особенно если вы хотите, чтобы коммиты корректно отображались в большинстве инструментов.
5. Можно ли отменить только часть уже примененных патчей?
Если вы уже применили, например, 5 патчей, а потом поняли, что последние 2 не нужны, можно сделать:
# Откатить два последних коммита
git reset --hard HEAD~2
Либо аккуратнее, если изменения уже ушли в общий репозиторий, использовать git revert для каждого нежелательного коммита:
git revert <hash4> <hash5>
Это создаст новые коммиты, которые логически отменяют изменения, не ломая историю для коллег.
Постройте личный план изучения Git до уровня Middle — бесплатно!
Git — часть карты развития Frontend
100+ шагов развития
30 бесплатных лекций
300 бонусных рублей на счет
Бесплатные лекции
Все гайды по Git
Лучшие курсы по теме

Основы Git
Антон Ларичев
TypeScript с нуля
Антон Ларичев