Gerrit ломает привычку к merge request, и команда сопротивляется месяц
Команда привыкла открывать merge request, копить в нём комментарии и мержить кнопкой, когда наберётся достаточно апрувов. Gerrit ломает это одним движением: вместо запроса на слияние ветки он ревьюит каждый коммит отдельно, а слияние происходит только после того, как конкретный патч набрал нужный Code-Review. Разница выглядит мелкой на бумаге и оказывается болезненной на практике — расскажем честно, что вы получаете и чем за это платите.
Содержание
- Модель Gerrit в двух словах: коммит, а не ветка
- Почему это выигрыш: дисциплина, которую GitLab не навязывает
- Что реально теряет команда при переходе
- Установка Gerrit на свой сервер: минимальный путь
- Настройка потока ревью: labels и правила проекта
- Реальный сценарий: команда из GitLab переезжает на Gerrit
- Когда Gerrit не стоит переезда
Модель Gerrit в двух словах: коммит, а не ветка
В GitLab и GitHub единица ревью — merge request (pull request): ветка с произвольным числом коммитов, которую ревьюер обсуждает целиком, а мержит одной кнопкой. В Gerrit единица ревью — сам коммит, точнее его конкретная версия, которую называют патч-сетом (patch set).
Технически это работает через git push в специальный refspec:
git push origin HEAD:refs/for/main
Вместо обычной ветки main Gerrit видит виртуальный namespace refs/for/<branch> и по нему понимает: это не прямой пуш, а предложение на ревью. Gerrit парсит коммит, ищет в его сообщении строку Change-Id: I... — если её нет, hook (commit-msg, который Gerrit сам подсовывает при первом клоне) добавит её автоматически при следующем коммите. Именно Change-Id — а не ветка и не номер MR — является постоянным идентификатором изменения на протяжении всей его жизни.
Дальше начинается ключевое отличие. Если вы правите коммит после ревью — не добавляете новый коммит поверх, а именно правите существующий через git commit --amend — и пушите снова с тем же Change-Id, Gerrit создаёт новый патч-сет той же самой записи (Change), а не новую запись. Ревьюер видит diff между патч-сетами и может убедиться, что именно было исправлено, не перечитывая весь код заново.
git commit --amend
git push origin HEAD:refs/for/main
Если у вас цепочка из нескольких логически независимых коммитов (сначала рефакторинг, потом фича на его основе) — Gerrit создаёт по одной записи (Change) на каждый коммит и выстраивает между ними зависимости автоматически по порядку в истории. Смержить второй коммит, пока не смержен первый, нельзя — Gerrit об этом явно предупредит.
Почему это выигрыш: дисциплина, которую GitLab не навязывает
Модель merge request де-факто поощряет накопление: разработчик открывает ветку, коммитит туда 15 раз за два дня («fix», «wip», «еще фикс», «дошло наконец»), а ревьюер получает на вход простыню diff'ов, где полезный сигнал перемешан с шумом отладки. Ревью в такой ситуации превращается в вычитку финального состояния файлов, а не истории изменений — что обычно и происходит на практике, даже если правила репозитория формально требуют «чистые коммиты».
Gerrit убирает эту лазейку структурно, а не правилами линтера:
- Каждый коммит проверяется отдельно — нельзя спрятать плохой коммит внутри хорошей ветки, потому что ветки как единицы ревью не существует.
- История после мержа выглядит так же, как её написал автор до пуша — Gerrit не делает squash и не создаёт merge-коммит по умолчанию (стратегия слияния настраивается, но типовая — fast-forward или cherry-pick конкретного патч-сета).
- Автора коммита физически неудобно приучить к сообщению вида «fix» — потому что это сообщение станет постоянной частью истории проекта, а не потеряется при squash в один клик.
Побочный эффект, который команды обычно не ожидают заранее: git log и git blame в репозитории на Gerrit оказываются пригодны для чтения через полгода. В типичном GitLab-репозитории с привычкой сквошить merge request в один коммит история превращается в список из полусотни записей вида «Merge branch 'feature/xyz' into 'main'» без единой содержательной строки — разбираться, почему конкретная строка выглядит именно так, приходится по номеру MR в трекере задач, а не по git log.
Второй практический плюс — гранулярность апрува. В merge request на 12 коммитов ревьюер либо одобряет всё разом, либо блокирует всё разом. В Gerrit можно одобрить первые три коммита цепочки (они уже смерджены и работают), а по четвёртому продолжать спорить — остальная работа при этом не заблокирована.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверЧто реально теряет команда при переходе
Здесь честность важнее маркетинга: три вещи, которые в GitLab работают из коробки, в Gerrit требуют либо привыкания, либо дополнительной настройки.
Веб-интерфейс read-only для git-операций. В Gerrit нельзя запушить прямо из веба, нельзя удобно поресолвить конфликт через кнопку «resolve conflicts» — придётся сделать git fetch, git rebase и снова git push origin HEAD:refs/for/main из терминала. Для команды, привыкшей мержить конфликты кнопкой в GitLab UI, это ощутимая потеря удобства в первые недели.
Rebase вместо merge как основной ритуал. Так как каждый Change привязан к конкретному родительскому коммиту через Change-Id, при устаревании ветки чаще нужен git rebase, а не git merge. Это увеличивает число операций с историей на каждого разработчика — а значит и число ситуаций, где неопытный человек сделает git rebase неправильно и получит дублирующиеся коммиты или потерянные изменения.
Порог входа для новичков в команде выше. Разработчик, который год работал только с GitHub, интуитивно понимает merge request за пять минут. С Gerrit тот же человек первую неделю путает git push origin HEAD:refs/for/main с обычным git push, забывает про Change-Id при копировании коммита между ветками (получает дубль записи вместо нового патч-сета) и не понимает, почему его апрув не смержил код автоматически — потому что Submit в Gerrit отдельное действие от Code-Review +2.
Ниже — что предсказуемо ломается в первый месяц и как это обычно решается.
| Проблема первого месяца | Причина | Что помогает |
|---|---|---|
| Дубли Change вместо новых патч-сетов | git cherry-pick теряет Change-Id из commit message | Явно сохранять footer при cherry-pick, git config для commit-msg hook на всех клонах |
| «Мой апрув есть, а замержить не могу» | Submit — отдельное действие, +2 не равно merge | Явная инструкция и, при желании, автосабмит через label Submit-Requirement |
| Конфликт при rebase пугает новичков | Нет привычной кнопки «resolve in browser» | Обучение git rebase -i в первую неделю, не постфактум |
| «Где мой PR» | В Gerrit это Change, а не ветка, ищется по номеру или Change-Id | Настроенные поисковые фильтры/дашборд под команду |
| Долгий пуш больших цепочек коммитов | Каждый коммит — отдельная запись с проверками | Меньшие атомарные коммиты по умолчанию — тоже дисциплина, но занимает время привыкнуть |
Установка Gerrit на свой сервер: минимальный путь
Gerrit — Java-приложение, официально распространяется как .war-файл, под капотом у него встроенный веб-сервер (Jetty) и требуется JDK. Актуальная стабильная линейка на конец августа 2026 года работает на Java 17 или новее — перед установкой стоит свериться с release notes конкретной версии, которую вы качаете, а не полагаться на цифру из старого мануала.
Минимальный сценарий на Ubuntu-сервере:
sudo apt update
sudo apt install -y openjdk-17-jdk-headless
sudo useradd -m -d /home/gerrit -s /bin/bash gerrit
sudo -iu gerrit
mkdir -p ~/gerrit_site
java -jar gerrit.war init -d ~/gerrit_site --batch
Мастер init в интерактивном режиме спросит про базу данных (для небольшой команды стандартный H2 из коробки достаточен, PostgreSQL — если ожидается рост), аутентификацию (LDAP, OAuth или встроенная), способ доставки git-протокола (SSH-демон Gerrit на отдельном порту, обычно 29418, плюс HTTP). После инициализации сервис поднимается так:
~/gerrit_site/bin/gerrit.sh start
Дальше — systemd-юнит, чтобы Gerrit переживал перезагрузку сервера, и обратный прокси (Nginx) перед HTTP-портом для TLS. Ресурсы под Gerrit скромнее, чем под полновесный omnibus-GitLab: для команды до 10-15 человек комфортно живёт на 2-4 GB RAM и 2 vCPU — сама Java-машина плюс встроенная H2-база не требуют такого объёма памяти, какой просит связка PostgreSQL+Redis+Gitaly+Sidekiq у GitLab CE. Если параллельно нужен полноценный CI, его придётся разворачивать отдельно — Zuul или Jenkins с плагином Gerrit Trigger — сам Gerrit пайплайнов не запускает.
Настройка потока ревью: labels и правила проекта
Ядро Gerrit-логики — система labels, из которых главные два: Code-Review (обычно диапазон от -2 до +2) и Verified (для CI, -1/0/+1). Правило по умолчанию: чтобы Change ушёл в Submit, нужен минимум один +2 по Code-Review и ни одного -2 (вето одного ревьюера блокирует весь Change, даже если остальные согласны).
Настраивается это в project.config (сам под git-контролем, ветка refs/meta/config):
[label "Code-Review"]
function = MaxWithBlock
value = -2 This shall not be merged
value = -1 I would prefer this is not merged as is
value = 0 No score
value = +1 Looks good to me, but someone else must approve
value = +2 Looks good to me, approved
Практический нюанс, который стоит решить на старте, а не через месяц споров: разрешаете ли автору коммита ревьюить самого себя (self-approve). По умолчанию Gerrit это не блокирует технически, но большинство команд явно запрещают через правило Self-Approve в конфиге проекта — иначе смысл ревью размывается в первую же неделю удобства.
Второй момент — Submit Requirements (более новый и гибкий механизм по сравнению со старыми label-функциями): можно явно потребовать не просто +2 по Code-Review, а ещё и зелёный Verified от CI, и отсутствие незакрытых замечаний (unresolved comments) — Gerrit умеет считать количество нерешённых тредов в патч-сете и блокировать Submit, пока автор явно не пометит их resolved.
Реальный сценарий: команда из GitLab переезжает на Gerrit
Разберём типичный кейс перехода: команда 6-8 человек, до этого год работала в GitLab CE (см. установку GitLab CE на VPS и сравнение GitLab CE и Forgejo, если рассматриваете альтернативы), переезжает на Gerrit ради строгого ревью в проекте с высокими требованиями к качеству кода (embedded/инфраструктурный код, где откат багов дорог).
Неделя 1: паника и просьбы вернуть кнопку «Merge». Разработчики массово путают refs/for/main с обычным пушем, получают ошибку missing Change-Id, не понимают разницы между Code-Review +2 и Submit. Это нормально — не признак провала перехода, а ожидаемая часть кривой обучения.
Неделя 2-3: формируется привычка амендить коммит вместо накопления новых. Здесь помогает жёсткое правило — тимлид физически не одобряет второй Change поверх первого, если это должен был быть один патч-сет, и явно объясняет разницу в комментарии. Люди быстро учатся на конкретных примерах, а не на абстрактной документации.
Неделя 4+: команда замечает, что history стала читаемой, а количество откатов из-за пропущенных в ревью проблем упало — потому что при мелких атомарных коммитах ревьюеру физически проще удержать в голове весь diff одного изменения, чем weeks-накопленную ветку на 40 файлов. Субъективно разработчики жалуются на «медленнее», но объективно это происходит потому, что раньше часть проблем откладывалась до продакшена, а не терялась.
Важная оговорка: этот сценарий не универсален. Для команды, которая делает быстрые итерации на фронтенде с частыми правками UI и низкой ценой отката, строгость Gerrit может быть избыточной платой за скорость — там merge-request-модель GitLab или GitHub объективно быстрее по циклу «написал — увидел на проде — поправил». Gerrit оправдан там, где цена плохого коммита в истории или пропущенной ошибки высока, а не универсально лучше во всех случаях.
Когда Gerrit не стоит переезда
Честный список ситуаций, где смена модели ревью принесёт больше боли, чем пользы:
- Команда меньше 3-4 человек — накладные расходы на обучение непропорциональны выигрышу в дисциплине, которую в маленькой команде и так легко поддерживать разговором.
- Высокая текучка контрибьюторов (open source с редкими one-off PR) — разовому контрибьютору проще один раз открыть pull request в привычном GitHub UI, чем разбираться с
refs/for/ради одного патча. - Продукт с частыми экспериментальными ветками и низкой ценой отката — переиспользуемость merge request как единицы фичи (со своим CI-раном на всю ветку целиком) там ценнее гранулярности по коммитам.
- Нет времени и ресурса на явное обучение команды в первый месяц — Gerrit прощает такое хуже, чем GitLab: интуитивно догадаться до модели патч-сетов с нуля почти никто не может.
Если ни один из пунктов не про вашу команду, а качество истории и строгость ревью критичны — стоит попробовать хотя бы на одном внутреннем проекте, прежде чем переводить весь стек. О смежном вопросе доступа при переходе на новый инструмент — в статье про разграничение доступов в команде из трёх человек.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Можно ли использовать Gerrit вместе с GitHub или GitLab, а не вместо?
Технически да — некоторые команды держат зеркало на GitHub для внешней видимости, а реальный поток ревью ведут через Gerrit как источник истины. Но это увеличивает операционную сложность (синхронизация, два набора прав) и оправдано только при явной внешней необходимости показывать код на GitHub.
Что произойдёт, если запушить обычным git push без refs/for/?
Если у вас нет прав на прямой push в защищённую ветку (а по умолчанию Gerrit это ограничивает через Push permission в конфиге проекта), пуш будет отклонён с понятной ошибкой. Прямой push стоит оставить только для админов и явно исключительных случаев.
Нужен ли отдельный CI-сервер или Gerrit умеет запускать джобы сам?
Gerrit не исполняет CI-пайплайны — только хранит и проверяет labels. Нужен внешний CI (Jenkins с плагином Gerrit Trigger, Zuul или собственный webhook-интегратор), который слушает события Gerrit и выставляет label Verified по результату сборки.
Как быть с большими legacy-репозиториями, где история и так грязная?
Модель Gerrit не переписывает существующую историю — она начинает применяться с момента перехода. Старые коммиты остаются как есть, дисциплина работает только на новые изменения, поэтому эффект на читаемость истории накопится постепенно, а не мгновенно.
Реально ли частично внедрить Gerrit только для критичных модулей?
Да, если репозитории разделены — можно держать часть проектов на GitLab/Forgejo с обычными merge request, а особо критичный модуль (например, ядро инфраструктурного кода) вести через Gerrit с более строгими требованиями. Смешивать модели ревью в одном репозитории Gerrit и GitLab одновременно нельзя — выбор делается на уровне репозитория.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →