github.com/abhinav/git-fu@v0.6.1-0.20171029234004-54218d68c11b/github/gateway.go (about)

     1  package github
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/abhinav/git-pr/gateway"
     8  	"github.com/abhinav/git-pr/repo"
     9  
    10  	"github.com/google/go-github/github"
    11  )
    12  
    13  //go:generate mockgen -package github -destination=mocks_test.go github.com/abhinav/git-pr/github GitService,PullRequestsService
    14  
    15  // GitService is a subset of the GitHub Git API.
    16  type GitService interface {
    17  	DeleteRef(
    18  		ctx context.Context,
    19  		owner string, repo string, ref string,
    20  	) (*github.Response, error)
    21  }
    22  
    23  var _ GitService = (*github.GitService)(nil)
    24  
    25  // PullRequestsService is a subset of the GitHub Pull Requests API.
    26  type PullRequestsService interface {
    27  	Edit(
    28  		ctx context.Context,
    29  		owner string, repo string, number int,
    30  		pull *github.PullRequest,
    31  	) (*github.PullRequest, *github.Response, error)
    32  
    33  	GetRaw(
    34  		ctx context.Context,
    35  		owner string, repo string, number int, opt github.RawOptions,
    36  	) (string, *github.Response, error)
    37  
    38  	List(
    39  		ctx context.Context,
    40  		owner string, repo string, opt *github.PullRequestListOptions,
    41  	) ([]*github.PullRequest, *github.Response, error)
    42  
    43  	ListReviews(
    44  		ctx context.Context,
    45  		owner, repo string, number int,
    46  	) ([]*github.PullRequestReview, *github.Response, error)
    47  
    48  	Merge(
    49  		ctx context.Context,
    50  		owner string, repo string, number int,
    51  		commitMessage string,
    52  		options *github.PullRequestOptions,
    53  	) (*github.PullRequestMergeResult, *github.Response, error)
    54  }
    55  
    56  var _ PullRequestsService = (*github.PullRequestsService)(nil)
    57  
    58  // RepositoriesService is a subset of the GitHub Repositories API.
    59  type RepositoriesService interface {
    60  	GetCombinedStatus(ctx context.Context,
    61  		owner, repo, ref string, opt *github.ListOptions,
    62  	) (*github.CombinedStatus, *github.Response, error)
    63  }
    64  
    65  var _ RepositoriesService = (*github.RepositoriesService)(nil)
    66  
    67  // Gateway is a GitHub gateway that makes actual requests to GitHub.
    68  type Gateway struct {
    69  	owner string
    70  	repo  string
    71  
    72  	git   GitService
    73  	pulls PullRequestsService
    74  	repos RepositoriesService
    75  }
    76  
    77  var _ gateway.GitHub = (*Gateway)(nil)
    78  
    79  // NewGatewayForRepository builds a new GitHub gateway for the given GitHub
    80  // repository.
    81  func NewGatewayForRepository(client *github.Client, repo *repo.Repo) *Gateway {
    82  	return &Gateway{
    83  		owner: repo.Owner,
    84  		repo:  repo.Name,
    85  		pulls: client.PullRequests,
    86  		repos: client.Repositories,
    87  		git:   client.Git,
    88  	}
    89  }
    90  
    91  func (g *Gateway) urlFor(number int) string {
    92  	return fmt.Sprintf("https://github.com/%v/%v/pull/%v", g.owner, g.repo, number)
    93  }
    94  
    95  // IsOwned checks if this branch is local to this repository.
    96  func (g *Gateway) IsOwned(ctx context.Context, br *github.PullRequestBranch) bool {
    97  	return *br.Repo.Owner.Login == g.owner && *br.Repo.Name == g.repo
    98  }
    99  
   100  // ListPullRequestReviews lists reviews for a pull request.
   101  func (g *Gateway) ListPullRequestReviews(ctx context.Context, number int) ([]*gateway.PullRequestReview, error) {
   102  	reviews, _, err := g.pulls.ListReviews(ctx, g.owner, g.repo, number)
   103  	if err != nil {
   104  		return nil, fmt.Errorf("failed to list reviews for %v: %v", g.urlFor(number), err)
   105  	}
   106  
   107  	result := make([]*gateway.PullRequestReview, len(reviews))
   108  	for i, r := range reviews {
   109  		result[i] = &gateway.PullRequestReview{
   110  			User:   r.User.GetLogin(),
   111  			Status: gateway.PullRequestReviewState(r.GetState()),
   112  		}
   113  	}
   114  	return result, nil
   115  }
   116  
   117  // GetBuildStatus gets the build status for the given ref.
   118  func (g *Gateway) GetBuildStatus(ctx context.Context, ref string) (*gateway.BuildStatus, error) {
   119  	s, _, err := g.repos.GetCombinedStatus(ctx, g.owner, g.repo, ref, nil)
   120  	if err != nil {
   121  		return nil, fmt.Errorf("failed to get build status for %q: %v", ref, err)
   122  	}
   123  
   124  	bs := gateway.BuildStatus{State: gateway.BuildState(s.GetState())}
   125  	for _, status := range s.Statuses {
   126  		bs.Statuses = append(bs.Statuses, &gateway.BuildContextStatus{
   127  			Name:    status.GetContext(),
   128  			Message: status.GetDescription(),
   129  			State:   gateway.BuildState(status.GetState()),
   130  		})
   131  	}
   132  
   133  	return &bs, nil
   134  }
   135  
   136  // ListPullRequestsByHead lists pull requests with the given head.
   137  func (g *Gateway) ListPullRequestsByHead(ctx context.Context, owner, branch string) ([]*github.PullRequest, error) {
   138  	if owner == "" {
   139  		owner = g.owner
   140  	}
   141  	// TODO: account for pagination
   142  	prs, _, err := g.pulls.List(
   143  		ctx,
   144  		g.owner,
   145  		g.repo,
   146  		&github.PullRequestListOptions{Head: owner + ":" + branch})
   147  	if err != nil {
   148  		err = fmt.Errorf(
   149  			"failed to list pull requests with head %v:%v: %v", owner, branch, err)
   150  	}
   151  	return prs, err
   152  }
   153  
   154  // ListPullRequestsByBase lists pull requests made against the given merge base.
   155  func (g *Gateway) ListPullRequestsByBase(ctx context.Context, branch string) ([]*github.PullRequest, error) {
   156  	// TODO: account for pagination
   157  	prs, _, err := g.pulls.List(
   158  		ctx,
   159  		g.owner,
   160  		g.repo,
   161  		&github.PullRequestListOptions{Base: branch})
   162  	if err != nil {
   163  		err = fmt.Errorf(
   164  			"failed to list pull requests with base %v: %v", branch, err)
   165  	}
   166  	return prs, err
   167  }
   168  
   169  // GetPullRequestPatch retrieves the raw patch for the given PR. The contents
   170  // of the patch may be applied using the git-am command.
   171  func (g *Gateway) GetPullRequestPatch(ctx context.Context, number int) (string, error) {
   172  	patch, _, err := g.pulls.GetRaw(
   173  		ctx, g.owner, g.repo, number, github.RawOptions{Type: github.Patch})
   174  	if err != nil {
   175  		err = fmt.Errorf("failed to retrieve patch for %v: %v", g.urlFor(number), err)
   176  	}
   177  	return patch, err
   178  }
   179  
   180  // SetPullRequestBase changes the merge base for the given PR.
   181  func (g *Gateway) SetPullRequestBase(ctx context.Context, number int, base string) error {
   182  	_, _, err := g.pulls.Edit(ctx, g.owner, g.repo, number,
   183  		&github.PullRequest{Base: &github.PullRequestBranch{Ref: &base}})
   184  	if err != nil {
   185  		return fmt.Errorf(
   186  			"failed to change merge base of %v to %v: %v", g.urlFor(number), base, err)
   187  	}
   188  	return nil
   189  }
   190  
   191  // SquashPullRequest merges given pull request. The title and description are
   192  // used as-is for the commit message.
   193  func (g *Gateway) SquashPullRequest(ctx context.Context, pr *github.PullRequest) error {
   194  	result, _, err := g.pulls.Merge(ctx, g.owner, g.repo, *pr.Number, *pr.Body,
   195  		&github.PullRequestOptions{CommitTitle: *pr.Title, MergeMethod: "squash"})
   196  	if err != nil {
   197  		return fmt.Errorf("failed to merge %v: %v", g.urlFor(*pr.Number), err)
   198  	}
   199  
   200  	if result.Merged == nil || !*result.Merged {
   201  		return fmt.Errorf("failed to merge %v: %v", g.urlFor(*pr.Number), *result.Message)
   202  	}
   203  
   204  	return nil
   205  }
   206  
   207  // DeleteBranch deletes the given remote branch.
   208  func (g *Gateway) DeleteBranch(ctx context.Context, name string) error {
   209  	if _, err := g.git.DeleteRef(ctx, g.owner, g.repo, "heads/"+name); err != nil {
   210  		return fmt.Errorf("failed to delete remote branch %v: %v", name, err)
   211  	}
   212  	return nil
   213  }