Commit 7e1d129093
7e1d129093f1ed7a9c96149e9fa4922b6d9a1b7e
parent: 433da19b99
Verified · cmc ci/build: success ci/test: failure
cmc <hello@cleberg.net> · 2026-09-11 16:20 UTC
control, wiki: a duplicate milestone title exits 1
milestone create sent a plain error through failErr, which reads it as a
usage error and exits 2; org milestone create already exits 1 for the
same duplicate. Users gains a sentence on the closing comment naming the
source repository by path, and the e2e comment says the org page checks
are anonymous.
Ref #203
.gitbay/wiki/Users.org
+4 −2
| @@ -348,8 +348,10 @@ Commit messages act on issues when the commits land on the default |
| 348 | 348 | branch (direct push or MR merge): =closes/fixes/resolves #4= closes the |
| 349 | 349 | issue with a linking comment, and a bare =#4= leaves a reference |
| 350 | 350 | comment. Each issue/commit pair acts once, ever. =Closes owner/name#N= closes an issue in another repository when |
| 351 | | you hold write there; otherwise it stays a plain link. A bare |
| 352 | | =owner/name#N= links and does nothing. |
| 351 | you hold write there; otherwise it stays a plain link. The comment left |
| 352 | on the closed issue links the closing commit by its repository path, so |
| 353 | closing a public repository's issue from a private one names the private |
| 354 | repository there. A bare =owner/name#N= links and does nothing. |
| 353 | 355 | |
| 354 | 356 | Milestones group issues and MRs toward a release (write access to |
| 355 | 357 | manage, attach with =issue milestone= / =mr milestone=; progress shows |
e2e/orglabels_test.go
+3 −2
| @@ -77,8 +77,9 @@ func TestOrgLabelsMilestonesAndCrossRepoCloses(t *testing.T) { |
| 77 | 77 | t.Fatalf("org milestone progress after close: %s", out) |
| 78 | 78 | } |
| 79 | 79 | |
| 80 | | // carol is outside: she reads the org pages because acme/lib is public, |
| 81 | | // and the counts stop at it. |
| 80 | // carol is outside: her counts stop at acme/lib, the public one. The |
| 81 | // page checks below are anonymous, which acme/lib being public allows |
| 82 | // just the same. |
| 82 | 83 | out = must(carolKey, "org", "milestone", "list", "acme", "--json") |
| 83 | 84 | if !strings.Contains(out, `"open":1`) || !strings.Contains(out, `"closed":0`) { |
| 84 | 85 | t.Fatalf("outsider progress: %s", out) |
internal/control/milestone.go
+3 −1
| @@ -64,7 +64,9 @@ func runMilestoneCreate(c *Ctx, args []string) int { |
| 64 | 64 | if errors.Is(err, store.ErrOrgScoped) { |
| 65 | 65 | return c.fail(protocol.ExitFailure, "%s", orgScopedMsg(repo, "milestone", title, "create")) |
| 66 | 66 | } |
| 67 | | return c.failErr(err) |
| 67 | // A duplicate title is a failure, not a usage error, which is what |
| 68 | // failErr would make of it; org milestone create answers the same. |
| 69 | return c.fail(protocol.ExitFailure, "%v", err) |
| 68 | 70 | } |
| 69 | 71 | return c.emit(map[string]string{"milestone": title}, func(w io.Writer) { |
| 70 | 72 | fmt.Fprintf(w, "created milestone %q on %s\n", title, repo.Path()) |
internal/control/orgscope_test.go
+22
| @@ -152,3 +152,25 @@ func TestRepoMilestoneCommandsRefuseOrgTitles(t *testing.T) { |
| 152 | 152 | t.Fatalf("milestone list: exit %d %s", code, out.String()) |
| 153 | 153 | } |
| 154 | 154 | } |
| 155 | |
| 156 | // A duplicate title fails; it is not a usage error, and the repo-level |
| 157 | // and org-level commands answer with the same code. |
| 158 | func TestDuplicateMilestoneTitleFails(t *testing.T) { |
| 159 | f := newOrgFixture(t) |
| 160 | c, out := f.ctx(f.alice) |
| 161 | if code := runMilestoneCreate(c, []string{"alice/app", "v1"}); code != protocol.ExitOK { |
| 162 | t.Fatalf("first create: exit %d %s", code, out.String()) |
| 163 | } |
| 164 | out.Reset() |
| 165 | if code := runMilestoneCreate(c, []string{"alice/app", "v1"}); code != protocol.ExitFailure { |
| 166 | t.Fatalf("duplicate create: exit %d %s", code, out.String()) |
| 167 | } |
| 168 | out.Reset() |
| 169 | if code := runOrgMilestoneCreate(c, []string{"acme", "v1"}); code != protocol.ExitOK { |
| 170 | t.Fatalf("first org create: exit %d %s", code, out.String()) |
| 171 | } |
| 172 | out.Reset() |
| 173 | if code := runOrgMilestoneCreate(c, []string{"acme", "v1"}); code != protocol.ExitFailure { |
| 174 | t.Fatalf("duplicate org create: exit %d %s", code, out.String()) |
| 175 | } |
| 176 | } |