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 }