diff --git a/chlog/merge_and_label.go b/chlog/merge_and_label.go index 233f515..91d74ea 100644 --- a/chlog/merge_and_label.go +++ b/chlog/merge_and_label.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "log" + "net/http" "os" "regexp" "strings" @@ -19,6 +20,11 @@ import ( const changeSectionLabelNone = "none" +const ( + releasePleaseWorkflowPath = ".github/workflows/release-please.yml" + releasePleaseMergeReply = "This repository uses Release Please, so `@jekyllbot: merge` is disabled. Please follow the maintainer docs: https://jekyllrb.com/docs/maintaining/merging-a-pull-request/" +) + // changelogCategory is a changelog category, like "Site Enhancements" and such. type changelogCategory struct { Prefix, Slug, Section string @@ -182,6 +188,31 @@ func MergeAndLabel(context *ctx.Context, payload interface{}) error { return errors.New("commenter isn't allowed to merge") } + prInfo, _, getPRErr := context.GitHub.PullRequests.Get(context.Context(), owner, repo, number) + if getPRErr != nil { + return context.NewError("MergeAndLabel: error getting PR info %s: %v", ref, getPRErr) + } + + if prInfo == nil { + return context.NewError("MergeAndLabel: tried to get PR, but couldn't. prInfo was nil.") + } + + releasePleaseEnabled, releasePleaseErr := hasReleasePleaseWorkflowOnBranch( + context, + owner, + repo, + prInfo.GetBase().GetRef(), + ) + if releasePleaseErr != nil { + return context.NewError("MergeAndLabel: error checking release-please workflow for %s: %v", ref, releasePleaseErr) + } + if releasePleaseEnabled { + if err := replyWithReleasePleaseMergeRefusal(context, owner, repo, number); err != nil { + return context.NewError("MergeAndLabel: error refusing merge request for %s: %v", ref, err) + } + return nil + } + // Merge commitMsg := fmt.Sprintf("Merge pull request %v", number) _, _, mergeErr := context.GitHub.PullRequests.Merge(context.Context(), owner, repo, number, commitMsg, mergeOptions) @@ -190,20 +221,10 @@ func MergeAndLabel(context *ctx.Context, payload interface{}) error { } // Delete branch - repoInfo, _, getRepoErr := context.GitHub.PullRequests.Get(context.Context(), owner, repo, number) - if getRepoErr != nil { - return context.NewError("MergeAndLabel: error getting PR info %s: %v", ref, getRepoErr) - } - - if repoInfo == nil { - return context.NewError("MergeAndLabel: tried to get PR, but couldn't. repoInfo was nil.") - } - - // Delete branch - if deletableRef(repoInfo, owner) { + if deletableRef(prInfo, owner) { wg.Add(1) go func() { - ref := fmt.Sprintf("heads/%s", *repoInfo.Head.Ref) + ref := fmt.Sprintf("heads/%s", *prInfo.Head.Ref) _, deleteBranchErr := context.GitHub.Git.DeleteRef(context.Context(), owner, repo, ref) if deleteBranchErr != nil { fmt.Printf("MergeAndLabel: error deleting branch %v\n", mergeErr) @@ -227,7 +248,7 @@ func MergeAndLabel(context *ctx.Context, payload interface{}) error { historyFileContents, historySHA := getHistoryContents(context, owner, repo) // Add merge reference to history - newHistoryFileContents := addMergeReference(historyFileContents, req.ChangeSectionLabel, *repoInfo.Title, number) + newHistoryFileContents := addMergeReference(historyFileContents, req.ChangeSectionLabel, *prInfo.Title, number) // Commit change to History.markdown commitErr := commitHistoryFile(context, historySHA, owner, repo, number, newHistoryFileContents) @@ -258,6 +279,38 @@ func parseMergeRequestComment(commentBody string) (bool, string) { return true, normalizeLabel(label) } +func hasReleasePleaseWorkflowOnBranch(context *ctx.Context, owner, repo, branch string) (bool, error) { + if branch == "" { + return false, nil + } + + _, _, response, err := context.GitHub.Repositories.GetContents( + context.Context(), + owner, + repo, + releasePleaseWorkflowPath, + &github.RepositoryContentGetOptions{Ref: branch}, + ) + if err != nil { + if response != nil && response.StatusCode == http.StatusNotFound { + return false, nil + } + return false, err + } + return true, nil +} + +func replyWithReleasePleaseMergeRefusal(context *ctx.Context, owner, repo string, number int) error { + _, _, err := context.GitHub.Issues.CreateComment( + context.Context(), + owner, + repo, + number, + &github.IssueComment{Body: github.String(releasePleaseMergeReply)}, + ) + return err +} + func downcaseAndHyphenize(label string) string { return strings.Replace(strings.ToLower(label), " ", "-", -1) } diff --git a/chlog/merge_and_label_test.go b/chlog/merge_and_label_test.go index 3c094c5..ca27e0d 100644 --- a/chlog/merge_and_label_test.go +++ b/chlog/merge_and_label_test.go @@ -1,7 +1,11 @@ package chlog import ( - "io/ioutil" + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "os" "testing" "github.com/google/go-github/v73/github" @@ -211,7 +215,7 @@ func TestParseMergeRequestComment(t *testing.T) { } func TestBase64Decode(t *testing.T) { - encoded, err := ioutil.ReadFile("history_contents.enc") + encoded, err := os.ReadFile("history_contents.enc") assert.NoError(t, err) decoded := base64Decode(string(encoded)) assert.Contains(t, decoded, "### Minor Enhancements") @@ -236,8 +240,50 @@ func TestAddMergeReference(t *testing.T) { "Development Fixes", "Another great change for !!!!!!!", 1) assert.Equal(t, "## HEAD\n\n### Development Fixes\n\n * Some great change (#1)\n * Another great change for <science>!!!!!!! (#1)\n", historyFile) - jekyllHistory, err := ioutil.ReadFile("History.markdown") + jekyllHistory, err := os.ReadFile("History.markdown") assert.NoError(t, err) historyFile = addMergeReference(string(jekyllHistory), "Development Fixes", "A marvelous change.", 41526) assert.Contains(t, historyFile, "* A marvelous change. (#41526)\n\n### Site Enhancements") } + +func TestHasReleasePleaseWorkflowOnBranch(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "/repos/owner-login/foo/contents/.github/workflows/release-please.yml", r.URL.Path) + assert.Equal(t, "main", r.URL.Query().Get("ref")) + fmt.Fprint(w, `{"name":"release-please.yml","path":".github/workflows/release-please.yml","sha":"abc123","content":"","encoding":"base64"}`) + })) + defer server.Close() + + client := github.NewClient(nil) + baseURL, err := url.Parse(server.URL + "/") + assert.NoError(t, err) + client.BaseURL = baseURL + client.UploadURL = baseURL + + context := ctx.NewTestContext() + context.GitHub = client + + enabled, err := hasReleasePleaseWorkflowOnBranch(context, "owner-login", "foo", "main") + assert.NoError(t, err) + assert.True(t, enabled) +} + +func TestHasReleasePleaseWorkflowOnBranchNotFound(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.NotFound(w, r) + })) + defer server.Close() + + client := github.NewClient(nil) + baseURL, err := url.Parse(server.URL + "/") + assert.NoError(t, err) + client.BaseURL = baseURL + client.UploadURL = baseURL + + context := ctx.NewTestContext() + context.GitHub = client + + enabled, err := hasReleasePleaseWorkflowOnBranch(context, "owner-login", "foo", "main") + assert.NoError(t, err) + assert.False(t, enabled) +}