Add pull_request webhook support to Gogs remote
This commit is contained in:
parent
f8f5fdfb40
commit
141eb4ea57
5 changed files with 261 additions and 3 deletions
|
@ -83,3 +83,55 @@ var HookPushTag = `{
|
||||||
"avatar_url": "https://secure.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87"
|
"avatar_url": "https://secure.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87"
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
|
// HookPullRequest is a sample pull_request webhook payload
|
||||||
|
var HookPullRequest = `{
|
||||||
|
"action": "opened",
|
||||||
|
"number": 1,
|
||||||
|
"pull_request": {
|
||||||
|
"html_url": "http://gogs.golang.org/gordon/hello-world/pull/1",
|
||||||
|
"state": "open",
|
||||||
|
"title": "Update the README with new information",
|
||||||
|
"body": "please merge",
|
||||||
|
"user": {
|
||||||
|
"id": 1,
|
||||||
|
"username": "gordon",
|
||||||
|
"full_name": "Gordon the Gopher",
|
||||||
|
"email": "gordon@golang.org",
|
||||||
|
"avatar_url": "http://gogs.golang.org///1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87"
|
||||||
|
},
|
||||||
|
"base": {
|
||||||
|
"label": "master",
|
||||||
|
"ref": "master",
|
||||||
|
"sha": "9353195a19e45482665306e466c832c46560532d"
|
||||||
|
},
|
||||||
|
"head": {
|
||||||
|
"label": "feature/changes",
|
||||||
|
"ref": "feature/changes",
|
||||||
|
"sha": "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"id": 35129377,
|
||||||
|
"name": "hello-world",
|
||||||
|
"full_name": "gordon/hello-world",
|
||||||
|
"owner": {
|
||||||
|
"id": 1,
|
||||||
|
"username": "gordon",
|
||||||
|
"full_name": "Gordon the Gopher",
|
||||||
|
"email": "gordon@golang.org",
|
||||||
|
"avatar_url": "https://secure.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87"
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
|
"html_url": "http://gogs.golang.org/gordon/hello-world",
|
||||||
|
"clone_url": "https://gogs.golang.org/gordon/hello-world.git",
|
||||||
|
"default_branch": "master"
|
||||||
|
},
|
||||||
|
"sender": {
|
||||||
|
"id": 1,
|
||||||
|
"username": "gordon",
|
||||||
|
"full_name": "Gordon the Gopher",
|
||||||
|
"email": "gordon@golang.org",
|
||||||
|
"avatar_url": "https://secure.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87"
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
|
@ -112,6 +112,30 @@ func buildFromTag(hook *pushHook) *model.Build {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// helper function that extracts the Build data from a Gogs pull_request hook
|
||||||
|
func buildFromPullRequest(hook *pullRequestHook) *model.Build {
|
||||||
|
avatar := expandAvatar(
|
||||||
|
hook.Repo.URL,
|
||||||
|
fixMalformedAvatar(hook.PullRequest.User.Avatar),
|
||||||
|
)
|
||||||
|
build := &model.Build{
|
||||||
|
Event: model.EventPull,
|
||||||
|
Commit: hook.PullRequest.Head.Sha,
|
||||||
|
Link: hook.PullRequest.URL,
|
||||||
|
Ref: fmt.Sprintf("refs/pull/%d/head", hook.Number),
|
||||||
|
Branch: hook.PullRequest.Base.Ref,
|
||||||
|
Message: hook.PullRequest.Title,
|
||||||
|
Author: hook.PullRequest.User.Username,
|
||||||
|
Avatar: avatar,
|
||||||
|
Title: hook.PullRequest.Title,
|
||||||
|
Refspec: fmt.Sprintf("%s:%s",
|
||||||
|
hook.PullRequest.Head.Ref,
|
||||||
|
hook.PullRequest.Base.Ref,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
return build
|
||||||
|
}
|
||||||
|
|
||||||
// helper function that extracts the Repository data from a Gogs push hook
|
// helper function that extracts the Repository data from a Gogs push hook
|
||||||
func repoFromPush(hook *pushHook) *model.Repo {
|
func repoFromPush(hook *pushHook) *model.Repo {
|
||||||
return &model.Repo{
|
return &model.Repo{
|
||||||
|
@ -122,6 +146,16 @@ func repoFromPush(hook *pushHook) *model.Repo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// helper function that extracts the Repository data from a Gogs pull_request hook
|
||||||
|
func repoFromPullRequest(hook *pullRequestHook) *model.Repo {
|
||||||
|
return &model.Repo{
|
||||||
|
Name: hook.Repo.Name,
|
||||||
|
Owner: hook.Repo.Owner.Username,
|
||||||
|
FullName: hook.Repo.FullName,
|
||||||
|
Link: hook.Repo.URL,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// helper function that parses a push hook from a read closer.
|
// helper function that parses a push hook from a read closer.
|
||||||
func parsePush(r io.Reader) (*pushHook, error) {
|
func parsePush(r io.Reader) (*pushHook, error) {
|
||||||
push := new(pushHook)
|
push := new(pushHook)
|
||||||
|
@ -129,6 +163,12 @@ func parsePush(r io.Reader) (*pushHook, error) {
|
||||||
return push, err
|
return push, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parsePullRequest(r io.Reader) (*pullRequestHook, error) {
|
||||||
|
pr := new(pullRequestHook)
|
||||||
|
err := json.NewDecoder(r).Decode(pr)
|
||||||
|
return pr, err
|
||||||
|
}
|
||||||
|
|
||||||
// fixMalformedAvatar is a helper function that fixes an avatar url if malformed
|
// fixMalformedAvatar is a helper function that fixes an avatar url if malformed
|
||||||
// (currently a known bug with gogs)
|
// (currently a known bug with gogs)
|
||||||
func fixMalformedAvatar(url string) string {
|
func fixMalformedAvatar(url string) string {
|
||||||
|
|
|
@ -53,6 +53,32 @@ func Test_parse(t *testing.T) {
|
||||||
g.Assert(hook.Sender.Avatar).Equal("https://secure.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87")
|
g.Assert(hook.Sender.Avatar).Equal("https://secure.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
g.It("Should parse pull_request hook payload", func() {
|
||||||
|
buf := bytes.NewBufferString(fixtures.HookPullRequest)
|
||||||
|
hook, err := parsePullRequest(buf)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(hook.Action).Equal("opened")
|
||||||
|
g.Assert(hook.Number).Equal(int64(1))
|
||||||
|
|
||||||
|
g.Assert(hook.Repo.Name).Equal("hello-world")
|
||||||
|
g.Assert(hook.Repo.URL).Equal("http://gogs.golang.org/gordon/hello-world")
|
||||||
|
g.Assert(hook.Repo.FullName).Equal("gordon/hello-world")
|
||||||
|
g.Assert(hook.Repo.Owner.Email).Equal("gordon@golang.org")
|
||||||
|
g.Assert(hook.Repo.Owner.Username).Equal("gordon")
|
||||||
|
g.Assert(hook.Repo.Private).Equal(true)
|
||||||
|
g.Assert(hook.Sender.Username).Equal("gordon")
|
||||||
|
g.Assert(hook.Sender.Avatar).Equal("https://secure.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87")
|
||||||
|
|
||||||
|
g.Assert(hook.PullRequest.Title).Equal("Update the README with new information")
|
||||||
|
g.Assert(hook.PullRequest.Body).Equal("please merge")
|
||||||
|
g.Assert(hook.PullRequest.State).Equal("open")
|
||||||
|
g.Assert(hook.PullRequest.User.Username).Equal("gordon")
|
||||||
|
g.Assert(hook.PullRequest.Base.Label).Equal("master")
|
||||||
|
g.Assert(hook.PullRequest.Base.Ref).Equal("master")
|
||||||
|
g.Assert(hook.PullRequest.Head.Label).Equal("feature/changes")
|
||||||
|
g.Assert(hook.PullRequest.Head.Ref).Equal("feature/changes")
|
||||||
|
})
|
||||||
|
|
||||||
g.It("Should return a Build struct from a push hook", func() {
|
g.It("Should return a Build struct from a push hook", func() {
|
||||||
buf := bytes.NewBufferString(fixtures.HookPush)
|
buf := bytes.NewBufferString(fixtures.HookPush)
|
||||||
hook, _ := parsePush(buf)
|
hook, _ := parsePush(buf)
|
||||||
|
@ -78,6 +104,31 @@ func Test_parse(t *testing.T) {
|
||||||
g.Assert(repo.Link).Equal(hook.Repo.URL)
|
g.Assert(repo.Link).Equal(hook.Repo.URL)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
g.It("Should return a Build struct from a pull_request hook", func() {
|
||||||
|
buf := bytes.NewBufferString(fixtures.HookPullRequest)
|
||||||
|
hook, _ := parsePullRequest(buf)
|
||||||
|
build := buildFromPullRequest(hook)
|
||||||
|
g.Assert(build.Event).Equal(model.EventPull)
|
||||||
|
g.Assert(build.Commit).Equal(hook.PullRequest.Head.Sha)
|
||||||
|
g.Assert(build.Ref).Equal("refs/pull/1/head")
|
||||||
|
g.Assert(build.Link).Equal(hook.PullRequest.URL)
|
||||||
|
g.Assert(build.Branch).Equal("master")
|
||||||
|
g.Assert(build.Message).Equal(hook.PullRequest.Title)
|
||||||
|
g.Assert(build.Avatar).Equal("http://1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87")
|
||||||
|
g.Assert(build.Author).Equal(hook.PullRequest.User.Username)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should return a Repo struct from a pull_request hook", func() {
|
||||||
|
buf := bytes.NewBufferString(fixtures.HookPullRequest)
|
||||||
|
hook, _ := parsePullRequest(buf)
|
||||||
|
repo := repoFromPullRequest(hook)
|
||||||
|
g.Assert(repo.Name).Equal(hook.Repo.Name)
|
||||||
|
g.Assert(repo.Owner).Equal(hook.Repo.Owner.Username)
|
||||||
|
g.Assert(repo.FullName).Equal("gordon/hello-world")
|
||||||
|
g.Assert(repo.Link).Equal(hook.Repo.URL)
|
||||||
|
})
|
||||||
|
|
||||||
g.It("Should return a Perm struct from a Gogs Perm", func() {
|
g.It("Should return a Perm struct from a Gogs Perm", func() {
|
||||||
perms := []gogs.Permission{
|
perms := []gogs.Permission{
|
||||||
{true, true, true},
|
{true, true, true},
|
||||||
|
|
|
@ -11,6 +11,12 @@ const (
|
||||||
hookEvent = "X-Gogs-Event"
|
hookEvent = "X-Gogs-Event"
|
||||||
hookPush = "push"
|
hookPush = "push"
|
||||||
hookCreated = "create"
|
hookCreated = "create"
|
||||||
|
hookPullRequest = "pull_request"
|
||||||
|
|
||||||
|
actionOpen = "opened"
|
||||||
|
actionSync = "synchronize"
|
||||||
|
|
||||||
|
stateOpen = "open"
|
||||||
|
|
||||||
refBranch = "branch"
|
refBranch = "branch"
|
||||||
refTag = "tag"
|
refTag = "tag"
|
||||||
|
@ -24,6 +30,8 @@ func parseHook(r *http.Request) (*model.Repo, *model.Build, error) {
|
||||||
return parsePushHook(r.Body)
|
return parsePushHook(r.Body)
|
||||||
case hookCreated:
|
case hookCreated:
|
||||||
return parseCreatedHook(r.Body)
|
return parseCreatedHook(r.Body)
|
||||||
|
case hookPullRequest:
|
||||||
|
return parsePullRequestHook(r.Body)
|
||||||
}
|
}
|
||||||
return nil, nil, nil
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
|
@ -72,3 +80,28 @@ func parseCreatedHook(payload io.Reader) (*model.Repo, *model.Build, error) {
|
||||||
build = buildFromTag(push)
|
build = buildFromTag(push)
|
||||||
return repo, build, err
|
return repo, build, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parsePullRequestHook parses a pull_request hook and returns the Repo and Build details.
|
||||||
|
func parsePullRequestHook(payload io.Reader) (*model.Repo, *model.Build, error) {
|
||||||
|
var (
|
||||||
|
repo *model.Repo
|
||||||
|
build *model.Build
|
||||||
|
)
|
||||||
|
|
||||||
|
pr, err := parsePullRequest(payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't trigger builds for non-code changes, or if PR is not open
|
||||||
|
if pr.Action != actionOpen && pr.Action != actionSync {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
if pr.PullRequest.State != stateOpen {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
repo = repoFromPullRequest(pr)
|
||||||
|
build = buildFromPullRequest(pr)
|
||||||
|
return repo, build, err
|
||||||
|
}
|
||||||
|
|
|
@ -40,3 +40,85 @@ type pushHook struct {
|
||||||
Avatar string `json:"avatar_url"`
|
Avatar string `json:"avatar_url"`
|
||||||
} `json:"sender"`
|
} `json:"sender"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type pullRequestHook struct {
|
||||||
|
Action string `json:"action"`
|
||||||
|
Number int64 `json:"number"`
|
||||||
|
PullRequest struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
User struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Name string `json:"full_name"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Avatar string `json:"avatar_url"`
|
||||||
|
} `json:"user"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Body string `json:"body"`
|
||||||
|
Labels []string `json:"labels"`
|
||||||
|
State string `json:"state"`
|
||||||
|
URL string `json:"html_url"`
|
||||||
|
Mergeable bool `json:"mergeable"`
|
||||||
|
Merged bool `json:"merged"`
|
||||||
|
MergeBase string `json:"merge_base"`
|
||||||
|
Base struct {
|
||||||
|
Label string `json:"label"`
|
||||||
|
Ref string `json:"ref"`
|
||||||
|
Sha string `json:"sha"`
|
||||||
|
Repo struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
FullName string `json:"full_name"`
|
||||||
|
URL string `json:"html_url"`
|
||||||
|
Private bool `json:"private"`
|
||||||
|
Owner struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Name string `json:"full_name"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Avatar string `json:"avatar_url"`
|
||||||
|
} `json:"owner"`
|
||||||
|
} `json:"repo"`
|
||||||
|
} `json:"base"`
|
||||||
|
Head struct {
|
||||||
|
Label string `json:"label"`
|
||||||
|
Ref string `json:"ref"`
|
||||||
|
Sha string `json:"sha"`
|
||||||
|
Repo struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
FullName string `json:"full_name"`
|
||||||
|
URL string `json:"html_url"`
|
||||||
|
Private bool `json:"private"`
|
||||||
|
Owner struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Name string `json:"full_name"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Avatar string `json:"avatar_url"`
|
||||||
|
} `json:"owner"`
|
||||||
|
} `json:"repo"`
|
||||||
|
} `json:"head"`
|
||||||
|
} `json:"pull_request"`
|
||||||
|
Repo struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
FullName string `json:"full_name"`
|
||||||
|
URL string `json:"html_url"`
|
||||||
|
Private bool `json:"private"`
|
||||||
|
Owner struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Name string `json:"full_name"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Avatar string `json:"avatar_url"`
|
||||||
|
} `json:"owner"`
|
||||||
|
} `json:"repository"`
|
||||||
|
Sender struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Name string `json:"full_name"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Avatar string `json:"avatar_url"`
|
||||||
|
} `json:"sender"`
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue