Skip to content

Commit

Permalink
Merge pull request #466 from runatlantis/gh-automerge
Browse files Browse the repository at this point in the history
GitHub Automerge Types
  • Loading branch information
lkysow authored Feb 11, 2019
2 parents 1de4034 + def41f6 commit 45a6823
Show file tree
Hide file tree
Showing 3 changed files with 255 additions and 3 deletions.
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": {
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

0 comments on commit 45a6823

Please sign in to comment.