mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2025-02-17 03:45:13 +00:00
significant improvement to GitHub remote coverage (from 5% to 50%)
This commit is contained in:
parent
a7d3873891
commit
4040dfceb8
13 changed files with 872 additions and 864 deletions
|
@ -1,6 +1,9 @@
|
||||||
package github
|
package github
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/drone/drone/model"
|
"github.com/drone/drone/model"
|
||||||
|
|
||||||
"github.com/google/go-github/github"
|
"github.com/google/go-github/github"
|
||||||
|
@ -22,6 +25,11 @@ const (
|
||||||
descError = "oops, something went wrong"
|
descError = "oops, something went wrong"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
headRefs = "refs/pull/%d/head" // pull request unmerged
|
||||||
|
mergeRefs = "refs/pull/%d/merge" // pull request merged with base
|
||||||
|
)
|
||||||
|
|
||||||
// convertStatus is a helper function used to convert a Drone status to a
|
// convertStatus is a helper function used to convert a Drone status to a
|
||||||
// GitHub commit status.
|
// GitHub commit status.
|
||||||
func convertStatus(status string) string {
|
func convertStatus(status string) string {
|
||||||
|
@ -124,3 +132,106 @@ func convertTeam(from github.Organization) *model.Team {
|
||||||
Avatar: *from.AvatarURL,
|
Avatar: *from.AvatarURL,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// convertRepoHook is a helper function used to extract the Repository details
|
||||||
|
// from a webhook and convert to the common Drone repository structure.
|
||||||
|
func convertRepoHook(from *webhook) *model.Repo {
|
||||||
|
repo := &model.Repo{
|
||||||
|
Owner: from.Repo.Owner.Login,
|
||||||
|
Name: from.Repo.Name,
|
||||||
|
FullName: from.Repo.FullName,
|
||||||
|
Link: from.Repo.HTMLURL,
|
||||||
|
IsPrivate: from.Repo.Private,
|
||||||
|
Clone: from.Repo.CloneURL,
|
||||||
|
Branch: from.Repo.DefaultBranch,
|
||||||
|
Kind: model.RepoGit,
|
||||||
|
}
|
||||||
|
if repo.Branch == "" {
|
||||||
|
repo.Branch = defaultBranch
|
||||||
|
}
|
||||||
|
if repo.Owner == "" { // legacy webhooks
|
||||||
|
repo.Owner = from.Repo.Owner.Name
|
||||||
|
}
|
||||||
|
if repo.FullName == "" {
|
||||||
|
repo.FullName = repo.Owner + "/" + repo.Name
|
||||||
|
}
|
||||||
|
return repo
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertPushHook is a helper function used to extract the Build details
|
||||||
|
// from a push webhook and convert to the common Drone Build structure.
|
||||||
|
func convertPushHook(from *webhook) *model.Build {
|
||||||
|
build := &model.Build{
|
||||||
|
Event: model.EventPush,
|
||||||
|
Commit: from.Head.ID,
|
||||||
|
Ref: from.Ref,
|
||||||
|
Link: from.Head.URL,
|
||||||
|
Branch: strings.Replace(from.Ref, "refs/heads/", "", -1),
|
||||||
|
Message: from.Head.Message,
|
||||||
|
Email: from.Head.Author.Email,
|
||||||
|
Avatar: from.Sender.Avatar,
|
||||||
|
Author: from.Sender.Login,
|
||||||
|
Remote: from.Repo.CloneURL,
|
||||||
|
}
|
||||||
|
if len(build.Author) == 0 {
|
||||||
|
build.Author = from.Head.Author.Username
|
||||||
|
}
|
||||||
|
if len(build.Email) == 0 {
|
||||||
|
// default to gravatar?
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(build.Ref, "refs/tags/") {
|
||||||
|
// just kidding, this is actually a tag event. Why did this come as a push
|
||||||
|
// event we'll never know!
|
||||||
|
build.Event = model.EventTag
|
||||||
|
}
|
||||||
|
return build
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertPushHook is a helper function used to extract the Build details
|
||||||
|
// from a deploy webhook and convert to the common Drone Build structure.
|
||||||
|
func convertDeployHook(from *webhook) *model.Build {
|
||||||
|
build := &model.Build{
|
||||||
|
Event: model.EventDeploy,
|
||||||
|
Commit: from.Deployment.Sha,
|
||||||
|
Link: from.Deployment.URL,
|
||||||
|
Message: from.Deployment.Desc,
|
||||||
|
Avatar: from.Sender.Avatar,
|
||||||
|
Author: from.Sender.Login,
|
||||||
|
Ref: from.Deployment.Ref,
|
||||||
|
Branch: from.Deployment.Ref,
|
||||||
|
Deploy: from.Deployment.Env,
|
||||||
|
}
|
||||||
|
// if the ref is a sha or short sha we need to manuallyconstruct the ref.
|
||||||
|
if strings.HasPrefix(build.Commit, build.Ref) || build.Commit == build.Ref {
|
||||||
|
build.Branch = from.Repo.DefaultBranch
|
||||||
|
if build.Branch == "" {
|
||||||
|
build.Branch = defaultBranch
|
||||||
|
}
|
||||||
|
build.Ref = fmt.Sprintf("refs/heads/%s", build.Branch)
|
||||||
|
}
|
||||||
|
// if the ref is a branch we should make sure it has refs/heads prefix
|
||||||
|
if !strings.HasPrefix(build.Ref, "refs/") { // branch or tag
|
||||||
|
build.Ref = fmt.Sprintf("refs/heads/%s", build.Branch)
|
||||||
|
}
|
||||||
|
return build
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertPullHook is a helper function used to extract the Build details
|
||||||
|
// from a pull request webhook and convert to the common Drone Build structure.
|
||||||
|
func convertPullHook(from *webhook, merge bool) *model.Build {
|
||||||
|
build := &model.Build{
|
||||||
|
Event: model.EventPull,
|
||||||
|
Commit: from.PullRequest.Head.SHA,
|
||||||
|
Link: from.PullRequest.HTMLURL,
|
||||||
|
Ref: fmt.Sprintf(headRefs, from.PullRequest.Number),
|
||||||
|
Branch: from.PullRequest.Head.Ref,
|
||||||
|
Message: from.PullRequest.Title,
|
||||||
|
Author: from.PullRequest.User.Login,
|
||||||
|
Avatar: from.PullRequest.User.Avatar,
|
||||||
|
Title: from.PullRequest.Title,
|
||||||
|
}
|
||||||
|
if merge {
|
||||||
|
build.Ref = fmt.Sprintf(mergeRefs, from.PullRequest.Number)
|
||||||
|
}
|
||||||
|
return build
|
||||||
|
}
|
||||||
|
|
|
@ -149,23 +149,101 @@ func Test_helper(t *testing.T) {
|
||||||
g.Assert(to[0].Avatar).Equal("http://...")
|
g.Assert(to[0].Avatar).Equal("http://...")
|
||||||
})
|
})
|
||||||
|
|
||||||
//
|
g.It("should convert a repository from webhook", func() {
|
||||||
// g.It("should convert user", func() {
|
from := &webhook{}
|
||||||
// token := &oauth2.Token{
|
from.Repo.Owner.Login = "octocat"
|
||||||
// AccessToken: "foo",
|
from.Repo.Owner.Name = "octocat"
|
||||||
// RefreshToken: "bar",
|
from.Repo.Name = "hello-world"
|
||||||
// Expiry: time.Now(),
|
from.Repo.FullName = "octocat/hello-world"
|
||||||
// }
|
from.Repo.Private = true
|
||||||
// user := &internal.Account{Login: "octocat"}
|
from.Repo.HTMLURL = "https://github.com/octocat/hello-world"
|
||||||
// user.Links.Avatar.Href = "http://..."
|
from.Repo.CloneURL = "https://github.com/octocat/hello-world.git"
|
||||||
//
|
from.Repo.DefaultBranch = "develop"
|
||||||
// result := convertUser(user, token)
|
|
||||||
// g.Assert(result.Avatar).Equal(user.Links.Avatar.Href)
|
repo := convertRepoHook(from)
|
||||||
// g.Assert(result.Login).Equal(user.Login)
|
g.Assert(repo.Owner).Equal(from.Repo.Owner.Login)
|
||||||
// g.Assert(result.Token).Equal(token.AccessToken)
|
g.Assert(repo.Name).Equal(from.Repo.Name)
|
||||||
// g.Assert(result.Token).Equal(token.AccessToken)
|
g.Assert(repo.FullName).Equal(from.Repo.FullName)
|
||||||
// g.Assert(result.Secret).Equal(token.RefreshToken)
|
g.Assert(repo.IsPrivate).Equal(from.Repo.Private)
|
||||||
// g.Assert(result.Expiry).Equal(token.Expiry.UTC().Unix())
|
g.Assert(repo.Link).Equal(from.Repo.HTMLURL)
|
||||||
// })
|
g.Assert(repo.Clone).Equal(from.Repo.CloneURL)
|
||||||
|
g.Assert(repo.Branch).Equal(from.Repo.DefaultBranch)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("should convert a pull request from webhook", func() {
|
||||||
|
from := &webhook{}
|
||||||
|
from.PullRequest.Head.Ref = "master"
|
||||||
|
from.PullRequest.Head.SHA = "f72fc19"
|
||||||
|
from.PullRequest.HTMLURL = "https://github.com/octocat/hello-world/pulls/42"
|
||||||
|
from.PullRequest.Number = 42
|
||||||
|
from.PullRequest.Title = "Updated README.md"
|
||||||
|
from.PullRequest.User.Login = "octocat"
|
||||||
|
from.PullRequest.User.Avatar = "https://avatars1.githubusercontent.com/u/583231"
|
||||||
|
|
||||||
|
build := convertPullHook(from, true)
|
||||||
|
g.Assert(build.Event).Equal(model.EventPull)
|
||||||
|
g.Assert(build.Branch).Equal(from.PullRequest.Head.Ref)
|
||||||
|
g.Assert(build.Ref).Equal("refs/pull/42/merge")
|
||||||
|
g.Assert(build.Commit).Equal(from.PullRequest.Head.SHA)
|
||||||
|
g.Assert(build.Message).Equal(from.PullRequest.Title)
|
||||||
|
g.Assert(build.Title).Equal(from.PullRequest.Title)
|
||||||
|
g.Assert(build.Author).Equal(from.PullRequest.User.Login)
|
||||||
|
g.Assert(build.Avatar).Equal(from.PullRequest.User.Avatar)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("should convert a deployment from webhook", func() {
|
||||||
|
from := &webhook{}
|
||||||
|
from.Deployment.Desc = ":shipit:"
|
||||||
|
from.Deployment.Env = "production"
|
||||||
|
from.Deployment.ID = 42
|
||||||
|
from.Deployment.Ref = "master"
|
||||||
|
from.Deployment.Sha = "f72fc19"
|
||||||
|
from.Deployment.URL = "https://github.com/octocat/hello-world"
|
||||||
|
from.Sender.Login = "octocat"
|
||||||
|
from.Sender.Avatar = "https://avatars1.githubusercontent.com/u/583231"
|
||||||
|
|
||||||
|
build := convertDeployHook(from)
|
||||||
|
g.Assert(build.Event).Equal(model.EventDeploy)
|
||||||
|
g.Assert(build.Branch).Equal("master")
|
||||||
|
g.Assert(build.Ref).Equal("refs/heads/master")
|
||||||
|
g.Assert(build.Commit).Equal(from.Deployment.Sha)
|
||||||
|
g.Assert(build.Message).Equal(from.Deployment.Desc)
|
||||||
|
g.Assert(build.Link).Equal(from.Deployment.URL)
|
||||||
|
g.Assert(build.Author).Equal(from.Sender.Login)
|
||||||
|
g.Assert(build.Avatar).Equal(from.Sender.Avatar)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("should convert a push from webhook", func() {
|
||||||
|
from := &webhook{}
|
||||||
|
from.Sender.Login = "octocat"
|
||||||
|
from.Sender.Avatar = "https://avatars1.githubusercontent.com/u/583231"
|
||||||
|
from.Repo.CloneURL = "https://github.com/octocat/hello-world.git"
|
||||||
|
from.Head.Author.Email = "octocat@github.com"
|
||||||
|
from.Head.Message = "updated README.md"
|
||||||
|
from.Head.URL = "https://github.com/octocat/hello-world"
|
||||||
|
from.Head.ID = "f72fc19"
|
||||||
|
from.Ref = "refs/heads/master"
|
||||||
|
|
||||||
|
build := convertPushHook(from)
|
||||||
|
g.Assert(build.Event).Equal(model.EventPush)
|
||||||
|
g.Assert(build.Branch).Equal("master")
|
||||||
|
g.Assert(build.Ref).Equal("refs/heads/master")
|
||||||
|
g.Assert(build.Commit).Equal(from.Head.ID)
|
||||||
|
g.Assert(build.Message).Equal(from.Head.Message)
|
||||||
|
g.Assert(build.Link).Equal(from.Head.URL)
|
||||||
|
g.Assert(build.Author).Equal(from.Sender.Login)
|
||||||
|
g.Assert(build.Avatar).Equal(from.Sender.Avatar)
|
||||||
|
g.Assert(build.Email).Equal(from.Head.Author.Email)
|
||||||
|
g.Assert(build.Remote).Equal(from.Repo.CloneURL)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("should convert a tag from webhook", func() {
|
||||||
|
from := &webhook{}
|
||||||
|
from.Ref = "refs/tags/v1.0.0"
|
||||||
|
|
||||||
|
build := convertPushHook(from)
|
||||||
|
g.Assert(build.Event).Equal(model.EventTag)
|
||||||
|
g.Assert(build.Ref).Equal("refs/tags/v1.0.0")
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
47
remote/github/fixtures/handler.go
Normal file
47
remote/github/fixtures/handler.go
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
package fixtures
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handler returns an http.Handler that is capable of handling a variety of mock
|
||||||
|
// Bitbucket requests and returning mock responses.
|
||||||
|
func Handler() http.Handler {
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
|
||||||
|
e := gin.New()
|
||||||
|
e.GET("/api/v3/repos/:owner/:name", getRepo)
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRepo(c *gin.Context) {
|
||||||
|
switch c.Param("name") {
|
||||||
|
case "repo_not_found":
|
||||||
|
c.String(404, "")
|
||||||
|
default:
|
||||||
|
c.String(200, repoPayload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var repoPayload = `
|
||||||
|
{
|
||||||
|
"owner": {
|
||||||
|
"login": "octocat",
|
||||||
|
"avatar_url": "https://github.com/images/error/octocat_happy.gif"
|
||||||
|
},
|
||||||
|
"name": "Hello-World",
|
||||||
|
"full_name": "octocat/Hello-World",
|
||||||
|
"private": true,
|
||||||
|
"html_url": "https://github.com/octocat/Hello-World",
|
||||||
|
"clone_url": "https://github.com/octocat/Hello-World.git",
|
||||||
|
"language": null,
|
||||||
|
"permissions": {
|
||||||
|
"admin": true,
|
||||||
|
"push": true,
|
||||||
|
"pull": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
157
remote/github/fixtures/hooks.go
Normal file
157
remote/github/fixtures/hooks.go
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
package fixtures
|
||||||
|
|
||||||
|
// HookPush is a sample push hook.
|
||||||
|
// https://developer.github.com/v3/activity/events/types/#pushevent
|
||||||
|
const HookPush = `
|
||||||
|
{
|
||||||
|
"ref": "refs/heads/changes",
|
||||||
|
"created": false,
|
||||||
|
"deleted": false,
|
||||||
|
"head_commit": {
|
||||||
|
"id": "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c",
|
||||||
|
"message": "Update README.md",
|
||||||
|
"timestamp": "2015-05-05T19:40:15-04:00",
|
||||||
|
"url": "https://github.com/baxterthehacker/public-repo/commit/0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c",
|
||||||
|
"author": {
|
||||||
|
"name": "baxterthehacker",
|
||||||
|
"email": "baxterthehacker@users.noreply.github.com",
|
||||||
|
"username": "baxterthehacker"
|
||||||
|
},
|
||||||
|
"committer": {
|
||||||
|
"name": "baxterthehacker",
|
||||||
|
"email": "baxterthehacker@users.noreply.github.com",
|
||||||
|
"username": "baxterthehacker"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"id": 35129377,
|
||||||
|
"name": "public-repo",
|
||||||
|
"full_name": "baxterthehacker/public-repo",
|
||||||
|
"owner": {
|
||||||
|
"name": "baxterthehacker",
|
||||||
|
"email": "baxterthehacker@users.noreply.github.com"
|
||||||
|
},
|
||||||
|
"private": false,
|
||||||
|
"html_url": "https://github.com/baxterthehacker/public-repo",
|
||||||
|
"default_branch": "master"
|
||||||
|
},
|
||||||
|
"pusher": {
|
||||||
|
"name": "baxterthehacker",
|
||||||
|
"email": "baxterthehacker@users.noreply.github.com"
|
||||||
|
},
|
||||||
|
"sender": {
|
||||||
|
"login": "baxterthehacker",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
// HookPush is a sample push hook that is marked as deleted, and is expected to
|
||||||
|
// be ignored.
|
||||||
|
const HookPushDeleted = `
|
||||||
|
{
|
||||||
|
"deleted": true
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
// HookPullRequest is a sample hook pull request
|
||||||
|
// https://developer.github.com/v3/activity/events/types/#pullrequestevent
|
||||||
|
const HookPullRequest = `
|
||||||
|
{
|
||||||
|
"action": "opened",
|
||||||
|
"number": 1,
|
||||||
|
"pull_request": {
|
||||||
|
"url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/1",
|
||||||
|
"html_url": "https://github.com/baxterthehacker/public-repo/pull/1",
|
||||||
|
"number": 1,
|
||||||
|
"state": "open",
|
||||||
|
"title": "Update the README with new information",
|
||||||
|
"user": {
|
||||||
|
"login": "baxterthehacker",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3"
|
||||||
|
},
|
||||||
|
"head": {
|
||||||
|
"label": "baxterthehacker:changes",
|
||||||
|
"ref": "changes",
|
||||||
|
"sha": "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"id": 35129377,
|
||||||
|
"name": "public-repo",
|
||||||
|
"full_name": "baxterthehacker/public-repo",
|
||||||
|
"owner": {
|
||||||
|
"login": "baxterthehacker",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3"
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
|
"html_url": "https://github.com/baxterthehacker/public-repo",
|
||||||
|
"clone_url": "https://github.com/baxterthehacker/public-repo.git",
|
||||||
|
"default_branch": "master"
|
||||||
|
},
|
||||||
|
"sender": {
|
||||||
|
"login": "baxterthehacker",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
// HookPullRequestInvalidAction is a sample hook pull request that has an
|
||||||
|
// action not equal to synchrize or opened, and is expected to be ignored.
|
||||||
|
const HookPullRequestInvalidAction = `
|
||||||
|
{
|
||||||
|
"action": "reopened",
|
||||||
|
"number": 1
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
// HookPullRequestInvalidState is a sample hook pull request that has a state
|
||||||
|
// not equal to open, and is expected to be ignored.
|
||||||
|
const HookPullRequestInvalidState = `
|
||||||
|
{
|
||||||
|
"action": "synchronize",
|
||||||
|
"pull_request": {
|
||||||
|
"number": 1,
|
||||||
|
"state": "closed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
// HookPush is a sample deployment hook.
|
||||||
|
// https://developer.github.com/v3/activity/events/types/#deploymentevent
|
||||||
|
const HookDeploy = `
|
||||||
|
{
|
||||||
|
"deployment": {
|
||||||
|
"url": "https://api.github.com/repos/baxterthehacker/public-repo/deployments/710692",
|
||||||
|
"id": 710692,
|
||||||
|
"sha": "9049f1265b7d61be4a8904a9a27120d2064dab3b",
|
||||||
|
"ref": "master",
|
||||||
|
"task": "deploy",
|
||||||
|
"payload": {
|
||||||
|
},
|
||||||
|
"environment": "production",
|
||||||
|
"description": null,
|
||||||
|
"creator": {
|
||||||
|
"login": "baxterthehacker",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"id": 35129377,
|
||||||
|
"name": "public-repo",
|
||||||
|
"full_name": "baxterthehacker/public-repo",
|
||||||
|
"owner": {
|
||||||
|
"login": "baxterthehacker",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3"
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
|
"html_url": "https://github.com/baxterthehacker/public-repo",
|
||||||
|
"clone_url": "https://github.com/baxterthehacker/public-repo.git",
|
||||||
|
"default_branch": "master"
|
||||||
|
},
|
||||||
|
"sender": {
|
||||||
|
"login": "baxterthehacker",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
|
@ -1,423 +0,0 @@
|
||||||
{
|
|
||||||
"action": "opened",
|
|
||||||
"number": 773,
|
|
||||||
"pull_request": {
|
|
||||||
"url": "https://api.github.com/repos/company/project/pulls/773",
|
|
||||||
"id": 50420217,
|
|
||||||
"html_url": "https://github.com/company/project/pull/773",
|
|
||||||
"diff_url": "https://github.com/company/project/pull/773.diff",
|
|
||||||
"patch_url": "https://github.com/company/project/pull/773.patch",
|
|
||||||
"issue_url": "https://api.github.com/repos/company/project/issues/773",
|
|
||||||
"number": 773,
|
|
||||||
"state": "open",
|
|
||||||
"locked": false,
|
|
||||||
"title": "working on feature X",
|
|
||||||
"user": {
|
|
||||||
"login": "author",
|
|
||||||
"id": 55555,
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/55555?v=3",
|
|
||||||
"gravatar_id": "",
|
|
||||||
"url": "https://api.github.com/users/author",
|
|
||||||
"html_url": "https://github.com/author",
|
|
||||||
"followers_url": "https://api.github.com/users/author/followers",
|
|
||||||
"following_url": "https://api.github.com/users/author/following{/other_user}",
|
|
||||||
"gists_url": "https://api.github.com/users/author/gists{/gist_id}",
|
|
||||||
"starred_url": "https://api.github.com/users/author/starred{/owner}{/repo}",
|
|
||||||
"subscriptions_url": "https://api.github.com/users/author/subscriptions",
|
|
||||||
"organizations_url": "https://api.github.com/users/author/orgs",
|
|
||||||
"repos_url": "https://api.github.com/users/author/repos",
|
|
||||||
"events_url": "https://api.github.com/users/author/events{/privacy}",
|
|
||||||
"received_events_url": "https://api.github.com/users/author/received_events",
|
|
||||||
"type": "User",
|
|
||||||
"site_admin": false
|
|
||||||
},
|
|
||||||
"body": "Check that the server builds without errors.",
|
|
||||||
"created_at": "2015-11-11T18:48:59Z",
|
|
||||||
"updated_at": "2015-11-11T18:48:59Z",
|
|
||||||
"closed_at": null,
|
|
||||||
"merged_at": null,
|
|
||||||
"merge_commit_sha": "a628859c51125d68cdf8db83e1a8d71b42a3aa55",
|
|
||||||
"assignee": null,
|
|
||||||
"milestone": null,
|
|
||||||
"commits_url": "https://api.github.com/repos/company/project/pulls/773/commits",
|
|
||||||
"review_comments_url": "https://api.github.com/repos/company/project/pulls/773/comments",
|
|
||||||
"review_comment_url": "https://api.github.com/repos/company/project/pulls/comments{/number}",
|
|
||||||
"comments_url": "https://api.github.com/repos/company/project/issues/773/comments",
|
|
||||||
"statuses_url": "https://api.github.com/repos/company/project/statuses/f2fe6d044518f607bf61d621200834643e364841",
|
|
||||||
"head": {
|
|
||||||
"label": "company:feature_branch",
|
|
||||||
"ref": "feature_branch",
|
|
||||||
"sha": "f2fe6d044518f607bf61d621200834643e364841",
|
|
||||||
"user": {
|
|
||||||
"login": "company",
|
|
||||||
"id": 66666,
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/66666?v=3",
|
|
||||||
"gravatar_id": "",
|
|
||||||
"url": "https://api.github.com/users/company",
|
|
||||||
"html_url": "https://github.com/company",
|
|
||||||
"followers_url": "https://api.github.com/users/company/followers",
|
|
||||||
"following_url": "https://api.github.com/users/company/following{/other_user}",
|
|
||||||
"gists_url": "https://api.github.com/users/company/gists{/gist_id}",
|
|
||||||
"starred_url": "https://api.github.com/users/company/starred{/owner}{/repo}",
|
|
||||||
"subscriptions_url": "https://api.github.com/users/company/subscriptions",
|
|
||||||
"organizations_url": "https://api.github.com/users/company/orgs",
|
|
||||||
"repos_url": "https://api.github.com/users/company/repos",
|
|
||||||
"events_url": "https://api.github.com/users/company/events{/privacy}",
|
|
||||||
"received_events_url": "https://api.github.com/users/company/received_events",
|
|
||||||
"type": "Organization",
|
|
||||||
"site_admin": false
|
|
||||||
},
|
|
||||||
"repo": {
|
|
||||||
"id": 13249623,
|
|
||||||
"name": "project",
|
|
||||||
"full_name": "company/project",
|
|
||||||
"owner": {
|
|
||||||
"login": "company",
|
|
||||||
"id": 66666,
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/66666?v=3",
|
|
||||||
"gravatar_id": "",
|
|
||||||
"url": "https://api.github.com/users/company",
|
|
||||||
"html_url": "https://github.com/company",
|
|
||||||
"followers_url": "https://api.github.com/users/company/followers",
|
|
||||||
"following_url": "https://api.github.com/users/company/following{/other_user}",
|
|
||||||
"gists_url": "https://api.github.com/users/company/gists{/gist_id}",
|
|
||||||
"starred_url": "https://api.github.com/users/company/starred{/owner}{/repo}",
|
|
||||||
"subscriptions_url": "https://api.github.com/users/company/subscriptions",
|
|
||||||
"organizations_url": "https://api.github.com/users/company/orgs",
|
|
||||||
"repos_url": "https://api.github.com/users/company/repos",
|
|
||||||
"events_url": "https://api.github.com/users/company/events{/privacy}",
|
|
||||||
"received_events_url": "https://api.github.com/users/company/received_events",
|
|
||||||
"type": "Organization",
|
|
||||||
"site_admin": false
|
|
||||||
},
|
|
||||||
"private": true,
|
|
||||||
"html_url": "https://github.com/company/project",
|
|
||||||
"description": "project description",
|
|
||||||
"fork": false,
|
|
||||||
"url": "https://api.github.com/repos/company/project",
|
|
||||||
"forks_url": "https://api.github.com/repos/company/project/forks",
|
|
||||||
"keys_url": "https://api.github.com/repos/company/project/keys{/key_id}",
|
|
||||||
"collaborators_url": "https://api.github.com/repos/company/project/collaborators{/collaborator}",
|
|
||||||
"teams_url": "https://api.github.com/repos/company/project/teams",
|
|
||||||
"hooks_url": "https://api.github.com/repos/company/project/hooks",
|
|
||||||
"issue_events_url": "https://api.github.com/repos/company/project/issues/events{/number}",
|
|
||||||
"events_url": "https://api.github.com/repos/company/project/events",
|
|
||||||
"assignees_url": "https://api.github.com/repos/company/project/assignees{/user}",
|
|
||||||
"branches_url": "https://api.github.com/repos/company/project/branches{/branch}",
|
|
||||||
"tags_url": "https://api.github.com/repos/company/project/tags",
|
|
||||||
"blobs_url": "https://api.github.com/repos/company/project/git/blobs{/sha}",
|
|
||||||
"git_tags_url": "https://api.github.com/repos/company/project/git/tags{/sha}",
|
|
||||||
"git_refs_url": "https://api.github.com/repos/company/project/git/refs{/sha}",
|
|
||||||
"trees_url": "https://api.github.com/repos/company/project/git/trees{/sha}",
|
|
||||||
"statuses_url": "https://api.github.com/repos/company/project/statuses/{sha}",
|
|
||||||
"languages_url": "https://api.github.com/repos/company/project/languages",
|
|
||||||
"stargazers_url": "https://api.github.com/repos/company/project/stargazers",
|
|
||||||
"contributors_url": "https://api.github.com/repos/company/project/contributors",
|
|
||||||
"subscribers_url": "https://api.github.com/repos/company/project/subscribers",
|
|
||||||
"subscription_url": "https://api.github.com/repos/company/project/subscription",
|
|
||||||
"commits_url": "https://api.github.com/repos/company/project/commits{/sha}",
|
|
||||||
"git_commits_url": "https://api.github.com/repos/company/project/git/commits{/sha}",
|
|
||||||
"comments_url": "https://api.github.com/repos/company/project/comments{/number}",
|
|
||||||
"issue_comment_url": "https://api.github.com/repos/company/project/issues/comments{/number}",
|
|
||||||
"contents_url": "https://api.github.com/repos/company/project/contents/{+path}",
|
|
||||||
"compare_url": "https://api.github.com/repos/company/project/compare/{base}...{head}",
|
|
||||||
"merges_url": "https://api.github.com/repos/company/project/merges",
|
|
||||||
"archive_url": "https://api.github.com/repos/company/project/{archive_format}{/ref}",
|
|
||||||
"downloads_url": "https://api.github.com/repos/company/project/downloads",
|
|
||||||
"issues_url": "https://api.github.com/repos/company/project/issues{/number}",
|
|
||||||
"pulls_url": "https://api.github.com/repos/company/project/pulls{/number}",
|
|
||||||
"milestones_url": "https://api.github.com/repos/company/project/milestones{/number}",
|
|
||||||
"notifications_url": "https://api.github.com/repos/company/project/notifications{?since,all,participating}",
|
|
||||||
"labels_url": "https://api.github.com/repos/company/project/labels{/name}",
|
|
||||||
"releases_url": "https://api.github.com/repos/company/project/releases{/id}",
|
|
||||||
"created_at": "2013-10-01T16:47:20Z",
|
|
||||||
"updated_at": "2015-05-08T20:19:10Z",
|
|
||||||
"pushed_at": "2015-11-11T18:48:59Z",
|
|
||||||
"git_url": "git://github.com/company/project.git",
|
|
||||||
"ssh_url": "git@github.com:company/project.git",
|
|
||||||
"clone_url": "https://github.com/company/project.git",
|
|
||||||
"svn_url": "https://github.com/company/project",
|
|
||||||
"homepage": "",
|
|
||||||
"size": 21672,
|
|
||||||
"stargazers_count": 1,
|
|
||||||
"watchers_count": 1,
|
|
||||||
"language": "Go",
|
|
||||||
"has_issues": true,
|
|
||||||
"has_downloads": true,
|
|
||||||
"has_wiki": true,
|
|
||||||
"has_pages": false,
|
|
||||||
"forks_count": 0,
|
|
||||||
"mirror_url": null,
|
|
||||||
"open_issues_count": 2,
|
|
||||||
"forks": 0,
|
|
||||||
"open_issues": 2,
|
|
||||||
"watchers": 1,
|
|
||||||
"default_branch": "master"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"base": {
|
|
||||||
"label": "company:master",
|
|
||||||
"ref": "master",
|
|
||||||
"sha": "19f3077e1c7e490247a8f4a563148a7f60ae03fa",
|
|
||||||
"user": {
|
|
||||||
"login": "company",
|
|
||||||
"id": 66666,
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/66666?v=3",
|
|
||||||
"gravatar_id": "",
|
|
||||||
"url": "https://api.github.com/users/company",
|
|
||||||
"html_url": "https://github.com/company",
|
|
||||||
"followers_url": "https://api.github.com/users/company/followers",
|
|
||||||
"following_url": "https://api.github.com/users/company/following{/other_user}",
|
|
||||||
"gists_url": "https://api.github.com/users/company/gists{/gist_id}",
|
|
||||||
"starred_url": "https://api.github.com/users/company/starred{/owner}{/repo}",
|
|
||||||
"subscriptions_url": "https://api.github.com/users/company/subscriptions",
|
|
||||||
"organizations_url": "https://api.github.com/users/company/orgs",
|
|
||||||
"repos_url": "https://api.github.com/users/company/repos",
|
|
||||||
"events_url": "https://api.github.com/users/company/events{/privacy}",
|
|
||||||
"received_events_url": "https://api.github.com/users/company/received_events",
|
|
||||||
"type": "Organization",
|
|
||||||
"site_admin": false
|
|
||||||
},
|
|
||||||
"repo": {
|
|
||||||
"id": 13249623,
|
|
||||||
"name": "project",
|
|
||||||
"full_name": "company/project",
|
|
||||||
"owner": {
|
|
||||||
"login": "company",
|
|
||||||
"id": 66666,
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/66666?v=3",
|
|
||||||
"gravatar_id": "",
|
|
||||||
"url": "https://api.github.com/users/company",
|
|
||||||
"html_url": "https://github.com/company",
|
|
||||||
"followers_url": "https://api.github.com/users/company/followers",
|
|
||||||
"following_url": "https://api.github.com/users/company/following{/other_user}",
|
|
||||||
"gists_url": "https://api.github.com/users/company/gists{/gist_id}",
|
|
||||||
"starred_url": "https://api.github.com/users/company/starred{/owner}{/repo}",
|
|
||||||
"subscriptions_url": "https://api.github.com/users/company/subscriptions",
|
|
||||||
"organizations_url": "https://api.github.com/users/company/orgs",
|
|
||||||
"repos_url": "https://api.github.com/users/company/repos",
|
|
||||||
"events_url": "https://api.github.com/users/company/events{/privacy}",
|
|
||||||
"received_events_url": "https://api.github.com/users/company/received_events",
|
|
||||||
"type": "Organization",
|
|
||||||
"site_admin": false
|
|
||||||
},
|
|
||||||
"private": true,
|
|
||||||
"html_url": "https://github.com/company/project",
|
|
||||||
"description": "project description",
|
|
||||||
"fork": false,
|
|
||||||
"url": "https://api.github.com/repos/company/project",
|
|
||||||
"forks_url": "https://api.github.com/repos/company/project/forks",
|
|
||||||
"keys_url": "https://api.github.com/repos/company/project/keys{/key_id}",
|
|
||||||
"collaborators_url": "https://api.github.com/repos/company/project/collaborators{/collaborator}",
|
|
||||||
"teams_url": "https://api.github.com/repos/company/project/teams",
|
|
||||||
"hooks_url": "https://api.github.com/repos/company/project/hooks",
|
|
||||||
"issue_events_url": "https://api.github.com/repos/company/project/issues/events{/number}",
|
|
||||||
"events_url": "https://api.github.com/repos/company/project/events",
|
|
||||||
"assignees_url": "https://api.github.com/repos/company/project/assignees{/user}",
|
|
||||||
"branches_url": "https://api.github.com/repos/company/project/branches{/branch}",
|
|
||||||
"tags_url": "https://api.github.com/repos/company/project/tags",
|
|
||||||
"blobs_url": "https://api.github.com/repos/company/project/git/blobs{/sha}",
|
|
||||||
"git_tags_url": "https://api.github.com/repos/company/project/git/tags{/sha}",
|
|
||||||
"git_refs_url": "https://api.github.com/repos/company/project/git/refs{/sha}",
|
|
||||||
"trees_url": "https://api.github.com/repos/company/project/git/trees{/sha}",
|
|
||||||
"statuses_url": "https://api.github.com/repos/company/project/statuses/{sha}",
|
|
||||||
"languages_url": "https://api.github.com/repos/company/project/languages",
|
|
||||||
"stargazers_url": "https://api.github.com/repos/company/project/stargazers",
|
|
||||||
"contributors_url": "https://api.github.com/repos/company/project/contributors",
|
|
||||||
"subscribers_url": "https://api.github.com/repos/company/project/subscribers",
|
|
||||||
"subscription_url": "https://api.github.com/repos/company/project/subscription",
|
|
||||||
"commits_url": "https://api.github.com/repos/company/project/commits{/sha}",
|
|
||||||
"git_commits_url": "https://api.github.com/repos/company/project/git/commits{/sha}",
|
|
||||||
"comments_url": "https://api.github.com/repos/company/project/comments{/number}",
|
|
||||||
"issue_comment_url": "https://api.github.com/repos/company/project/issues/comments{/number}",
|
|
||||||
"contents_url": "https://api.github.com/repos/company/project/contents/{+path}",
|
|
||||||
"compare_url": "https://api.github.com/repos/company/project/compare/{base}...{head}",
|
|
||||||
"merges_url": "https://api.github.com/repos/company/project/merges",
|
|
||||||
"archive_url": "https://api.github.com/repos/company/project/{archive_format}{/ref}",
|
|
||||||
"downloads_url": "https://api.github.com/repos/company/project/downloads",
|
|
||||||
"issues_url": "https://api.github.com/repos/company/project/issues{/number}",
|
|
||||||
"pulls_url": "https://api.github.com/repos/company/project/pulls{/number}",
|
|
||||||
"milestones_url": "https://api.github.com/repos/company/project/milestones{/number}",
|
|
||||||
"notifications_url": "https://api.github.com/repos/company/project/notifications{?since,all,participating}",
|
|
||||||
"labels_url": "https://api.github.com/repos/company/project/labels{/name}",
|
|
||||||
"releases_url": "https://api.github.com/repos/company/project/releases{/id}",
|
|
||||||
"created_at": "2013-10-01T16:47:20Z",
|
|
||||||
"updated_at": "2015-05-08T20:19:10Z",
|
|
||||||
"pushed_at": "2015-11-11T18:48:59Z",
|
|
||||||
"git_url": "git://github.com/company/project.git",
|
|
||||||
"ssh_url": "git@github.com:company/project.git",
|
|
||||||
"clone_url": "https://github.com/company/project.git",
|
|
||||||
"svn_url": "https://github.com/company/project",
|
|
||||||
"homepage": "",
|
|
||||||
"size": 21672,
|
|
||||||
"stargazers_count": 1,
|
|
||||||
"watchers_count": 1,
|
|
||||||
"language": "Go",
|
|
||||||
"has_issues": true,
|
|
||||||
"has_downloads": true,
|
|
||||||
"has_wiki": true,
|
|
||||||
"has_pages": false,
|
|
||||||
"forks_count": 0,
|
|
||||||
"mirror_url": null,
|
|
||||||
"open_issues_count": 2,
|
|
||||||
"forks": 0,
|
|
||||||
"open_issues": 2,
|
|
||||||
"watchers": 1,
|
|
||||||
"default_branch": "master"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"_links": {
|
|
||||||
"self": {
|
|
||||||
"href": "https://api.github.com/repos/company/project/pulls/773"
|
|
||||||
},
|
|
||||||
"html": {
|
|
||||||
"href": "https://github.com/company/project/pull/773"
|
|
||||||
},
|
|
||||||
"issue": {
|
|
||||||
"href": "https://api.github.com/repos/company/project/issues/773"
|
|
||||||
},
|
|
||||||
"comments": {
|
|
||||||
"href": "https://api.github.com/repos/company/project/issues/773/comments"
|
|
||||||
},
|
|
||||||
"review_comments": {
|
|
||||||
"href": "https://api.github.com/repos/company/project/pulls/773/comments"
|
|
||||||
},
|
|
||||||
"review_comment": {
|
|
||||||
"href": "https://api.github.com/repos/company/project/pulls/comments{/number}"
|
|
||||||
},
|
|
||||||
"commits": {
|
|
||||||
"href": "https://api.github.com/repos/company/project/pulls/773/commits"
|
|
||||||
},
|
|
||||||
"statuses": {
|
|
||||||
"href": "https://api.github.com/repos/company/project/statuses/f2fe6d044518f607bf61d621200834643e364841"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"merged": false,
|
|
||||||
"mergeable": true,
|
|
||||||
"mergeable_state": "clean",
|
|
||||||
"merged_by": null,
|
|
||||||
"comments": 0,
|
|
||||||
"review_comments": 0,
|
|
||||||
"commits": 1,
|
|
||||||
"additions": 1,
|
|
||||||
"deletions": 0,
|
|
||||||
"changed_files": 1
|
|
||||||
},
|
|
||||||
"repository": {
|
|
||||||
"id": 13249623,
|
|
||||||
"name": "project",
|
|
||||||
"full_name": "company/project",
|
|
||||||
"owner": {
|
|
||||||
"login": "company",
|
|
||||||
"id": 66666,
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/66666?v=3",
|
|
||||||
"gravatar_id": "",
|
|
||||||
"url": "https://api.github.com/users/company",
|
|
||||||
"html_url": "https://github.com/company",
|
|
||||||
"followers_url": "https://api.github.com/users/company/followers",
|
|
||||||
"following_url": "https://api.github.com/users/company/following{/other_user}",
|
|
||||||
"gists_url": "https://api.github.com/users/company/gists{/gist_id}",
|
|
||||||
"starred_url": "https://api.github.com/users/company/starred{/owner}{/repo}",
|
|
||||||
"subscriptions_url": "https://api.github.com/users/company/subscriptions",
|
|
||||||
"organizations_url": "https://api.github.com/users/company/orgs",
|
|
||||||
"repos_url": "https://api.github.com/users/company/repos",
|
|
||||||
"events_url": "https://api.github.com/users/company/events{/privacy}",
|
|
||||||
"received_events_url": "https://api.github.com/users/company/received_events",
|
|
||||||
"type": "Organization",
|
|
||||||
"site_admin": false
|
|
||||||
},
|
|
||||||
"private": true,
|
|
||||||
"html_url": "https://github.com/company/project",
|
|
||||||
"description": "project description",
|
|
||||||
"fork": false,
|
|
||||||
"url": "https://api.github.com/repos/company/project",
|
|
||||||
"forks_url": "https://api.github.com/repos/company/project/forks",
|
|
||||||
"keys_url": "https://api.github.com/repos/company/project/keys{/key_id}",
|
|
||||||
"collaborators_url": "https://api.github.com/repos/company/project/collaborators{/collaborator}",
|
|
||||||
"teams_url": "https://api.github.com/repos/company/project/teams",
|
|
||||||
"hooks_url": "https://api.github.com/repos/company/project/hooks",
|
|
||||||
"issue_events_url": "https://api.github.com/repos/company/project/issues/events{/number}",
|
|
||||||
"events_url": "https://api.github.com/repos/company/project/events",
|
|
||||||
"assignees_url": "https://api.github.com/repos/company/project/assignees{/user}",
|
|
||||||
"branches_url": "https://api.github.com/repos/company/project/branches{/branch}",
|
|
||||||
"tags_url": "https://api.github.com/repos/company/project/tags",
|
|
||||||
"blobs_url": "https://api.github.com/repos/company/project/git/blobs{/sha}",
|
|
||||||
"git_tags_url": "https://api.github.com/repos/company/project/git/tags{/sha}",
|
|
||||||
"git_refs_url": "https://api.github.com/repos/company/project/git/refs{/sha}",
|
|
||||||
"trees_url": "https://api.github.com/repos/company/project/git/trees{/sha}",
|
|
||||||
"statuses_url": "https://api.github.com/repos/company/project/statuses/{sha}",
|
|
||||||
"languages_url": "https://api.github.com/repos/company/project/languages",
|
|
||||||
"stargazers_url": "https://api.github.com/repos/company/project/stargazers",
|
|
||||||
"contributors_url": "https://api.github.com/repos/company/project/contributors",
|
|
||||||
"subscribers_url": "https://api.github.com/repos/company/project/subscribers",
|
|
||||||
"subscription_url": "https://api.github.com/repos/company/project/subscription",
|
|
||||||
"commits_url": "https://api.github.com/repos/company/project/commits{/sha}",
|
|
||||||
"git_commits_url": "https://api.github.com/repos/company/project/git/commits{/sha}",
|
|
||||||
"comments_url": "https://api.github.com/repos/company/project/comments{/number}",
|
|
||||||
"issue_comment_url": "https://api.github.com/repos/company/project/issues/comments{/number}",
|
|
||||||
"contents_url": "https://api.github.com/repos/company/project/contents/{+path}",
|
|
||||||
"compare_url": "https://api.github.com/repos/company/project/compare/{base}...{head}",
|
|
||||||
"merges_url": "https://api.github.com/repos/company/project/merges",
|
|
||||||
"archive_url": "https://api.github.com/repos/company/project/{archive_format}{/ref}",
|
|
||||||
"downloads_url": "https://api.github.com/repos/company/project/downloads",
|
|
||||||
"issues_url": "https://api.github.com/repos/company/project/issues{/number}",
|
|
||||||
"pulls_url": "https://api.github.com/repos/company/project/pulls{/number}",
|
|
||||||
"milestones_url": "https://api.github.com/repos/company/project/milestones{/number}",
|
|
||||||
"notifications_url": "https://api.github.com/repos/company/project/notifications{?since,all,participating}",
|
|
||||||
"labels_url": "https://api.github.com/repos/company/project/labels{/name}",
|
|
||||||
"releases_url": "https://api.github.com/repos/company/project/releases{/id}",
|
|
||||||
"created_at": "2013-10-01T16:47:20Z",
|
|
||||||
"updated_at": "2015-05-08T20:19:10Z",
|
|
||||||
"pushed_at": "2015-11-11T18:48:59Z",
|
|
||||||
"git_url": "git://github.com/company/project.git",
|
|
||||||
"ssh_url": "git@github.com:company/project.git",
|
|
||||||
"clone_url": "https://github.com/company/project.git",
|
|
||||||
"svn_url": "https://github.com/company/project",
|
|
||||||
"homepage": "",
|
|
||||||
"size": 21672,
|
|
||||||
"stargazers_count": 1,
|
|
||||||
"watchers_count": 1,
|
|
||||||
"language": "Go",
|
|
||||||
"has_issues": true,
|
|
||||||
"has_downloads": true,
|
|
||||||
"has_wiki": true,
|
|
||||||
"has_pages": false,
|
|
||||||
"forks_count": 0,
|
|
||||||
"mirror_url": null,
|
|
||||||
"open_issues_count": 2,
|
|
||||||
"forks": 0,
|
|
||||||
"open_issues": 2,
|
|
||||||
"watchers": 1,
|
|
||||||
"default_branch": "master"
|
|
||||||
},
|
|
||||||
"organization": {
|
|
||||||
"login": "company",
|
|
||||||
"id": 66666,
|
|
||||||
"url": "https://api.github.com/orgs/company",
|
|
||||||
"repos_url": "https://api.github.com/orgs/company/repos",
|
|
||||||
"events_url": "https://api.github.com/orgs/company/events",
|
|
||||||
"members_url": "https://api.github.com/orgs/company/members{/member}",
|
|
||||||
"public_members_url": "https://api.github.com/orgs/company/public_members{/member}",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/66666?v=3",
|
|
||||||
"description": null
|
|
||||||
},
|
|
||||||
"sender": {
|
|
||||||
"login": "author",
|
|
||||||
"id": 55555,
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/55555?v=3",
|
|
||||||
"gravatar_id": "",
|
|
||||||
"url": "https://api.github.com/users/author",
|
|
||||||
"html_url": "https://github.com/author",
|
|
||||||
"followers_url": "https://api.github.com/users/author/followers",
|
|
||||||
"following_url": "https://api.github.com/users/author/following{/other_user}",
|
|
||||||
"gists_url": "https://api.github.com/users/author/gists{/gist_id}",
|
|
||||||
"starred_url": "https://api.github.com/users/author/starred{/owner}{/repo}",
|
|
||||||
"subscriptions_url": "https://api.github.com/users/author/subscriptions",
|
|
||||||
"organizations_url": "https://api.github.com/users/author/orgs",
|
|
||||||
"repos_url": "https://api.github.com/users/author/repos",
|
|
||||||
"events_url": "https://api.github.com/users/author/events{/privacy}",
|
|
||||||
"received_events_url": "https://api.github.com/users/author/received_events",
|
|
||||||
"type": "User",
|
|
||||||
"site_admin": false
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,7 +2,6 @@ package github
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/base32"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -14,9 +13,9 @@ import (
|
||||||
"github.com/drone/drone/model"
|
"github.com/drone/drone/model"
|
||||||
"github.com/drone/drone/remote"
|
"github.com/drone/drone/remote"
|
||||||
"github.com/drone/drone/shared/httputil"
|
"github.com/drone/drone/shared/httputil"
|
||||||
"github.com/gorilla/securecookie"
|
|
||||||
|
|
||||||
"github.com/google/go-github/github"
|
"github.com/google/go-github/github"
|
||||||
|
"golang.org/x/net/context"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -89,30 +88,38 @@ func (c *client) Login(res http.ResponseWriter, req *http.Request) (*model.User,
|
||||||
|
|
||||||
code := req.FormValue("code")
|
code := req.FormValue("code")
|
||||||
if len(code) == 0 {
|
if len(code) == 0 {
|
||||||
rand := base32.StdEncoding.EncodeToString(securecookie.GenerateRandomKey(32))
|
// TODO(bradrydzewski) we really should be using a random value here and
|
||||||
http.Redirect(res, req, config.AuthCodeURL(rand), http.StatusSeeOther)
|
// storing in a cookie for verification in the next stage of the workflow.
|
||||||
|
|
||||||
|
http.Redirect(res, req, config.AuthCodeURL("drone"), http.StatusSeeOther)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(bradrydzewski) what is the best way to provide a SkipVerify flag
|
token, err := config.Exchange(c.newContext(), code)
|
||||||
// when exchanging the token?
|
|
||||||
|
|
||||||
token, err := config.Exchange(oauth2.NoContext, code)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
client := c.newClientToken(token.AccessToken)
|
client := c.newClientToken(token.AccessToken)
|
||||||
useremail, err := GetUserEmail(client)
|
user, _, err := client.Users.Get("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
emails, _, err := client.Users.ListEmails(nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
email := matchingEmail(emails, c.API)
|
||||||
|
if email == nil {
|
||||||
|
return nil, fmt.Errorf("No verified Email address for GitHub account")
|
||||||
|
}
|
||||||
|
|
||||||
return &model.User{
|
return &model.User{
|
||||||
Login: *useremail.Login,
|
Login: *user.Login,
|
||||||
Email: *useremail.Email,
|
Email: *email.Email,
|
||||||
Token: token.AccessToken,
|
Token: token.AccessToken,
|
||||||
Avatar: *useremail.AvatarURL,
|
Avatar: *user.AvatarURL,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,7 +224,39 @@ func (c *client) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// helper function to return the bitbucket oauth2 config
|
// Deactivate deactives the repository be removing registered push hooks from
|
||||||
|
// the GitHub repository.
|
||||||
|
func (c *client) Deactivate(u *model.User, r *model.Repo, link string) error {
|
||||||
|
client := c.newClientToken(u.Token)
|
||||||
|
hooks, _, err := client.Repositories.ListHooks(r.Owner, r.Name, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
match := matchingHooks(hooks, link)
|
||||||
|
if match == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
_, err = client.Repositories.DeleteHook(r.Owner, r.Name, *match.ID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper function to return the GitHub oauth2 context using an HTTPClient that
|
||||||
|
// disables TLS verification if disabled in the remote settings.
|
||||||
|
func (c *client) newContext() context.Context {
|
||||||
|
if !c.SkipVerify {
|
||||||
|
return oauth2.NoContext
|
||||||
|
}
|
||||||
|
return context.WithValue(nil, oauth2.HTTPClient, &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
Proxy: http.ProxyFromEnvironment,
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper function to return the GitHub oauth2 config
|
||||||
func (c *client) newConfig(redirect string) *oauth2.Config {
|
func (c *client) newConfig(redirect string) *oauth2.Config {
|
||||||
return &oauth2.Config{
|
return &oauth2.Config{
|
||||||
ClientID: c.Client,
|
ClientID: c.Client,
|
||||||
|
@ -230,7 +269,7 @@ func (c *client) newConfig(redirect string) *oauth2.Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// helper function to return the bitbucket oauth2 client
|
// helper function to return the GitHub oauth2 client
|
||||||
func (c *client) newClientToken(token string) *github.Client {
|
func (c *client) newClientToken(token string) *github.Client {
|
||||||
ts := oauth2.StaticTokenSource(
|
ts := oauth2.StaticTokenSource(
|
||||||
&oauth2.Token{AccessToken: token},
|
&oauth2.Token{AccessToken: token},
|
||||||
|
@ -249,6 +288,50 @@ func (c *client) newClientToken(token string) *github.Client {
|
||||||
return github
|
return github
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// helper function to return matching user email.
|
||||||
|
func matchingEmail(emails []github.UserEmail, rawurl string) *github.UserEmail {
|
||||||
|
for _, email := range emails {
|
||||||
|
if email.Email == nil || email.Primary == nil || email.Verified == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if *email.Primary && *email.Verified {
|
||||||
|
return &email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// github enterprise does not support verified email addresses so instead
|
||||||
|
// we'll return the first email address in the list.
|
||||||
|
if len(emails) != 0 && rawurl != defaultAPI {
|
||||||
|
return &emails[0]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper function to return matching hook.
|
||||||
|
func matchingHooks(hooks []github.Hook, rawurl string) *github.Hook {
|
||||||
|
link, err := url.Parse(rawurl)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, hook := range hooks {
|
||||||
|
if hook.ID == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
v, ok := hook.Config["url"]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s, ok := v.(string)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
hookurl, err := url.Parse(s)
|
||||||
|
if err == nil && hookurl.Host == link.Host {
|
||||||
|
return &hook
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// TODO(bradrydzewski) refactor below functions
|
// TODO(bradrydzewski) refactor below functions
|
||||||
//
|
//
|
||||||
|
@ -297,29 +380,28 @@ func deploymentStatus(client *github.Client, r *model.Repo, b *model.Build, link
|
||||||
// Activate activates a repository by creating the post-commit hook and
|
// Activate activates a repository by creating the post-commit hook and
|
||||||
// adding the SSH deploy key, if applicable.
|
// adding the SSH deploy key, if applicable.
|
||||||
func (c *client) Activate(u *model.User, r *model.Repo, link string) error {
|
func (c *client) Activate(u *model.User, r *model.Repo, link string) error {
|
||||||
|
if err := c.Deactivate(u, r, link); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
client := c.newClientToken(u.Token)
|
client := c.newClientToken(u.Token)
|
||||||
_, err := CreateUpdateHook(client, r.Owner, r.Name, link)
|
hook := &github.Hook{
|
||||||
|
Name: github.String("web"),
|
||||||
|
Events: []string{
|
||||||
|
"push",
|
||||||
|
"pull_request",
|
||||||
|
"deployment",
|
||||||
|
},
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"url": link,
|
||||||
|
"content_type": "form",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, _, err := client.Repositories.CreateHook(r.Owner, r.Name, hook)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deactivate removes a repository by removing all the post-commit hooks
|
|
||||||
// which are equal to link and removing the SSH deploy key.
|
|
||||||
func (c *client) Deactivate(u *model.User, r *model.Repo, link string) error {
|
|
||||||
client := c.newClientToken(u.Token)
|
|
||||||
return DeleteHook(client, r.Owner, r.Name, link)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hook parses the post-commit hook from the Request body
|
// Hook parses the post-commit hook from the Request body
|
||||||
// and returns the required data in a standard format.
|
// and returns the required data in a standard format.
|
||||||
func (c *client) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
|
func (c *client) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
|
||||||
switch r.Header.Get("X-Github-Event") {
|
return parseHook(r, c.MergeRef)
|
||||||
case "pull_request":
|
|
||||||
return c.pullRequest(r)
|
|
||||||
case "push":
|
|
||||||
return c.push(r)
|
|
||||||
case "deployment":
|
|
||||||
return c.deployment(r)
|
|
||||||
default:
|
|
||||||
return nil, nil, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,77 +1,158 @@
|
||||||
package github
|
package github
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"net/http/httptest"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/drone/drone/model"
|
||||||
|
"github.com/drone/drone/remote/github/fixtures"
|
||||||
|
|
||||||
"github.com/franela/goblin"
|
"github.com/franela/goblin"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHook(t *testing.T) {
|
func Test_github(t *testing.T) {
|
||||||
var (
|
gin.SetMode(gin.TestMode)
|
||||||
github client
|
|
||||||
r *http.Request
|
s := httptest.NewServer(fixtures.Handler())
|
||||||
body *bytes.Buffer
|
c, _ := New(Opts{
|
||||||
)
|
URL: s.URL,
|
||||||
|
SkipVerify: true,
|
||||||
|
})
|
||||||
|
|
||||||
g := goblin.Goblin(t)
|
g := goblin.Goblin(t)
|
||||||
|
g.Describe("GitHub", func() {
|
||||||
|
|
||||||
g.Describe("Hook", func() {
|
g.After(func() {
|
||||||
g.BeforeEach(func() {
|
s.Close()
|
||||||
github = client{}
|
|
||||||
body = bytes.NewBuffer([]byte{})
|
|
||||||
r, _ = http.NewRequest("POST", "https://drone.com/hook", body)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
g.Describe("For a Pull Request", func() {
|
g.Describe("Creating a remote", func() {
|
||||||
g.BeforeEach(func() {
|
g.It("Should return client with specified options", func() {
|
||||||
r.Header.Set("X-Github-Event", "pull_request")
|
remote, _ := New(Opts{
|
||||||
|
URL: "http://localhost:8080/",
|
||||||
|
Client: "0ZXh0IjoiI",
|
||||||
|
Secret: "I1NiIsInR5",
|
||||||
|
Username: "someuser",
|
||||||
|
Password: "password",
|
||||||
|
SkipVerify: true,
|
||||||
|
PrivateMode: true,
|
||||||
|
})
|
||||||
|
g.Assert(remote.(*client).URL).Equal("http://localhost:8080")
|
||||||
|
g.Assert(remote.(*client).API).Equal("http://localhost:8080/api/v3/")
|
||||||
|
g.Assert(remote.(*client).Machine).Equal("localhost")
|
||||||
|
g.Assert(remote.(*client).Username).Equal("someuser")
|
||||||
|
g.Assert(remote.(*client).Password).Equal("password")
|
||||||
|
g.Assert(remote.(*client).Client).Equal("0ZXh0IjoiI")
|
||||||
|
g.Assert(remote.(*client).Secret).Equal("I1NiIsInR5")
|
||||||
|
g.Assert(remote.(*client).SkipVerify).Equal(true)
|
||||||
|
g.Assert(remote.(*client).PrivateMode).Equal(true)
|
||||||
})
|
})
|
||||||
|
g.It("Should handle malformed url", func() {
|
||||||
g.It("Should set build author to the pull request author", func() {
|
_, err := New(Opts{URL: "%gh&%ij"})
|
||||||
hookJSON, ioerr := ioutil.ReadFile("fixtures/pull_request.json")
|
g.Assert(err != nil).IsTrue()
|
||||||
if ioerr != nil {
|
|
||||||
panic(ioerr)
|
|
||||||
}
|
|
||||||
body.Write(hookJSON)
|
|
||||||
|
|
||||||
_, build, err := github.Hook(r)
|
|
||||||
g.Assert(err).Equal(nil)
|
|
||||||
g.Assert(build.Author).Equal("author")
|
|
||||||
g.Assert(build.Avatar).Equal("https://avatars.githubusercontent.com/u/55555?v=3")
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
g.Describe("Generating a netrc file", func() {
|
||||||
|
g.It("Should return a netrc with the user token", func() {
|
||||||
|
remote, _ := New(Opts{
|
||||||
|
URL: "http://github.com:443",
|
||||||
|
})
|
||||||
|
netrc, _ := remote.Netrc(fakeUser, nil)
|
||||||
|
g.Assert(netrc.Machine).Equal("github.com")
|
||||||
|
g.Assert(netrc.Login).Equal(fakeUser.Token)
|
||||||
|
g.Assert(netrc.Password).Equal("x-oauth-basic")
|
||||||
|
})
|
||||||
|
g.It("Should return a netrc with the machine account", func() {
|
||||||
|
remote, _ := New(Opts{
|
||||||
|
URL: "http://github.com:443",
|
||||||
|
Username: "someuser",
|
||||||
|
Password: "password",
|
||||||
|
})
|
||||||
|
netrc, _ := remote.Netrc(nil, nil)
|
||||||
|
g.Assert(netrc.Machine).Equal("github.com")
|
||||||
|
g.Assert(netrc.Login).Equal("someuser")
|
||||||
|
g.Assert(netrc.Password).Equal("password")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
g.Describe("Requesting a repository", func() {
|
||||||
|
g.It("Should return the repository details", func() {
|
||||||
|
repo, err := c.Repo(fakeUser, fakeRepo.Owner, fakeRepo.Name)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(repo.Owner).Equal(fakeRepo.Owner)
|
||||||
|
g.Assert(repo.Name).Equal(fakeRepo.Name)
|
||||||
|
g.Assert(repo.FullName).Equal(fakeRepo.FullName)
|
||||||
|
g.Assert(repo.IsPrivate).IsTrue()
|
||||||
|
g.Assert(repo.Clone).Equal(fakeRepo.Clone)
|
||||||
|
g.Assert(repo.Link).Equal(fakeRepo.Link)
|
||||||
|
})
|
||||||
|
g.It("Should handle a not found error", func() {
|
||||||
|
_, err := c.Repo(fakeUser, fakeRepoNotFound.Owner, fakeRepoNotFound.Name)
|
||||||
|
g.Assert(err != nil).IsTrue()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
g.Describe("Requesting repository permissions", func() {
|
||||||
|
g.It("Should return the permission details", func() {
|
||||||
|
perm, err := c.Perm(fakeUser, fakeRepo.Owner, fakeRepo.Name)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(perm.Admin).IsTrue()
|
||||||
|
g.Assert(perm.Push).IsTrue()
|
||||||
|
g.Assert(perm.Pull).IsTrue()
|
||||||
|
})
|
||||||
|
g.It("Should handle a not found error", func() {
|
||||||
|
_, err := c.Perm(fakeUser, fakeRepoNotFound.Owner, fakeRepoNotFound.Name)
|
||||||
|
g.Assert(err != nil).IsTrue()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should return a user repository list")
|
||||||
|
|
||||||
|
g.It("Should return a user team list")
|
||||||
|
|
||||||
|
g.It("Should register repositroy hooks")
|
||||||
|
|
||||||
|
g.It("Should return a repository file")
|
||||||
|
|
||||||
|
g.Describe("Given an authentication request", func() {
|
||||||
|
g.It("Should redirect to the GitHub login page")
|
||||||
|
g.It("Should create an access token")
|
||||||
|
g.It("Should handle an access token error")
|
||||||
|
g.It("Should return the authenticated user")
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
var (
|
||||||
// func TestLoad(t *testing.T) {
|
fakeUser = &model.User{
|
||||||
// conf := "https://github.com?client_id=client&client_secret=secret&scope=scope1,scope2"
|
Login: "octocat",
|
||||||
//
|
Token: "cfcd2084",
|
||||||
// g := Load(conf)
|
}
|
||||||
// if g.URL != "https://github.com" {
|
|
||||||
// t.Errorf("g.URL = %q; want https://github.com", g.URL)
|
fakeUserNoRepos = &model.User{
|
||||||
// }
|
Login: "octocat",
|
||||||
// if g.Client != "client" {
|
Token: "repos_not_found",
|
||||||
// t.Errorf("g.Client = %q; want client", g.Client)
|
}
|
||||||
// }
|
|
||||||
// if g.Secret != "secret" {
|
fakeRepo = &model.Repo{
|
||||||
// t.Errorf("g.Secret = %q; want secret", g.Secret)
|
Owner: "octocat",
|
||||||
// }
|
Name: "Hello-World",
|
||||||
// if g.Scope != "scope1,scope2" {
|
FullName: "octocat/Hello-World",
|
||||||
// t.Errorf("g.Scope = %q; want scope1,scope2", g.Scope)
|
Avatar: "https://github.com/images/error/octocat_happy.gif",
|
||||||
// }
|
Link: "https://github.com/octocat/Hello-World",
|
||||||
// if g.API != DefaultAPI {
|
Clone: "https://github.com/octocat/Hello-World.git",
|
||||||
// t.Errorf("g.API = %q; want %q", g.API, DefaultAPI)
|
IsPrivate: true,
|
||||||
// }
|
}
|
||||||
// if g.MergeRef != DefaultMergeRef {
|
|
||||||
// t.Errorf("g.MergeRef = %q; want %q", g.MergeRef, DefaultMergeRef)
|
fakeRepoNotFound = &model.Repo{
|
||||||
// }
|
Owner: "test_name",
|
||||||
//
|
Name: "repo_not_found",
|
||||||
// g = Load("")
|
FullName: "test_name/repo_not_found",
|
||||||
// if g.Scope != DefaultScope {
|
}
|
||||||
// t.Errorf("g.Scope = %q; want %q", g.Scope, DefaultScope)
|
|
||||||
// }
|
fakeBuild = &model.Build{
|
||||||
// }
|
Commit: "9ecad50",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
|
@ -1,118 +0,0 @@
|
||||||
package github
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/google/go-github/github"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetUserEmail is a heper function that retrieves the currently
|
|
||||||
// authenticated user from GitHub + Email address.
|
|
||||||
func GetUserEmail(client *github.Client) (*github.User, error) {
|
|
||||||
user, _, err := client.Users.Get("")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
emails, _, err := client.Users.ListEmails(nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, email := range emails {
|
|
||||||
if *email.Primary && *email.Verified {
|
|
||||||
user.Email = email.Email
|
|
||||||
return user, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WARNING, HACK
|
|
||||||
// for out-of-date github enterprise editions the primary
|
|
||||||
// and verified fields won't exist.
|
|
||||||
if !strings.HasPrefix(*user.URL, defaultAPI) && len(emails) != 0 {
|
|
||||||
user.Email = emails[0].Email
|
|
||||||
return user, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("No verified Email address for GitHub account")
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHook is a heper function that retrieves a hook by
|
|
||||||
// hostname. To do this, it will retrieve a list of all hooks
|
|
||||||
// and iterate through the list.
|
|
||||||
func GetHook(client *github.Client, owner, name, url string) (*github.Hook, error) {
|
|
||||||
hooks, _, err := client.Repositories.ListHooks(owner, name, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, hook := range hooks {
|
|
||||||
hookurl, ok := hook.Config["url"].(string)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(hookurl, url) {
|
|
||||||
return &hook, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteHook does exactly what you think it does.
|
|
||||||
func DeleteHook(client *github.Client, owner, name, url string) error {
|
|
||||||
hook, err := GetHook(client, owner, name, url)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if hook == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
_, err = client.Repositories.DeleteHook(owner, name, *hook.ID)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateHook is a heper 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{"push", "pull_request", "deployment"}
|
|
||||||
hook.Config = map[string]interface{}{}
|
|
||||||
hook.Config["url"] = url
|
|
||||||
hook.Config["content_type"] = "form"
|
|
||||||
created, _, err := client.Repositories.CreateHook(owner, name, hook)
|
|
||||||
return created, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateUpdateHook is a heper function that creates a post-commit hook
|
|
||||||
// for the specified repository if it does not already exist, otherwise
|
|
||||||
// it updates the existing hook
|
|
||||||
func CreateUpdateHook(client *github.Client, owner, name, url string) (*github.Hook, error) {
|
|
||||||
var hook, _ = GetHook(client, owner, name, url)
|
|
||||||
if hook != nil {
|
|
||||||
hook.Name = github.String("web")
|
|
||||||
hook.Events = []string{"push", "pull_request"}
|
|
||||||
hook.Config = map[string]interface{}{}
|
|
||||||
hook.Config["url"] = url
|
|
||||||
hook.Config["content_type"] = "form"
|
|
||||||
var updated, _, err = client.Repositories.EditHook(owner, name, *hook.ID, hook)
|
|
||||||
return updated, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return CreateHook(client, owner, name, url)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPayload is a helper function that will parse the JSON payload. It will
|
|
||||||
// first check for a `payload` parameter in a POST, but can fallback to a
|
|
||||||
// raw JSON body as well.
|
|
||||||
func GetPayload(req *http.Request) []byte {
|
|
||||||
var payload = req.FormValue("payload")
|
|
||||||
if len(payload) == 0 {
|
|
||||||
defer req.Body.Close()
|
|
||||||
raw, _ := ioutil.ReadAll(req.Body)
|
|
||||||
return raw
|
|
||||||
}
|
|
||||||
return []byte(payload)
|
|
||||||
}
|
|
92
remote/github/parse.go
Normal file
92
remote/github/parse.go
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
package github
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/drone/drone/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
hookEvent = "X-Github-Event"
|
||||||
|
hookField = "payload"
|
||||||
|
hookDeploy = "deployment"
|
||||||
|
hookPush = "push"
|
||||||
|
hookPull = "pull_request"
|
||||||
|
|
||||||
|
actionOpen = "opened"
|
||||||
|
actionSync = "synchronize"
|
||||||
|
|
||||||
|
stateOpen = "open"
|
||||||
|
)
|
||||||
|
|
||||||
|
// parseHook parses a Bitbucket hook from an http.Request request and returns
|
||||||
|
// Repo and Build detail. If a hook type is unsupported nil values are returned.
|
||||||
|
func parseHook(r *http.Request, merge bool) (*model.Repo, *model.Build, error) {
|
||||||
|
var reader io.Reader = r.Body
|
||||||
|
|
||||||
|
if payload := r.FormValue(hookField); payload != "" {
|
||||||
|
reader = bytes.NewBufferString(payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
raw, err := ioutil.ReadAll(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch r.Header.Get(hookEvent) {
|
||||||
|
case hookPush:
|
||||||
|
return parsePushHook(raw)
|
||||||
|
case hookDeploy:
|
||||||
|
return parseDeployHook(raw)
|
||||||
|
case hookPull:
|
||||||
|
return parsePullHook(raw, merge)
|
||||||
|
}
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parsePushHook parses a push hook and returns the Repo and Build details.
|
||||||
|
// If the commit type is unsupported nil values are returned.
|
||||||
|
func parsePushHook(payload []byte) (*model.Repo, *model.Build, error) {
|
||||||
|
hook := new(webhook)
|
||||||
|
err := json.Unmarshal(payload, hook)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if hook.Deleted {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return convertRepoHook(hook), convertPushHook(hook), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseDeployHook parses a deployment and returns the Repo and Build details.
|
||||||
|
// If the commit type is unsupported nil values are returned.
|
||||||
|
func parseDeployHook(payload []byte) (*model.Repo, *model.Build, error) {
|
||||||
|
hook := new(webhook)
|
||||||
|
if err := json.Unmarshal(payload, hook); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return convertRepoHook(hook), convertDeployHook(hook), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parsePullHook parses a pull request hook and returns the Repo and Build
|
||||||
|
// details. If the pull request is closed nil values are returned.
|
||||||
|
func parsePullHook(payload []byte, merge bool) (*model.Repo, *model.Build, error) {
|
||||||
|
hook := new(webhook)
|
||||||
|
err := json.Unmarshal(payload, hook)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore these
|
||||||
|
if hook.Action != actionOpen && hook.Action != actionSync {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
if hook.PullRequest.State != stateOpen {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
return convertRepoHook(hook), convertPullHook(hook, merge), nil
|
||||||
|
}
|
97
remote/github/parse_test.go
Normal file
97
remote/github/parse_test.go
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
package github
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/drone/drone/model"
|
||||||
|
"github.com/drone/drone/remote/github/fixtures"
|
||||||
|
"github.com/franela/goblin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_parser(t *testing.T) {
|
||||||
|
|
||||||
|
g := goblin.Goblin(t)
|
||||||
|
g.Describe("GitHub parser", func() {
|
||||||
|
|
||||||
|
g.It("should ignore unsupported hook events", func() {
|
||||||
|
buf := bytes.NewBufferString(fixtures.HookPullRequest)
|
||||||
|
req, _ := http.NewRequest("POST", "/hook", buf)
|
||||||
|
req.Header = http.Header{}
|
||||||
|
req.Header.Set(hookEvent, "issues")
|
||||||
|
|
||||||
|
r, b, err := parseHook(req, false)
|
||||||
|
g.Assert(r == nil).IsTrue()
|
||||||
|
g.Assert(b == nil).IsTrue()
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
})
|
||||||
|
|
||||||
|
g.Describe("given a push hook", func() {
|
||||||
|
g.It("should skip when action is deleted", func() {
|
||||||
|
raw := []byte(fixtures.HookPushDeleted)
|
||||||
|
r, b, err := parsePushHook(raw)
|
||||||
|
g.Assert(r == nil).IsTrue()
|
||||||
|
g.Assert(b == nil).IsTrue()
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
})
|
||||||
|
g.It("should extract repository and build details", func() {
|
||||||
|
buf := bytes.NewBufferString(fixtures.HookPush)
|
||||||
|
req, _ := http.NewRequest("POST", "/hook", buf)
|
||||||
|
req.Header = http.Header{}
|
||||||
|
req.Header.Set(hookEvent, hookPush)
|
||||||
|
|
||||||
|
r, b, err := parseHook(req, false)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(r != nil).IsTrue()
|
||||||
|
g.Assert(b != nil).IsTrue()
|
||||||
|
g.Assert(b.Event).Equal(model.EventPush)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
g.Describe("given a pull request hook", func() {
|
||||||
|
g.It("should skip when action is not open or sync", func() {
|
||||||
|
raw := []byte(fixtures.HookPullRequestInvalidAction)
|
||||||
|
r, b, err := parsePullHook(raw, false)
|
||||||
|
g.Assert(r == nil).IsTrue()
|
||||||
|
g.Assert(b == nil).IsTrue()
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
})
|
||||||
|
g.It("should skip when state is not open", func() {
|
||||||
|
raw := []byte(fixtures.HookPullRequestInvalidState)
|
||||||
|
r, b, err := parsePullHook(raw, false)
|
||||||
|
g.Assert(r == nil).IsTrue()
|
||||||
|
g.Assert(b == nil).IsTrue()
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
})
|
||||||
|
g.It("should extract repository and build details", func() {
|
||||||
|
buf := bytes.NewBufferString(fixtures.HookPullRequest)
|
||||||
|
req, _ := http.NewRequest("POST", "/hook", buf)
|
||||||
|
req.Header = http.Header{}
|
||||||
|
req.Header.Set(hookEvent, hookPull)
|
||||||
|
|
||||||
|
r, b, err := parseHook(req, false)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(r != nil).IsTrue()
|
||||||
|
g.Assert(b != nil).IsTrue()
|
||||||
|
g.Assert(b.Event).Equal(model.EventPull)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
g.Describe("given a deployment hook", func() {
|
||||||
|
g.It("should extract repository and build details", func() {
|
||||||
|
buf := bytes.NewBufferString(fixtures.HookDeploy)
|
||||||
|
req, _ := http.NewRequest("POST", "/hook", buf)
|
||||||
|
req.Header = http.Header{}
|
||||||
|
req.Header.Set(hookEvent, hookDeploy)
|
||||||
|
|
||||||
|
r, b, err := parseHook(req, false)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(r != nil).IsTrue()
|
||||||
|
g.Assert(b != nil).IsTrue()
|
||||||
|
g.Assert(b.Event).Equal(model.EventDeploy)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,184 +0,0 @@
|
||||||
package github
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/drone/drone/model"
|
|
||||||
"github.com/google/go-github/github"
|
|
||||||
)
|
|
||||||
|
|
||||||
// push parses a hook with event type `push` and returns
|
|
||||||
// the commit data.
|
|
||||||
func (c *client) push(r *http.Request) (*model.Repo, *model.Build, error) {
|
|
||||||
payload := GetPayload(r)
|
|
||||||
hook := &pushHook{}
|
|
||||||
err := json.Unmarshal(payload, hook)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if hook.Deleted {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
repo := &model.Repo{}
|
|
||||||
repo.Owner = hook.Repo.Owner.Login
|
|
||||||
if len(repo.Owner) == 0 {
|
|
||||||
repo.Owner = hook.Repo.Owner.Name
|
|
||||||
}
|
|
||||||
repo.Name = hook.Repo.Name
|
|
||||||
// Generating rather than using hook.Repo.FullName as it's
|
|
||||||
// not always present
|
|
||||||
repo.FullName = fmt.Sprintf("%s/%s", repo.Owner, repo.Name)
|
|
||||||
repo.Link = hook.Repo.HTMLURL
|
|
||||||
repo.IsPrivate = hook.Repo.Private
|
|
||||||
repo.Clone = hook.Repo.CloneURL
|
|
||||||
repo.Branch = hook.Repo.DefaultBranch
|
|
||||||
repo.Kind = model.RepoGit
|
|
||||||
|
|
||||||
build := &model.Build{}
|
|
||||||
build.Event = model.EventPush
|
|
||||||
build.Commit = hook.Head.ID
|
|
||||||
build.Ref = hook.Ref
|
|
||||||
build.Link = hook.Head.URL
|
|
||||||
build.Branch = strings.Replace(build.Ref, "refs/heads/", "", -1)
|
|
||||||
build.Message = hook.Head.Message
|
|
||||||
// build.Timestamp = hook.Head.Timestamp
|
|
||||||
build.Email = hook.Head.Author.Email
|
|
||||||
build.Avatar = hook.Sender.Avatar
|
|
||||||
build.Author = hook.Sender.Login
|
|
||||||
build.Remote = hook.Repo.CloneURL
|
|
||||||
|
|
||||||
if len(build.Author) == 0 {
|
|
||||||
build.Author = hook.Head.Author.Username
|
|
||||||
// default gravatar?
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(build.Ref, "refs/tags/") {
|
|
||||||
// just kidding, this is actually a tag event
|
|
||||||
build.Event = model.EventTag
|
|
||||||
}
|
|
||||||
|
|
||||||
return repo, build, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// pullRequest parses a hook with event type `pullRequest`
|
|
||||||
// and returns the commit data.
|
|
||||||
func (c *client) pullRequest(r *http.Request) (*model.Repo, *model.Build, error) {
|
|
||||||
payload := GetPayload(r)
|
|
||||||
hook := &struct {
|
|
||||||
Action string `json:"action"`
|
|
||||||
PullRequest *github.PullRequest `json:"pull_request"`
|
|
||||||
Repo *github.Repository `json:"repository"`
|
|
||||||
}{}
|
|
||||||
err := json.Unmarshal(payload, hook)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ignore these
|
|
||||||
if hook.Action != "opened" && hook.Action != "synchronize" {
|
|
||||||
return nil, nil, nil
|
|
||||||
}
|
|
||||||
if *hook.PullRequest.State != "open" {
|
|
||||||
return nil, nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
repo := &model.Repo{}
|
|
||||||
repo.Owner = *hook.Repo.Owner.Login
|
|
||||||
repo.Name = *hook.Repo.Name
|
|
||||||
repo.FullName = *hook.Repo.FullName
|
|
||||||
repo.Link = *hook.Repo.HTMLURL
|
|
||||||
repo.IsPrivate = *hook.Repo.Private
|
|
||||||
repo.Clone = *hook.Repo.CloneURL
|
|
||||||
repo.Kind = model.RepoGit
|
|
||||||
repo.Branch = "master"
|
|
||||||
if hook.Repo.DefaultBranch != nil {
|
|
||||||
repo.Branch = *hook.Repo.DefaultBranch
|
|
||||||
}
|
|
||||||
|
|
||||||
build := &model.Build{}
|
|
||||||
build.Event = model.EventPull
|
|
||||||
build.Commit = *hook.PullRequest.Head.SHA
|
|
||||||
build.Link = *hook.PullRequest.HTMLURL
|
|
||||||
build.Branch = *hook.PullRequest.Head.Ref
|
|
||||||
build.Message = *hook.PullRequest.Title
|
|
||||||
build.Author = *hook.PullRequest.User.Login
|
|
||||||
build.Avatar = *hook.PullRequest.User.AvatarURL
|
|
||||||
build.Remote = *hook.PullRequest.Base.Repo.CloneURL
|
|
||||||
build.Title = *hook.PullRequest.Title
|
|
||||||
|
|
||||||
if c.MergeRef {
|
|
||||||
build.Ref = fmt.Sprintf("refs/pull/%d/merge", *hook.PullRequest.Number)
|
|
||||||
} else {
|
|
||||||
build.Ref = fmt.Sprintf("refs/pull/%d/head", *hook.PullRequest.Number)
|
|
||||||
}
|
|
||||||
|
|
||||||
// build.Timestamp = time.Now().UTC().Format("2006-01-02 15:04:05.000000000 +0000 MST")
|
|
||||||
|
|
||||||
return repo, build, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *client) deployment(r *http.Request) (*model.Repo, *model.Build, error) {
|
|
||||||
payload := GetPayload(r)
|
|
||||||
hook := &deployHook{}
|
|
||||||
|
|
||||||
err := json.Unmarshal(payload, hook)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// for older versions of GitHub. Remove.
|
|
||||||
if hook.Deployment.ID == 0 {
|
|
||||||
hook.Deployment.ID = hook.ID
|
|
||||||
hook.Deployment.Sha = hook.Sha
|
|
||||||
hook.Deployment.Ref = hook.Ref
|
|
||||||
hook.Deployment.Task = hook.Name
|
|
||||||
hook.Deployment.Env = hook.Env
|
|
||||||
hook.Deployment.Desc = hook.Desc
|
|
||||||
}
|
|
||||||
|
|
||||||
repo := &model.Repo{}
|
|
||||||
repo.Owner = hook.Repo.Owner.Login
|
|
||||||
if len(repo.Owner) == 0 {
|
|
||||||
repo.Owner = hook.Repo.Owner.Name
|
|
||||||
}
|
|
||||||
repo.Name = hook.Repo.Name
|
|
||||||
repo.FullName = fmt.Sprintf("%s/%s", repo.Owner, repo.Name)
|
|
||||||
repo.Link = hook.Repo.HTMLURL
|
|
||||||
repo.IsPrivate = hook.Repo.Private
|
|
||||||
repo.Clone = hook.Repo.CloneURL
|
|
||||||
repo.Branch = hook.Repo.DefaultBranch
|
|
||||||
repo.Kind = model.RepoGit
|
|
||||||
|
|
||||||
// ref can be
|
|
||||||
// branch, tag, or sha
|
|
||||||
|
|
||||||
build := &model.Build{}
|
|
||||||
build.Event = model.EventDeploy
|
|
||||||
build.Commit = hook.Deployment.Sha
|
|
||||||
build.Link = hook.Deployment.URL
|
|
||||||
build.Message = hook.Deployment.Desc
|
|
||||||
build.Avatar = hook.Sender.Avatar
|
|
||||||
build.Author = hook.Sender.Login
|
|
||||||
build.Ref = hook.Deployment.Ref
|
|
||||||
build.Branch = hook.Deployment.Ref
|
|
||||||
build.Deploy = hook.Deployment.Env
|
|
||||||
|
|
||||||
// if the ref is a sha or short sha we need to manually
|
|
||||||
// construct the ref.
|
|
||||||
if strings.HasPrefix(build.Commit, build.Ref) || build.Commit == build.Ref {
|
|
||||||
build.Branch = repo.Branch
|
|
||||||
build.Ref = fmt.Sprintf("refs/heads/%s", repo.Branch)
|
|
||||||
|
|
||||||
}
|
|
||||||
// if the ref is a branch we should make sure it has refs/heads prefix
|
|
||||||
if !strings.HasPrefix(build.Ref, "refs/") { // branch or tag
|
|
||||||
build.Ref = fmt.Sprintf("refs/heads/%s", build.Branch)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return repo, build, nil
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
package github
|
|
|
@ -1,7 +1,8 @@
|
||||||
package github
|
package github
|
||||||
|
|
||||||
type pushHook struct {
|
type webhook struct {
|
||||||
Ref string `json:"ref"`
|
Ref string `json:"ref"`
|
||||||
|
Action string `json:"action"`
|
||||||
Deleted bool `json:"deleted"`
|
Deleted bool `json:"deleted"`
|
||||||
|
|
||||||
Head struct {
|
Head struct {
|
||||||
|
@ -28,6 +29,7 @@ type pushHook struct {
|
||||||
Avatar string `json:"avatar_url"`
|
Avatar string `json:"avatar_url"`
|
||||||
} `json:"sender"`
|
} `json:"sender"`
|
||||||
|
|
||||||
|
// repository details
|
||||||
Repo struct {
|
Repo struct {
|
||||||
Owner struct {
|
Owner struct {
|
||||||
Login string `json:"login"`
|
Login string `json:"login"`
|
||||||
|
@ -42,9 +44,8 @@ type pushHook struct {
|
||||||
CloneURL string `json:"clone_url"`
|
CloneURL string `json:"clone_url"`
|
||||||
DefaultBranch string `json:"default_branch"`
|
DefaultBranch string `json:"default_branch"`
|
||||||
} `json:"repository"`
|
} `json:"repository"`
|
||||||
}
|
|
||||||
|
|
||||||
type deployHook struct {
|
// deployment hook details
|
||||||
Deployment struct {
|
Deployment struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
Sha string `json:"sha"`
|
Sha string `json:"sha"`
|
||||||
|
@ -55,33 +56,21 @@ type deployHook struct {
|
||||||
Desc string `json:"description"`
|
Desc string `json:"description"`
|
||||||
} `json:"deployment"`
|
} `json:"deployment"`
|
||||||
|
|
||||||
Sender struct {
|
// pull request details
|
||||||
Login string `json:"login"`
|
PullRequest struct {
|
||||||
Avatar string `json:"avatar_url"`
|
Number int `json:"number"`
|
||||||
} `json:"sender"`
|
State string `json:"state"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
HTMLURL string `json:"html_url"`
|
||||||
|
|
||||||
Repo struct {
|
User struct {
|
||||||
Owner struct {
|
Login string `json:"login"`
|
||||||
Login string `json:"login"`
|
Avatar string `json:"avatar_url"`
|
||||||
Name string `json:"name"`
|
} `json:"user"`
|
||||||
} `json:"owner"`
|
|
||||||
|
|
||||||
Name string `json:"name"`
|
Head struct {
|
||||||
FullName string `json:"full_name"`
|
SHA string
|
||||||
Language string `json:"language"`
|
Ref string
|
||||||
Private bool `json:"private"`
|
} `json:"head"`
|
||||||
HTMLURL string `json:"html_url"`
|
} `json:"pull_request"`
|
||||||
CloneURL string `json:"clone_url"`
|
|
||||||
DefaultBranch string `json:"default_branch"`
|
|
||||||
} `json:"repository"`
|
|
||||||
|
|
||||||
// these are legacy fields that have been added to the deployment section.
|
|
||||||
// They are here for older versions of GitHub and will be removed.
|
|
||||||
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
Sha string `json:"sha"`
|
|
||||||
Ref string `json:"ref"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Env string `json:"environment"`
|
|
||||||
Desc string `json:"description"`
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue