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

Add PR List, Get and Merge support for GitHub and GitLab #111

Merged
merged 2 commits into from
Oct 4, 2021
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
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