Add pull_request webhook support to Gogs remote

This commit is contained in:
Michael de Wit 2017-01-03 09:38:05 +01:00
parent f8f5fdfb40
commit 141eb4ea57
5 changed files with 261 additions and 3 deletions

View file

@ -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"
}
}`

View file

@ -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 {

View file

@ -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},

View file

@ -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
}

View file

@ -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"`
}