GitLab service interface

This commit is contained in:
Kirill Zaitsev 2015-08-22 06:32:22 +03:00 committed by Kirilll Zaitsev
parent 370e53da1e
commit fd98e26575
21 changed files with 629 additions and 80 deletions

2
Godeps/Godeps.json generated
View file

@ -12,7 +12,7 @@
}, },
{ {
"ImportPath": "github.com/Bugagazavr/go-gitlab-client", "ImportPath": "github.com/Bugagazavr/go-gitlab-client",
"Rev": "912567bb7e65212c910733b3bfa178b11049a70e" "Rev": "fa361f26087a2ff8fbb267fbe2d82037fc35e51a"
}, },
{ {
"ImportPath": "github.com/BurntSushi/migration", "ImportPath": "github.com/BurntSushi/migration",

View file

@ -18,6 +18,9 @@ go-gitlab-client is a simple client written in golang to consume gitlab API.
###Projects [gitlab api doc](http://doc.gitlab.com/ce/api/projects.html) ###Projects [gitlab api doc](http://doc.gitlab.com/ce/api/projects.html)
* list projects * list projects
* get single project * get single project
* list project merge requests
* list notes on merge requests
* add comments to merge requests
* *
###Repositories [gitlab api doc](http://doc.gitlab.com/ce/api/repositories.html) ###Repositories [gitlab api doc](http://doc.gitlab.com/ce/api/repositories.html)

View file

@ -41,11 +41,26 @@ func main() {
" > -m hooks -id PROJECT_ID\n"+ " > -m hooks -id PROJECT_ID\n"+
" > -m branches -id PROJECT_ID\n"+ " > -m branches -id PROJECT_ID\n"+
" > -m merge_requests -id PROJECT_ID\n"+ " > -m merge_requests -id PROJECT_ID\n"+
" > -m team -id PROJECT_ID") " > -m merge_request_notes -id PROJECT_ID -merge_id MERGE_REQUEST_ID\n"+
" > -m merge_request_comment -id PROJECT_ID -merge_id MERGE_REQUEST_ID -comment COMMENT_BODY\n"+
" > -m team -id PROJECT_ID\n"+
" > -m add_drone -id PROJECT_ID\n -token DRONE_TOKEN -url DRONE_URL")
var id string var id string
flag.StringVar(&id, "id", "", "Specify repository id") flag.StringVar(&id, "id", "", "Specify repository id")
var merge_id string
flag.StringVar(&merge_id, "merge_id", "", "Specify merge request id")
var comment string
flag.StringVar(&comment, "comment", "", "The body of the new comment")
var drone_token string
flag.StringVar(&drone_token, "drone_token", "", "Drone service token")
var drone_url string
flag.StringVar(&drone_url, "drone_url", "", "Drone service url")
flag.Usage = func() { flag.Usage = func() {
fmt.Printf("Usage:\n") fmt.Printf("Usage:\n")
flag.PrintDefaults() flag.PrintDefaults()
@ -151,11 +166,54 @@ func main() {
if mr.Assignee != nil { if mr.Assignee != nil {
assignee = mr.Assignee.Username assignee = mr.Assignee.Username
} }
fmt.Printf(" %s -> %s [%s] author[%s] assignee[%s]\n", fmt.Printf(" (#%d) %s -> %s [%s] author[%s] assignee[%s]\n",
mr.SourceBranch, mr.TargetBranch, mr.State, mr.Id, mr.SourceBranch, mr.TargetBranch, mr.State,
author, assignee) author, assignee)
} }
case "merge_request_notes":
fmt.Println("Fetching merge_request notes…")
if id == "" {
flag.Usage()
return
}
notes, err := gitlab.MergeRequestNotes(id, merge_id, 0, 30)
if err != nil {
fmt.Println(err.Error())
return
}
for _, note := range notes {
author := ""
if note.Author != nil {
author = note.Author.Username
}
fmt.Printf(" [%d] author: %s <%s> %s\n",
note.Id, author, note.CreatedAt, note.Body)
}
case "merge_request_comment":
fmt.Println("Sending new merge_request comment…")
if id == "" {
flag.Usage()
return
}
note, err := gitlab.SendMergeRequestComment(id, merge_id, comment)
if err != nil {
fmt.Println(err.Error())
return
}
author := ""
if note.Author != nil {
author = note.Author.Username
}
fmt.Printf(" [%d] author: %s <%s> %s\n",
note.Id, author, note.CreatedAt, note.Body)
case "hooks": case "hooks":
fmt.Println("Fetching project hooks…") fmt.Println("Fetching project hooks…")
@ -191,5 +249,17 @@ func main() {
for _, member := range members { for _, member := range members {
fmt.Printf("> [%d] %s (%s) since %s\n", member.Id, member.Username, member.Name, member.CreatedAt) fmt.Printf("> [%d] %s (%s) since %s\n", member.Id, member.Username, member.Name, member.CreatedAt)
} }
case "add_drone":
fmt.Println("Adding drone service to project")
if id == "" || drone_token == "" || drone_url == "" {
flag.Usage()
return
}
if err := gitlab.AddDroneService(id, map[string]string{"token": drone_token, "drone_url": drone_url}); err != nil {
fmt.Println(err)
return
}
} }
} }

View file

@ -36,11 +36,19 @@ func main() {
" > branches\n"+ " > branches\n"+
" > branch\n"+ " > branch\n"+
" > tags\n"+ " > tags\n"+
" > commits") " > commits\n"+
" > commit_comments -sha COMMIT_SHA\n"+
" > comment_a_commit -sha COMMIT_SHA -comment COMMENT_BODY")
var id string var id string
flag.StringVar(&id, "id", "", "Specify repository id") flag.StringVar(&id, "id", "", "Specify repository id")
var sha string
flag.StringVar(&sha, "sha", "", "Specify commit sha")
var comment string
flag.StringVar(&comment, "comment", "", "The body of the new comment")
flag.Usage = func() { flag.Usage = func() {
fmt.Printf("Usage:\n") fmt.Printf("Usage:\n")
flag.PrintDefaults() flag.PrintDefaults()
@ -90,7 +98,27 @@ func main() {
} }
for _, commit := range commits { for _, commit := range commits {
fmt.Printf("%s > [%s] %s\n", commit.CreatedAt.Format("Mon 02 Jan 15:04"), commit.Author_Name, commit.Title) fmt.Printf("(%s) %s > [%s] %s\n", commit.Id, commit.CreatedAt.Format("Mon 02 Jan 15:04"), commit.Author_Name, commit.Title)
} }
case "commit_comments":
fmt.Println("Fetching comments on a repository commit…")
comments, err := gitlab.RepoCommitComments(id, sha)
if err != nil {
fmt.Println(err.Error())
}
for _, c := range comments {
fmt.Printf("[%s] %s\n", c.Author.Username, c.Note)
}
case "comment_a_commit":
fmt.Println("Sending a new comment on a repository commit…")
c, err := gitlab.SendRepoCommitComment(id, sha, comment)
if err != nil {
fmt.Println(err.Error())
}
fmt.Printf("[%s] %s\n", c.Author.Username, c.Note)
} }
} }

View file

@ -128,6 +128,35 @@ func (g *Gitlab) ResourceUrlQuery(u string, params, query map[string]string) str
} }
func (g *Gitlab) ResourceUrlQueryRaw(u string, params, query map[string]string) (string, string) {
if params != nil {
for key, val := range params {
u = strings.Replace(u, key, encodeParameter(val), -1)
}
}
query_params := url.Values{}
if !g.Bearer {
query_params.Add("private_token", g.Token)
}
if query != nil {
for key, val := range query {
query_params.Set(key, val)
}
}
u = g.BaseUrl + g.ApiPath + u + "?" + query_params.Encode()
p, err := url.Parse(u)
if err != nil {
return u, ""
}
opaque := "//" + p.Host + p.Path
return u, opaque
}
func (g *Gitlab) ResourceUrlRaw(u string, params map[string]string) (string, string) { func (g *Gitlab) ResourceUrlRaw(u string, params map[string]string) (string, string) {
if params != nil { if params != nil {

View file

@ -27,6 +27,7 @@ type HookObjAttr struct {
StDiffs string `json:"st_diffs,omitempty"` StDiffs string `json:"st_diffs,omitempty"`
MergeStatus string `json:"merge_status,omitempty"` MergeStatus string `json:"merge_status,omitempty"`
TargetProjectId int `json:"target_project_id,omitempty"` TargetProjectId int `json:"target_project_id,omitempty"`
URL string `json:"url,omitempty"`
Source *hProject `json:"source,omitempty"` Source *hProject `json:"source,omitempty"`
Target *hProject `json:"target,omitempty"` Target *hProject `json:"target,omitempty"`
LastCommit *hCommit `json:"last_commit,omitempty"` LastCommit *hCommit `json:"last_commit,omitempty"`

View file

@ -88,13 +88,13 @@ Parameters:
merge_requests_events Trigger hook on merge_requests events merge_requests_events Trigger hook on merge_requests events
*/ */
func (g *Gitlab) AddProjectHook(id, hook_url string, push_events, issues_events, merge_requests_events bool) error { func (g *Gitlab) AddProjectHook(id, hook_url string, push_events, issues_events, merge_requests_events, tag_events bool) error {
url, opaque := g.ResourceUrlRaw(project_url_hooks, map[string]string{":id": id}) url, opaque := g.ResourceUrlRaw(project_url_hooks, map[string]string{":id": id})
var err error var err error
body := buildHookQuery(hook_url, push_events, issues_events, merge_requests_events) body := buildHookQuery(hook_url, push_events, issues_events, merge_requests_events, tag_events)
_, err = g.buildAndExecRequestRaw("POST", url, opaque, []byte(body)) _, err = g.buildAndExecRequestRaw("POST", url, opaque, []byte(body))
return err return err
@ -115,7 +115,7 @@ Parameters:
merge_requests_events Trigger hook on merge_requests events merge_requests_events Trigger hook on merge_requests events
*/ */
func (g *Gitlab) EditProjectHook(id, hook_id, hook_url string, push_events, issues_events, merge_requests_events bool) error { func (g *Gitlab) EditProjectHook(id, hook_id, hook_url string, push_events, issues_events, merge_requests_events, tag_events bool) error {
url, opaque := g.ResourceUrlRaw(project_url_hook, map[string]string{ url, opaque := g.ResourceUrlRaw(project_url_hook, map[string]string{
":id": id, ":id": id,
@ -124,7 +124,7 @@ func (g *Gitlab) EditProjectHook(id, hook_id, hook_url string, push_events, issu
var err error var err error
body := buildHookQuery(hook_url, push_events, issues_events, merge_requests_events) body := buildHookQuery(hook_url, push_events, issues_events, merge_requests_events, tag_events)
_, err = g.buildAndExecRequestRaw("PUT", url, opaque, []byte(body)) _, err = g.buildAndExecRequestRaw("PUT", url, opaque, []byte(body))
return err return err
@ -158,7 +158,7 @@ func (g *Gitlab) RemoveProjectHook(id, hook_id string) error {
/* /*
Build HTTP query to add or edit hook Build HTTP query to add or edit hook
*/ */
func buildHookQuery(hook_url string, push_events, issues_events, merge_requests_events bool) string { func buildHookQuery(hook_url string, push_events, issues_events, merge_requests_events, tag_events bool) string {
v := url.Values{} v := url.Values{}
v.Set("url", hook_url) v.Set("url", hook_url)
@ -178,6 +178,10 @@ func buildHookQuery(hook_url string, push_events, issues_events, merge_requests_
} else { } else {
v.Set("merge_requests_events", "false") v.Set("merge_requests_events", "false")
} }
if tag_events {
v.Set("tag_push_events", "true")
} else {
v.Set("tag_push_events", "false")
}
return v.Encode() return v.Encode()
} }

View file

@ -2,6 +2,7 @@ package gogitlab
import ( import (
"encoding/json" "encoding/json"
"fmt"
"strconv" "strconv"
"strings" "strings"
) )
@ -15,6 +16,7 @@ const (
project_url_members = "/projects/:id/members" // List project team members project_url_members = "/projects/:id/members" // List project team members
project_url_member = "/projects/:id/members/:user_id" // Get project team member project_url_member = "/projects/:id/members/:user_id" // Get project team member
project_url_merge_requests = "/projects/:id/merge_requests" // List all merge requests of a project project_url_merge_requests = "/projects/:id/merge_requests" // List all merge requests of a project
merge_request_url_notes = "/projects/:id/merge_requests/:merge_request_id/notes" // Manage comments for a given merge request
) )
type Member struct { type Member struct {
@ -89,6 +91,14 @@ type MergeRequest struct {
Description string `json:"description,omitempty"` Description string `json:"description,omitempty"`
} }
type MergeRequestNote struct {
Attachment interface{} `json:"attachment"`
Body string `json:"body"`
CreatedAt string `json:"created_at"`
Id int `json:"id"`
Author *Member `json:"author"`
}
/* /*
Get a list of all projects owned by the authenticated user. Get a list of all projects owned by the authenticated user.
*/ */
@ -209,6 +219,43 @@ func (g *Gitlab) ProjectMergeRequests(id string, page int, per_page int, state s
return mr, err return mr, err
} }
/*
Lists all comments on merge request.
*/
func (g *Gitlab) MergeRequestNotes(id string, merge_request_id string, page int, per_page int) ([]*MergeRequestNote, error) {
par := map[string]string{":id": id, ":merge_request_id": merge_request_id}
qry := map[string]string{
"page": strconv.Itoa(page),
"per_page": strconv.Itoa(per_page)}
url := g.ResourceUrlQuery(merge_request_url_notes, par, qry)
var mr []*MergeRequestNote
contents, err := g.buildAndExecRequest("GET", url, nil)
if err == nil {
err = json.Unmarshal(contents, &mr)
}
return mr, err
}
/*
Creates a new comment on a merge request.
*/
func (g *Gitlab) SendMergeRequestComment(id string, merge_request_id string, comment string) (*MergeRequestNote, error) {
par := map[string]string{":id": id, ":merge_request_id": merge_request_id}
url := g.ResourceUrlQuery(merge_request_url_notes, par, map[string]string{})
var mr *MergeRequestNote
contents, err := g.buildAndExecRequest("POST", url, []byte(fmt.Sprintf("body=%s", comment)))
if err == nil {
err = json.Unmarshal(contents, &mr)
}
return mr, err
}
/* /*
Get single project id. Get single project id.
@ -236,7 +283,7 @@ func (g *Gitlab) SearchProjectId(namespace string, name string) (id int, err err
} }
for _, project := range projects { for _, project := range projects {
if project.Namespace.Name == namespace { if project.Namespace.Name == namespace && strings.ToLower(project.Name) == strings.ToLower(name) {
id = project.Id id = project.Id
} }
} }

View file

@ -57,6 +57,21 @@ func TestProjectMergeRequests(t *testing.T) {
} }
} }
func TestMergeRequestNotes(t *testing.T) {
ts, gitlab := Stub("stubs/projects/merge_requests/notes/index.json")
defer ts.Close()
notes, err := gitlab.MergeRequestNotes("1", "1", 0, 30)
assert.Equal(t, err, nil)
assert.Equal(t, len(notes), 1)
if len(notes) > 0 {
assert.Equal(t, notes[0].Id, 301)
assert.Equal(t, notes[0].Body, "Comment for MR")
assert.Equal(t, notes[0].Author.Username, "pipin")
}
}
func TestSearchProjectId(t *testing.T) { func TestSearchProjectId(t *testing.T) {
ts, gitlab := Stub("stubs/projects/index.json") ts, gitlab := Stub("stubs/projects/index.json")

View file

@ -2,6 +2,7 @@ package gogitlab
import ( import (
"encoding/json" "encoding/json"
"fmt"
"net/url" "net/url"
"time" "time"
) )
@ -11,6 +12,7 @@ const (
repo_url_branch = "/projects/:id/repository/branches/:branch" // Get a specific branch of a project. repo_url_branch = "/projects/:id/repository/branches/:branch" // Get a specific branch of a project.
repo_url_tags = "/projects/:id/repository/tags" // List project repository tags repo_url_tags = "/projects/:id/repository/tags" // List project repository tags
repo_url_commits = "/projects/:id/repository/commits" // List repository commits repo_url_commits = "/projects/:id/repository/commits" // List repository commits
repo_url_commit_comments = "/projects/:id/repository/commits/:sha/comments" // New comment or list of commit comments
repo_url_tree = "/projects/:id/repository/tree" // List repository tree repo_url_tree = "/projects/:id/repository/tree" // List repository tree
repo_url_raw_file = "/projects/:id/repository/blobs/:sha" // Get raw file content for specific commit/branch repo_url_raw_file = "/projects/:id/repository/blobs/:sha" // Get raw file content for specific commit/branch
) )
@ -62,6 +64,14 @@ type File struct {
Children []*File Children []*File
} }
type CommitComment struct {
Author *Member `json:"author,omitempty"`
Line int `json:"line,omitempty"`
LineType string `json:"line_type,omitempty"`
Note string `json:"note,omitempty"`
Path string `json:"path,omitempty"`
}
/* /*
Get a list of repository branches from a project, sorted by name alphabetically. Get a list of repository branches from a project, sorted by name alphabetically.
@ -195,6 +205,73 @@ func (g *Gitlab) RepoCommits(id string) ([]*Commit, error) {
return commits, err return commits, err
} }
/*
Get a list of comments in a repository commit.
GET /projects/:id/repository/commits/:sha/comments
Parameters:
id The ID of a project
sha The sha of the commit
Usage:
comments, err := gitlab.RepoCommitComments("your_projet_id", "commit_sha")
if err != nil {
fmt.Println(err.Error())
}
for _, comment := range comments {
fmt.Printf("%+v\n", comment)
}
*/
func (g *Gitlab) RepoCommitComments(id string, sha string) ([]*CommitComment, error) {
url, opaque := g.ResourceUrlRaw(repo_url_commit_comments, map[string]string{":id": id, ":sha": sha})
var comments []*CommitComment
contents, err := g.buildAndExecRequestRaw("GET", url, opaque, nil)
if err == nil {
err = json.Unmarshal(contents, &comments)
}
return comments, err
}
/*
Create a comment in a repository commit.
POST /projects/:id/repository/commits/:sha/comments
Parameters:
id The ID of a project
sha The sha of the commit
body The body of the comment
Usage:
comment, err := gitlab.SendRepoCommitComment("your_projet_id", "commit_sha", "your comment goes here")
if err != nil {
fmt.Println(err.Error())
}
fmt.Printf("%+v\n", comment)
*/
func (g *Gitlab) SendRepoCommitComment(id string, sha string, body string) (*CommitComment, error) {
url, opaque := g.ResourceUrlRaw(repo_url_commit_comments, map[string]string{":id": id, ":sha": sha})
var comment *CommitComment
contents, err := g.buildAndExecRequestRaw("POST", url, opaque, []byte(fmt.Sprintf("note=%s", body)))
if err == nil {
err = json.Unmarshal(contents, &comment)
}
return comment, err
}
/* /*
Get Raw file content Get Raw file content
*/ */

View file

@ -41,3 +41,12 @@ func TestRepoCommits(t *testing.T) {
assert.Equal(t, len(commits), 2) assert.Equal(t, len(commits), 2)
defer ts.Close() defer ts.Close()
} }
func TestRepoCommitComments(t *testing.T) {
ts, gitlab := Stub("stubs/commits/comments/index.json")
comments, err := gitlab.RepoCommitComments("1", "a9e6a5io4e695923c995ed2e836789b50oi77e0b")
assert.Equal(t, err, nil)
assert.Equal(t, len(comments), 1)
defer ts.Close()
}

View file

@ -0,0 +1,19 @@
package gogitlab
const (
drone_service_url = "/projects/:id/services/drone-ci"
)
func (g *Gitlab) AddDroneService(id string, params map[string]string) error {
url, opaque := g.ResourceUrlQueryRaw(drone_service_url, map[string]string{":id": id}, params)
_, err := g.buildAndExecRequestRaw("PUT", url, opaque, nil)
return err
}
func (g *Gitlab) DeleteDroneService(id string) error {
url, opaque := g.ResourceUrlQueryRaw(drone_service_url, map[string]string{":id": id}, nil)
_, err := g.buildAndExecRequestRaw("DELETE", url, opaque, nil)
return err
}

View file

@ -0,0 +1,16 @@
[
{
"author": {
"id": 1,
"username": "admin",
"email": "admin@local.host",
"name": "Administrator",
"blocked": false,
"created_at": "2012-04-29T08:46:00Z"
},
"note": "text1",
"path": "example.rb",
"line": 5,
"line_type": "new"
}
]

View file

@ -0,0 +1,16 @@
[
{
"id": 301,
"body": "Comment for MR",
"attachment": null,
"author": {
"id": 1,
"username": "pipin",
"email": "admin@example.com",
"name": "Pip",
"state": "active",
"created_at": "2013-09-30T13:46:01Z"
},
"created_at": "2013-10-02T08:57:14Z"
}
]

View file

@ -185,6 +185,14 @@ func main() {
repo.GET("/logs/:number/:task", server.GetLogs) repo.GET("/logs/:number/:task", server.GetLogs)
// repo.POST("/status/:number", server.PostBuildStatus) // repo.POST("/status/:number", server.PostBuildStatus)
} }
// Routes for external services
repoExternal := repos.Group("")
{
repoExternal.Use(server.SetRepo())
repoExternal.GET("/pr/:number", server.GetPullRequest)
}
} }
badges := api.Group("/badges/:owner/:name") badges := api.Group("/badges/:owner/:name")
@ -236,6 +244,14 @@ func main() {
auth.POST("", server.GetLogin) auth.POST("", server.GetLogin)
} }
redirects := r.Group("/redirect")
{
redirects.Use(server.SetDatastore(store))
redirects.Use(server.SetRepo())
redirects.GET("/:owner/:name/commit/:sha", server.RedirectSha)
}
r.SetHTMLTemplate(index()) r.SetHTMLTemplate(index())
r.NoRoute(func(c *gin.Context) { r.NoRoute(func(c *gin.Context) {
c.HTML(200, "index.html", nil) c.HTML(200, "index.html", nil)

View file

@ -6,8 +6,10 @@ import (
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"regexp"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/drone/drone/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client" "github.com/drone/drone/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client"
"github.com/drone/drone/Godeps/_workspace/src/github.com/hashicorp/golang-lru" "github.com/drone/drone/Godeps/_workspace/src/github.com/hashicorp/golang-lru"
@ -190,25 +192,15 @@ func (r *Gitlab) Activate(user *common.User, repo *common.Repo, k *common.Keypai
return err return err
} }
title, err := GetKeyTitle(link) uri, err := url.Parse(link)
if err != nil { if err != nil {
return err return err
} }
// if the repository is private we'll need droneUrl := fmt.Sprintf("%s://%s", uri.Scheme, uri.Host)
// to upload a github key to the repository droneToken := uri.Query().Get("access_token")
if repo.Private {
if err := client.AddProjectDeployKey(id, title, k.Public); err != nil {
return err
}
}
// append the repo owner / name to the hook url since gitlab return client.AddDroneService(id, map[string]string{"token": droneToken, "drone_url": droneUrl})
// doesn't send this detail in the post-commit hook
link += "&owner=" + repo.Owner + "&name=" + repo.Name
// add the hook
return client.AddProjectHook(id, link, true, false, true)
} }
// Deactivate removes a repository by removing all the post-commit hooks // Deactivate removes a repository by removing all the post-commit hooks
@ -220,33 +212,7 @@ func (r *Gitlab) Deactivate(user *common.User, repo *common.Repo, link string) e
return err return err
} }
keys, err := client.ProjectDeployKeys(id) return client.DeleteDroneService(id)
if err != nil {
return err
}
var pubkey = strings.TrimSpace(repo.Keys.Public)
for _, k := range keys {
if pubkey == strings.TrimSpace(k.Key) {
if err := client.RemoveProjectDeployKey(id, strconv.Itoa(k.Id)); err != nil {
return err
}
break
}
}
hooks, err := client.ProjectHooks(id)
if err != nil {
return err
}
link += "&owner=" + repo.Owner + "&name=" + repo.Name
for _, h := range hooks {
if link == h.Url {
if err := client.RemoveProjectHook(id, strconv.Itoa(h.Id)); err != nil {
return err
}
break
}
}
return nil
} }
// ParseHook parses the post-commit hook from the Request body // ParseHook parses the post-commit hook from the Request body
@ -259,20 +225,65 @@ func (r *Gitlab) Hook(req *http.Request) (*common.Hook, error) {
return nil, err return nil, err
} }
switch parsed.ObjectKind {
case "merge_request":
if (parsed.ObjectAttributes.State != "reopened" || parsed.ObjectAttributes.State != "opened") && parsed.ObjectAttributes.MergeStatus != "unchecked" {
return nil, nil
}
return mergeRequest(parsed, req)
case "push":
if len(parsed.After) == 0 || parsed.TotalCommitsCount == 0 { if len(parsed.After) == 0 || parsed.TotalCommitsCount == 0 {
return nil, nil return nil, nil
} }
if parsed.ObjectKind == "merge_request" { return push(parsed, req)
// NOTE: in gitlab 8.0, gitlab will get same MR models as github default:
// https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/981/diffs
return nil, nil return nil, nil
} }
if len(parsed.After) == 0 {
return nil, nil
} }
func mergeRequest(parsed *gogitlab.HookPayload, req *http.Request) (*common.Hook, error) {
var hook = new(common.Hook)
re := regexp.MustCompile(".git$")
hook.Repo = &common.Repo{}
hook.Repo.Owner = req.FormValue("owner")
hook.Repo.Name = req.FormValue("name")
hook.Repo.FullName = fmt.Sprintf("%s/%s", hook.Repo.Owner, hook.Repo.Name)
hook.Repo.Link = re.ReplaceAllString(parsed.ObjectAttributes.Target.HttpUrl, "$1")
hook.Repo.Clone = parsed.ObjectAttributes.Target.HttpUrl
hook.Repo.Branch = "master"
hook.Commit = &common.Commit{}
hook.Commit.Message = parsed.ObjectAttributes.LastCommit.Message
hook.Commit.Sha = parsed.ObjectAttributes.LastCommit.Id
hook.Commit.Remote = parsed.ObjectAttributes.Source.HttpUrl
if parsed.ObjectAttributes.Source.HttpUrl == parsed.ObjectAttributes.Target.HttpUrl {
hook.Commit.Ref = fmt.Sprintf("refs/heads/%s", parsed.ObjectAttributes.SourceBranch)
hook.Commit.Branch = parsed.ObjectAttributes.SourceBranch
hook.Commit.Timestamp = time.Now().UTC().Format("2006-01-02 15:04:05.000000000 +0000 MST")
} else {
hook.Commit.Ref = fmt.Sprintf("refs/merge-requests/%d/head", parsed.ObjectAttributes.IId)
hook.Commit.Branch = parsed.ObjectAttributes.SourceBranch
hook.Commit.Timestamp = time.Now().UTC().Format("2006-01-02 15:04:05.000000000 +0000 MST")
}
hook.Commit.Author = &common.Author{}
hook.Commit.Author.Login = parsed.ObjectAttributes.LastCommit.Author.Name
hook.Commit.Author.Email = parsed.ObjectAttributes.LastCommit.Author.Email
hook.PullRequest = &common.PullRequest{}
hook.PullRequest.Number = parsed.ObjectAttributes.IId
hook.PullRequest.Title = parsed.ObjectAttributes.Description
hook.PullRequest.Link = parsed.ObjectAttributes.URL
return hook, nil
}
func push(parsed *gogitlab.HookPayload, req *http.Request) (*common.Hook, error) {
var cloneUrl = parsed.Repository.GitHttpUrl var cloneUrl = parsed.Repository.GitHttpUrl
var hook = new(common.Hook) var hook = new(common.Hook)

View file

@ -41,6 +41,40 @@ func GetCommit(c *gin.Context) {
} }
} }
// GetPullRequest accepts a requests to retvie a pull request
// from the datastore for the given repository and
// pull request number
//
// GET /api/repos/:owner/:name/pr/:number
//
func GetPullRequest(c *gin.Context) {
store := ToDatastore(c)
repo := ToRepo(c)
// get the token and verify the hook is authorized
if c.Request.FormValue("access_token") != hash(repo.FullName, repo.Hash) {
c.AbortWithStatus(403)
return
}
num, err := strconv.Atoi(c.Params.ByName("number"))
if err != nil {
c.Fail(400, err)
return
}
build, err := store.BuildPullRequestNumber(repo, num)
if err != nil {
c.Fail(404, err)
return
}
build.Jobs, err = store.JobList(build)
if err != nil {
c.Fail(404, err)
} else {
c.JSON(200, build)
}
}
// GetCommits accepts a request to retrieve a list // GetCommits accepts a request to retrieve a list
// of commits from the datastore for the given repository. // of commits from the datastore for the given repository.
// //

61
pkg/server/redirect.go Normal file
View file

@ -0,0 +1,61 @@
package server
import (
"fmt"
"strconv"
"github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin"
)
// RedirectSha accepts a request to retvie a redirect
// to job from the datastore for the given repository
// and commit sha
//
// GET /redirect/:owner/:name/commit/:sha
//
func RedirectSha(c *gin.Context) {
var branch string
store := ToDatastore(c)
repo := ToRepo(c)
sha := c.Params.ByName("sha")
branch = c.Request.FormValue("branch")
if branch == "" {
branch = repo.Branch
}
build, err := store.BuildSha(repo, sha, branch)
if err != nil {
c.Redirect(301, "/")
return
}
c.Redirect(301, fmt.Sprintf("/%s/%s/%d", repo.Owner, repo.Name, build.ID))
return
}
// RedirectSha accepts a request to retvie a redirect
// to job from the datastore for the given repository
// and pull request number
//
// GET /redirect/:owner/:name/pr/:number
//
func RedirectPullRequest(c *gin.Context) {
store := ToDatastore(c)
repo := ToRepo(c)
num, err := strconv.Atoi(c.Params.ByName("number"))
if err != nil {
c.Redirect(301, "/")
return
}
build, err := store.BuildPullRequestNumber(repo, num)
if err != nil {
c.Redirect(301, "/")
return
}
c.Redirect(301, fmt.Sprintf("/%s/%s/%d", repo.Owner, repo.Name, build.ID))
return
}

View file

@ -25,6 +25,18 @@ func (db *Buildstore) BuildNumber(repo *types.Repo, seq int) (*types.Build, erro
return getBuild(db, rebind(stmtBuildSelectBuildNumber), repo.ID, seq) return getBuild(db, rebind(stmtBuildSelectBuildNumber), repo.ID, seq)
} }
// BuildPullRequest gets the specific build for the
// named repository and pull request number
func (db *Buildstore) BuildPullRequestNumber(repo *types.Repo, seq int) (*types.Build, error) {
return getBuild(db, rebind(stmtBuildSelectPullRequestNumber), repo.ID, seq)
}
// BuildSha gets the specific build for the
// named repository and sha
func (db *Buildstore) BuildSha(repo *types.Repo, sha, branch string) (*types.Build, error) {
return getBuild(db, rebind(stmtBuildSelectSha), repo.ID, sha, branch)
}
// BuildLast gets the last executed build for the // BuildLast gets the last executed build for the
// named repository. // named repository.
func (db *Buildstore) BuildLast(repo *types.Repo, branch string) (*types.Build, error) { func (db *Buildstore) BuildLast(repo *types.Repo, branch string) (*types.Build, error) {

View file

@ -596,6 +596,79 @@ WHERE build_repo_id = ?
AND build_number = ? AND build_number = ?
` `
const stmtBuildSelectPullRequestNumber = `
SELECT
build_id
,build_repo_id
,build_number
,build_status
,build_started
,build_finished
,build_commit_sha
,build_commit_ref
,build_commit_link
,build_commit_branch
,build_commit_message
,build_commit_timestamp
,build_commit_remote
,build_commit_author_login
,build_commit_author_email
,build_pull_request_number
,build_pull_request_title
,build_pull_request_link
,build_pull_request_base_sha
,build_pull_request_base_ref
,build_pull_request_base_link
,build_pull_request_base_branch
,build_pull_request_base_message
,build_pull_request_base_timestamp
,build_pull_request_base_remote
,build_pull_request_base_author_login
,build_pull_request_base_author_email
FROM builds
WHERE build_repo_id = ?
AND build_pull_request_number = ?
ORDER BY build_number DESC
LIMIT 1
`
const stmtBuildSelectSha = `
SELECT
build_id
,build_repo_id
,build_number
,build_status
,build_started
,build_finished
,build_commit_sha
,build_commit_ref
,build_commit_link
,build_commit_branch
,build_commit_message
,build_commit_timestamp
,build_commit_remote
,build_commit_author_login
,build_commit_author_email
,build_pull_request_number
,build_pull_request_title
,build_pull_request_link
,build_pull_request_base_sha
,build_pull_request_base_ref
,build_pull_request_base_link
,build_pull_request_base_branch
,build_pull_request_base_message
,build_pull_request_base_timestamp
,build_pull_request_base_remote
,build_pull_request_base_author_login
,build_pull_request_base_author_email
FROM builds
WHERE build_repo_id = ?
AND build_commit_sha = ?
AND build_commit_branch = ?
ORDER BY build_number DESC
LIMIT 1
`
const stmtBuildSelectCommitBranch = ` const stmtBuildSelectCommitBranch = `
SELECT SELECT
build_id build_id

View file

@ -131,6 +131,14 @@ type Store interface {
// named repository and build number // named repository and build number
BuildNumber(*types.Repo, int) (*types.Build, error) BuildNumber(*types.Repo, int) (*types.Build, error)
// BuildPullRequestNumber gets the specified build number for the
// named repository and build number
BuildPullRequestNumber(*types.Repo, int) (*types.Build, error)
// BuildSha gets the specified build number for the
// named repository and sha
BuildSha(*types.Repo, string, string) (*types.Build, error)
// BuildLast gets the last executed build for the // BuildLast gets the last executed build for the
// named repository and branch // named repository and branch
BuildLast(*types.Repo, string) (*types.Build, error) BuildLast(*types.Repo, string) (*types.Build, error)