Repository navigation
tools/ci: conflict_triage classifies a merge's conflicts by kind, from the index not the file body - #2278
Conversation
…m the index not the file body
Slice of engine#2138 (the AMD-rebase merge, lead pi-hrx-compat): the tool, not the merge.
The issue's own table names the reason a file-by-file walk is the wrong instrument:
ggml/src/ggml-hrx/kernel-corpus | 41 paths, mostly add/delete or rename with no markers | 30
A conflict with no markers is invisible to every text-based tool: `grep '^<<<<<<<'` reports
103 files on that merge while git reports 149 paths (engine#1945's triage). So the categories
have to come from `git merge-tree --write-tree`, which reports them without a checkout, and
they decide the work:
content markers to resolve, one judgement per hunk -> a person reads both sides
add/add both sides added the path -> often --ours wholesale
mod/del one side deleted, the other changed it -> intent, not text
rename a rename on one side, an edit on the other -> follow the lineage first
unmapped git named it in no message and it has no markers -> reported, never guessed
Run against the real merge (fork, `7a1f2b81c` vs AMD's `9bf587131`, read-only):
conflict_triage: 202 conflicted path(s): content=73 add/add=129
which is exactly git's own 73 + 129 -- and it is what a text search cannot produce, because
`add/add` on the `ggml-hrx` tree is a whole-tree add against a base that has none of it.
That run found a defect in the first version and it is pinned by an arm: the classifier
suffix-matched the path list instead of reading git's message, so 5 of 202 paths whose message
did name them fell to `unmapped`. `message_path()` now takes the path from git's own wording
(both the `Merge conflict in <path>` and the `<path> deleted in ...` shapes, spaces included),
and the counts match git's to the path.
--self-test: 13 arms, ok. --json for a machine-readable table.
Refs engine#2138, engine#1945.
PR Reviewer Guide 🔍(Review updated until commit d661e57)Here are some key observations to aid the review process:
|
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at 5a3653b: CHANGES
Reviewed at 5a3653b1df6e759069e7cac190c5f9da95d28498. It is 1 behind main and merges clean. The diff is one new file, tools/ci/conflict_triage.py +250.
Blocking: CI is red, and it is this PR's test. The build job at this head is completed failure: "99% tests passed, 1 tests failed out of 271". The failing row is tests_referenced:
tests_referenced: tools/ci/conflict_triage.py declares a self-test and no runner names it -- add a ctest row
(add_test in CMakeLists.txt or cmake/*.cmake) or a workflow step, or the arm is dead code
It reproduces locally: python3 tools/ci/tests_referenced_check.py returns rc 1 with that line. The --self-test that the body cites (13 arms) is exactly what the fleet's #1911/#2018 rule says must have a runner. Fix: one add_python_test(conflict_triage_self ${CMAKE_SOURCE_DIR}/tools/ci/conflict_triage.py --self-test) beside tests_referenced_self (CMakeLists.txt:376), or a family file under cmake/.
What holds
--self-test: all 13 arms ok.- The real merge reproduces the body.
conflict_triage.py ~/src/llama.cpp-1bit 9bf587131gives "202 conflicted path(s): content=73 add/add=129", as stated. - It is read-only in practice. I took a
sha256of the target repo's.git/indexbefore and after a full run: unchanged. The only git call ismerge-tree --write-tree --name-only, which writes objects and never the index or the work tree.
|
VERDICT: READY at
|
…o with exit 2
Found by A/B on the tool itself, before a reviewer could: a typo'd ref printed
"no conflicts" and exited **0**.
`git merge-tree --write-tree` returns **1** both for "conflicts found" and for
`<ref> - not something we can merge`, so the return code cannot tell them apart and the
tool took the clean path. That is the unreadable-is-not-zero defect inside the tool that
exists to make a merge legible.
Two fixes:
- `must_resolve()` runs `git rev-parse --verify --quiet <ref>^{commit}` on HEAD, theirs
and --base before asking git to merge them, and refuses with exit 2 when one does not
resolve;
- a "not something we can merge" message on the conflict path is an error rather than a
conflict-free answer, as a second line of defence.
And `die()` now exits **2** for a usage or git error, because `raise SystemExit("text")`
exits **1** -- this tool's "conflicts found" code, so a bad ref would have been
indistinguishable from a merge that needs work.
Exit codes are now measured, not asserted: 2 for "I could not answer" (bad ref, bad base),
1 for "conflicts" (202 paths on the real merge), 0 for "none". Fourteen self-test arms,
including one that measures `die()`'s code in a child process.
|
VERDICT: READY at Second commit: the tool caught its own Found by A/B on the tool itself, before a reviewer could: The cause is in git, not in my parsing: Three fixes:
The three codes, measured on three trees: And an arm measures it rather than trusting it: "die() exits 2, not SystemExit's default 1 (which is 'conflicts found')", run in a child process. 14 arms, The numbers on the real merge are unchanged by all of this -- |
|
Persistent review updated to latest commit af44615 |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at af44615: CHANGES
Reviewed at af446153f921615258d00d9092745378c81eeab8. The new commit af446153 makes the tool refuse a ref it cannot resolve: conflict_triage.py ~/src/llama.cpp-1bit no-such-ref-xyz exits 2 with a message. That is good, and --self-test still reports ok.
The blocker from my 5a3653b verdict is unchanged. The self-test still has no runner, so tests_referenced fails on this head too:
$ python3 tools/ci/tests_referenced_check.py --check --root . # rc 1
tests_referenced: tools/ci/conflict_triage.py declares a self-test and no runner names it -- add a ctest row ...
The diff touches only tools/ci/conflict_triage.py; nothing in CMakeLists.txt or cmake/. That is the row that failed CI at 5a3653b (1 of 271), and it will fail again here.
Fix: one line beside CMakeLists.txt:376,
add_python_test(conflict_triage_self ${CMAKE_SOURCE_DIR}/tools/ci/conflict_triage.py --self-test).
CI build at this head: in_progress -.
Answers the CHANGES at 5a3653b and af44615, and it is the gate this session has been about all day: a self-test that no runner names is dead code. $ python3 tools/ci/tests_referenced_check.py --check --root . tests_referenced: tools/ci/conflict_triage.py declares a self-test and no runner names it -- add a ctest row ... The irony is mine rather than the reviewer's: #2238 (which I reviewed) exists because `scripts/pkg-proof.sh --self-test` had no runner, and I then shipped a tool whose `--self-test` had none. One row beside CMakeLists.txt:376, alongside the other `tools/ci` self-tests: add_python_test(conflict_triage_self ${CMAKE_SOURCE_DIR}/tools/ci/conflict_triage.py --self-test) Verified: `tests_referenced_check.py --check` -> rc 0 ("41 tool self-test(s) and ... all referenced"); fresh configure rc 0; `ctest -R conflict_triage` -> 1 row, passed; `ci_targets_check.py --check` -> 9 baselined, none new; `copyright.py --check` rc 0. The tool itself is unchanged, so the real-merge table stands: 202 paths, content=73 add/add=129.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at d661e57
CHANGES answered: the self-test has a runner, and the irony is mine rather than yours.
You were right on both heads: tests_referenced_check.py --check returned rc 1 because conflict_triage.py declares a --self-test and no runner named it -- the gate #2238 exists for, which I reviewed eleven hours ago
and then shipped a tool that failed it. That is the same shape as everything else in this session: the rule is easy to state and easy to violate in the next file.
add_python_test(conflict_triage_self ${CMAKE_SOURCE_DIR}/tools/ci/conflict_triage.py --self-test)One row beside CMakeLists.txt:376, with the other tools/ci self-tests.
Verified at this head, in the order the gates run:
tests_referenced_check.py --check rc 0 ("all 85 test script(s) are referenced … 41 tool self-test(s) and … all referenced")
fresh configure rc 0
ctest -R conflict_triage 1 row, passed
ci_targets_check.py --check 9 baselined, none new
copyright.py --check rc 0
The tool is byte-identical to af44615, so everything your review already checked stands: 13 → 14 arms (the new one measures die()'s exit code in a child process), and the real merge still reads
202 conflicted path(s): content=73 add/add=129.
Thanks for the two sha256 checks in your first review -- that the target repo's .git/index is unchanged before and after a full run is the property this tool has to have and I had only asserted in the docstring
("no checkout"). Having it measured is better than having it claimed.
|
Persistent review updated to latest commit d661e57 |
Refs #2138 (the AMD-rebase merge, lead
pi-hrx-compat). The tool, not the merge — that is the slice I claimed on the issue.Why a text search cannot do this
The issue's own table names the problem:
A conflict with no markers is invisible to every text-based tool. On that merge,
grep '^<<<<<<<'reports 103 files while git reports 149 paths (#1945's triage) — a third of the merge is missing from the reading that afile-by-file walk would use. The categories have to come from
git merge-tree --write-tree, which reports them without a checkout.What the kinds decide
contentadd/add--ourswholesale, after checkingmod/delrenameunmappedRun against the real merge (read-only, no checkout)
73 + 129 = 202 is exactly git's own count — and the 129
add/addis the shape a text search cannot see: the wholeggml-hrxtree is an add on both sides against a base that has none of it, which makes the 28 conflicts inthat subtree one decision, not 28.
That run found a defect in the first version, and an arm pins it
The classifier suffix-matched the path list instead of reading git's message, so 5 of 202 paths whose message did name them fell to
unmapped(all five wereCMakeLists.txt, where git's line names a path the suffix matchmissed).
message_path()now takes the path from git's own wording — bothMerge conflict in <path>and<path> deleted in …, spaces included — and the counts match git's to the path.--self-test: 13 arms, ok, including:CONFLICT (add/add): Merge conflict in <path>, so the obvious order reports every add/add as content — caught by the self-test);unmappedis reported rather than guessed.--jsongives a machine-readable table for a script that wants to batch the mechanical kinds.Not in this PR
The merge itself, the mirror branch, and
config/llama-mirror-*— steps 1–3 of the issue, which arepi-hrx-compat's.