Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GitHub Automerge Types #466

Merged
merged 2 commits into from
Feb 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions server/events/vcs/fixtures/github-repo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
{
"id": 167228802,
"node_id": "MDEwOlJlcG9zaXRvcnkxNjcyMjg4MDI=",
"name": "atlantis",
"full_name": "runatlantis/atlantis",
"private": false,
"owner": {
"login": "runatlantis",
"id": 1034429,
"node_id": "MDQ6VXNlcjEwMzQ0Mjk=",
"avatar_url": "https://avatars1.githubusercontent.com/u/1034429?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/runatlantis",
"html_url": "https://github.com/runatlantis",
"followers_url": "https://api.github.com/users/runatlantis/followers",
"following_url": "https://api.github.com/users/runatlantis/following{/other_user}",
"gists_url": "https://api.github.com/users/runatlantis/gists{/gist_id}",
"starred_url": "https://api.github.com/users/runatlantis/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/runatlantis/subscriptions",
"organizations_url": "https://api.github.com/users/runatlantis/orgs",
"repos_url": "https://api.github.com/users/runatlantis/repos",
"events_url": "https://api.github.com/users/runatlantis/events{/privacy}",
"received_events_url": "https://api.github.com/users/runatlantis/received_events",
"type": "User",
"site_admin": false
},
"html_url": "https://github.com/runatlantis/atlantis",
"description": null,
"fork": false,
"url": "https://api.github.com/repos/runatlantis/atlantis",
"forks_url": "https://api.github.com/repos/runatlantis/atlantis/forks",
"keys_url": "https://api.github.com/repos/runatlantis/atlantis/keys{/key_id}",
"collaborators_url": "https://api.github.com/repos/runatlantis/atlantis/collaborators{/collaborator}",
"teams_url": "https://api.github.com/repos/runatlantis/atlantis/teams",
"hooks_url": "https://api.github.com/repos/runatlantis/atlantis/hooks",
"issue_events_url": "https://api.github.com/repos/runatlantis/atlantis/issues/events{/number}",
"events_url": "https://api.github.com/repos/runatlantis/atlantis/events",
"assignees_url": "https://api.github.com/repos/runatlantis/atlantis/assignees{/user}",
"branches_url": "https://api.github.com/repos/runatlantis/atlantis/branches{/branch}",
"tags_url": "https://api.github.com/repos/runatlantis/atlantis/tags",
"blobs_url": "https://api.github.com/repos/runatlantis/atlantis/git/blobs{/sha}",
"git_tags_url": "https://api.github.com/repos/runatlantis/atlantis/git/tags{/sha}",
"git_refs_url": "https://api.github.com/repos/runatlantis/atlantis/git/refs{/sha}",
"trees_url": "https://api.github.com/repos/runatlantis/atlantis/git/trees{/sha}",
"statuses_url": "https://api.github.com/repos/runatlantis/atlantis/statuses/{sha}",
"languages_url": "https://api.github.com/repos/runatlantis/atlantis/languages",
"stargazers_url": "https://api.github.com/repos/runatlantis/atlantis/stargazers",
"contributors_url": "https://api.github.com/repos/runatlantis/atlantis/contributors",
"subscribers_url": "https://api.github.com/repos/runatlantis/atlantis/subscribers",
"subscription_url": "https://api.github.com/repos/runatlantis/atlantis/subscription",
"commits_url": "https://api.github.com/repos/runatlantis/atlantis/commits{/sha}",
"git_commits_url": "https://api.github.com/repos/runatlantis/atlantis/git/commits{/sha}",
"comments_url": "https://api.github.com/repos/runatlantis/atlantis/comments{/number}",
"issue_comment_url": "https://api.github.com/repos/runatlantis/atlantis/issues/comments{/number}",
"contents_url": "https://api.github.com/repos/runatlantis/atlantis/contents/{+path}",
"compare_url": "https://api.github.com/repos/runatlantis/atlantis/compare/{base}...{head}",
"merges_url": "https://api.github.com/repos/runatlantis/atlantis/merges",
"archive_url": "https://api.github.com/repos/runatlantis/atlantis/{archive_format}{/ref}",
"downloads_url": "https://api.github.com/repos/runatlantis/atlantis/downloads",
"issues_url": "https://api.github.com/repos/runatlantis/atlantis/issues{/number}",
"pulls_url": "https://api.github.com/repos/runatlantis/atlantis/pulls{/number}",
"milestones_url": "https://api.github.com/repos/runatlantis/atlantis/milestones{/number}",
"notifications_url": "https://api.github.com/repos/runatlantis/atlantis/notifications{?since,all,participating}",
"labels_url": "https://api.github.com/repos/runatlantis/atlantis/labels{/name}",
"releases_url": "https://api.github.com/repos/runatlantis/atlantis/releases{/id}",
"deployments_url": "https://api.github.com/repos/runatlantis/atlantis/deployments",
"created_at": "2019-01-23T17:58:45Z",
"updated_at": "2019-02-08T21:46:28Z",
"pushed_at": "2019-02-10T01:49:25Z",
"git_url": "git://github.com/runatlantis/atlantis.git",
"ssh_url": "[email protected]:runatlantis/atlantis.git",
"clone_url": "https://github.com/runatlantis/atlantis.git",
"svn_url": "https://github.com/runatlantis/atlantis",
"homepage": null,
"size": 32,
"stargazers_count": 0,
"watchers_count": 0,
"language": "HCL",
"has_issues": true,
"has_projects": true,
"has_downloads": true,
"has_wiki": true,
"has_pages": false,
"forks_count": 0,
"mirror_url": null,
"archived": false,
"open_issues_count": 1,
"license": null,
"forks": 0,
"open_issues": 1,
"watchers": 0,
"default_branch": "master",
"permissions": {
"admin": true,
"push": true,
"pull": true
},
"allow_squash_merge": true,
"allow_merge_commit": true,
"allow_rebase_merge": true,
"network_count": 0,
"subscribers_count": 0
}
26 changes: 25 additions & 1 deletion server/events/vcs/github_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,13 +178,37 @@ func (g *GithubClient) UpdateStatus(repo models.Repo, pull models.PullRequest, s

// MergePull merges the pull request.
func (g *GithubClient) MergePull(pull models.PullRequest) error {
// Users can set their repo to disallow certain types of merging.
// We detect which types aren't allowed and use the type that is.
repo, _, err := g.client.Repositories.Get(g.ctx, pull.BaseRepo.Owner, pull.BaseRepo.Name)
if err != nil {
return errors.Wrap(err, "fetching repo info")
}
const (
defaultMergeMethod = "merge"
rebaseMergeMethod = "rebase"
squashMergeMethod = "squash"
)
method := defaultMergeMethod
if !repo.GetAllowMergeCommit() {
if repo.GetAllowRebaseMerge() {
method = rebaseMergeMethod
} else if repo.GetAllowSquashMerge() {
method = squashMergeMethod
}
}

// Now we're ready to make our API call to merge the pull request.
options := &github.PullRequestOptions{
MergeMethod: method,
}
mergeResult, _, err := g.client.PullRequests.Merge(
g.ctx,
pull.BaseRepo.Owner,
pull.BaseRepo.Name,
pull.Num,
common.AutomergeCommitMsg,
nil)
options)
if err != nil {
return errors.Wrap(err, "merging pull request")
}
Expand Down
129 changes: 127 additions & 2 deletions server/events/vcs/github_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package vcs_test

import (
"crypto/tls"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
Expand Down Expand Up @@ -287,7 +288,7 @@ func TestGithubClient_PullIsMergeable(t *testing.T) {
}
}

func TestGithubClient_MergePull(t *testing.T) {
func TestGithubClient_MergePullHandlesError(t *testing.T) {
cases := []struct {
code int
message string
Expand All @@ -312,15 +313,21 @@ func TestGithubClient_MergePull(t *testing.T) {
},
}

jsBytes, err := ioutil.ReadFile("fixtures/github-repo.json")
Ok(t, err)

for _, c := range cases {
t.Run(c.message, func(t *testing.T) {
testServer := httptest.NewTLSServer(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.RequestURI {
case "/api/v3/repos/owner/repo":
w.Write(jsBytes) // nolint: errcheck
return
case "/api/v3/repos/owner/repo/pulls/1/merge":
body, err := ioutil.ReadAll(r.Body)
Ok(t, err)
exp := "{\"commit_message\":\"[Atlantis] Automatically merging after successful apply\"}\n"
exp := "{\"commit_message\":\"[Atlantis] Automatically merging after successful apply\",\"merge_method\":\"merge\"}\n"
Equals(t, exp, string(body))
var resp string
if c.code == 200 {
Expand Down Expand Up @@ -369,6 +376,124 @@ func TestGithubClient_MergePull(t *testing.T) {
}
}

// Test that if the pull request only allows a certain merge method that we
// use that method
func TestGithubClient_MergePullCorrectMethod(t *testing.T) {
cases := map[string]struct {
allowMerge bool
allowRebase bool
allowSquash bool
expMethod string
}{
"all true": {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉

allowMerge: true,
allowRebase: true,
allowSquash: true,
expMethod: "merge",
},
"all false (edge case)": {
allowMerge: false,
allowRebase: false,
allowSquash: false,
expMethod: "merge",
},
"merge: false rebase: true squash: true": {
allowMerge: false,
allowRebase: true,
allowSquash: true,
expMethod: "rebase",
},
"merge: false rebase: false squash: true": {
allowMerge: false,
allowRebase: false,
allowSquash: true,
expMethod: "squash",
},
"merge: false rebase: true squash: false": {
allowMerge: false,
allowRebase: true,
allowSquash: false,
expMethod: "rebase",
},
}

for name, c := range cases {
t.Run(name, func(t *testing.T) {

// Modify response.
jsBytes, err := ioutil.ReadFile("fixtures/github-repo.json")
Ok(t, err)
resp := string(jsBytes)
resp = strings.Replace(resp,
`"allow_squash_merge": true`,
fmt.Sprintf(`"allow_squash_merge": %t`, c.allowSquash),
-1)
resp = strings.Replace(resp,
`"allow_merge_commit": true`,
fmt.Sprintf(`"allow_merge_commit": %t`, c.allowMerge),
-1)
resp = strings.Replace(resp,
`"allow_rebase_merge": true`,
fmt.Sprintf(`"allow_rebase_merge": %t`, c.allowRebase),
-1)

testServer := httptest.NewTLSServer(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.RequestURI {
case "/api/v3/repos/runatlantis/atlantis":
w.Write([]byte(resp)) // nolint: errcheck
return
case "/api/v3/repos/runatlantis/atlantis/pulls/1/merge":
body, err := ioutil.ReadAll(r.Body)
Ok(t, err)
defer r.Body.Close() // nolint: errcheck
type bodyJSON struct {
CommitMessage string `json:"commit_message"`
MergeMethod string `json:"merge_method"`
}
expBody := bodyJSON{
CommitMessage: "[Atlantis] Automatically merging after successful apply",
MergeMethod: c.expMethod,
}
expBytes, err := json.Marshal(expBody)
Ok(t, err)
Equals(t, string(expBytes)+"\n", string(body))

resp := `{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e","merged":true,"message":"Pull Request successfully merged"}`
w.Write([]byte(resp)) // nolint: errcheck
default:
t.Errorf("got unexpected request at %q", r.RequestURI)
http.Error(w, "not found", http.StatusNotFound)
return
}
}))

testServerURL, err := url.Parse(testServer.URL)
Ok(t, err)
client, err := vcs.NewGithubClient(testServerURL.Host, "user", "pass")
Ok(t, err)
defer disableSSLVerification()()

err = client.MergePull(
models.PullRequest{
BaseRepo: models.Repo{
FullName: "runatlantis/atlantis",
Owner: "runatlantis",
Name: "atlantis",
CloneURL: "",
SanitizedCloneURL: "",
VCSHost: models.VCSHost{
Type: models.Github,
Hostname: "github.com",
},
},
Num: 1,
})
Ok(t, err)
})
}
}

// disableSSLVerification disables ssl verification for the global http client
// and returns a function to be called in a defer that will re-enable it.
func disableSSLVerification() func() {
Expand Down