Skip to content

Commit

Permalink
Merge pull request #111 from jrryjcksn/add-simple-merge-support-for-p…
Browse files Browse the repository at this point in the history
…ull-requests

Add PR List, Get and Merge support for GitHub and GitLab
  • Loading branch information
yiannistri authored Oct 4, 2021
2 parents cae6dec + 8022415 commit 4605275
Show file tree
Hide file tree
Showing 9 changed files with 234 additions and 0 deletions.
44 changes: 44 additions & 0 deletions github/client_repository_pullrequest.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,22 @@ type PullRequestClient struct {
ref gitprovider.RepositoryRef
}

// List lists all pull requests in the repository
func (c *PullRequestClient) List(ctx context.Context) ([]gitprovider.PullRequest, error) {
prs, _, err := c.c.Client().PullRequests.List(ctx, c.ref.GetIdentity(), c.ref.GetRepository(), nil)
if err != nil {
return nil, err
}

requests := make([]gitprovider.PullRequest, len(prs))

for idx, pr := range prs {
requests[idx] = newPullRequest(c.clientContext, pr)
}

return requests, nil
}

// Create creates a pull request with the given specifications.
func (c *PullRequestClient) Create(ctx context.Context, title, branch, baseBranch, description string) (gitprovider.PullRequest, error) {

Expand All @@ -49,3 +65,31 @@ func (c *PullRequestClient) Create(ctx context.Context, title, branch, baseBranc

return newPullRequest(c.clientContext, pr), nil
}

// Get retrieves an existing pull request by number
func (c *PullRequestClient) Get(ctx context.Context, number int) (gitprovider.PullRequest, error) {

pr, _, err := c.c.Client().PullRequests.Get(ctx, c.ref.GetIdentity(), c.ref.GetRepository(), number)
if err != nil {
return nil, err
}

return newPullRequest(c.clientContext, pr), nil
}

// Merge merges a pull request with the given specifications.
func (c *PullRequestClient) Merge(ctx context.Context, number int, mergeMethod gitprovider.MergeMethod, message string) error {

prOpts := &github.PullRequestOptions{
CommitTitle: "",
SHA: "",
MergeMethod: string(mergeMethod),
}

_, _, err := c.c.Client().PullRequests.Merge(ctx, c.ref.GetIdentity(), c.ref.GetRepository(), number, message, prOpts)
if err != nil {
return err
}

return nil
}
38 changes: 38 additions & 0 deletions github/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,44 @@ var _ = Describe("GitHub Provider", func() {
pr, err := userRepo.PullRequests().Create(ctx, "Added config file", branchName, *defaultBranch, "added config file")
Expect(err).ToNot(HaveOccurred())
Expect(pr.Get().WebURL).ToNot(BeEmpty())
Expect(pr.Get().Merged).To(BeFalse())

prs, err := userRepo.PullRequests().List(ctx)
Expect(len(prs)).To(Equal(1))
Expect(prs[0].Get().WebURL).To(Equal(pr.Get().WebURL))

err = userRepo.PullRequests().Merge(ctx, pr.Get().Number, gitprovider.MergeMethodSquash, "squash merged")
Expect(err).ToNot(HaveOccurred())

getPR, err := userRepo.PullRequests().Get(ctx, pr.Get().Number)
Expect(err).ToNot(HaveOccurred())

Expect(getPR.Get().Merged).To(BeTrue())

path = "setup/config2.txt"
content = "yaml content"
files = []gitprovider.CommitFile{
gitprovider.CommitFile{
Path: &path,
Content: &content,
},
}

_, err = userRepo.Commits().Create(ctx, branchName, "added second config file", files)
Expect(err).ToNot(HaveOccurred())

pr, err = userRepo.PullRequests().Create(ctx, "Added second config file", branchName, *defaultBranch, "added second config file")
Expect(err).ToNot(HaveOccurred())
Expect(pr.Get().WebURL).ToNot(BeEmpty())
Expect(pr.Get().Merged).To(BeFalse())

err = userRepo.PullRequests().Merge(ctx, pr.Get().Number, gitprovider.MergeMethodMerge, "merged")
Expect(err).ToNot(HaveOccurred())

getPR, err = userRepo.PullRequests().Get(ctx, pr.Get().Number)
Expect(err).ToNot(HaveOccurred())

Expect(getPR.Get().Merged).To(BeTrue())
})

AfterSuite(func() {
Expand Down
2 changes: 2 additions & 0 deletions github/resource_pullrequest.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ func (pr *pullrequest) APIObject() interface{} {

func pullrequestFromAPI(apiObj *github.PullRequest) gitprovider.PullRequestInfo {
return gitprovider.PullRequestInfo{
Merged: apiObj.GetMerged(),
Number: apiObj.GetNumber(),
WebURL: apiObj.GetHTMLURL(),
}
}
85 changes: 85 additions & 0 deletions gitlab/client_repository_pullrequest.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,16 @@ package gitlab

import (
"context"
"fmt"
"time"

"github.com/fluxcd/go-git-providers/gitprovider"
"github.com/xanzy/go-gitlab"
)

// mergeStatusChecking indicates that gitlab has not yet asynchronously updated the merge status for a merge request
const mergeStatusChecking = "checking"

// PullRequestClient implements the gitprovider.PullRequestClient interface.
var _ gitprovider.PullRequestClient = &PullRequestClient{}

Expand All @@ -32,6 +37,22 @@ type PullRequestClient struct {
ref gitprovider.RepositoryRef
}

// List lists all pull requests in the repository
func (c *PullRequestClient) List(ctx context.Context) ([]gitprovider.PullRequest, error) {
mrs, _, err := c.c.Client().MergeRequests.ListMergeRequests(nil)
if err != nil {
return nil, err
}

requests := make([]gitprovider.PullRequest, len(mrs))

for idx, mr := range mrs {
requests[idx] = newPullRequest(c.clientContext, mr)
}

return requests, nil
}

// Create creates a pull request with the given specifications.
func (c *PullRequestClient) Create(ctx context.Context, title, branch, baseBranch, description string) (gitprovider.PullRequest, error) {

Expand All @@ -49,3 +70,67 @@ func (c *PullRequestClient) Create(ctx context.Context, title, branch, baseBranc

return newPullRequest(c.clientContext, mr), nil
}

// Get retrieves an existing pull request by number
func (c *PullRequestClient) Get(ctx context.Context, number int) (gitprovider.PullRequest, error) {

mr, _, err := c.c.Client().MergeRequests.GetMergeRequest(getRepoPath(c.ref), number, &gitlab.GetMergeRequestsOptions{})
if err != nil {
return nil, err
}

return newPullRequest(c.clientContext, mr), nil
}

// Merge merges a pull request with the given specifications.
func (c *PullRequestClient) Merge(ctx context.Context, number int, mergeMethod gitprovider.MergeMethod, message string) error {
if err := c.waitForMergeRequestToBeMergeable(number); err != nil {
return err
}

var squash bool

var mergeCommitMessage *string
var squashCommitMessage *string

switch mergeMethod {
case gitprovider.MergeMethodSquash:
squashCommitMessage = &message
squash = true
case gitprovider.MergeMethodMerge:
mergeCommitMessage = &message
default:
return fmt.Errorf("unknown merge method: %s", mergeMethod)
}

amrOpts := &gitlab.AcceptMergeRequestOptions{
MergeCommitMessage: mergeCommitMessage,
SquashCommitMessage: squashCommitMessage,
Squash: &squash,
ShouldRemoveSourceBranch: nil,
MergeWhenPipelineSucceeds: nil,
SHA: nil,
}

_, _, err := c.c.Client().MergeRequests.AcceptMergeRequest(getRepoPath(c.ref), number, amrOpts)
if err != nil {
return err
}

return nil
}

func (c *PullRequestClient) waitForMergeRequestToBeMergeable(number int) error {
// gitlab says to poll for merge status
for retries := 0; retries < 10; retries++ {
mr, _, err := c.c.Client().MergeRequests.GetMergeRequest(getRepoPath(c.ref), number, &gitlab.GetMergeRequestsOptions{})
if err != nil || mr.MergeStatus == mergeStatusChecking {
time.Sleep(time.Second * 2)
continue
}

return nil
}

return fmt.Errorf("merge status unavailable for pull request number: %d", number)
}
38 changes: 38 additions & 0 deletions gitlab/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -785,7 +785,45 @@ var _ = Describe("GitLab Provider", func() {

pr, err := userRepo.PullRequests().Create(ctx, "Added config file", branchName, defaultBranch, "added config file")
Expect(err).ToNot(HaveOccurred())

prs, err := userRepo.PullRequests().List(ctx)
Expect(len(prs)).To(Equal(1))
Expect(prs[0].Get().WebURL).To(Equal(pr.Get().WebURL))

Expect(pr.Get().WebURL).ToNot(BeEmpty())
Expect(pr.Get().Merged).To(BeFalse())
err = userRepo.PullRequests().Merge(ctx, pr.Get().Number, gitprovider.MergeMethodSquash, "squash merged")
Expect(err).ToNot(HaveOccurred())

getPR, err := userRepo.PullRequests().Get(ctx, pr.Get().Number)
Expect(err).ToNot(HaveOccurred())

Expect(getPR.Get().Merged).To(BeTrue())

path = "setup/config2.txt"
content = "yaml content"
files = []gitprovider.CommitFile{
gitprovider.CommitFile{
Path: &path,
Content: &content,
},
}

_, err = userRepo.Commits().Create(ctx, branchName, "added second config file", files)
Expect(err).ToNot(HaveOccurred())

pr, err = userRepo.PullRequests().Create(ctx, "Added second config file", branchName, defaultBranch, "added second config file")
Expect(err).ToNot(HaveOccurred())
Expect(pr.Get().WebURL).ToNot(BeEmpty())
Expect(pr.Get().Merged).To(BeFalse())

err = userRepo.PullRequests().Merge(ctx, pr.Get().Number, gitprovider.MergeMethodMerge, "merged")
Expect(err).ToNot(HaveOccurred())

getPR, err = userRepo.PullRequests().Get(ctx, pr.Get().Number)
Expect(err).ToNot(HaveOccurred())

Expect(getPR.Get().Merged).To(BeTrue())
})

AfterSuite(func() {
Expand Down
5 changes: 5 additions & 0 deletions gitlab/resource_pullrequest.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ import (
"github.com/xanzy/go-gitlab"
)

// The value of the "State" field of a gitlab merge request after it has been merged"
const mergedState = "merged"

func newPullRequest(ctx *clientContext, apiObj *gitlab.MergeRequest) *pullrequest {
return &pullrequest{
clientContext: ctx,
Expand All @@ -46,6 +49,8 @@ func (pr *pullrequest) APIObject() interface{} {

func pullrequestFromAPI(apiObj *gitlab.MergeRequest) gitprovider.PullRequestInfo {
return gitprovider.PullRequestInfo{
Merged: apiObj.State == mergedState,
Number: apiObj.IID,
WebURL: apiObj.WebURL,
}
}
6 changes: 6 additions & 0 deletions gitprovider/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,12 @@ type BranchClient interface {
// PullRequestClient operates on the pull requests for a specific repository.
// This client can be accessed through Repository.PullRequests().
type PullRequestClient interface {
// List lists all pull requests in the repository
List(ctx context.Context) ([]PullRequest, error)
// Create creates a pull request with the given specifications.
Create(ctx context.Context, title, branch, baseBranch, description string) (PullRequest, error)
// Get retrieves an existing pull request by number
Get(ctx context.Context, number int) (PullRequest, error)
// Merge merges a pull request with via either the "Squash" or "Merge" method
Merge(ctx context.Context, number int, mergeMethod MergeMethod, message string) error
}
10 changes: 10 additions & 0 deletions gitprovider/enums.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,13 @@ const (
// Read/Write permission for public/private repositories.
TokenPermissionRWRepository TokenPermission = iota + 1
)

type MergeMethod string

const (
// MergeMethodMerge causes a pull request merge to create a simple merge commit
MergeMethodMerge = MergeMethod("merge")

// MergeMethodMerge causes a pull request merge to first squash commits
MergeMethodSquash = MergeMethod("squash")
)
6 changes: 6 additions & 0 deletions gitprovider/types_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,12 @@ type CommitFile struct {
}

type PullRequestInfo struct {
// Merged specifes whether or not this pull request has been merged
Merged bool `json:"merged"`

// Number is the number of the pull request that can be used to merge
Number int `json:"number"`

// WebURL is the URL of the pull request in the git provider web interface.
// +required
WebURL string `json:"web_url"`
Expand Down

0 comments on commit 4605275

Please sign in to comment.