@biffo/cli 0.298.19 → 0.298.21
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.
|
@@ -489,7 +489,7 @@ function formatGroundTruthFailure({ closingIssuesReferences }) {
|
|
|
489
489
|
`GitHub's own closingIssuesReferences says this PR will close ${refs.join(', ')} on`,
|
|
490
490
|
'merge — but nothing in the PR body, title or commit messages reads as a',
|
|
491
491
|
'DELIBERATE closing directive (a keyword+reference at the start of the',
|
|
492
|
-
|
|
492
|
+
"document, a line, or a sentence). GitHub's linker does not care about",
|
|
493
493
|
'paths or sentence position; it only needs the lexical shape, wherever it',
|
|
494
494
|
'sits.',
|
|
495
495
|
'',
|
|
@@ -738,24 +738,64 @@ export async function resolveCommits({
|
|
|
738
738
|
* Fetch a PR's `closingIssuesReferences` via the GitHub CLI — GitHub's own
|
|
739
739
|
* ground truth for which issues this PR will close on merge (#1686). Same
|
|
740
740
|
* split as the other fetchers so tests can inject a fake. Each element is
|
|
741
|
-
* the shape `gh pr view --json closingIssuesReferences` returns:
|
|
742
741
|
* `{ id, number, repository: {...}, url }` (confirmed live against PR #1417,
|
|
743
|
-
* which genuinely closes an issue).
|
|
742
|
+
* which genuinely closes an issue, and against #1730/tabsii-crm#379 below).
|
|
743
|
+
*
|
|
744
|
+
* This calls `gh api graphql` with an explicit query, NOT `gh pr view --json
|
|
745
|
+
* closingIssuesReferences`. That shorthand only works if the installed `gh`
|
|
746
|
+
* binary's OWN hardcoded `--json` field allowlist happens to include the
|
|
747
|
+
* field — `closingIssuesReferences` was added to that allowlist partway
|
|
748
|
+
* through gh's release history, so it is a property of the CLI binary, not
|
|
749
|
+
* of the GitHub API. tabsii-crm#379 failed identically on two attempts of
|
|
750
|
+
* the same commit with `Unknown JSON field: "closingIssuesReferences"` —
|
|
751
|
+
* this repo's own `gh` (2.96.0) lists the field, but tabsii-crm's Release
|
|
752
|
+
* Guards runs on ITS OWN self-hosted runner fleet (`vars.RUNNER_LABEL:
|
|
753
|
+
* tabsii`), whose baked-in `gh` binary predates it. That is a real, and
|
|
754
|
+
* recurring, source of drift: every satellite's runner image can lag behind
|
|
755
|
+
* whatever `gh` happens to be on the machine this file was last tested on,
|
|
756
|
+
* and `check-closing-keywords.mjs` is distributed VERBATIM (`shared-files.json`
|
|
757
|
+
* `files`) to every one of them — so pinning to a newer allowlisted field is
|
|
758
|
+
* a bug this file WILL hit again on the next satellite with an older image,
|
|
759
|
+
* not a one-off.
|
|
760
|
+
*
|
|
761
|
+
* `gh api graphql` has no such allowlist: it sends the query text through
|
|
762
|
+
* to GitHub's GraphQL endpoint verbatim, and has done so since `gh api` was
|
|
763
|
+
* introduced, long before `closingIssuesReferences` reached `pr view --json`.
|
|
764
|
+
* Asking for a field GitHub's schema does not have is still a real failure —
|
|
765
|
+
* it always was, and always will be, GitHub's error rather than the local
|
|
766
|
+
* binary's — but a locally-out-of-date `gh` can no longer manufacture a
|
|
767
|
+
* false one. This removes the CLI-version dependency instead of chasing it
|
|
768
|
+
* runner image by runner image.
|
|
744
769
|
*/
|
|
745
770
|
export async function fetchPrClosingIssuesReferencesViaGh({ GH_TOKEN, PR_NUMBER, GH_REPO }) {
|
|
746
771
|
const { execFileSync } = await import('node:child_process')
|
|
772
|
+
const [owner, repo] = GH_REPO.split('/')
|
|
773
|
+
const query = `
|
|
774
|
+
query($owner: String!, $repo: String!, $num: Int!) {
|
|
775
|
+
repository(owner: $owner, name: $repo) {
|
|
776
|
+
pullRequest(number: $num) {
|
|
777
|
+
closingIssuesReferences(first: 50) {
|
|
778
|
+
nodes { id number url repository { nameWithOwner } }
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
`
|
|
747
784
|
const raw = execFileSync(
|
|
748
785
|
'gh',
|
|
749
786
|
[
|
|
750
|
-
'
|
|
751
|
-
'
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
'
|
|
787
|
+
'api',
|
|
788
|
+
'graphql',
|
|
789
|
+
'-f',
|
|
790
|
+
`query=${query}`,
|
|
791
|
+
'-f',
|
|
792
|
+
`owner=${owner}`,
|
|
793
|
+
'-f',
|
|
794
|
+
`repo=${repo}`,
|
|
795
|
+
'-F',
|
|
796
|
+
`num=${PR_NUMBER}`,
|
|
757
797
|
'--jq',
|
|
758
|
-
'.closingIssuesReferences',
|
|
798
|
+
'.data.repository.pullRequest.closingIssuesReferences.nodes',
|
|
759
799
|
],
|
|
760
800
|
{ encoding: 'utf8', env: { ...process.env, GH_TOKEN } },
|
|
761
801
|
).trim()
|
|
@@ -489,7 +489,7 @@ function formatGroundTruthFailure({ closingIssuesReferences }) {
|
|
|
489
489
|
`GitHub's own closingIssuesReferences says this PR will close ${refs.join(', ')} on`,
|
|
490
490
|
'merge — but nothing in the PR body, title or commit messages reads as a',
|
|
491
491
|
'DELIBERATE closing directive (a keyword+reference at the start of the',
|
|
492
|
-
|
|
492
|
+
"document, a line, or a sentence). GitHub's linker does not care about",
|
|
493
493
|
'paths or sentence position; it only needs the lexical shape, wherever it',
|
|
494
494
|
'sits.',
|
|
495
495
|
'',
|
|
@@ -738,24 +738,64 @@ export async function resolveCommits({
|
|
|
738
738
|
* Fetch a PR's `closingIssuesReferences` via the GitHub CLI — GitHub's own
|
|
739
739
|
* ground truth for which issues this PR will close on merge (#1686). Same
|
|
740
740
|
* split as the other fetchers so tests can inject a fake. Each element is
|
|
741
|
-
* the shape `gh pr view --json closingIssuesReferences` returns:
|
|
742
741
|
* `{ id, number, repository: {...}, url }` (confirmed live against PR #1417,
|
|
743
|
-
* which genuinely closes an issue).
|
|
742
|
+
* which genuinely closes an issue, and against #1730/tabsii-crm#379 below).
|
|
743
|
+
*
|
|
744
|
+
* This calls `gh api graphql` with an explicit query, NOT `gh pr view --json
|
|
745
|
+
* closingIssuesReferences`. That shorthand only works if the installed `gh`
|
|
746
|
+
* binary's OWN hardcoded `--json` field allowlist happens to include the
|
|
747
|
+
* field — `closingIssuesReferences` was added to that allowlist partway
|
|
748
|
+
* through gh's release history, so it is a property of the CLI binary, not
|
|
749
|
+
* of the GitHub API. tabsii-crm#379 failed identically on two attempts of
|
|
750
|
+
* the same commit with `Unknown JSON field: "closingIssuesReferences"` —
|
|
751
|
+
* this repo's own `gh` (2.96.0) lists the field, but tabsii-crm's Release
|
|
752
|
+
* Guards runs on ITS OWN self-hosted runner fleet (`vars.RUNNER_LABEL:
|
|
753
|
+
* tabsii`), whose baked-in `gh` binary predates it. That is a real, and
|
|
754
|
+
* recurring, source of drift: every satellite's runner image can lag behind
|
|
755
|
+
* whatever `gh` happens to be on the machine this file was last tested on,
|
|
756
|
+
* and `check-closing-keywords.mjs` is distributed VERBATIM (`shared-files.json`
|
|
757
|
+
* `files`) to every one of them — so pinning to a newer allowlisted field is
|
|
758
|
+
* a bug this file WILL hit again on the next satellite with an older image,
|
|
759
|
+
* not a one-off.
|
|
760
|
+
*
|
|
761
|
+
* `gh api graphql` has no such allowlist: it sends the query text through
|
|
762
|
+
* to GitHub's GraphQL endpoint verbatim, and has done so since `gh api` was
|
|
763
|
+
* introduced, long before `closingIssuesReferences` reached `pr view --json`.
|
|
764
|
+
* Asking for a field GitHub's schema does not have is still a real failure —
|
|
765
|
+
* it always was, and always will be, GitHub's error rather than the local
|
|
766
|
+
* binary's — but a locally-out-of-date `gh` can no longer manufacture a
|
|
767
|
+
* false one. This removes the CLI-version dependency instead of chasing it
|
|
768
|
+
* runner image by runner image.
|
|
744
769
|
*/
|
|
745
770
|
export async function fetchPrClosingIssuesReferencesViaGh({ GH_TOKEN, PR_NUMBER, GH_REPO }) {
|
|
746
771
|
const { execFileSync } = await import('node:child_process')
|
|
772
|
+
const [owner, repo] = GH_REPO.split('/')
|
|
773
|
+
const query = `
|
|
774
|
+
query($owner: String!, $repo: String!, $num: Int!) {
|
|
775
|
+
repository(owner: $owner, name: $repo) {
|
|
776
|
+
pullRequest(number: $num) {
|
|
777
|
+
closingIssuesReferences(first: 50) {
|
|
778
|
+
nodes { id number url repository { nameWithOwner } }
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
`
|
|
747
784
|
const raw = execFileSync(
|
|
748
785
|
'gh',
|
|
749
786
|
[
|
|
750
|
-
'
|
|
751
|
-
'
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
'
|
|
787
|
+
'api',
|
|
788
|
+
'graphql',
|
|
789
|
+
'-f',
|
|
790
|
+
`query=${query}`,
|
|
791
|
+
'-f',
|
|
792
|
+
`owner=${owner}`,
|
|
793
|
+
'-f',
|
|
794
|
+
`repo=${repo}`,
|
|
795
|
+
'-F',
|
|
796
|
+
`num=${PR_NUMBER}`,
|
|
757
797
|
'--jq',
|
|
758
|
-
'.closingIssuesReferences',
|
|
798
|
+
'.data.repository.pullRequest.closingIssuesReferences.nodes',
|
|
759
799
|
],
|
|
760
800
|
{ encoding: 'utf8', env: { ...process.env, GH_TOKEN } },
|
|
761
801
|
).trim()
|
package/package.json
CHANGED
package/scripts/claim.sh
CHANGED
|
@@ -101,9 +101,12 @@
|
|
|
101
101
|
# morning, three were "work exists, label does not" — so a gate keyed on
|
|
102
102
|
# the label would have caught one of four. This checks only what git and
|
|
103
103
|
# GitHub cannot help but know: an open PR, a remote branch.
|
|
104
|
-
# - **Never compares identity.** A claim on #1109 was
|
|
105
|
-
# `github-actions[bot]` because of a repo-local `user.email`
|
|
106
|
-
# usernames are not a trustworthy signal here.
|
|
104
|
+
# - **Never compares identity by GitHub username.** A claim on #1109 was
|
|
105
|
+
# recorded as `github-actions[bot]` because of a repo-local `user.email`
|
|
106
|
+
# override — usernames are not a trustworthy signal here. What #1698 adds
|
|
107
|
+
# below is not a username comparison: it is the claim record's own
|
|
108
|
+
# `claim-branch` field, written by `--as` at claim time, checked against
|
|
109
|
+
# the branch actually being pushed.
|
|
107
110
|
# - **Excludes the branch being pushed, and any PR whose head IS that
|
|
108
111
|
# branch**, from counting as a conflict. Without this, pushing your own
|
|
109
112
|
# branch a second time blocks you on your own work.
|
|
@@ -122,6 +125,18 @@
|
|
|
122
125
|
# waved through the most ordinary rival there is. Note the discount applies
|
|
123
126
|
# to the BRANCH signal only: an OPEN PR is a live claim regardless of
|
|
124
127
|
# lineage and is never discounted.
|
|
128
|
+
# - **The discount above is ALSO gated on the claim record (#1698).** Patch
|
|
129
|
+
# equivalence alone cannot tell "my own rebase of my own predecessor" from
|
|
130
|
+
# "an independent rival's rebase of the same starting point" — two sessions
|
|
131
|
+
# rebasing the same predecessor onto the same tip produce identical
|
|
132
|
+
# patches, with zero merge commits, and no open PR on either side for the
|
|
133
|
+
# first several minutes. `branch_is_absorbed` therefore only DISCOUNTS a
|
|
134
|
+
# candidate when the issue's claim record — `claim_guard_branch`, read from
|
|
135
|
+
# the same durable comment `--as` already writes, never a new store —
|
|
136
|
+
# names the branch being pushed as the CURRENT claim. Missing, unreadable,
|
|
137
|
+
# written before this field existed, or naming a different branch: all
|
|
138
|
+
# fail CLOSED into a conflict, same as an ordinary rival, with a message
|
|
139
|
+
# explaining why rather than a silent pass.
|
|
125
140
|
# - **A real conflict — another branch or another open PR naming the same
|
|
126
141
|
# issue — exits 1**, naming what was found. AGENTS.md permits stealing a
|
|
127
142
|
# claim that is over an hour stale, with a comment; the message points
|
|
@@ -141,6 +156,7 @@ REPO=""
|
|
|
141
156
|
HOLDER=""
|
|
142
157
|
RELEASE=""
|
|
143
158
|
HOLDER_MARK="claim-holder:"
|
|
159
|
+
BRANCH_MARK="claim-branch:"
|
|
144
160
|
CHECK_ONLY=""
|
|
145
161
|
GUARD_BRANCH=""
|
|
146
162
|
|
|
@@ -280,6 +296,36 @@ claim_held_by() {
|
|
|
280
296
|
esac
|
|
281
297
|
}
|
|
282
298
|
|
|
299
|
+
# claim_guard_branch <issue> -- tri-state read of the RECORDED branch on
|
|
300
|
+
# $issue's most recent claim comment (the `claim-branch:` field the write
|
|
301
|
+
# side below adds, #1698). Prints the branch name and returns 0 when a claim
|
|
302
|
+
# comment carries one; returns 1 when the comment list is readable but gives
|
|
303
|
+
# no confirmed branch (no claim comment at all, or one written before this
|
|
304
|
+
# field existed); returns 2 when the comment list itself could not be read.
|
|
305
|
+
#
|
|
306
|
+
# 1 and 2 are BOTH "not confirmed", never "confirmed free" -- the same
|
|
307
|
+
# fail-closed shape `claim_held_by` already uses for the holder token, for the
|
|
308
|
+
# same reason: a caller that collapsed them would treat an unclaimed issue or
|
|
309
|
+
# a flaky API call as proof nobody else is here, which is exactly the
|
|
310
|
+
# fail-open `--guard`'s own lineage discount shipped with (#1696) and this
|
|
311
|
+
# function exists to close.
|
|
312
|
+
claim_guard_branch() {
|
|
313
|
+
_c=$(gh_issue view "$1" --json comments \
|
|
314
|
+
--jq "[.comments[]? | select(.body | contains(\"$HOLDER_MARK\"))] | last | .body // \"\"" \
|
|
315
|
+
2>/dev/null) || return 2
|
|
316
|
+
[ -n "$_c" ] || return 1
|
|
317
|
+
case "$_c" in
|
|
318
|
+
*"$BRANCH_MARK"*)
|
|
319
|
+
_gb_rest=${_c#*"$BRANCH_MARK"}
|
|
320
|
+
_gb_branch=${_gb_rest%% *}
|
|
321
|
+
[ -n "$_gb_branch" ] || return 1
|
|
322
|
+
printf '%s\n' "$_gb_branch"
|
|
323
|
+
return 0
|
|
324
|
+
;;
|
|
325
|
+
*) return 1 ;;
|
|
326
|
+
esac
|
|
327
|
+
}
|
|
328
|
+
|
|
283
329
|
# This repo as `owner/name`, so a closing reference can be checked against it.
|
|
284
330
|
# GitHub records a CROSS-REPO closing reference in the same list as a local one
|
|
285
331
|
# -- tabsii-lms#43 closes tabsii-platform#553 -- so the number alone is not an
|
|
@@ -743,6 +789,39 @@ if [ -n "$GUARD_BRANCH" ]; then
|
|
|
743
789
|
findings="${findings} ${RED}open PR${OFF} $(printf '%s' "$open_prs" | head -1)\n"
|
|
744
790
|
fi
|
|
745
791
|
|
|
792
|
+
# --- the claim record's own idea of who currently holds $guard_issue --------
|
|
793
|
+
#
|
|
794
|
+
# #1698: `branch_is_absorbed` below answers "does the candidate carry work I
|
|
795
|
+
# lack?" -- a content test, not an identity one. Two independent sessions
|
|
796
|
+
# rebasing the same predecessor onto the same tip produce patch-id-equivalent
|
|
797
|
+
# commits, so content alone cannot tell "my own rebase of my own predecessor"
|
|
798
|
+
# from a genuine rival's rebase of the same starting point. Read once, used
|
|
799
|
+
# below to gate every absorption on this push -- not by comparing $2's shape
|
|
800
|
+
# the way `claim_held_by` does (`--guard` runs with no token, by design, see
|
|
801
|
+
# the header), but by checking whether the record's own `claim-branch` names
|
|
802
|
+
# THIS branch.
|
|
803
|
+
_guard_confirmed_branch=$(claim_guard_branch "$guard_issue")
|
|
804
|
+
_guard_confirmed_rc=$?
|
|
805
|
+
case "$_guard_confirmed_rc" in
|
|
806
|
+
0)
|
|
807
|
+
if [ "$_guard_confirmed_branch" = "$GUARD_BRANCH" ]; then
|
|
808
|
+
_guard_identity_ok=1
|
|
809
|
+
_guard_identity_reason=""
|
|
810
|
+
else
|
|
811
|
+
_guard_identity_ok=0
|
|
812
|
+
_guard_identity_reason="the claim record for #$guard_issue names \`$_guard_confirmed_branch\`, not this branch"
|
|
813
|
+
fi
|
|
814
|
+
;;
|
|
815
|
+
1)
|
|
816
|
+
_guard_identity_ok=0
|
|
817
|
+
_guard_identity_reason="#$guard_issue carries no claim record naming a branch (unclaimed, or claimed before #1698's fix)"
|
|
818
|
+
;;
|
|
819
|
+
*)
|
|
820
|
+
_guard_identity_ok=0
|
|
821
|
+
_guard_identity_reason="the claim record for #$guard_issue could not be read"
|
|
822
|
+
;;
|
|
823
|
+
esac
|
|
824
|
+
|
|
746
825
|
# --- a remote branch naming the issue, excluding our own branch -------------
|
|
747
826
|
branch_err=$(mktemp)
|
|
748
827
|
raw_branches=$(remote_branches 2>"$branch_err")
|
|
@@ -759,11 +838,15 @@ if [ -n "$GUARD_BRANCH" ]; then
|
|
|
759
838
|
# substring search for $guard_issue inside the candidate's raw text. See
|
|
760
839
|
# `derive_branch_issue` above for why that asymmetry was the defect.
|
|
761
840
|
# Each candidate that names the same issue is then classified by LINEAGE
|
|
762
|
-
# (see `branch_is_absorbed`)
|
|
763
|
-
#
|
|
841
|
+
# (see `branch_is_absorbed`) AND, when lineage says absorbed, by IDENTITY
|
|
842
|
+
# (`$_guard_identity_ok`, above): a genuine rival is always `rival`; a
|
|
843
|
+
# content-superseded predecessor is only actually `absorbed` when the
|
|
844
|
+
# claim record also confirms this push as the current claim -- otherwise
|
|
845
|
+
# it is `denied` and treated as a conflict too (#1698 fails this closed,
|
|
846
|
+
# rather than the silent pass it replaces), with a message explaining why.
|
|
764
847
|
# The candidate's SHA -- `git ls-remote`'s first, tab-separated field -- is
|
|
765
|
-
# what makes
|
|
766
|
-
#
|
|
848
|
+
# what makes lineage answerable, so it is no longer thrown away by a `sed`
|
|
849
|
+
# that kept only the name.
|
|
767
850
|
_classified=$(printf '%s\n' "$raw_branches" |
|
|
768
851
|
while IFS= read -r _line; do
|
|
769
852
|
case "$_line" in
|
|
@@ -778,27 +861,39 @@ if [ -n "$GUARD_BRANCH" ]; then
|
|
|
778
861
|
[ "$_cand_issue" = "$guard_issue" ] || continue
|
|
779
862
|
_cand_sha=$(printf '%s\n' "$_line" | cut -f1)
|
|
780
863
|
if branch_is_absorbed "$_cand_sha" "$guard_tip"; then
|
|
781
|
-
|
|
864
|
+
if [ "$_guard_identity_ok" -eq 1 ]; then
|
|
865
|
+
printf 'absorbed %s\n' "$_cand"
|
|
866
|
+
else
|
|
867
|
+
printf 'denied %s\n' "$_cand"
|
|
868
|
+
fi
|
|
782
869
|
else
|
|
783
870
|
printf 'rival %s\n' "$_cand"
|
|
784
871
|
fi
|
|
785
872
|
done)
|
|
786
873
|
|
|
787
874
|
other_branch=$(printf '%s\n' "$_classified" | sed -n 's/^rival //p' | head -1)
|
|
875
|
+
denied_branch=$(printf '%s\n' "$_classified" | sed -n 's/^denied //p' | head -1)
|
|
788
876
|
absorbed_branches=$(printf '%s\n' "$_classified" | sed -n 's/^absorbed //p')
|
|
789
877
|
if [ -n "$other_branch" ]; then
|
|
790
878
|
conflict=1
|
|
791
879
|
findings="${findings} ${RED}branch${OFF} $other_branch\n"
|
|
880
|
+
elif [ -n "$denied_branch" ]; then
|
|
881
|
+
conflict=1
|
|
882
|
+
findings="${findings} ${RED}branch${OFF} $denied_branch ${DIM}(content looks like a superseded predecessor, but $_guard_identity_reason)${OFF}\n"
|
|
792
883
|
fi
|
|
793
884
|
fi
|
|
794
885
|
|
|
795
886
|
# A discount a guard grants silently is a guard nobody can audit, so say so
|
|
796
|
-
# -- on stderr, only when it actually fired
|
|
797
|
-
#
|
|
887
|
+
# -- on stderr, only when it actually fired. One line per branch (#1698):
|
|
888
|
+
# $absorbed_branches can hold more than one name, and explaining several in
|
|
889
|
+
# the singular sent whoever read it looking for the wrong branch.
|
|
798
890
|
if [ -n "$absorbed_branches" ]; then
|
|
799
|
-
printf '%
|
|
800
|
-
|
|
801
|
-
|
|
891
|
+
printf '%s\n' "$absorbed_branches" | while IFS= read -r _ab; do
|
|
892
|
+
[ -n "$_ab" ] || continue
|
|
893
|
+
echo "${DIM}claim --guard: discounted $_ab -- every commit on it is already carried by${OFF}" >&2
|
|
894
|
+
echo "${DIM}$GUARD_BRANCH, and the claim record for #$guard_issue confirms $GUARD_BRANCH, so${OFF}" >&2
|
|
895
|
+
echo "${DIM}it is a superseded predecessor rather than a rival claim (#1112).${OFF}" >&2
|
|
896
|
+
done
|
|
802
897
|
fi
|
|
803
898
|
|
|
804
899
|
if [ "$conflict" -eq 1 ]; then
|
|
@@ -1156,13 +1251,28 @@ gh_label create "$LABEL" \
|
|
|
1156
1251
|
# which is why a claim could be written with nothing to identify it. That branch
|
|
1157
1252
|
# is now unreachable (the requirement above exits 2 first), so the conditional
|
|
1158
1253
|
# would only be a place for the old behaviour to come back.
|
|
1254
|
+
#
|
|
1255
|
+
# `claim-branch` (#1698) is the fact `--guard` reads back via
|
|
1256
|
+
# `claim_guard_branch` to confirm a discount is safe -- this comment is that
|
|
1257
|
+
# mechanism's one durable, non-worktree record, so nothing new is invented,
|
|
1258
|
+
# only this field added to it. AGENTS.md's own sequence puts worktree-and-
|
|
1259
|
+
# branch creation before this claim runs, so the branch normally already
|
|
1260
|
+
# exists; when it genuinely does not (detached HEAD, or `HEAD` itself, which
|
|
1261
|
+
# `--abbrev-ref` returns rather than a real name), the field is OMITTED
|
|
1262
|
+
# entirely rather than written empty -- an empty `claim-branch:` would match
|
|
1263
|
+
# any `case *"$BRANCH_MARK"*` probe as "present" while naming nothing, the
|
|
1264
|
+
# same empty-token trap `claim_held_by` already guards against for $2 (#826).
|
|
1265
|
+
_claim_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo '')
|
|
1266
|
+
case "$_claim_branch" in
|
|
1267
|
+
'' | HEAD) _claim_branch='' ;;
|
|
1268
|
+
esac
|
|
1159
1269
|
gh_issue edit "$ISSUE" --add-label "$LABEL" >/dev/null 2>&1 || {
|
|
1160
1270
|
echo "${RED}claim: could not apply the '$LABEL' label.${OFF}" >&2
|
|
1161
1271
|
echo "${DIM} Not claimed. Do not start work on the assumption that it worked.${OFF}" >&2
|
|
1162
1272
|
exit 2
|
|
1163
1273
|
}
|
|
1164
1274
|
gh_issue comment "$ISSUE" \
|
|
1165
|
-
--body "Claimed at $(date -u +%FT%TZ) by \`$(git config user.name 2>/dev/null || echo agent)\`. ${HOLDER_MARK}${HOLDER} Release it — remove the label — on merge, or if you stop." \
|
|
1275
|
+
--body "Claimed at $(date -u +%FT%TZ) by \`$(git config user.name 2>/dev/null || echo agent)\`. ${HOLDER_MARK}${HOLDER}${_claim_branch:+ ${BRANCH_MARK}${_claim_branch}} Release it — remove the label — on merge, or if you stop." \
|
|
1166
1276
|
>/dev/null 2>&1
|
|
1167
1277
|
|
|
1168
1278
|
echo "${GREEN}Claimed.${OFF}"
|