@biffo/cli 0.297.1 → 0.297.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/scripts/claim.sh +195 -7
package/package.json
CHANGED
package/scripts/claim.sh
CHANGED
|
@@ -107,6 +107,21 @@
|
|
|
107
107
|
# - **Excludes the branch being pushed, and any PR whose head IS that
|
|
108
108
|
# branch**, from counting as a conflict. Without this, pushing your own
|
|
109
109
|
# branch a second time blocks you on your own work.
|
|
110
|
+
# - **Excludes a SUPERSEDED PREDECESSOR** — a remote branch naming the same
|
|
111
|
+
# issue whose every commit is already carried by the branch being pushed,
|
|
112
|
+
# which is what a rebase leaves behind. An issue number has no lineage, so
|
|
113
|
+
# the number-only comparison could not tell that branch from a rival
|
|
114
|
+
# session's, and a dead predecessor blocked every later push naming the
|
|
115
|
+
# issue (tabsii-com/tabsii-platform#1112). Decided from the object graph by
|
|
116
|
+
# `branch_is_absorbed` below, which fails CLOSED — anything it cannot
|
|
117
|
+
# positively demonstrate stays a conflict, INCLUDING a candidate carrying
|
|
118
|
+
# no commits of its own. A branch sitting at `dev`'s tip is a reservation
|
|
119
|
+
# (AGENTS.md asks for exactly that: "push your branch as soon as it
|
|
120
|
+
# exists"), and "every commit on it is already carried" is vacuously true
|
|
121
|
+
# of a branch with no commits — so without that requirement the discount
|
|
122
|
+
# waved through the most ordinary rival there is. Note the discount applies
|
|
123
|
+
# to the BRANCH signal only: an OPEN PR is a live claim regardless of
|
|
124
|
+
# lineage and is never discounted.
|
|
110
125
|
# - **A real conflict — another branch or another open PR naming the same
|
|
111
126
|
# issue — exits 1**, naming what was found. AGENTS.md permits stealing a
|
|
112
127
|
# claim that is over an hour stale, with a comment; the message points
|
|
@@ -308,6 +323,146 @@ derive_branch_issue() {
|
|
|
308
323
|
printf '%s' "$_dbi_n" | sed 's/^0*\([0-9]\)/\1/'
|
|
309
324
|
}
|
|
310
325
|
|
|
326
|
+
# --- lineage: is remote branch tip $1 already carried by local branch $2? -----
|
|
327
|
+
#
|
|
328
|
+
# The branch check below compares ISSUE NUMBERS, and a number has no lineage.
|
|
329
|
+
# So it could not tell a genuine RIVAL CLAIMANT -- another session working the
|
|
330
|
+
# same issue -- from the pusher's OWN SUPERSEDED PREDECESSOR, the branch they
|
|
331
|
+
# abandoned and rebased away from. Measured live on tabsii-platform:
|
|
332
|
+
# `fix/1050-1033-1061-upstream-carry` is still on the remote at 5b1b8977 while
|
|
333
|
+
# its successor `fix/1050-1033-1061-carry-rebased` was auto-deleted on merge,
|
|
334
|
+
# so every future push naming 1050 is refused by a dead branch
|
|
335
|
+
# (tabsii-com/tabsii-platform#1112).
|
|
336
|
+
#
|
|
337
|
+
# **`git merge-base --is-ancestor` is not the primitive.** A rebase rewrites
|
|
338
|
+
# every commit, so the predecessor's tip stops being an ancestor of its own
|
|
339
|
+
# successor -- verified by experiment (`is-ancestor` rc=1 on exactly the pair
|
|
340
|
+
# `git cherry` reports as fully equivalent). Ancestry answers a strictly
|
|
341
|
+
# narrower question and would have caught none of the reported case.
|
|
342
|
+
#
|
|
343
|
+
# So the question asked here is not "is this branch mine?" -- identity is not a
|
|
344
|
+
# trustworthy signal in this estate and this script never compares it -- but the
|
|
345
|
+
# stronger, decidable one:
|
|
346
|
+
#
|
|
347
|
+
# **does the candidate carry any work the branch being pushed does not
|
|
348
|
+
# already have?**
|
|
349
|
+
#
|
|
350
|
+
# If it does not, there is nothing to collide over whoever made it: pushing
|
|
351
|
+
# cannot duplicate or lose work that is already in hand. That is a property of
|
|
352
|
+
# the object graph, not of a name, and it is what makes the discount safe to
|
|
353
|
+
# grant automatically rather than via a hand-maintained skip list.
|
|
354
|
+
#
|
|
355
|
+
# **A claim reserves FUTURE work; a content test can only see PAST work.**
|
|
356
|
+
# This is the predicate's structural limit, and the next reader should have it
|
|
357
|
+
# rather than rediscover it. `git cherry` compares commits that exist; a claim
|
|
358
|
+
# is about commits that do not exist yet. So no content-subset test can ever be
|
|
359
|
+
# complete, and the only safe posture is the one taken here: discount ONLY on
|
|
360
|
+
# positive evidence that the candidate is a replay of work already in hand, and
|
|
361
|
+
# read everything else -- including its own silence -- as a rival.
|
|
362
|
+
#
|
|
363
|
+
# It FAILS CLOSED at every step -- each `return 1` means "conflict", and the
|
|
364
|
+
# only route to `return 0` is positive evidence:
|
|
365
|
+
#
|
|
366
|
+
# 1. Both refs must be known. No local tip, no candidate sha, no discount.
|
|
367
|
+
# 2. The candidate's objects must be present LOCALLY. A branch this machine
|
|
368
|
+
# has never fetched cannot be shown to be superseded, and absence of
|
|
369
|
+
# evidence is not evidence of supersession.
|
|
370
|
+
# 3. The candidate must carry AT LEAST ONE COMMIT OF ITS OWN. Step 5 asks
|
|
371
|
+
# "does the candidate carry work this push does not already have?", and
|
|
372
|
+
# that question is VACUOUSLY TRUE of a candidate with no commits at all --
|
|
373
|
+
# a branch pointing at `dev`'s tip, or at anything else already reachable
|
|
374
|
+
# from the pusher. `git cherry` then emits nothing, no `+` line is found,
|
|
375
|
+
# and a LIVE RESERVATION is discounted while the notice calls it a
|
|
376
|
+
# superseded predecessor. Reproduced: with `fix/1050-other-agent` pushed
|
|
377
|
+
# at `dev`'s tip, `--guard fix/1050-mine` printed `discounted
|
|
378
|
+
# fix/1050-other-agent` and exited 0.
|
|
379
|
+
#
|
|
380
|
+
# That is the LIKELY rival shape, not an exotic one. AGENTS.md asks for
|
|
381
|
+
# exactly it -- "push your branch as soon as it exists. The claim is a
|
|
382
|
+
# reservation; the branch is the evidence" -- so an agent that stakes an
|
|
383
|
+
# issue before writing code produces a commitless branch by following the
|
|
384
|
+
# documented protocol. It is also symmetric: two sessions staking one
|
|
385
|
+
# issue seconds apart both sit at `dev`'s tip and would each discount the
|
|
386
|
+
# other, which is the 2026-08-03 collision shape. Nothing else catches it
|
|
387
|
+
# either -- a commitless branch has no open PR to find, and `--guard`
|
|
388
|
+
# deliberately never reads the `in-progress` label.
|
|
389
|
+
#
|
|
390
|
+
# **Own commits are counted against the branch being pushed, not against
|
|
391
|
+
# `dev`** -- `git rev-list <candidate> --not <local tip>`, i.e. the
|
|
392
|
+
# commits on the candidate's side of the merge base with this push. Three
|
|
393
|
+
# reasons that is the right base:
|
|
394
|
+
# - It is the SAME frame of reference steps 4 and 5 already use, so the
|
|
395
|
+
# three cannot disagree about what "beyond" means. A second base would
|
|
396
|
+
# be a second authority, which is this estate's most-repeated defect.
|
|
397
|
+
# - `dev` is not knowable here. `--guard` is handed one branch name by a
|
|
398
|
+
# pre-push hook, is never told the integration branch, and a candidate
|
|
399
|
+
# need not derive from it anyway.
|
|
400
|
+
# - It answers the question actually being asked. Every candidate that
|
|
401
|
+
# must not be discounted counts zero against it: at `dev`'s tip, at
|
|
402
|
+
# the pusher's own tip (the symmetric race), and at any older ancestor
|
|
403
|
+
# -- all are already reachable, so none carries anything of its own.
|
|
404
|
+
# A candidate whose only commits are MERGES counts non-zero here and is
|
|
405
|
+
# refused by step 4 instead; this step is deliberately not the one that
|
|
406
|
+
# decides that case.
|
|
407
|
+
# 4. No unmerged MERGE COMMIT. `git cherry` compares non-merge commits by
|
|
408
|
+
# patch id and omits merges entirely -- verified: a candidate carrying one
|
|
409
|
+
# merge commit had that commit listed by neither `+` nor `-`, so an evil
|
|
410
|
+
# merge's own resolution would be invisible. Refuse rather than guess.
|
|
411
|
+
# 5. `git cherry <local tip> <candidate>` must emit no `+` line. `-` means an
|
|
412
|
+
# equivalent patch is already present (what a rebase produces); `+` means
|
|
413
|
+
# the candidate carries something this push does not.
|
|
414
|
+
#
|
|
415
|
+
# Known FALSE POSITIVES (still blocks, conservatively):
|
|
416
|
+
# - a predecessor that was SQUASHED or amended rather than replayed -- the
|
|
417
|
+
# patch ids differ, so it reads as a rival. Verified: `git merge --squash`
|
|
418
|
+
# of the predecessor produces `+` on both of its commits.
|
|
419
|
+
# - a predecessor whose objects are not in this clone (fresh machine).
|
|
420
|
+
# - a candidate carrying a merge commit.
|
|
421
|
+
# - a predecessor left pointing at a commit already reachable from this push
|
|
422
|
+
# (someone reset it back onto `dev`). It carries nothing of its own, so
|
|
423
|
+
# step 3 refuses it -- correctly, because that branch is indistinguishable
|
|
424
|
+
# from a rival's fresh reservation.
|
|
425
|
+
#
|
|
426
|
+
# Known FALSE NEGATIVES (discounts something that was not ours):
|
|
427
|
+
# - a genuine rival that has committed real work, EVERY commit of which has
|
|
428
|
+
# a patch-id equivalent already in the pushed branch (a cherry-pick of
|
|
429
|
+
# exactly this work and nothing more), AND whose objects happen to be in
|
|
430
|
+
# this clone. Their work is then already fully in hand, so there is no
|
|
431
|
+
# duplicated effort left to warn about -- the discount is right for the
|
|
432
|
+
# wrong reason. This is the residual gap. Step 3 narrows it to rivals who
|
|
433
|
+
# have actually duplicated this push's content, rather than leaving it open
|
|
434
|
+
# to anyone who merely staked a branch, but it cannot close it: see the
|
|
435
|
+
# future/past limit at the top of this comment.
|
|
436
|
+
# - the reverse of (4) cannot happen: a merge is refused, never absorbed.
|
|
437
|
+
branch_is_absorbed() {
|
|
438
|
+
_abs_cand="$1"
|
|
439
|
+
_abs_tip="$2"
|
|
440
|
+
|
|
441
|
+
[ -n "$_abs_cand" ] || return 1
|
|
442
|
+
[ -n "$_abs_tip" ] || return 1
|
|
443
|
+
|
|
444
|
+
git cat-file -e "${_abs_cand}^{commit}" 2>/dev/null || return 1
|
|
445
|
+
|
|
446
|
+
# Step 3 (see above): zero own commits is a RESERVATION, never a
|
|
447
|
+
# supersession. Counted against the branch being pushed, which is the same
|
|
448
|
+
# base the two checks below use. A count that cannot be computed, or that
|
|
449
|
+
# comes back non-numeric, is a cannot-tell and fails closed like everything
|
|
450
|
+
# else here -- `[` itself returns non-zero on a non-integer operand, so the
|
|
451
|
+
# `|| return 1` covers that without a second parse.
|
|
452
|
+
_abs_own=$(git rev-list --count "$_abs_cand" --not "$_abs_tip" 2>/dev/null) || return 1
|
|
453
|
+
[ -n "$_abs_own" ] || return 1
|
|
454
|
+
[ "$_abs_own" -gt 0 ] 2>/dev/null || return 1
|
|
455
|
+
|
|
456
|
+
_abs_merges=$(git rev-list --merges --count "$_abs_cand" --not "$_abs_tip" 2>/dev/null) || return 1
|
|
457
|
+
[ "$_abs_merges" = "0" ] || return 1
|
|
458
|
+
|
|
459
|
+
_abs_cherry=$(git cherry "$_abs_tip" "$_abs_cand" 2>/dev/null) || return 1
|
|
460
|
+
if printf '%s\n' "$_abs_cherry" | grep -q '^+'; then
|
|
461
|
+
return 1
|
|
462
|
+
fi
|
|
463
|
+
return 0
|
|
464
|
+
}
|
|
465
|
+
|
|
311
466
|
# --- the structural claim predicate (#1411, class #1362 instance 8) ---------
|
|
312
467
|
#
|
|
313
468
|
# "Does this open PR claim issue $1?" used to be answered independently at
|
|
@@ -467,6 +622,12 @@ if [ -n "$GUARD_BRANCH" ]; then
|
|
|
467
622
|
conflict=0
|
|
468
623
|
findings=""
|
|
469
624
|
cannot_tell_reasons=""
|
|
625
|
+
absorbed_branches=""
|
|
626
|
+
|
|
627
|
+
# The local tip of the branch being pushed, resolved once. Empty when it
|
|
628
|
+
# cannot be resolved (the caller named a branch this repo does not have), and
|
|
629
|
+
# `branch_is_absorbed` then discounts nothing -- exactly today's behaviour.
|
|
630
|
+
guard_tip=$(git rev-parse --verify --quiet "refs/heads/$GUARD_BRANCH^{commit}" 2>/dev/null) || guard_tip=""
|
|
470
631
|
|
|
471
632
|
# --- an open PR referencing the issue, excluding our own branch's PR --------
|
|
472
633
|
slug=$(repo_slug)
|
|
@@ -502,22 +663,49 @@ if [ -n "$GUARD_BRANCH" ]; then
|
|
|
502
663
|
# derived (#1672), and compare the two normalised numbers -- not a
|
|
503
664
|
# substring search for $guard_issue inside the candidate's raw text. See
|
|
504
665
|
# `derive_branch_issue` above for why that asymmetry was the defect.
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
666
|
+
# Each candidate that names the same issue is then classified by LINEAGE
|
|
667
|
+
# (see `branch_is_absorbed`): `rival` is a conflict, `absorbed` is this
|
|
668
|
+
# pusher's own superseded predecessor and is reported but not blocked.
|
|
669
|
+
# The candidate's SHA -- `git ls-remote`'s first, tab-separated field -- is
|
|
670
|
+
# what makes that question answerable, so it is no longer thrown away by a
|
|
671
|
+
# `sed` that kept only the name.
|
|
672
|
+
_classified=$(printf '%s\n' "$raw_branches" |
|
|
673
|
+
while IFS= read -r _line; do
|
|
674
|
+
case "$_line" in
|
|
675
|
+
*"refs/heads/"*) ;;
|
|
676
|
+
*) continue ;;
|
|
677
|
+
esac
|
|
678
|
+
_cand=${_line##*refs/heads/}
|
|
509
679
|
[ -n "$_cand" ] || continue
|
|
680
|
+
[ "$_cand" = "$GUARD_BRANCH" ] && continue
|
|
510
681
|
_cand_issue=$(derive_branch_issue "$_cand")
|
|
511
|
-
|
|
512
|
-
|
|
682
|
+
[ -n "$_cand_issue" ] || continue
|
|
683
|
+
[ "$_cand_issue" = "$guard_issue" ] || continue
|
|
684
|
+
_cand_sha=$(printf '%s\n' "$_line" | cut -f1)
|
|
685
|
+
if branch_is_absorbed "$_cand_sha" "$guard_tip"; then
|
|
686
|
+
printf 'absorbed %s\n' "$_cand"
|
|
687
|
+
else
|
|
688
|
+
printf 'rival %s\n' "$_cand"
|
|
513
689
|
fi
|
|
514
|
-
done
|
|
690
|
+
done)
|
|
691
|
+
|
|
692
|
+
other_branch=$(printf '%s\n' "$_classified" | sed -n 's/^rival //p' | head -1)
|
|
693
|
+
absorbed_branches=$(printf '%s\n' "$_classified" | sed -n 's/^absorbed //p')
|
|
515
694
|
if [ -n "$other_branch" ]; then
|
|
516
695
|
conflict=1
|
|
517
696
|
findings="${findings} ${RED}branch${OFF} $other_branch\n"
|
|
518
697
|
fi
|
|
519
698
|
fi
|
|
520
699
|
|
|
700
|
+
# A discount a guard grants silently is a guard nobody can audit, so say so
|
|
701
|
+
# -- on stderr, only when it actually fired, and whether or not a real rival
|
|
702
|
+
# was also found.
|
|
703
|
+
if [ -n "$absorbed_branches" ]; then
|
|
704
|
+
printf '%b' "${DIM}claim --guard: discounted $(printf '%s' "$absorbed_branches" | tr '\n' ' ') ${OFF}" >&2
|
|
705
|
+
echo "${DIM}-- every commit on it is already carried by $GUARD_BRANCH, so it is a${OFF}" >&2
|
|
706
|
+
echo "${DIM}superseded predecessor rather than a rival claim (#1112).${OFF}" >&2
|
|
707
|
+
fi
|
|
708
|
+
|
|
521
709
|
if [ "$conflict" -eq 1 ]; then
|
|
522
710
|
printf '%b' "${RED}claim --guard: issue #$guard_issue looks claimed by someone else.${OFF}\n$findings"
|
|
523
711
|
echo
|