Files
houseplan-card/test/branch-state.test.mjs
Claude 4d73774779 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
2026-08-29 10:06:41 +03:00

51 lines
2.7 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { conflictingPaths, devMovedNote, rebaseAdvice } from '../scripts/branch-state.mjs';
// #364. Конвейер приводит ветку к dev сам (#257) и при конфликте возвращает
// задачу, не тратя цикл ревью. Но конфликт всплывает в комментарии через сорок
// минут, а чинится на машине автора; и после любого ребейза разбор становится
// полным, а не по дельте (§7.2). Эти helpers переносят обнаружение туда, где
// есть руки, и делают возврат адресным.
test('приведённая ветка совета не требует (#364)', () => {
assert.equal(rebaseAdvice({ behind: 0 }), null);
assert.equal(rebaseAdvice({ behind: -1 }), null);
assert.equal(rebaseAdvice({ behind: NaN }), null);
});
test('отставшая ветка получает число и готовую команду (#364)', () => {
const one = rebaseAdvice({ behind: 1 });
assert.match(one, /отстала от origin\/dev на 1 коммит\./);
assert.match(one, /git rebase origin\/dev/);
// Причина названа: без неё совет читается как придирка, а он про цену.
assert.match(one, /разбор станет\s+полным, а не по дельте/);
assert.match(rebaseAdvice({ behind: 3 }), /на 3 коммита/);
assert.match(rebaseAdvice({ behind: 12 }), /на 12 коммитов/);
});
test('база может быть не только origin/dev (#364)', () => {
const advice = rebaseAdvice({ behind: 2, base: 'origin/main' });
assert.match(advice, /от origin\/main на 2 коммита/);
assert.match(advice, /git rebase origin\/main/);
});
test('конфликтующие пути читаются, чистятся и сортируются (#364)', () => {
assert.deepEqual(
conflictingPaths('src/b.ts\nsrc/a.ts\n\n src/b.ts \n'),
['src/a.ts', 'src/b.ts'],
);
assert.deepEqual(conflictingPaths(''), []);
assert.deepEqual(conflictingPaths(null), []);
});
test('уход dev во время ревью описывается только когда он был (#364)', () => {
assert.equal(devMovedNote({ moved: 0 }), null);
const note = devMovedNote({ moved: 2, sha: 'abc1234' });
assert.match(note, /продвинулся на 2 коммита/);
assert.match(note, /`abc1234`/);
// Вывод, ради которого строка и нужна: вердикт вынесен по другому дереву.
assert.match(note, /§7\.2/);
});