a deploy key closes its own repository's issue by full path !381
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 |
| 350 | issue with a linking comment, and a bare =#4= leaves a reference | 350 | issue with a linking comment, and a bare =#4= leaves a reference |
| 351 | comment. Each issue/commit pair acts once, ever. =Closes owner/name#N= closes an issue in another repository when | 351 | comment. Each issue/commit pair acts once, ever. =Closes owner/name#N= closes an issue in another repository when |
| 352 | you hold write there; otherwise it stays a plain link. The comment left | 352 | you hold write there with a key whose scope reaches it; otherwise it |
| 353 | stays a plain link. A deploy key closes nothing outside the repository | ||
| 354 | it is bound to, but naming that repository by full path works as the | ||
| 355 | bare form does. The comment left | ||
| 353 | on the closed issue links the closing commit by its repository path, so | 356 | on the closed issue links the closing commit by its repository path, so |
| 354 | closing a public repository's issue from a private one names the private | 357 | closing a public repository's issue from a private one names the private |
| 355 | repository there. A bare =owner/name#N= links and does nothing. | 358 | repository 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) |