ci: ребейз виден автору до пуша, конфликт называет файлы, уход dev отмечается

Конвейер приводит ветку к dev сам (#257) и при конфликте возвращает задачу, не
тратя цикл ревью. Оставались три щели, и все три про то, что человек узнаёт
поздно и без подробностей.

Первое. Отставание теперь видно в scripts/pre-push-gate.mjs до пуша, с числом
коммитов и готовой командой. Это предупреждение, а не гейт: гейтом остаётся
конвейер, который забыть не может. Смысл в цене — после любого ребейза разбор
становится полным, а не по дельте (§7.2), а конфликт всё равно чинится на машине
автора. Отключается --no-rebase-check.

Второе. Конфликт называет файлы. Список снимается ДО `git rebase --abort`: abort
снимает состояние конфликта вместе с ним, и раньше автору доставалось «не
ребейзится» без единого имени. Логика проверена на настоящем конфликте в
одноразовом репозитории — два файла названы.

Третье. Если dev ушёл вперёд, пока шло ревью, это записывается в summary
прогона, а при зелёном вердикте ещё и комментарием: вердикт вынесен по дереву,
которое уже не совпадает с вершиной линии, и слияние приведёт ветку к dev.
Комментарий только при зелёном — шуметь на каждом прогоне ни к чему, а вот
молчать перед слиянием нельзя.

Чего задача не делает: не заставляет dev стоять на месте, пока идёт ревью. Если
возвраты частые именно из-за темпа, лечится очередью слияний, а это решение о
процессе, не о скрипте.

Два мутанта проверены руками — «советовать ребейз всегда» и «никогда не сообщать
про уход dev», — каждый убит.

Issue: #364
User-Visible: no
This commit is contained in:
Claude
2026-08-29 10:06:41 +03:00
parent d3d3312adb
commit 4d73774779
5 changed files with 172 additions and 4 deletions
+50
View File
@@ -0,0 +1,50 @@
/**
* Состояние ветки задачи относительно `dev` (#364).
*
* Конвейер приводит ветку к `dev` сам (#257) и при конфликте возвращает задачу,
* не тратя цикл ревью. Но конфликт при этом обнаруживается в комментарии через
* сорок минут, а чинится всё равно на машине автора. Дешевле сказать ему это до
* пуша — тогда конвейеру нечего ребейзить, а конфликт виден там, где есть руки.
*
* Отставание — предупреждение, а не запрет: гейтом остаётся конвейер, который
* забыть не может. Локальная проверка лишь убирает лишний круг.
*/
/**
* Совет по ребейзу. `null` означает «ветка содержит весь dev, делать нечего».
*
* @param {{ behind: number, base?: string }} state
*/
export function rebaseAdvice({ behind, base = 'origin/dev' }) {
const count = Number(behind);
if (!Number.isFinite(count) || count <= 0) return null;
const commits = count === 1 ? 'коммит' : count < 5 ? 'коммита' : 'коммитов';
return `ветка отстала от ${base} на ${count} ${commits}.`
+ ` Конвейер приведёт её сам перед ревью, но после ребейза разбор станет`
+ ` полным, а не по дельте (§7.2). Дешевле сделать это здесь:`
+ ` git fetch origin && git rebase ${base}`;
}
/**
* Пути, на которых встал ребейз, из вывода `git diff --name-only --diff-filter=U`.
*
* Читается ДО `git rebase --abort`: abort снимает состояние конфликта вместе с
* этим списком, и тогда автору остаётся только «не ребейзится», без единого
* имени файла.
*/
export function conflictingPaths(output) {
return [...new Set(String(output ?? '').split('\n')
.map((line) => line.trim())
.filter(Boolean))].sort();
}
/** Строка о том, что `dev` ушёл вперёд, пока шло ревью. `null`, если не ушёл. */
export function devMovedNote({ moved, sha }) {
const count = Number(moved);
if (!Number.isFinite(count) || count <= 0) return null;
const commits = count === 1 ? 'коммит' : count < 5 ? 'коммита' : 'коммитов';
const at = sha ? ` Материал ревью — \`${sha}\`.` : '';
return `Пока шло ревью, \`dev\` продвинулся на ${count} ${commits}.${at}`
+ ' Вердикт вынесен по дереву, которое уже не совпадает с вершиной линии:'
+ ' слияние приведёт ветку к dev, и это другой код (§7.2).';
}