a deploy key closes its own repository's issue by full path !381

merged merged by cmc on 2026-09-11 23:42 UTC · krz/gitbay:fix-213-deploy-self-path into main

4 files changed, +32 −2

Layout: unified · split

.gitbay/wiki/Users.org +4 −1
@@ -349,7 +349,10 @@ branch (direct push, MR merge, =repo commit-file= or the web editor):
349=closes/fixes/resolves #4= closes the 349=closes/fixes/resolves #4= closes the
350issue with a linking comment, and a bare =#4= leaves a reference 350issue with a linking comment, and a bare =#4= leaves a reference
351comment. Each issue/commit pair acts once, ever. =Closes owner/name#N= closes an issue in another repository when 351comment. Each issue/commit pair acts once, ever. =Closes owner/name#N= closes an issue in another repository when
352you hold write there; otherwise it stays a plain link. The comment left 352you hold write there with a key whose scope reaches it; otherwise it
353stays a plain link. A deploy key closes nothing outside the repository
354it is bound to, but naming that repository by full path works as the
355bare form does. The comment left
353on the closed issue links the closing commit by its repository path, so 356on the closed issue links the closing commit by its repository path, so
354closing a public repository's issue from a private one names the private 357closing a public repository's issue from a private one names the private
355repository there. A bare =owner/name#N= links and does nothing. 358repository there. A bare =owner/name#N= links and does nothing.
e2e/deploykey_test.go +13 −1
@@ -77,8 +77,20 @@ func TestDeployKeys(t *testing.T) {
77 rwWork := t.TempDir() 77 rwWork := t.TempDir()
78 mustGit(t, rwWork, rwEnv, "clone", inst.sshURL("alice/app"), "w") 78 mustGit(t, rwWork, rwEnv, "clone", inst.sshURL("alice/app"), "w")
79 rwDir := filepath.Join(rwWork, "w") 79 rwDir := filepath.Join(rwWork, "w")
80 mustGit(t, rwDir, rwEnv, "commit", "-q", "--allow-empty", "-m", "ci push") 80 // Its push closes the bound repository's issue whether the message
81 // names it bare or by full path (#213).
82 for _, title := range []string{"'bare'", "'full path'"} {
83 if _, errOut, code := inst.ssh(t, aliceKey, "", "issue", "create", "alice/app", "--title", title); code != 0 {
84 t.Fatalf("issue create: %s", errOut)
85 }
86 }
87 mustGit(t, rwDir, rwEnv, "commit", "-q", "--allow-empty", "-m", "ci push\n\nCloses #1, closes alice/app#2")
81 mustGit(t, rwDir, rwEnv, "push", "-q", "origin", "main") 88 mustGit(t, rwDir, rwEnv, "push", "-q", "origin", "main")
89 for _, n := range []string{"1", "2"} {
90 if out, _, _ := inst.ssh(t, aliceKey, "", "issue", "show", "alice/app", n, "--json"); !strings.Contains(out, `"state":"closed"`) {
91 t.Fatalf("deploy key push did not close issue %s: %s", n, out)
92 }
93 }
82 94
83 // The binding survives an owner rename (keys bind to the repo ID). 95 // The binding survives an owner rename (keys bind to the repo ID).
84 if _, errOut, code = inst.ssh(t, aliceKey, "", "org", "create", "moved"); code != 0 { 96 if _, errOut, code = inst.ssh(t, aliceKey, "", "org", "create", "moved"); code != 0 {
internal/control/commitrefs.go +6
@@ -163,6 +163,12 @@ func closeTarget(st *store.Store, source store.Repo, actorID int64, scope, path
163 if err != nil { 163 if err != nil {
164 return store.Repo{}, false 164 return store.Repo{}, false
165 } 165 }
166 // The source repository named by its full path is the bare form
167 // spelled out, so a deploy key bound to it closes there as a bare #N
168 // would (#213).
169 if target.ID == source.ID {
170 return source, true
171 }
166 actor, err := st.UserByID(actorID) 172 actor, err := st.UserByID(actorID)
167 if err != nil { 173 if err != nil {
168 return store.Repo{}, false 174 return store.Repo{}, false
internal/control/commitrefs_test.go +9
@@ -67,6 +67,15 @@ func TestMRDescriptionClosesAcrossRepos(t *testing.T) {
67 if iss, _ := f.st.IssueByNumber(f.priv.ID, 1); iss.State != "open" { 67 if iss, _ := f.st.IssueByNumber(f.priv.ID, 1); iss.State != "open" {
68 t.Fatal("a deploy key closed an issue outside its binding") 68 t.Fatal("a deploy key closed an issue outside its binding")
69 } 69 }
70 // The same key naming its own repository by full path is the bare
71 // form spelled out, not a cross-repo close (#213).
72 if _, err := f.st.CreateIssue(f.app.ID, f.alice, "in app too", "", "md"); err != nil {
73 t.Fatal(err)
74 }
75 ProcessMRDescription(f.st, f.app, mr(6, "Closes alice/app#2"), f.alice, deploy)
76 if iss, _ := f.st.IssueByNumber(f.app.ID, 2); iss.State != "closed" {
77 t.Fatal("a deploy key did not close its own repository's issue by full path")
78 }
70 // An archived target is read-only, cross-repo closes included. 79 // An archived target is read-only, cross-repo closes included.
71 if _, err := f.st.UpdateRepoSettings(f.priv.ID, func(rs *store.RepoSettings) { rs.Archived = true }); err != nil { 80 if _, err := f.st.UpdateRepoSettings(f.priv.ID, func(rs *store.RepoSettings) { rs.Archived = true }); err != nil {
72 t.Fatal(err) 81 t.Fatal(err)