Симптом обычно маскируется под простую задержку: bug report уже есть, файл найден, автор строки виден в git blame, но исправление всё равно ждёт ответа. Причина в смешении механизмов. История говорит, как строка оказалась в дереве. CODEOWNERS может направить pull request в нужную область. Review фиксирует решение по изменению. Ни один из этих следов сам не назначает человека, который подтвердит смысл контракта после инцидента.
Цена смешения видна на модульных стыках. Разработчик меняет gateway, потому что он последний касался parser. Reviewer смотрит на diff и одобряет синтаксис. После merge неизвестный ответ всё ещё трактуется неверно, поскольку ни у кого не было явной обязанности решить, что этот ответ означает. Разберём механизм без легенды о «единственном настоящем владельце»: у кода, review и последующего действия разные границы.
История строки отвечает только на исторический вопрос
git blame аннотирует строку revision и автором, которые последними её изменили. Это удобно для поиска контекста, особенно с ограничением диапазона -L. Но команда не знает, был ли коммит рефакторингом, переносом форматирования, временным обходом или решением контракта. Документация Git прямо описывает изменение строк, а не право принимать сегодняшнее решение о поведении системы.
Поэтому исторический поиск лучше делать двухшаговым. Сначала берём узкий диапазон и историю конкретного пути. Затем смотрим, какие документы, тесты и соседние модули упоминались в изменениях. Если история даёт несколько имён, это нормальный результат исследования: она расширяет круг вопросов, а не выбирает виноватого по дате коммита.
# Сначала собираем факты, не назначая владельца по одной строке.
git blame -L 42,72 -- web/checkout/gateway/status.js
git log --follow -- web/checkout/gateway/status.js
git log -- web/checkout/gateway/ docs/checkout-contract.md
# Затем фиксируем отдельные роли в задаче или pull request.
decision owner: @example/payments
code path owner: @example/checkout
reviewer: @example/payments + @example/checkout
follow-up owner: @example/checkout
| След | Надёжный вывод | Неверный вывод | Следующее действие |
|---|---|---|---|
| git blame | какой revision последним изменил конкретную строку | этот автор владеет текущим бизнес-решением | прочитать diff и связанный context |
| git log по пути | какие commits затрагивали файл или каталог | история покрывает все внешние зависимости | сравнить с contract и тестами |
| CODEOWNERS | кого платформа запросит на review для совпавшего пути | эти люди приняли решение или проверили production | проверить base branch, порядок правил и доступ |
| Review в PR | какое решение reviewer отправил для этого diff | кто выполнит проверку после merge | назначить follow-up отдельно |
Эта граница не обесценивает Git. Наоборот, она делает расследование короче: не спорим с историей, а используем её по назначению. Если git blame показывает технического автора, а контракт указывает на другую область, задача должна сохранить оба факта. Пропасть между ними — не ошибка инструмента, а место, где нужен явный владелец решения.
CODEOWNERS вычисляет маршрут по пути и base branch
CODEOWNERS — не часть формата Git-репозитория, а правило конкретной платформы. В GitHub файл может находиться в .github/, корне или docs/; для запроса code-owner review используется вариант из base branch pull request. Это важная деталь: изменение правила в feature branch не должно позволить автору change переназначить reviewer для того же merge.
Синтаксис похож на .gitignore, но не полностью совпадает. GitHub отдельно предупреждает, что отрицание через ! и диапазоны в квадратных скобках не работают как в gitignore. Для проекта это не повод писать большой исключающий список. Проще выбрать непрерывные каталоги и положить более специфичное правило ниже общего, потому что последнее совпадение имеет преимущество.
# .github/CODEOWNERS
# Псевдонимы и пути ниже учебные: это не фрагмент рабочего репозитория.
* @example/platform-review
/web/checkout/ @example/checkout
/web/checkout/gateway/ @example/payments
/docs/checkout-contract.md @example/payments @example/checkout
/.github/CODEOWNERS @example/repository-admins
В учебной карте общий owner ловит всё дерево, checkout — экранную область, а gateway — более узкий интеграционный путь. Строка документа контракта имеет двух owners, поскольку изменение текста способно поменять решение и реализацию сразу. Последняя строка защищает сам механизм маршрутизации. В настоящем проекте вместо учебных псевдонимов нужны реальные пользователи или видимые команды с нужными правами; иначе GitHub не назначит code owner.
Запрос review и его результат — разные состояния
Автоматический запрос reviewer ещё не означает review. В GitHub итог review бывает comment, approve или request changes. Политика защищённой ветки может требовать approval, но сам факт участия code owner не заменяет список вопросов. У маленького change его стоит написать прямо: «проверьте трактовку unknown status» или «подтвердите, что retry не создаёт второй transition».
Это особенно важно, если один path имеет несколько owners. Одобрение одного code owner может быть достаточно для правила платформы, но продуктовый риск иногда требует двух разных ответов. Не стоит выдавать локальную настройку merge за модель знаний команды. Если решение касается API-контракта и UI-перехода, запросите соответствующих людей и сохраните в описании, что именно каждый из них подтвердил.
Последующее действие не хранится в diff автоматически
После merge появляется другой вопрос: доказал ли выпуск, что выбранное решение работает на настоящем пути? Ни Git commit, ни CODEOWNERS, ни approve сами по себе не создают этот ответ. В 2019 году достаточно простого артефакта: назначить человека на проверку лога или сценария, указать срок и записать ожидаемый сигнал. Это не попытка сделать из одной команды круглосуточную службу; это защита от случая «код уже merged, а баг всё ещё ничей».
Если проверка должна быть выполнена другой группой, не прячьте её в комментарии к PR. Откройте отдельную задачу, свяжите её с change и оставьте один исход: подтверждённый результат, откат или новая проблема. Так reviewer может закончить работу с diff, а владелец follow-up получает свою собственную проверяемую очередь.
Собираем минимальный контракт без лишнего процесса
- Выбрать один дефект и один точный вопрос, который требует решения. Сначала записать симптом, а не искать владельца по списку сотрудников.
- Взять историю только нужных строк и путей. Сохранить ссылки или revision hashes как контекст, не превращая их в поле «ответственный».
- Проверить, какое правило CODEOWNERS совпадает в base branch и кого платформа запросит на review. Если путь спорный, назвать owner решения в описании change.
- Разбить review на вопросы: семантика контракта, риск реализации, тест отрицательного случая. Один человек может закрыть несколько вопросов, но это видно явно.
- До merge назначить последующее действие и его сигнал. Например: проверить, что unknown status остаётся отдельным состоянием, а не считается успешным.
- После проверки закрыть след или создать новую задачу с тем же контекстом. Не оставлять результат в памяти reviewer или в неразрешённом комментарии.
Типичные ложные упрощения
Первое: назначить автором решения человека из git blame. Это ломается на рефакторинге и переносе кода. Второе: считать CODEOWNERS каталогом экспертов. Файл знает путь и права платформы, но не знает, кто согласовал изменение за пределами пути. Третье: считать review завершённым после появления аватара. Решение review должно отвечать на риск, а после merge ещё остаётся проверка фактического результата.
Здоровая минимальность выглядит скучно: один symptom, один owner решения, один или два reviewer с конкретными вопросами, тест, запись после merge. Но именно она переживает смену людей и перенос модулей. Следующий разработчик не обязан угадывать логику по автору строки: он видит границу, маршрут проверки и место, куда возвращаться при новом варианте сбоя.
Проверяемые источники
- Git: git-blame documentation — git blame показывает revision и автора, которые последними изменили каждую строку; это исторический факт, а не назначение текущего владельца решения
- Git: git-log documentation — git log позволяет ограничивать просмотр истории, чтобы собрать контекст изменения до исправления
- GitHub Docs: About code owners — CODEOWNERS назначает владельцев путей, запрашивает их review для изменений и использует правила из base branch pull request
- GitHub Docs: Pull request reviews — review имеет отдельные решения Comment, Approve и Request changes; правила ветки могут требовать approval перед merge