Skip to content
This repository has been archived by the owner on Jan 26, 2018. It is now read-only.

First pr merge semver #27

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 15 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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ bindata.go
web/react/node_modules
web/static/files/script.js
#web/static/files/*.css

.idea
3 changes: 3 additions & 0 deletions model/branch_status.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package model

type BranchStatus string
6 changes: 4 additions & 2 deletions model/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ type Config struct {
Pattern string `json:"pattern" toml:"pattern"`
Team string `json:"team" toml:"team"`
SelfApprovalOff bool `json:"self_approval_off" toml:"self_approval_off"`
DoMerge bool `json:"do_merge" toml:"do_merge"`
DoVersion bool `json:"do_version" toml:"do_version"`

re *regexp.Regexp
re *regexp.Regexp
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is gofmt run on the code? I would expect it to remove the spacing after re


// ParseConfig parses a projects .lgtm file
Expand All @@ -31,7 +33,7 @@ func ParseConfigStr(data string) (*Config, error) {
c.Approvals = 2
}
if len(c.Pattern) == 0 {
c.Pattern = "(?i)LGTM"
c.Pattern = `(?i)LGTM\s*(\S*)`
}
if len(c.Team) == 0 {
c.Team = "MAINTAINERS"
Expand Down
17 changes: 17 additions & 0 deletions model/status_hook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package model

type StatusHook struct {
SHA string
Repo *Repo
}

type PullRequest struct {
Issue
Branch Branch
}

type Branch struct {
Name string
BranchStatus string
Mergeable bool
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename BranchStatus to Status since Branch is implied by the struct name

}
3 changes: 3 additions & 0 deletions model/tag.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package model

type Tag string
191 changes: 183 additions & 8 deletions remote/github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
"github.com/lgtmco/lgtm/model"
"github.com/lgtmco/lgtm/shared/httputil"
"golang.org/x/oauth2"
"github.com/hashicorp/go-version"
"errors"
)

// name of the status message posted to GitHub
Expand Down Expand Up @@ -180,11 +182,6 @@ func (g *Github) GetRepos(u *model.User) ([]*model.Repo, error) {
func (g *Github) SetHook(user *model.User, repo *model.Repo, link string) error {
client := setupClient(g.API, user.Token)

repo_, _, err := client.Repositories.Get(repo.Owner, repo.Name)
if err != nil {
return err
}

old, err := GetHook(client, repo.Owner, repo.Name, link)
if err == nil && old != nil {
client.Repositories.DeleteHook(repo.Owner, repo.Name, *old.ID)
Expand All @@ -196,12 +193,24 @@ func (g *Github) SetHook(user *model.User, repo *model.Repo, link string) error
return err
}

repo_, _, err := client.Repositories.Get(repo.Owner, repo.Name)
if err != nil {
return err
}

in := new(Branch)
in.Protection.Enabled = true
in.Protection.Checks.Enforcement = "non_admins"
in.Protection.Checks.Contexts = []string{context}

/*
JCB 04/21/16 confirmed with Github support -- must specify all existing contexts when
adding a new one, otherwise the other contexts will be removed.
*/
client_ := NewClientToken(g.API, user.Token)
branch, _ := client_.Branch(repo.Owner, repo.Name, *repo_.DefaultBranch)

in.Protection.Checks.Contexts = append(buildOtherContextSlice(branch), context)

err = client_.BranchProtect(repo.Owner, repo.Name, *repo_.DefaultBranch, in)
if err != nil {
if g.URL == "https://github.com" {
Expand Down Expand Up @@ -236,14 +245,21 @@ func (g *Github) DelHook(user *model.User, repo *model.Repo, link string) error
if len(branch.Protection.Checks.Contexts) == 0 {
return nil
}

branch.Protection.Checks.Contexts = buildOtherContextSlice(branch)

return client_.BranchProtect(repo.Owner, repo.Name, *repo_.DefaultBranch, branch)
}

// buildOtherContextSlice returns all contexts besides the one for LGTM
func buildOtherContextSlice(branch *Branch) []string {
checks := []string{}
for _, check := range branch.Protection.Checks.Contexts {
if check != context {
checks = append(checks, check)
}
}
branch.Protection.Checks.Contexts = checks
return client_.BranchProtect(repo.Owner, repo.Name, *repo_.DefaultBranch, branch)
return checks
}

func (g *Github) GetComments(u *model.User, r *model.Repo, num int) ([]*model.Comment, error) {
Expand Down Expand Up @@ -330,3 +346,162 @@ func (g *Github) GetHook(r *http.Request) (*model.Hook, error) {

return hook, nil
}

func (g *Github) GetStatusHook(r *http.Request) (*model.StatusHook, error) {

// only process comment hooks
if r.Header.Get("X-Github-Event") != "status" {
return nil, nil
}

data := statusHook{}
err := json.NewDecoder(r.Body).Decode(&data)
if err != nil {
return nil, err
}

log.Debug(data)

if data.State != "success" {
return nil, nil
}

hook := new(model.StatusHook)

hook.SHA = data.SHA

hook.Repo = new(model.Repo)
hook.Repo.Owner = data.Repository.Owner.Login
hook.Repo.Name = data.Repository.Name
hook.Repo.Slug = data.Repository.FullName

log.Debug(*hook)

return hook, nil
}

func (g *Github) GetPullRequestsForCommit(u *model.User, r *model.Repo, sha *string) ([]model.PullRequest, error) {
client := setupClient(g.API, u.Token)
fmt.Println("sha == ", sha, *sha)
issues, _, err := client.Search.Issues(fmt.Sprintf("%s&type=pr", *sha), &github.SearchOptions {
TextMatch: false,
})
if err != nil {
return nil, err
}
out := make([]model.PullRequest, len(issues.Issues))
for k, v := range issues.Issues {
pr, _, err := client.PullRequests.Get(r.Owner, r.Name, *v.Number)
if err != nil {
return nil, err
}

mergeable:= true
if pr.Mergeable != nil {
mergeable = *pr.Mergeable
}

status, _, err := client.Repositories.GetCombinedStatus(r.Owner, r.Name, *sha, nil)
if err != nil {
return nil, err
}

out[k] = model.PullRequest{
Issue: model.Issue{
Number: *v.Number,
Title: *v.Title,
Author: *v.User.Login,
},
Branch: model.Branch {
Name: *pr.Head.Ref,
BranchStatus: *status.State,
Mergeable: mergeable,

},
}
}
return out, nil
}

func (g *Github) GetBranchStatus(u *model.User, r *model.Repo, branch string) (*model.BranchStatus, error) {
client := setupClient(g.API, u.Token)
statuses, _, err := client.Repositories.GetCombinedStatus(r.Owner, r.Name, branch, nil)
if err != nil {
return nil, err
}

return (*model.BranchStatus)(statuses.State), nil
}

func (g *Github) MergePR(u *model.User, r *model.Repo, pullRequest model.PullRequest) (*string, error) {
client := setupClient(g.API, u.Token)

result, _, err := client.PullRequests.Merge(r.Owner, r.Name, pullRequest.Number, "Merged by LGTM")
if err != nil {
return nil, err
}

if !(*result.Merged) {
return nil, errors.New(*result.Message)
}
return result.SHA, nil
}

func (g *Github) GetMaxExistingTag(u *model.User, r *model.Repo) (*version.Version, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm planning on combining the remote packages from drone, lgtm and aircover so I'm going to want to keep some of this generic. Let's instead expose a GetTagList function to return a list.

We can then add a helper function to separately get the max. The added benefit here is that if we ever support additional remotes (like gogs, etc) we don't have to duplicate the max logic in each remote.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for the helper function, perhaps follow a pattern similar to this, adding a GetMaxTag helper to the remote package that wraps GetTagList
https://github.com/lgtmco/lgtm/blob/master/cache/helper.go

client := setupClient(g.API, u.Token)

//find the previous largest semver value
var maxVer *version.Version

tags, _, err := client.Repositories.ListTags(r.Owner, r.Name, nil)
if err != nil {
return nil, err
}
for _, v := range tags {
curVer, err := version.NewVersion(*v.Name)
if err != nil {
continue
}
if maxVer == nil || curVer.GreaterThan(maxVer) {
maxVer = curVer
}
}

if maxVer == nil {
maxVer, _ = version.NewVersion("v0.0.0")
}
log.Debugf("maxVer found is %s", maxVer.String())
return maxVer, nil
}

func (g *Github) Tag(u *model.User, r *model.Repo, version *version.Version, sha *string) error {
client := setupClient(g.API, u.Token)

t := time.Now()
tag, _, err := client.Git.CreateTag(r.Owner, r.Name, &github.Tag{
Tag: github.String(version.String()),
SHA: sha,
Message: github.String("Tagged by LGTM"),
Tagger: &github.CommitAuthor{
Date: &t,
Name: github.String("LGTM"),
Email: github.String("[email protected]"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also maybe make this configurable as part of the github configuration? Not sure if people will care / want to customize or not. Open to suggestions here. If not, we can ignore the idea

},
Object: &github.GitObject{
SHA: sha,
Type: github.String("commit"),
},
})

if err != nil {
return err
}
_, _, err = client.Git.CreateRef(r.Owner, r.Name, &github.Reference{
Ref: github.String("refs/tags/" + version.String()),
Object: &github.GitObject{
SHA: tag.SHA,
},
})

return err
}
39 changes: 28 additions & 11 deletions remote/github/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,32 @@ type commentHook struct {
} `json:"user"`
} `json:"comment"`

Repository struct {
Name string `json:"name"`
FullName string `json:"full_name"`
Desc string `json:"description"`
Private bool `json:"private"`
Owner struct {
Login string `json:"login"`
Type string `json:"type"`
Avatar string `json:"avatar_url"`
} `json:"owner"`
} `json:"repository"`
Repository Repository `json:"repository"`
}

type statusHook struct {
SHA string `json:"sha"`
State string `json:"state"`

Branches []struct {
Name string `json:"name"`
Commit struct {
SHA string `json:"sha"`
URL string `json:"url"`
} `json:"commit"`
} `json:"branches"`

Repository Repository `json:"repository"`
}

type Repository struct {
Name string `json:"name"`
FullName string `json:"full_name"`
Desc string `json:"description"`
Private bool `json:"private"`
Owner struct {
Login string `json:"login"`
Type string `json:"type"`
Avatar string `json:"avatar_url"`
} `json:"owner"`
}
4 changes: 2 additions & 2 deletions remote/github/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,12 @@ func DeleteHook(client *github.Client, owner, name, url string) error {
return err
}

// CreateHook is a heper function that creates a post-commit hook
// CreateHook is a helper function that creates a post-commit hook
// for the specified repository.
func CreateHook(client *github.Client, owner, name, url string) (*github.Hook, error) {
var hook = new(github.Hook)
hook.Name = github.String("web")
hook.Events = []string{"issue_comment"}
hook.Events = []string{"issue_comment", "status"}
hook.Config = map[string]interface{}{}
hook.Config["url"] = url
hook.Config["content_type"] = "json"
Expand Down
Loading