Commit acb7f82838

acb7f82838156b034aad81b4613ecf9b9add372e

parent: 245c368edd

Verified · cmc ci/build: success ci/test: success

cmc <hello@cleberg.net> · 2026-09-11 19:34 UTC

control, e2e, wiki: repo commit-file acts on commit references

A commit written by repo commit-file, the web editor's path, lands on
the default branch without receive-pack or a merge, so Closes #N in
its message closed nothing. It now runs ProcessCommitMessages for the
one commit with the session's scope, as the merge path does.

Closes #210
.gitbay/wiki/Users.org +2 −1
@@ -345,7 +345,8 @@ activity on their public repositories at =/you/activity.atom=. The
345345pages carry the discovery link.
346346
347347Commit messages act on issues when the commits land on the default
348branch (direct push or MR merge): =closes/fixes/resolves #4= closes the
348branch (direct push, MR merge, =repo commit-file= or the web editor):
349=closes/fixes/resolves #4= closes the
349350issue with a linking comment, and a bare =#4= leaves a reference
350351comment. Each issue/commit pair acts once, ever. =Closes owner/name#N= closes an issue in another repository when
351352you hold write there; otherwise it stays a plain link. The comment left
e2e/commitrefs_test.go +24
@@ -109,4 +109,28 @@ func TestCommitMessageIssueActions(t *testing.T) {
109109 if strings.Count(out, "referenced in commit") != 1 {
110110 t.Fatalf("reference duplicated: %s", out)
111111 }
112
113 // A commit written by repo commit-file (the web editor's path) lands
114 // on the default branch without receive-pack or a merge, and acts
115 // the same (#210). On another branch it does nothing.
116 if _, errOut, code := inst.ssh(t, aliceKey, "e\n", "repo", "commit-file", "alice/app", "e.txt",
117 "--ref", "feat", "--message", "'Closes #2 on a branch'", "--file", "-"); code != 0 {
118 t.Fatalf("commit-file on branch: %s", errOut)
119 }
120 out, _, _ = inst.ssh(t, aliceKey, "", "issue", "show", "alice/app", "2", "--json")
121 if !strings.Contains(out, `"state":"open"`) {
122 t.Fatalf("commit-file on a branch acted: %s", out)
123 }
124 if _, errOut, code := inst.ssh(t, aliceKey, "e\n", "repo", "commit-file", "alice/app", "e.txt",
125 "--ref", "main", "--message", "'Closes #2 from the editor'", "--file", "-"); code != 0 {
126 t.Fatalf("commit-file: %s", errOut)
127 }
128 out, _, _ = inst.ssh(t, aliceKey, "", "issue", "show", "alice/app", "2", "--json")
129 if !strings.Contains(out, `"state":"closed"`) || !strings.Contains(out, "closed by commit") ||
130 !strings.Contains(out, "by [alice](/alice): Closes #2 from the editor") {
131 t.Fatalf("commit-file did not close issue 2: %s", out)
132 }
133 if strings.Count(out, "referenced in commit") != 1 {
134 t.Fatalf("commit-file re-acted on earlier commits: %s", out)
135 }
112136}
internal/control/commitfile.go +9
@@ -90,12 +90,21 @@ func runCommitFile(c *Ctx, args []string) int {
9090 }
9191
9292 dir := RepoDir(c.Cfg.Server.Root, repo.OwnerName, repo.Name)
93 // The head before the commit bounds what landed; a branch that does
94 // not exist fails in CommitFileChange with its own message.
95 parent, _ := gitutil.ResolveRef(dir, "refs/heads/"+ref)
9396 sha, err := gitutil.CommitFileChange(dir, ref, filePath, content,
9497 c.User.Username, email, message)
9598 if err != nil {
9699 return c.fail(protocol.ExitFailure, "%v", err)
97100 }
98101 c.Store.MarkMirrorsDirty(repo.ID, "push")
102 // This bypasses receive-pack like a merge does, so the commit-message
103 // issue actions (closes #N, references) run here for the default
104 // branch, with the session's scope (#210).
105 if ref == repo.DefaultBranch {
106 ProcessCommitMessages(c.Store, dir, repo, c.User.ID, c.Scope, parent, sha)
107 }
99108
100109 d := struct {
101110 Path string `json:"path"`