mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-03 05:08:53 +00:00
process: the rebase merges what two tasks never disagree on (#698)
14 of 48 returns in #600–#691 were rebase or merge conflicts on shared files where the two edits do not contradict each other. - .gitattributes: docs/CHANGELOG.md and docs/CHANGELOG.ru.md use the built-in merge=union driver — both tasks' lines in ## Unreleased survive a rebase, a merge and git merge-tree (#696's clean-merge test) without a stop. - rebase-generated.mjs: UPSTREAM_WINS — on a conflict in scripts/monolith-baseline.json the rebase takes dev's side; the band test on the candidate's Validate judges the merged tree (#699). Any other conflicting path aborts exactly as before, with the full list. - merge-candidate.mjs: the candidate's patch-id excludes the changelogs and the monolith baseline next to docs/reviews, so a neighbour's line next to the task entry does not re-send a green task to review. - screenshots.json needs nothing: after #697 task branches do not commit it. Canon: PROCESS.md, the rebase paragraph of the review index (#643). Issue: #698 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
This commit is contained in:
@@ -10,8 +10,8 @@
|
||||
// и не «их», а пересборка по дереву, в котором остановился ребейз.
|
||||
//
|
||||
// Правило одно: остановку разрешает только набор конфликтов, в котором ВСЕ
|
||||
// пути — индекс (или пути, которые вызывающий объявил своими: бандл в
|
||||
// `rebase-on-dev.mjs`). Хоть один другой путь — `git rebase --abort` и
|
||||
// пути — индекс, данные, где права сторона dev (`UPSTREAM_WINS`, #698), или
|
||||
// пути, которые вызывающий объявил своими (бандл в `rebase-on-dev.mjs`). Хоть один другой путь — `git rebase --abort` и
|
||||
// перечень ВСЕХ конфликтующих файлов, индекс в нём тоже: автор видит полную
|
||||
// картину, дерево и HEAD как были.
|
||||
//
|
||||
@@ -32,6 +32,14 @@ import { fileURLToPath } from 'node:url';
|
||||
import { isMainModule } from './spawn-portable.mjs';
|
||||
|
||||
export const REVIEWS_INDEX_PATH = 'docs/reviews/INDEX.md';
|
||||
/**
|
||||
* #698: данные, в которых на конфликте права сторона `dev`. База метрик
|
||||
* монолита — снимок чисел, а не решение задачи: правильное значение для
|
||||
* объединённого дерева не «наше» и не «их», и судит его тест полосы на
|
||||
* Validate кандидата (#699), а не автор руками. Ченджлоги сюда не входят —
|
||||
* их объединяет `merge=union` в `.gitattributes`, и конфликта не бывает.
|
||||
*/
|
||||
export const UPSTREAM_WINS = Object.freeze(['scripts/monolith-baseline.json']);
|
||||
/** Скрипт индекса — по абсолютному пути: ребейз идёт и из чужого cwd (worktree кандидата). */
|
||||
export const REVIEWS_INDEX_SCRIPT = fileURLToPath(new URL('./reviews-index.mjs', import.meta.url));
|
||||
/** Предохранитель от зацикливания: коммитов в ветке задачи единицы, не тысячи. */
|
||||
@@ -46,18 +54,19 @@ const uniquePaths = (paths) => [...new Set(paths.map((p) => String(p).trim()).fi
|
||||
*
|
||||
* @param {string[]} paths конфликтующие пути (`git diff --name-only --diff-filter=U`)
|
||||
* @param {{ extra?: (path: string) => boolean }} [opts] пути, которые вызывающий решает сам
|
||||
* @returns {{ action: 'resolve', index: boolean, extra: string[], conflicts: string[] }
|
||||
* @returns {{ action: 'resolve', index: boolean, upstream: string[], extra: string[], conflicts: string[] }
|
||||
* | { action: 'abort', reason: 'no-conflicts'|'manual', manual: string[], conflicts: string[] }}
|
||||
*/
|
||||
export function planStop(paths, { extra = () => false } = {}) {
|
||||
const conflicts = uniquePaths(paths);
|
||||
if (!conflicts.length) return { action: 'abort', reason: 'no-conflicts', manual: [], conflicts };
|
||||
const manual = conflicts.filter((path) => path !== REVIEWS_INDEX_PATH && !extra(path));
|
||||
const manual = conflicts.filter((path) => path !== REVIEWS_INDEX_PATH && !UPSTREAM_WINS.includes(path) && !extra(path));
|
||||
if (manual.length) return { action: 'abort', reason: 'manual', manual, conflicts };
|
||||
return {
|
||||
action: 'resolve',
|
||||
index: conflicts.includes(REVIEWS_INDEX_PATH),
|
||||
extra: conflicts.filter((path) => path !== REVIEWS_INDEX_PATH),
|
||||
upstream: conflicts.filter((path) => UPSTREAM_WINS.includes(path)),
|
||||
extra: conflicts.filter((path) => path !== REVIEWS_INDEX_PATH && !UPSTREAM_WINS.includes(path)),
|
||||
conflicts,
|
||||
};
|
||||
}
|
||||
@@ -127,6 +136,12 @@ export function rebaseRegenerating({
|
||||
return abort({ reason: plan.reason, conflicts: plan.conflicts, manual: plan.manual, output: step.stderr || step.stdout });
|
||||
}
|
||||
for (const path of plan.extra) resolved.push(`${path} ← ${extra.resolve(path)}`);
|
||||
// На ребейзе `--ours` — сторона, НА которую ребейзят, то есть dev.
|
||||
for (const path of plan.upstream) {
|
||||
git(['checkout', '--ours', '--', path]);
|
||||
git(['add', '--', path]);
|
||||
resolved.push(`${path} ← dev`);
|
||||
}
|
||||
if (plan.index) {
|
||||
// Не --ours и не --theirs: ни одна сторона не знает документов другой.
|
||||
rebuildIndex();
|
||||
|
||||
Reference in New Issue
Block a user