Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

Close linked issues when a change request lands

183d48e55e14b0355db4268275893274433ce8ee · Keenan Nemetz · 13d ago · parent 097b2110

Close linked issues when a change request lands

A change request can now name the issues it closes. Landing writes closed
onto each of them in the same commit that marks the request merged, so the
merge and the closure cannot be separated: a request cannot be merged with
its issues still open, and an issue cannot be closed by a merge that did
not happen.

The link lives in request.md as a list of full issue ids, which is what
makes it reviewable: it is part of the file a reviewer reads, not a
reference buried in prose. Ids are validated when the request is written,
including that the issue exists, so a typo fails while the author is still
looking at the command instead of at the landing they have waited for.

`vole change new --closes <id>` (repeatable, comma lists accepted) sets it,
`vole change merge` reports what it closed, and the browser form takes the
same field. The request page links each issue, and the merge answer names
the ones it closed.

An issue that is already closed, or that is no longer there, is left alone:
the promise is about what a merge does, not permission to overrule somebody
who closed it deliberately. Re-marking an already-merged request closes
nothing, so a retried landing is harmless.

Closes #01M5XG8WYZ1566KZFC73WQWGX3.

cmd/vole/change.go +24−2

@@ -15,7 +15,7 @@ import (
15 15 func changeUsage() string {
16 16 return `usage: vole change <command> [options] [args]
17 17
18 new --title <text> --base <branch> [--head <rev>] [-m <text> | -F <file>]
18 new --title <text> --base <branch> [--head <rev>] [--closes <issue>]... [-m <text> | -F <file>]
19 19 list [--state open|closed|all] [--format <fmt>]
20 20 show <id>
21 21 comment <id> [-m <text> | -F <file>] [--path <file> --line <n> [--side new|old]] [--reply-to <comment-id>]
@@ -46,6 +46,7 @@ func cmdChange(args []string) error {
46 46 {long: "title", short: "t"},
47 47 {long: "base", short: "b"},
48 48 {long: "head", short: "H"},
49 {long: "closes"},
49 50 {long: "message", short: "m"},
50 51 {long: "file", short: "F"},
51 52 {long: "as"},
@@ -67,7 +68,7 @@ func cmdChange(args []string) error {
67 68 return nil
68 69 }
69 70 changeFlags := map[string][]string{
70 "new": {"title", "base", "head", "message", "file", "remote", "no-push"},
71 "new": {"title", "base", "head", "closes", "message", "file", "remote", "no-push"},
71 72 "comment": {"message", "file", "path", "line", "side", "reply-to", "remote", "no-push"},
72 73 "edit-comment": {"message", "file", "remote", "no-push"},
73 74 "rm-comment": {"remote", "no-push"},
@@ -136,6 +137,10 @@ func cmdChange(args []string) error {
136 137 if err != nil {
137 138 return err
138 139 }
140 // A linked issue is a promise that landing this request closes it, so the ids
141 // are checked when the request is written rather than at the merge: a typo
142 // should fail while the author is still looking at the command.
143 r.Closes = closesFlag(f.all("closes"))
139 144 reapply := func() error { return metadata.CreateRequest(c.Repo, r, author) }
140 145 if err := reapply(); err != nil {
141 146 return err
@@ -144,6 +149,9 @@ func cmdChange(args []string) error {
144 149 return err
145 150 }
146 151 fmt.Printf("%s\t%s\t%s\t%s\n", r.ID, r.Base, format.ID(r.Head), r.Title)
152 for _, id := range r.Closes {
153 fmt.Fprintf(os.Stderr, "closes %s when merged\n", id)
154 }
147 155 if u := who.Page("requests", r.ID, ""); u != "" {
148 156 fmt.Fprintf(os.Stderr, "opened at %s\n", u)
149 157 }
@@ -427,3 +435,17 @@ func cmdChange(args []string) error {
427 435 fmt.Fprintln(os.Stderr, changeUsage())
428 436 return usagef("unknown change subcommand %q", sub)
429 437 }
438
439 // closesFlag normalises --closes, which is repeatable and may also carry a
440 // comma-separated list: both habits are common and neither is worth a second flag.
441 func closesFlag(values []string) []string {
442 var out []string
443 for _, v := range values {
444 for _, id := range strings.Split(v, ",") {
445 if id = strings.TrimSpace(id); id != "" {
446 out = append(out, id)
447 }
448 }
449 }
450 return out
451 }