You've already forked woodpecker
							
							
				mirror of
				https://github.com/woodpecker-ci/woodpecker.git
				synced 2025-10-30 23:27:39 +02:00 
			
		
		
		
	Support ChangedFiles for Github & Gitlab PRs and Gitlab pushes (#697)
This commit is contained in:
		| @@ -159,7 +159,7 @@ when: | ||||
|  | ||||
| :::info | ||||
| This feature is currently only available for GitHub, Gitlab and Gitea. | ||||
| Pull requests aren't supported at the moment ([#697](https://github.com/woodpecker-ci/woodpecker/pull/697)).   | ||||
| Pull requests aren't supported by gitea at the moment ([go-gitea/gitea#18228](https://github.com/go-gitea/gitea/pull/18228)).   | ||||
| Path conditions are ignored for tag events. | ||||
| ::: | ||||
|  | ||||
|   | ||||
| @@ -78,7 +78,7 @@ func BlockTilQueueHasRunningItem(c *gin.Context) { | ||||
| func PostHook(c *gin.Context) { | ||||
| 	_store := store.FromContext(c) | ||||
|  | ||||
| 	tmpRepo, build, err := server.Config.Services.Remote.Hook(c.Request) | ||||
| 	tmpRepo, build, err := server.Config.Services.Remote.Hook(c, c.Request) | ||||
| 	if err != nil { | ||||
| 		msg := "failure to parse hook" | ||||
| 		log.Debug().Err(err).Msg(msg) | ||||
| @@ -288,6 +288,7 @@ func branchFiltered(build *model.Build, remoteYamlConfigs []*remote.FileMeta) (b | ||||
| 			return false, nil | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return true, nil | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -284,7 +284,7 @@ func (c *config) Branches(ctx context.Context, u *model.User, r *model.Repo) ([] | ||||
|  | ||||
| // Hook parses the incoming Bitbucket hook and returns the Repository and | ||||
| // Build details. If the hook is unsupported nil values are returned. | ||||
| func (c *config) Hook(req *http.Request) (*model.Repo, *model.Build, error) { | ||||
| func (c *config) Hook(ctx context.Context, req *http.Request) (*model.Repo, *model.Build, error) { | ||||
| 	return parseHook(req) | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -264,7 +264,7 @@ func Test_bitbucket(t *testing.T) { | ||||
| 			req.Header = http.Header{} | ||||
| 			req.Header.Set(hookEvent, hookPush) | ||||
|  | ||||
| 			r, _, err := c.Hook(req) | ||||
| 			r, _, err := c.Hook(ctx, req) | ||||
| 			g.Assert(err).IsNil() | ||||
| 			g.Assert(r.FullName).Equal("user_name/repo_name") | ||||
| 		}) | ||||
|   | ||||
| @@ -236,7 +236,7 @@ func (c *Config) Deactivate(ctx context.Context, u *model.User, r *model.Repo, l | ||||
| 	return client.DeleteHook(r.Owner, r.Name, link) | ||||
| } | ||||
|  | ||||
| func (c *Config) Hook(r *http.Request) (*model.Repo, *model.Build, error) { | ||||
| func (c *Config) Hook(ctx context.Context, r *http.Request) (*model.Repo, *model.Build, error) { | ||||
| 	return parseHook(r, c.URL) | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -284,7 +284,7 @@ func (c *Coding) Branches(ctx context.Context, u *model.User, r *model.Repo) ([] | ||||
|  | ||||
| // Hook parses the post-commit hook from the Request body and returns the | ||||
| // required data in a standard format. | ||||
| func (c *Coding) Hook(r *http.Request) (*model.Repo, *model.Build, error) { | ||||
| func (c *Coding) Hook(ctx context.Context, r *http.Request) (*model.Repo, *model.Build, error) { | ||||
| 	repo, build, err := parseHook(r) | ||||
| 	if build != nil { | ||||
| 		build.Avatar = c.resourceLink(build.Avatar) | ||||
|   | ||||
| @@ -223,7 +223,7 @@ func Test_coding(t *testing.T) { | ||||
| 				req.Header = http.Header{} | ||||
| 				req.Header.Set(hookEvent, hookPush) | ||||
|  | ||||
| 				r, _, err := c.Hook(req) | ||||
| 				r, _, err := c.Hook(ctx, req) | ||||
| 				g.Assert(err).IsNil() | ||||
| 				g.Assert(r.FullName).Equal("demo1/test1") | ||||
| 			}) | ||||
|   | ||||
| @@ -442,7 +442,7 @@ func (c *Gitea) Branches(ctx context.Context, u *model.User, r *model.Repo) ([]s | ||||
|  | ||||
| // Hook parses the incoming Gitea hook and returns the Repository and Build | ||||
| // details. If the hook is unsupported nil values are returned. | ||||
| func (c *Gitea) Hook(r *http.Request) (*model.Repo, *model.Build, error) { | ||||
| func (c *Gitea) Hook(ctx context.Context, r *http.Request) (*model.Repo, *model.Build, error) { | ||||
| 	return parseHook(r) | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -25,6 +25,7 @@ import ( | ||||
| 	"code.gitea.io/sdk/gitea" | ||||
|  | ||||
| 	"github.com/woodpecker-ci/woodpecker/server/model" | ||||
| 	"github.com/woodpecker-ci/woodpecker/shared/utils" | ||||
| ) | ||||
|  | ||||
| // helper function that converts a Gitea repository to a Woodpecker repository. | ||||
| @@ -110,15 +111,15 @@ func buildFromPush(hook *pushHook) *model.Build { | ||||
| } | ||||
|  | ||||
| func getChangedFilesFromPushHook(hook *pushHook) []string { | ||||
| 	files := make([]string, 0) | ||||
|  | ||||
| 	// assume a capacity of 4 changed files per commit | ||||
| 	files := make([]string, 0, len(hook.Commits)*4) | ||||
| 	for _, c := range hook.Commits { | ||||
| 		files = append(files, c.Added...) | ||||
| 		files = append(files, c.Removed...) | ||||
| 		files = append(files, c.Modified...) | ||||
| 	} | ||||
|  | ||||
| 	return files | ||||
| 	return utils.DedupStrings(files) | ||||
| } | ||||
|  | ||||
| // helper function that extracts the Build data from a Gitea tag hook | ||||
|   | ||||
| @@ -15,9 +15,6 @@ | ||||
| package github | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/google/go-github/v39/github" | ||||
|  | ||||
| 	"github.com/woodpecker-ci/woodpecker/server/model" | ||||
| @@ -44,7 +41,7 @@ const ( | ||||
| const ( | ||||
| 	headRefs  = "refs/pull/%d/head"  // pull request unmerged | ||||
| 	mergeRefs = "refs/pull/%d/merge" // pull request merged with base | ||||
| 	refspec   = "%s:%s" | ||||
| 	refSpec   = "%s:%s" | ||||
| ) | ||||
|  | ||||
| // convertStatus is a helper function used to convert a Woodpecker status to a | ||||
| @@ -85,19 +82,19 @@ func convertDesc(status model.StatusValue) string { | ||||
| // structure to the common Woodpecker repository structure. | ||||
| func convertRepo(from *github.Repository, private bool) *model.Repo { | ||||
| 	repo := &model.Repo{ | ||||
| 		Owner:        *from.Owner.Login, | ||||
| 		Name:         *from.Name, | ||||
| 		FullName:     *from.FullName, | ||||
| 		Link:         *from.HTMLURL, | ||||
| 		IsSCMPrivate: *from.Private, | ||||
| 		Clone:        *from.CloneURL, | ||||
| 		Avatar:       *from.Owner.AvatarURL, | ||||
| 		Name:         from.GetName(), | ||||
| 		FullName:     from.GetFullName(), | ||||
| 		Link:         from.GetHTMLURL(), | ||||
| 		IsSCMPrivate: from.GetPrivate(), | ||||
| 		Clone:        from.GetCloneURL(), | ||||
| 		Branch:       from.GetDefaultBranch(), | ||||
| 		Owner:        from.GetOwner().GetLogin(), | ||||
| 		Avatar:       from.GetOwner().GetAvatarURL(), | ||||
| 		Perm:         convertPerm(from.GetPermissions()), | ||||
| 		SCMKind:      model.RepoGit, | ||||
| 		Branch:       defaultBranch, | ||||
| 		Perm:         convertPerm(from), | ||||
| 	} | ||||
| 	if from.DefaultBranch != nil { | ||||
| 		repo.Branch = *from.DefaultBranch | ||||
| 	if len(repo.Branch) == 0 { | ||||
| 		repo.Branch = defaultBranch | ||||
| 	} | ||||
| 	if private { | ||||
| 		repo.IsSCMPrivate = true | ||||
| @@ -107,11 +104,11 @@ func convertRepo(from *github.Repository, private bool) *model.Repo { | ||||
|  | ||||
| // convertPerm is a helper function used to convert a GitHub repository | ||||
| // permissions to the common Woodpecker permissions structure. | ||||
| func convertPerm(from *github.Repository) *model.Perm { | ||||
| func convertPerm(perm map[string]bool) *model.Perm { | ||||
| 	return &model.Perm{ | ||||
| 		Admin: from.Permissions["admin"], | ||||
| 		Push:  from.Permissions["push"], | ||||
| 		Pull:  from.Permissions["pull"], | ||||
| 		Admin: perm["admin"], | ||||
| 		Push:  perm["push"], | ||||
| 		Pull:  perm["pull"], | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -139,143 +136,29 @@ func convertTeamList(from []*github.Organization) []*model.Team { | ||||
| // to the common Woodpecker repository structure. | ||||
| func convertTeam(from *github.Organization) *model.Team { | ||||
| 	return &model.Team{ | ||||
| 		Login:  *from.Login, | ||||
| 		Avatar: *from.AvatarURL, | ||||
| 		Login:  from.GetLogin(), | ||||
| 		Avatar: from.GetAvatarURL(), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // convertRepoHook is a helper function used to extract the Repository details | ||||
| // from a webhook and convert to the common Woodpecker repository structure. | ||||
| func convertRepoHook(from *webhook) *model.Repo { | ||||
| func convertRepoHook(eventRepo *github.PushEventRepository) *model.Repo { | ||||
| 	repo := &model.Repo{ | ||||
| 		Owner:        from.Repo.Owner.Login, | ||||
| 		Name:         from.Repo.Name, | ||||
| 		FullName:     from.Repo.FullName, | ||||
| 		Link:         from.Repo.HTMLURL, | ||||
| 		IsSCMPrivate: from.Repo.Private, | ||||
| 		Clone:        from.Repo.CloneURL, | ||||
| 		Branch:       from.Repo.DefaultBranch, | ||||
| 		Owner:        eventRepo.GetOwner().GetLogin(), | ||||
| 		Name:         eventRepo.GetName(), | ||||
| 		FullName:     eventRepo.GetFullName(), | ||||
| 		Link:         eventRepo.GetHTMLURL(), | ||||
| 		IsSCMPrivate: eventRepo.GetPrivate(), | ||||
| 		Clone:        eventRepo.GetCloneURL(), | ||||
| 		Branch:       eventRepo.GetDefaultBranch(), | ||||
| 		SCMKind:      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 Woodpecker Build structure. | ||||
| func convertPushHook(from *webhook) *model.Build { | ||||
| 	files := getChangedFilesFromWebhook(from) | ||||
| 	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, | ||||
| 		Sender:       from.Sender.Login, | ||||
| 		ChangedFiles: files, | ||||
| 	} | ||||
|  | ||||
| 	if len(build.Author) == 0 { | ||||
| 		build.Author = from.Head.Author.Username | ||||
| 	} | ||||
|  | ||||
| 	// if len(build.Email) == 0 { | ||||
| 	// TODO: 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 | ||||
| 		// For tags, if the base_ref (tag's base branch) is set, we're using it | ||||
| 		// as build's branch so that we can filter events base on it | ||||
| 		if strings.HasPrefix(from.BaseRef, "refs/heads/") { | ||||
| 			build.Branch = strings.Replace(from.BaseRef, "refs/heads/", "", -1) | ||||
| 		} | ||||
|  | ||||
| 		// tags should not have changed files | ||||
| 		build.ChangedFiles = nil | ||||
| 	} | ||||
|  | ||||
| 	return build | ||||
| } | ||||
|  | ||||
| // convertPushHook is a helper function used to extract the Build details | ||||
| // from a deploy webhook and convert to the common Woodpecker 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, | ||||
| 		Sender:  from.Sender.Login, | ||||
| 	} | ||||
| 	// 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 Woodpecker 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.Base.Ref, | ||||
| 		Message: from.PullRequest.Title, | ||||
| 		Author:  from.PullRequest.User.Login, | ||||
| 		Avatar:  from.PullRequest.User.Avatar, | ||||
| 		Title:   from.PullRequest.Title, | ||||
| 		Sender:  from.Sender.Login, | ||||
| 		Remote:  from.PullRequest.Head.Repo.CloneURL, | ||||
| 		Refspec: fmt.Sprintf(refspec, | ||||
| 			from.PullRequest.Head.Ref, | ||||
| 			from.PullRequest.Base.Ref, | ||||
| 		), | ||||
| 	} | ||||
| 	if merge { | ||||
| 		build.Ref = fmt.Sprintf(mergeRefs, from.PullRequest.Number) | ||||
| 	} | ||||
| 	return build | ||||
| } | ||||
|  | ||||
| func getChangedFilesFromWebhook(from *webhook) []string { | ||||
| 	var files []string | ||||
| 	files = append(files, from.Head.Added...) | ||||
| 	files = append(files, from.Head.Removed...) | ||||
| 	files = append(files, from.Head.Modified...) | ||||
| 	if len(files) == 0 { | ||||
| 		files = make([]string, 0) | ||||
| 	} | ||||
| 	return files | ||||
| } | ||||
|   | ||||
| @@ -129,7 +129,7 @@ func Test_helper(t *testing.T) { | ||||
| 				}, | ||||
| 			} | ||||
|  | ||||
| 			to := convertPerm(from) | ||||
| 			to := convertPerm(from.GetPermissions()) | ||||
| 			g.Assert(to.Push).IsTrue() | ||||
| 			g.Assert(to.Pull).IsTrue() | ||||
| 			g.Assert(to.Admin).IsTrue() | ||||
| @@ -158,124 +158,144 @@ func Test_helper(t *testing.T) { | ||||
| 		}) | ||||
|  | ||||
| 		g.It("should convert a repository from webhook", func() { | ||||
| 			from := &webhook{} | ||||
| 			from.Repo.Owner.Login = "octocat" | ||||
| 			from.Repo.Owner.Name = "octocat" | ||||
| 			from.Repo.Name = "hello-world" | ||||
| 			from.Repo.FullName = "octocat/hello-world" | ||||
| 			from.Repo.Private = true | ||||
| 			from.Repo.HTMLURL = "https://github.com/octocat/hello-world" | ||||
| 			from.Repo.CloneURL = "https://github.com/octocat/hello-world.git" | ||||
| 			from.Repo.DefaultBranch = "develop" | ||||
| 			from := &github.PushEventRepository{Owner: &github.User{}} | ||||
| 			from.Owner.Login = github.String("octocat") | ||||
| 			from.Owner.Name = github.String("octocat") | ||||
| 			from.Name = github.String("hello-world") | ||||
| 			from.FullName = github.String("octocat/hello-world") | ||||
| 			from.Private = github.Bool(true) | ||||
| 			from.HTMLURL = github.String("https://github.com/octocat/hello-world") | ||||
| 			from.CloneURL = github.String("https://github.com/octocat/hello-world.git") | ||||
| 			from.DefaultBranch = github.String("develop") | ||||
|  | ||||
| 			repo := convertRepoHook(from) | ||||
| 			g.Assert(repo.Owner).Equal(from.Repo.Owner.Login) | ||||
| 			g.Assert(repo.Name).Equal(from.Repo.Name) | ||||
| 			g.Assert(repo.FullName).Equal(from.Repo.FullName) | ||||
| 			g.Assert(repo.IsSCMPrivate).Equal(from.Repo.Private) | ||||
| 			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.Assert(repo.Owner).Equal(*from.Owner.Login) | ||||
| 			g.Assert(repo.Name).Equal(*from.Name) | ||||
| 			g.Assert(repo.FullName).Equal(*from.FullName) | ||||
| 			g.Assert(repo.IsSCMPrivate).Equal(*from.Private) | ||||
| 			g.Assert(repo.Link).Equal(*from.HTMLURL) | ||||
| 			g.Assert(repo.Clone).Equal(*from.CloneURL) | ||||
| 			g.Assert(repo.Branch).Equal(*from.DefaultBranch) | ||||
| 		}) | ||||
|  | ||||
| 		g.It("should convert a pull request from webhook", func() { | ||||
| 			from := &webhook{} | ||||
| 			from.PullRequest.Base.Ref = "master" | ||||
| 			from.PullRequest.Head.Ref = "changes" | ||||
| 			from.PullRequest.Head.SHA = "f72fc19" | ||||
| 			from.PullRequest.Head.Repo.CloneURL = "https://github.com/octocat/hello-world-fork" | ||||
| 			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" | ||||
| 			from.Sender.Login = "octocat" | ||||
|  | ||||
| 			build := convertPullHook(from, true) | ||||
| 			from := &github.PullRequestEvent{ | ||||
| 				Action: github.String(actionOpen), | ||||
| 				PullRequest: &github.PullRequest{ | ||||
| 					State:   github.String(stateOpen), | ||||
| 					HTMLURL: github.String("https://github.com/octocat/hello-world/pulls/42"), | ||||
| 					Number:  github.Int(42), | ||||
| 					Title:   github.String("Updated README.md"), | ||||
| 					Base: &github.PullRequestBranch{ | ||||
| 						Ref: github.String("master"), | ||||
| 					}, | ||||
| 					Head: &github.PullRequestBranch{ | ||||
| 						Ref: github.String("changes"), | ||||
| 						SHA: github.String("f72fc19"), | ||||
| 						Repo: &github.Repository{ | ||||
| 							CloneURL: github.String("https://github.com/octocat/hello-world-fork"), | ||||
| 						}, | ||||
| 					}, | ||||
| 					User: &github.User{ | ||||
| 						Login:     github.String("octocat"), | ||||
| 						AvatarURL: github.String("https://avatars1.githubusercontent.com/u/583231"), | ||||
| 					}, | ||||
| 				}, Sender: &github.User{ | ||||
| 					Login: github.String("octocat"), | ||||
| 				}, | ||||
| 			} | ||||
| 			pull, _, build, err := parsePullHook(from, true, false) | ||||
| 			g.Assert(err).IsNil() | ||||
| 			g.Assert(pull).IsNotNil() | ||||
| 			g.Assert(build.Event).Equal(model.EventPull) | ||||
| 			g.Assert(build.Branch).Equal(from.PullRequest.Base.Ref) | ||||
| 			g.Assert(build.Branch).Equal(*from.PullRequest.Base.Ref) | ||||
| 			g.Assert(build.Ref).Equal("refs/pull/42/merge") | ||||
| 			g.Assert(build.Refspec).Equal("changes:master") | ||||
| 			g.Assert(build.Remote).Equal("https://github.com/octocat/hello-world-fork") | ||||
| 			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.Assert(build.Sender).Equal(from.Sender.Login) | ||||
| 			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.AvatarURL) | ||||
| 			g.Assert(build.Sender).Equal(*from.Sender.Login) | ||||
| 		}) | ||||
|  | ||||
| 		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" | ||||
| 			from := &github.DeploymentEvent{Deployment: &github.Deployment{}, Sender: &github.User{}} | ||||
| 			from.Deployment.Description = github.String(":shipit:") | ||||
| 			from.Deployment.Environment = github.String("production") | ||||
| 			from.Deployment.ID = github.Int64(42) | ||||
| 			from.Deployment.Ref = github.String("master") | ||||
| 			from.Deployment.SHA = github.String("f72fc19") | ||||
| 			from.Deployment.URL = github.String("https://github.com/octocat/hello-world") | ||||
| 			from.Sender.Login = github.String("octocat") | ||||
| 			from.Sender.AvatarURL = github.String("https://avatars1.githubusercontent.com/u/583231") | ||||
|  | ||||
| 			build := convertDeployHook(from) | ||||
| 			_, build, err := parseDeployHook(from, false) | ||||
| 			g.Assert(err).IsNil() | ||||
| 			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.Assert(build.Commit).Equal(*from.Deployment.SHA) | ||||
| 			g.Assert(build.Message).Equal(*from.Deployment.Description) | ||||
| 			g.Assert(build.Link).Equal(*from.Deployment.URL) | ||||
| 			g.Assert(build.Author).Equal(*from.Sender.Login) | ||||
| 			g.Assert(build.Avatar).Equal(*from.Sender.AvatarURL) | ||||
| 		}) | ||||
|  | ||||
| 		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" | ||||
| 			from := &github.PushEvent{Sender: &github.User{}, Repo: &github.PushEventRepository{}, HeadCommit: &github.HeadCommit{Author: &github.CommitAuthor{}}} | ||||
| 			from.Sender.Login = github.String("octocat") | ||||
| 			from.Sender.AvatarURL = github.String("https://avatars1.githubusercontent.com/u/583231") | ||||
| 			from.Repo.CloneURL = github.String("https://github.com/octocat/hello-world.git") | ||||
| 			from.HeadCommit.Author.Email = github.String("github.String(octocat@github.com") | ||||
| 			from.HeadCommit.Message = github.String("updated README.md") | ||||
| 			from.HeadCommit.URL = github.String("https://github.com/octocat/hello-world") | ||||
| 			from.HeadCommit.ID = github.String("f72fc19") | ||||
| 			from.Ref = github.String("refs/heads/master") | ||||
|  | ||||
| 			build := convertPushHook(from) | ||||
| 			_, build, err := parsePushHook(from) | ||||
| 			g.Assert(err).IsNil() | ||||
| 			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.Assert(build.Commit).Equal(*from.HeadCommit.ID) | ||||
| 			g.Assert(build.Message).Equal(*from.HeadCommit.Message) | ||||
| 			g.Assert(build.Link).Equal(*from.HeadCommit.URL) | ||||
| 			g.Assert(build.Author).Equal(*from.Sender.Login) | ||||
| 			g.Assert(build.Avatar).Equal(*from.Sender.AvatarURL) | ||||
| 			g.Assert(build.Email).Equal(*from.HeadCommit.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" | ||||
| 			from := &github.PushEvent{} | ||||
| 			from.Ref = github.String("refs/tags/v1.0.0") | ||||
|  | ||||
| 			build := convertPushHook(from) | ||||
| 			_, build, err := parsePushHook(from) | ||||
| 			g.Assert(err).IsNil() | ||||
| 			g.Assert(build.Event).Equal(model.EventTag) | ||||
| 			g.Assert(build.Ref).Equal("refs/tags/v1.0.0") | ||||
| 		}) | ||||
|  | ||||
| 		g.It("should convert tag's base branch from webhook to build's branch ", func() { | ||||
| 			from := &webhook{} | ||||
| 			from.Ref = "refs/tags/v1.0.0" | ||||
| 			from.BaseRef = "refs/heads/master" | ||||
| 			from := &github.PushEvent{} | ||||
| 			from.Ref = github.String("refs/tags/v1.0.0") | ||||
| 			from.BaseRef = github.String("refs/heads/master") | ||||
|  | ||||
| 			build := convertPushHook(from) | ||||
| 			_, build, err := parsePushHook(from) | ||||
| 			g.Assert(err).IsNil() | ||||
| 			g.Assert(build.Event).Equal(model.EventTag) | ||||
| 			g.Assert(build.Branch).Equal("master") | ||||
| 		}) | ||||
|  | ||||
| 		g.It("should not convert tag's base_ref from webhook if not prefixed with 'ref/heads/'", func() { | ||||
| 			from := &webhook{} | ||||
| 			from.Ref = "refs/tags/v1.0.0" | ||||
| 			from.BaseRef = "refs/refs/master" | ||||
| 			from := &github.PushEvent{} | ||||
| 			from.Ref = github.String("refs/tags/v1.0.0") | ||||
| 			from.BaseRef = github.String("refs/refs/master") | ||||
|  | ||||
| 			build := convertPushHook(from) | ||||
| 			_, build, err := parsePushHook(from) | ||||
| 			g.Assert(err).IsNil() | ||||
| 			g.Assert(build.Event).Equal(model.EventTag) | ||||
| 			g.Assert(build.Branch).Equal("refs/tags/v1.0.0") | ||||
| 		}) | ||||
|   | ||||
| @@ -16,55 +16,230 @@ 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" | ||||
|     }, | ||||
|     "added": ["CHANGELOG.md"], | ||||
|     "removed": [], | ||||
|     "modified": ["app/controller/application.rb"] | ||||
|   }, | ||||
| const HookPush = `{ | ||||
|   "ref": "refs/heads/master", | ||||
|   "before": "2f780193b136b72bfea4eeb640786a8c4450c7a2", | ||||
|   "after": "366701fde727cb7a9e7f21eb88264f59f6f9b89c", | ||||
|   "repository": { | ||||
|     "id": 35129377, | ||||
|     "name": "public-repo", | ||||
|     "full_name": "baxterthehacker/public-repo", | ||||
|     "owner": { | ||||
|       "name": "baxterthehacker", | ||||
|       "email": "baxterthehacker@users.noreply.github.com" | ||||
|     }, | ||||
|     "id": 179344069, | ||||
|     "node_id": "MDEwOlJlcG9zaXRvcnkxNzkzNDQwNjk=", | ||||
|     "name": "woodpecker", | ||||
|     "full_name": "woodpecker-ci/woodpecker", | ||||
|     "private": false, | ||||
|     "html_url": "https://github.com/baxterthehacker/public-repo", | ||||
|     "default_branch": "master" | ||||
|     "owner": { | ||||
|       "name": "woodpecker-ci", | ||||
|       "email": null, | ||||
|       "login": "woodpecker-ci", | ||||
|       "id": 84780935, | ||||
|       "node_id": "MDEyOk9yZ2FuaXphdGlvbjg0NzgwOTM1", | ||||
|       "avatar_url": "https://avatars.githubusercontent.com/u/84780935?v=4", | ||||
|       "gravatar_id": "", | ||||
|       "url": "https://api.github.com/users/woodpecker-ci", | ||||
|       "html_url": "https://github.com/woodpecker-ci", | ||||
|       "followers_url": "https://api.github.com/users/woodpecker-ci/followers", | ||||
|       "following_url": "https://api.github.com/users/woodpecker-ci/following{/other_user}", | ||||
|       "gists_url": "https://api.github.com/users/woodpecker-ci/gists{/gist_id}", | ||||
|       "starred_url": "https://api.github.com/users/woodpecker-ci/starred{/owner}{/repo}", | ||||
|       "subscriptions_url": "https://api.github.com/users/woodpecker-ci/subscriptions", | ||||
|       "organizations_url": "https://api.github.com/users/woodpecker-ci/orgs", | ||||
|       "repos_url": "https://api.github.com/users/woodpecker-ci/repos", | ||||
|       "events_url": "https://api.github.com/users/woodpecker-ci/events{/privacy}", | ||||
|       "received_events_url": "https://api.github.com/users/woodpecker-ci/received_events", | ||||
|       "type": "Organization", | ||||
|       "site_admin": false | ||||
|     }, | ||||
|     "html_url": "https://github.com/woodpecker-ci/woodpecker", | ||||
|     "description": "Woodpecker is a community fork of the Drone CI system.", | ||||
|     "fork": false, | ||||
|     "url": "https://github.com/woodpecker-ci/woodpecker", | ||||
|     "forks_url": "https://api.github.com/repos/woodpecker-ci/woodpecker/forks", | ||||
|     "keys_url": "https://api.github.com/repos/woodpecker-ci/woodpecker/keys{/key_id}", | ||||
|     "collaborators_url": "https://api.github.com/repos/woodpecker-ci/woodpecker/collaborators{/collaborator}", | ||||
|     "teams_url": "https://api.github.com/repos/woodpecker-ci/woodpecker/teams", | ||||
|     "hooks_url": "https://api.github.com/repos/woodpecker-ci/woodpecker/hooks", | ||||
|     "issue_events_url": "https://api.github.com/repos/woodpecker-ci/woodpecker/issues/events{/number}", | ||||
|     "events_url": "https://api.github.com/repos/woodpecker-ci/woodpecker/events", | ||||
|     "assignees_url": "https://api.github.com/repos/woodpecker-ci/woodpecker/assignees{/user}", | ||||
|     "branches_url": "https://api.github.com/repos/woodpecker-ci/woodpecker/branches{/branch}", | ||||
|     "tags_url": "https://api.github.com/repos/woodpecker-ci/woodpecker/tags", | ||||
|     "blobs_url": "https://api.github.com/repos/woodpecker-ci/woodpecker/git/blobs{/sha}", | ||||
|     "git_tags_url": "https://api.github.com/repos/woodpecker-ci/woodpecker/git/tags{/sha}", | ||||
|     "git_refs_url": "https://api.github.com/repos/woodpecker-ci/woodpecker/git/refs{/sha}", | ||||
|     "trees_url": "https://api.github.com/repos/woodpecker-ci/woodpecker/git/trees{/sha}", | ||||
|     "statuses_url": "https://api.github.com/repos/woodpecker-ci/woodpecker/statuses/{sha}", | ||||
|     "languages_url": "https://api.github.com/repos/woodpecker-ci/woodpecker/languages", | ||||
|     "stargazers_url": "https://api.github.com/repos/woodpecker-ci/woodpecker/stargazers", | ||||
|     "contributors_url": "https://api.github.com/repos/woodpecker-ci/woodpecker/contributors", | ||||
|     "subscribers_url": "https://api.github.com/repos/woodpecker-ci/woodpecker/subscribers", | ||||
|     "subscription_url": "https://api.github.com/repos/woodpecker-ci/woodpecker/subscription", | ||||
|     "commits_url": "https://api.github.com/repos/woodpecker-ci/woodpecker/commits{/sha}", | ||||
|     "git_commits_url": "https://api.github.com/repos/woodpecker-ci/woodpecker/git/commits{/sha}", | ||||
|     "comments_url": "https://api.github.com/repos/woodpecker-ci/woodpecker/comments{/number}", | ||||
|     "issue_comment_url": "https://api.github.com/repos/woodpecker-ci/woodpecker/issues/comments{/number}", | ||||
|     "contents_url": "https://api.github.com/repos/woodpecker-ci/woodpecker/contents/{+path}", | ||||
|     "compare_url": "https://api.github.com/repos/woodpecker-ci/woodpecker/compare/{base}...{head}", | ||||
|     "merges_url": "https://api.github.com/repos/woodpecker-ci/woodpecker/merges", | ||||
|     "archive_url": "https://api.github.com/repos/woodpecker-ci/woodpecker/{archive_format}{/ref}", | ||||
|     "downloads_url": "https://api.github.com/repos/woodpecker-ci/woodpecker/downloads", | ||||
|     "issues_url": "https://api.github.com/repos/woodpecker-ci/woodpecker/issues{/number}", | ||||
|     "pulls_url": "https://api.github.com/repos/woodpecker-ci/woodpecker/pulls{/number}", | ||||
|     "milestones_url": "https://api.github.com/repos/woodpecker-ci/woodpecker/milestones{/number}", | ||||
|     "notifications_url": "https://api.github.com/repos/woodpecker-ci/woodpecker/notifications{?since,all,participating}", | ||||
|     "labels_url": "https://api.github.com/repos/woodpecker-ci/woodpecker/labels{/name}", | ||||
|     "releases_url": "https://api.github.com/repos/woodpecker-ci/woodpecker/releases{/id}", | ||||
|     "deployments_url": "https://api.github.com/repos/woodpecker-ci/woodpecker/deployments", | ||||
|     "created_at": 1554314798, | ||||
|     "updated_at": "2022-01-16T20:19:33Z", | ||||
|     "pushed_at": 1642370257, | ||||
|     "git_url": "git://github.com/woodpecker-ci/woodpecker.git", | ||||
|     "ssh_url": "git@github.com:woodpecker-ci/woodpecker.git", | ||||
|     "clone_url": "https://github.com/woodpecker-ci/woodpecker.git", | ||||
|     "svn_url": "https://github.com/woodpecker-ci/woodpecker", | ||||
|     "homepage": "https://woodpecker-ci.org", | ||||
|     "size": 81324, | ||||
|     "stargazers_count": 659, | ||||
|     "watchers_count": 659, | ||||
|     "language": "Go", | ||||
|     "has_issues": true, | ||||
|     "has_projects": false, | ||||
|     "has_downloads": true, | ||||
|     "has_wiki": false, | ||||
|     "has_pages": false, | ||||
|     "forks_count": 84, | ||||
|     "mirror_url": null, | ||||
|     "archived": false, | ||||
|     "disabled": false, | ||||
|     "open_issues_count": 123, | ||||
|     "license": { | ||||
|       "key": "apache-2.0", | ||||
|       "name": "Apache License 2.0", | ||||
|       "spdx_id": "Apache-2.0", | ||||
|       "url": "https://api.github.com/licenses/apache-2.0", | ||||
|       "node_id": "MDc6TGljZW5zZTI=" | ||||
|     }, | ||||
|     "allow_forking": true, | ||||
|     "is_template": false, | ||||
|     "topics": [ | ||||
|       "ci", | ||||
|       "devops", | ||||
|       "docker", | ||||
|       "hacktoberfest", | ||||
|       "hacktoberfest2021", | ||||
|       "woodpeckerci" | ||||
|     ], | ||||
|     "visibility": "public", | ||||
|     "forks": 84, | ||||
|     "open_issues": 123, | ||||
|     "watchers": 659, | ||||
|     "default_branch": "master", | ||||
|     "stargazers": 659, | ||||
|     "master_branch": "master", | ||||
|     "organization": "woodpecker-ci" | ||||
|   }, | ||||
|   "pusher": { | ||||
|     "name": "baxterthehacker", | ||||
|     "email": "baxterthehacker@users.noreply.github.com" | ||||
|     "name": "6543", | ||||
|     "email": "noreply@6543.de" | ||||
|   }, | ||||
|   "organization": { | ||||
|     "login": "woodpecker-ci", | ||||
|     "id": 84780935, | ||||
|     "node_id": "MDEyOk9yZ2FuaXphdGlvbjg0NzgwOTM1", | ||||
|     "url": "https://api.github.com/orgs/woodpecker-ci", | ||||
|     "repos_url": "https://api.github.com/orgs/woodpecker-ci/repos", | ||||
|     "events_url": "https://api.github.com/orgs/woodpecker-ci/events", | ||||
|     "hooks_url": "https://api.github.com/orgs/woodpecker-ci/hooks", | ||||
|     "issues_url": "https://api.github.com/orgs/woodpecker-ci/issues", | ||||
|     "members_url": "https://api.github.com/orgs/woodpecker-ci/members{/member}", | ||||
|     "public_members_url": "https://api.github.com/orgs/woodpecker-ci/public_members{/member}", | ||||
|     "avatar_url": "https://avatars.githubusercontent.com/u/84780935?v=4", | ||||
|     "description": "Woodpecker is a community fork of the Drone CI system." | ||||
|   }, | ||||
|   "sender": { | ||||
|     "login": "baxterthehacker", | ||||
|     "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3" | ||||
|   } | ||||
| } | ||||
| ` | ||||
|     "login": "6543", | ||||
|     "id": 24977596, | ||||
|     "node_id": "MDQ6VXNlcjI0OTc3NTk2", | ||||
|     "avatar_url": "https://avatars.githubusercontent.com/u/24977596?v=4", | ||||
|     "gravatar_id": "", | ||||
|     "url": "https://api.github.com/users/6543", | ||||
|     "html_url": "https://github.com/6543", | ||||
|     "followers_url": "https://api.github.com/users/6543/followers", | ||||
|     "following_url": "https://api.github.com/users/6543/following{/other_user}", | ||||
|     "gists_url": "https://api.github.com/users/6543/gists{/gist_id}", | ||||
|     "starred_url": "https://api.github.com/users/6543/starred{/owner}{/repo}", | ||||
|     "subscriptions_url": "https://api.github.com/users/6543/subscriptions", | ||||
|     "organizations_url": "https://api.github.com/users/6543/orgs", | ||||
|     "repos_url": "https://api.github.com/users/6543/repos", | ||||
|     "events_url": "https://api.github.com/users/6543/events{/privacy}", | ||||
|     "received_events_url": "https://api.github.com/users/6543/received_events", | ||||
|     "type": "User", | ||||
|     "site_admin": false | ||||
|   }, | ||||
|   "created": false, | ||||
|   "deleted": false, | ||||
|   "forced": false, | ||||
|   "base_ref": null, | ||||
|   "compare": "https://github.com/woodpecker-ci/woodpecker/compare/2f780193b136...366701fde727", | ||||
|   "commits": [ | ||||
|     { | ||||
|       "id": "366701fde727cb7a9e7f21eb88264f59f6f9b89c", | ||||
|       "tree_id": "638e046f1e1e15dbed1ddf40f9471bf1af4d64ce", | ||||
|       "distinct": true, | ||||
|       "message": "Fix multiline secrets replacer (#700)\n\n* Fix multiline secrets replacer\r\n\r\n* Add tests", | ||||
|       "timestamp": "2022-01-16T22:57:37+01:00", | ||||
|       "url": "https://github.com/woodpecker-ci/woodpecker/commit/366701fde727cb7a9e7f21eb88264f59f6f9b89c", | ||||
|       "author": { | ||||
|         "name": "Philipp", | ||||
|         "email": "noreply@philipp.xzy", | ||||
|         "username": "nupplaphil" | ||||
|       }, | ||||
|       "committer": { | ||||
|         "name": "GitHub", | ||||
|         "email": "noreply@github.com", | ||||
|         "username": "web-flow" | ||||
|       }, | ||||
|       "added": [ | ||||
|  | ||||
| // HookPush is a sample push hook that is marked as deleted, and is expected to | ||||
| // be ignored. | ||||
|       ], | ||||
|       "removed": [ | ||||
|  | ||||
|       ], | ||||
|       "modified": [ | ||||
|         "pipeline/shared/replace_secrets.go", | ||||
|         "pipeline/shared/replace_secrets_test.go" | ||||
|       ] | ||||
|     } | ||||
|   ], | ||||
|   "head_commit": { | ||||
|     "id": "366701fde727cb7a9e7f21eb88264f59f6f9b89c", | ||||
|     "tree_id": "638e046f1e1e15dbed1ddf40f9471bf1af4d64ce", | ||||
|     "distinct": true, | ||||
|     "message": "Fix multiline secrets replacer (#700)\n\n* Fix multiline secrets replacer\r\n\r\n* Add tests", | ||||
|     "timestamp": "2022-01-16T22:57:37+01:00", | ||||
|     "url": "https://github.com/woodpecker-ci/woodpecker/commit/366701fde727cb7a9e7f21eb88264f59f6f9b89c", | ||||
|     "author": { | ||||
|       "name": "Philipp", | ||||
|       "email": "admin@philipp.info", | ||||
|       "username": "nupplaphil" | ||||
|     }, | ||||
|     "committer": { | ||||
|       "name": "GitHub", | ||||
|       "email": "noreply@github.com", | ||||
|       "username": "web-flow" | ||||
|     }, | ||||
|     "added": [ | ||||
|  | ||||
|     ], | ||||
|     "removed": [ | ||||
|  | ||||
|     ], | ||||
|     "modified": [ | ||||
|       "pipeline/shared/replace_secrets.go", | ||||
|       "pipeline/shared/replace_secrets_test.go" | ||||
|     ] | ||||
|   } | ||||
| }` | ||||
|  | ||||
| // HookPushDeleted is a sample push hook that is marked as deleted, and is expected to be ignored. | ||||
| const HookPushDeleted = ` | ||||
| { | ||||
|   "deleted": true | ||||
|   | ||||
							
								
								
									
										1
									
								
								server/remote/github/fixtures/mock_server.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								server/remote/github/fixtures/mock_server.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| package fixtures | ||||
| @@ -26,12 +26,15 @@ import ( | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/google/go-github/v39/github" | ||||
| 	"github.com/rs/zerolog/log" | ||||
| 	"golang.org/x/oauth2" | ||||
|  | ||||
| 	"github.com/woodpecker-ci/woodpecker/server" | ||||
| 	"github.com/woodpecker-ci/woodpecker/server/model" | ||||
| 	"github.com/woodpecker-ci/woodpecker/server/remote" | ||||
| 	"github.com/woodpecker-ci/woodpecker/server/remote/common" | ||||
| 	"github.com/woodpecker-ci/woodpecker/server/store" | ||||
| 	"github.com/woodpecker-ci/woodpecker/shared/utils" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| @@ -219,7 +222,7 @@ func (c *client) Perm(ctx context.Context, u *model.User, r *model.Repo) (*model | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return convertPerm(repo), nil | ||||
| 	return convertPerm(repo.GetPermissions()), nil | ||||
| } | ||||
|  | ||||
| // File fetches the file from the GitHub repository and returns its contents. | ||||
| @@ -491,6 +494,54 @@ func (c *client) Branches(ctx context.Context, u *model.User, r *model.Repo) ([] | ||||
|  | ||||
| // Hook parses the post-commit hook from the Request body | ||||
| // and returns the required data in a standard format. | ||||
| func (c *client) Hook(r *http.Request) (*model.Repo, *model.Build, error) { | ||||
| 	return parseHook(r, c.MergeRef) | ||||
| func (c *client) Hook(ctx context.Context, r *http.Request) (*model.Repo, *model.Build, error) { | ||||
| 	pull, repo, build, err := parseHook(r, c.MergeRef, c.PrivateMode) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
|  | ||||
| 	if pull != nil && len(build.ChangedFiles) == 0 { | ||||
| 		build, err = c.loadChangedFilesFromPullRequest(ctx, pull, repo, build) | ||||
| 		if err != nil { | ||||
| 			return nil, nil, err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return repo, build, nil | ||||
| } | ||||
|  | ||||
| func (c *client) loadChangedFilesFromPullRequest(ctx context.Context, pull *github.PullRequest, tmpRepo *model.Repo, build *model.Build) (*model.Build, error) { | ||||
| 	_store, ok := store.TryFromContext(ctx) | ||||
| 	if !ok { | ||||
| 		log.Error().Msg("could not get store from context") | ||||
| 		return build, nil | ||||
| 	} | ||||
|  | ||||
| 	repo, err := _store.GetRepoName(tmpRepo.Owner + "/" + tmpRepo.Name) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	user, err := _store.GetUser(repo.UserID) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	opts := &github.ListOptions{Page: 1} | ||||
| 	fileList := make([]string, 0, 16) | ||||
| 	for opts.Page > 0 { | ||||
| 		files, resp, err := c.newClientToken(ctx, user.Token).PullRequests.ListFiles(ctx, repo.Owner, repo.Name, pull.GetNumber(), opts) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
|  | ||||
| 		for _, file := range files { | ||||
| 			fileList = append(fileList, file.GetFilename(), file.GetPreviousFilename()) | ||||
| 		} | ||||
|  | ||||
| 		opts.Page = resp.NextPage | ||||
| 	} | ||||
| 	build.ChangedFiles = utils.DedupStrings(fileList) | ||||
|  | ||||
| 	return build, nil | ||||
| } | ||||
|   | ||||
| @@ -16,20 +16,20 @@ package github | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/google/go-github/v39/github" | ||||
|  | ||||
| 	"github.com/woodpecker-ci/woodpecker/server/model" | ||||
| 	"github.com/woodpecker-ci/woodpecker/shared/utils" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	hookEvent  = "X-Github-Event" | ||||
| 	hookField  = "payload" | ||||
| 	hookDeploy = "deployment" | ||||
| 	hookPush   = "push" | ||||
| 	hookPull   = "pull_request" | ||||
| 	hookField = "payload" | ||||
|  | ||||
| 	actionOpen = "opened" | ||||
| 	actionSync = "synchronize" | ||||
| @@ -39,7 +39,7 @@ const ( | ||||
|  | ||||
| // parseHook parses a GitHub 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) { | ||||
| func parseHook(r *http.Request, merge, privateMode bool) (*github.PullRequest, *model.Repo, *model.Build, error) { | ||||
| 	var reader io.Reader = r.Body | ||||
|  | ||||
| 	if payload := r.FormValue(hookField); payload != "" { | ||||
| @@ -48,59 +48,143 @@ func parseHook(r *http.Request, merge bool) (*model.Repo, *model.Build, error) { | ||||
|  | ||||
| 	raw, err := ioutil.ReadAll(reader) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 		return nil, nil, nil, err | ||||
| 	} | ||||
|  | ||||
| 	switch r.Header.Get(hookEvent) { | ||||
| 	case hookPush: | ||||
| 		return parsePushHook(raw) | ||||
| 	case hookDeploy: | ||||
| 		return parseDeployHook(raw) | ||||
| 	case hookPull: | ||||
| 		return parsePullHook(raw, merge) | ||||
| 	payload, err := github.ParseWebHook(github.WebHookType(r), raw) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, nil, err | ||||
| 	} | ||||
| 	return nil, nil, nil | ||||
|  | ||||
| 	switch hook := payload.(type) { | ||||
| 	case *github.PushEvent: | ||||
| 		repo, build, err := parsePushHook(hook) | ||||
| 		return nil, repo, build, err | ||||
| 	case *github.DeploymentEvent: | ||||
| 		repo, build, err := parseDeployHook(hook, privateMode) | ||||
| 		return nil, repo, build, err | ||||
| 	case *github.PullRequestEvent: | ||||
| 		return parsePullHook(hook, merge, privateMode) | ||||
| 	} | ||||
| 	return nil, 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 | ||||
| func parsePushHook(hook *github.PushEvent) (*model.Repo, *model.Build, error) { | ||||
| 	if hook.Deleted != nil && *hook.Deleted { | ||||
| 		return nil, nil, nil | ||||
| 	} | ||||
| 	if hook.Deleted { | ||||
| 		return nil, nil, err | ||||
|  | ||||
| 	build := &model.Build{ | ||||
| 		Event:        model.EventPush, | ||||
| 		Commit:       hook.GetHeadCommit().GetID(), | ||||
| 		Ref:          hook.GetRef(), | ||||
| 		Link:         hook.GetHeadCommit().GetURL(), | ||||
| 		Branch:       strings.Replace(hook.GetRef(), "refs/heads/", "", -1), | ||||
| 		Message:      hook.GetHeadCommit().GetMessage(), | ||||
| 		Email:        hook.GetHeadCommit().GetAuthor().GetEmail(), | ||||
| 		Avatar:       hook.GetSender().GetAvatarURL(), | ||||
| 		Author:       hook.GetSender().GetLogin(), | ||||
| 		Remote:       hook.GetRepo().GetCloneURL(), | ||||
| 		Sender:       hook.GetSender().GetLogin(), | ||||
| 		ChangedFiles: getChangedFilesFromCommits(hook.Commits), | ||||
| 	} | ||||
| 	return convertRepoHook(hook), convertPushHook(hook), nil | ||||
|  | ||||
| 	if len(build.Author) == 0 { | ||||
| 		build.Author = hook.GetHeadCommit().GetAuthor().GetLogin() | ||||
| 	} | ||||
| 	// if len(build.Email) == 0 { | ||||
| 	// TODO: 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 | ||||
| 		build.ChangedFiles = nil | ||||
| 		// For tags, if the base_ref (tag's base branch) is set, we're using it | ||||
| 		// as build's branch so that we can filter events base on it | ||||
| 		if strings.HasPrefix(hook.GetBaseRef(), "refs/heads/") { | ||||
| 			build.Branch = strings.Replace(hook.GetBaseRef(), "refs/heads/", "", -1) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return convertRepoHook(hook.GetRepo()), build, 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 | ||||
| func parseDeployHook(hook *github.DeploymentEvent, privateMode bool) (*model.Repo, *model.Build, error) { | ||||
| 	build := &model.Build{ | ||||
| 		Event:   model.EventDeploy, | ||||
| 		Commit:  hook.GetDeployment().GetSHA(), | ||||
| 		Link:    hook.GetDeployment().GetURL(), | ||||
| 		Message: hook.GetDeployment().GetDescription(), | ||||
| 		Ref:     hook.GetDeployment().GetRef(), | ||||
| 		Branch:  hook.GetDeployment().GetRef(), | ||||
| 		Deploy:  hook.GetDeployment().GetEnvironment(), | ||||
| 		Avatar:  hook.GetSender().GetAvatarURL(), | ||||
| 		Author:  hook.GetSender().GetLogin(), | ||||
| 		Sender:  hook.GetSender().GetLogin(), | ||||
| 	} | ||||
| 	return convertRepoHook(hook), convertDeployHook(hook), nil | ||||
| 	// 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 = hook.GetRepo().GetDefaultBranch() | ||||
| 		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 convertRepo(hook.GetRepo(), privateMode), build, 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 | ||||
| func parsePullHook(hook *github.PullRequestEvent, merge, privateMode bool) (*github.PullRequest, *model.Repo, *model.Build, error) { | ||||
| 	// only listen to new merge-requests and pushes to open ones | ||||
| 	if hook.GetAction() != actionOpen && hook.GetAction() != actionSync { | ||||
| 		return nil, nil, nil, nil | ||||
| 	} | ||||
| 	if hook.GetPullRequest().GetState() != stateOpen { | ||||
| 		return nil, nil, nil, nil | ||||
| 	} | ||||
|  | ||||
| 	// ignore these | ||||
| 	if hook.Action != actionOpen && hook.Action != actionSync { | ||||
| 		return nil, nil, nil | ||||
| 	build := &model.Build{ | ||||
| 		Event:   model.EventPull, | ||||
| 		Commit:  hook.GetPullRequest().GetHead().GetSHA(), | ||||
| 		Link:    hook.GetPullRequest().GetHTMLURL(), | ||||
| 		Ref:     fmt.Sprintf(headRefs, hook.GetPullRequest().GetNumber()), | ||||
| 		Branch:  hook.GetPullRequest().GetBase().GetRef(), | ||||
| 		Message: hook.GetPullRequest().GetTitle(), | ||||
| 		Author:  hook.GetPullRequest().GetUser().GetLogin(), | ||||
| 		Avatar:  hook.GetPullRequest().GetUser().GetAvatarURL(), | ||||
| 		Title:   hook.GetPullRequest().GetTitle(), | ||||
| 		Sender:  hook.GetSender().GetLogin(), | ||||
| 		Remote:  hook.GetPullRequest().GetHead().GetRepo().GetCloneURL(), | ||||
| 		Refspec: fmt.Sprintf(refSpec, | ||||
| 			hook.GetPullRequest().GetHead().GetRef(), | ||||
| 			hook.GetPullRequest().GetBase().GetRef(), | ||||
| 		), | ||||
| 	} | ||||
| 	if hook.PullRequest.State != stateOpen { | ||||
| 		return nil, nil, nil | ||||
| 	if merge { | ||||
| 		build.Ref = fmt.Sprintf(mergeRefs, hook.GetPullRequest().GetNumber()) | ||||
| 	} | ||||
| 	return convertRepoHook(hook), convertPullHook(hook, merge), nil | ||||
|  | ||||
| 	return hook.GetPullRequest(), convertRepo(hook.GetRepo(), privateMode), build, nil | ||||
| } | ||||
|  | ||||
| func getChangedFilesFromCommits(commits []*github.HeadCommit) []string { | ||||
| 	// assume a capacity of 4 changed files per commit | ||||
| 	files := make([]string, 0, len(commits)*4) | ||||
| 	for _, cm := range commits { | ||||
| 		files = append(files, cm.Added...) | ||||
| 		files = append(files, cm.Removed...) | ||||
| 		files = append(files, cm.Modified...) | ||||
| 	} | ||||
| 	return utils.DedupStrings(files) | ||||
| } | ||||
|   | ||||
| @@ -17,6 +17,7 @@ package github | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"net/http" | ||||
| 	"sort" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/franela/goblin" | ||||
| @@ -25,85 +26,91 @@ import ( | ||||
| 	"github.com/woodpecker-ci/woodpecker/server/remote/github/fixtures" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	hookEvent  = "X-Github-Event" | ||||
| 	hookDeploy = "deployment" | ||||
| 	hookPush   = "push" | ||||
| 	hookPull   = "pull_request" | ||||
| ) | ||||
|  | ||||
| func testHookRequest(payload []byte, event string) *http.Request { | ||||
| 	buf := bytes.NewBuffer(payload) | ||||
| 	req, _ := http.NewRequest("POST", "/hook", buf) | ||||
| 	req.Header = http.Header{} | ||||
| 	req.Header.Set(hookEvent, event) | ||||
| 	return req | ||||
| } | ||||
|  | ||||
| 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) | ||||
| 			req := testHookRequest([]byte(fixtures.HookPullRequest), "issues") | ||||
| 			p, r, b, err := parseHook(req, false, false) | ||||
| 			g.Assert(r).IsNil() | ||||
| 			g.Assert(b).IsNil() | ||||
| 			g.Assert(err).IsNil() | ||||
| 			g.Assert(p).IsNil() | ||||
| 		}) | ||||
|  | ||||
| 		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) | ||||
| 				req := testHookRequest([]byte(fixtures.HookPushDeleted), hookPush) | ||||
| 				p, r, b, err := parseHook(req, false, false) | ||||
| 				g.Assert(r).IsNil() | ||||
| 				g.Assert(b).IsNil() | ||||
| 				g.Assert(err).IsNil() | ||||
| 				g.Assert(p).IsNil() | ||||
| 			}) | ||||
| 			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) | ||||
| 				req := testHookRequest([]byte(fixtures.HookPush), hookPush) | ||||
| 				p, r, b, err := parseHook(req, false, false) | ||||
| 				g.Assert(err).IsNil() | ||||
| 				g.Assert(p).IsNil() | ||||
| 				g.Assert(r).IsNotNil() | ||||
| 				g.Assert(b).IsNotNil() | ||||
| 				g.Assert(b.Event).Equal(model.EventPush) | ||||
| 				expectedFiles := []string{"CHANGELOG.md", "app/controller/application.rb"} | ||||
| 				g.Assert(b.ChangedFiles).Equal(expectedFiles) | ||||
| 				sort.Strings(b.ChangedFiles) | ||||
| 				g.Assert(b.ChangedFiles).Equal([]string{"pipeline/shared/replace_secrets.go", "pipeline/shared/replace_secrets_test.go"}) | ||||
| 			}) | ||||
| 		}) | ||||
|  | ||||
| 		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) | ||||
| 				req := testHookRequest([]byte(fixtures.HookPullRequestInvalidAction), hookPull) | ||||
| 				p, r, b, err := parseHook(req, false, false) | ||||
| 				g.Assert(r).IsNil() | ||||
| 				g.Assert(b).IsNil() | ||||
| 				g.Assert(err).IsNil() | ||||
| 				g.Assert(p).IsNil() | ||||
| 			}) | ||||
| 			g.It("should skip when state is not open", func() { | ||||
| 				raw := []byte(fixtures.HookPullRequestInvalidState) | ||||
| 				r, b, err := parsePullHook(raw, false) | ||||
| 				req := testHookRequest([]byte(fixtures.HookPullRequestInvalidState), hookPull) | ||||
| 				p, r, b, err := parseHook(req, false, false) | ||||
| 				g.Assert(r).IsNil() | ||||
| 				g.Assert(b).IsNil() | ||||
| 				g.Assert(err).IsNil() | ||||
| 				g.Assert(p).IsNil() | ||||
| 			}) | ||||
| 			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) | ||||
| 				req := testHookRequest([]byte(fixtures.HookPullRequest), hookPull) | ||||
| 				p, r, b, err := parseHook(req, false, false) | ||||
| 				g.Assert(err).IsNil() | ||||
| 				g.Assert(r).IsNotNil() | ||||
| 				g.Assert(b).IsNotNil() | ||||
| 				g.Assert(p).IsNotNil() | ||||
| 				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) | ||||
| 				req := testHookRequest([]byte(fixtures.HookDeploy), hookDeploy) | ||||
| 				p, r, b, err := parseHook(req, false, false) | ||||
| 				g.Assert(err).IsNil() | ||||
| 				g.Assert(r).IsNotNil() | ||||
| 				g.Assert(b).IsNotNil() | ||||
| 				g.Assert(p).IsNil() | ||||
| 				g.Assert(b.Event).Equal(model.EventDeploy) | ||||
| 			}) | ||||
| 		}) | ||||
|   | ||||
| @@ -1,102 +0,0 @@ | ||||
| // Copyright 2018 Drone.IO Inc. | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //      http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package github | ||||
|  | ||||
| type webhook struct { | ||||
| 	Ref     string `json:"ref"` | ||||
| 	Action  string `json:"action"` | ||||
| 	Deleted bool   `json:"deleted"` | ||||
| 	BaseRef string `json:"base_ref"` | ||||
|  | ||||
| 	Head struct { | ||||
| 		ID        string `json:"id"` | ||||
| 		URL       string `json:"url"` | ||||
| 		Message   string `json:"message"` | ||||
| 		Timestamp string `json:"timestamp"` | ||||
|  | ||||
| 		Author struct { | ||||
| 			Name     string `json:"name"` | ||||
| 			Email    string `json:"email"` | ||||
| 			Username string `json:"username"` | ||||
| 		} `json:"author"` | ||||
|  | ||||
| 		Committer struct { | ||||
| 			Name     string `json:"name"` | ||||
| 			Email    string `json:"email"` | ||||
| 			Username string `json:"username"` | ||||
| 		} `json:"committer"` | ||||
|  | ||||
| 		Added    []string `json:"added"` | ||||
| 		Removed  []string `json:"removed"` | ||||
| 		Modified []string `json:"modified"` | ||||
| 	} `json:"head_commit"` | ||||
|  | ||||
| 	Sender struct { | ||||
| 		Login  string `json:"login"` | ||||
| 		Avatar string `json:"avatar_url"` | ||||
| 	} `json:"sender"` | ||||
|  | ||||
| 	// repository details | ||||
| 	Repo struct { | ||||
| 		Owner struct { | ||||
| 			Login string `json:"login"` | ||||
| 			Name  string `json:"name"` | ||||
| 		} `json:"owner"` | ||||
|  | ||||
| 		Name          string `json:"name"` | ||||
| 		FullName      string `json:"full_name"` | ||||
| 		Language      string `json:"language"` | ||||
| 		Private       bool   `json:"private"` | ||||
| 		HTMLURL       string `json:"html_url"` | ||||
| 		CloneURL      string `json:"clone_url"` | ||||
| 		DefaultBranch string `json:"default_branch"` | ||||
| 	} `json:"repository"` | ||||
|  | ||||
| 	// deployment hook details | ||||
| 	Deployment struct { | ||||
| 		ID   int64  `json:"id"` | ||||
| 		Sha  string `json:"sha"` | ||||
| 		Ref  string `json:"ref"` | ||||
| 		Task string `json:"task"` | ||||
| 		Env  string `json:"environment"` | ||||
| 		URL  string `json:"url"` | ||||
| 		Desc string `json:"description"` | ||||
| 	} `json:"deployment"` | ||||
|  | ||||
| 	// pull request details | ||||
| 	PullRequest struct { | ||||
| 		Number  int    `json:"number"` | ||||
| 		State   string `json:"state"` | ||||
| 		Title   string `json:"title"` | ||||
| 		HTMLURL string `json:"html_url"` | ||||
|  | ||||
| 		User struct { | ||||
| 			Login  string `json:"login"` | ||||
| 			Avatar string `json:"avatar_url"` | ||||
| 		} `json:"user"` | ||||
|  | ||||
| 		Base struct { | ||||
| 			Ref string `json:"ref"` | ||||
| 		} `json:"base"` | ||||
|  | ||||
| 		Head struct { | ||||
| 			SHA  string `json:"sha"` | ||||
| 			Ref  string `json:"ref"` | ||||
| 			Repo struct { | ||||
| 				CloneURL string `json:"clone_url"` | ||||
| 			} `json:"repo"` | ||||
| 		} `json:"head"` | ||||
| 	} `json:"pull_request"` | ||||
| } | ||||
| @@ -24,6 +24,11 @@ import ( | ||||
| 	"github.com/xanzy/go-gitlab" | ||||
|  | ||||
| 	"github.com/woodpecker-ci/woodpecker/server/model" | ||||
| 	"github.com/woodpecker-ci/woodpecker/shared/utils" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	mergeRefs = "refs/merge-requests/%d/head" // merge request merged with base | ||||
| ) | ||||
|  | ||||
| func (g *Gitlab) convertGitlabRepo(_repo *gitlab.Project) (*model.Repo, error) { | ||||
| @@ -59,7 +64,7 @@ func (g *Gitlab) convertGitlabRepo(_repo *gitlab.Project) (*model.Repo, error) { | ||||
| 	return repo, nil | ||||
| } | ||||
|  | ||||
| func convertMergeRequestHock(hook *gitlab.MergeEvent, req *http.Request) (*model.Repo, *model.Build, error) { | ||||
| func convertMergeRequestHook(hook *gitlab.MergeEvent, req *http.Request) (int, *model.Repo, *model.Build, error) { | ||||
| 	repo := &model.Repo{} | ||||
| 	build := &model.Build{} | ||||
|  | ||||
| @@ -68,17 +73,17 @@ func convertMergeRequestHock(hook *gitlab.MergeEvent, req *http.Request) (*model | ||||
| 	obj := hook.ObjectAttributes | ||||
|  | ||||
| 	if target == nil && source == nil { | ||||
| 		return nil, nil, fmt.Errorf("target and source keys expected in merge request hook") | ||||
| 		return 0, nil, nil, fmt.Errorf("target and source keys expected in merge request hook") | ||||
| 	} else if target == nil { | ||||
| 		return nil, nil, fmt.Errorf("target key expected in merge request hook") | ||||
| 		return 0, nil, nil, fmt.Errorf("target key expected in merge request hook") | ||||
| 	} else if source == nil { | ||||
| 		return nil, nil, fmt.Errorf("source key expected in merge request hook") | ||||
| 		return 0, nil, nil, fmt.Errorf("source key expected in merge request hook") | ||||
| 	} | ||||
|  | ||||
| 	if target.PathWithNamespace != "" { | ||||
| 		var err error | ||||
| 		if repo.Owner, repo.Name, err = extractFromPath(target.PathWithNamespace); err != nil { | ||||
| 			return nil, nil, err | ||||
| 			return 0, nil, nil, err | ||||
| 		} | ||||
| 		repo.FullName = target.PathWithNamespace | ||||
| 	} else { | ||||
| @@ -113,8 +118,7 @@ func convertMergeRequestHock(hook *gitlab.MergeEvent, req *http.Request) (*model | ||||
| 	build.Commit = lastCommit.ID | ||||
| 	build.Remote = obj.Source.HTTPURL | ||||
|  | ||||
| 	build.Ref = fmt.Sprintf("refs/merge-requests/%d/head", obj.IID) | ||||
|  | ||||
| 	build.Ref = fmt.Sprintf(mergeRefs, obj.IID) | ||||
| 	build.Branch = obj.SourceBranch | ||||
|  | ||||
| 	author := lastCommit.Author | ||||
| @@ -129,10 +133,10 @@ func convertMergeRequestHock(hook *gitlab.MergeEvent, req *http.Request) (*model | ||||
| 	build.Title = obj.Title | ||||
| 	build.Link = obj.URL | ||||
|  | ||||
| 	return repo, build, nil | ||||
| 	return obj.IID, repo, build, nil | ||||
| } | ||||
|  | ||||
| func convertPushHock(hook *gitlab.PushEvent) (*model.Repo, *model.Build, error) { | ||||
| func convertPushHook(hook *gitlab.PushEvent) (*model.Repo, *model.Build, error) { | ||||
| 	repo := &model.Repo{} | ||||
| 	build := &model.Build{} | ||||
|  | ||||
| @@ -161,6 +165,8 @@ func convertPushHock(hook *gitlab.PushEvent) (*model.Repo, *model.Build, error) | ||||
| 	build.Branch = strings.TrimPrefix(hook.Ref, "refs/heads/") | ||||
| 	build.Ref = hook.Ref | ||||
|  | ||||
| 	// assume a capacity of 4 changed files per commit | ||||
| 	files := make([]string, 0, len(hook.Commits)*4) | ||||
| 	for _, cm := range hook.Commits { | ||||
| 		if hook.After == cm.ID { | ||||
| 			build.Author = cm.Author.Name | ||||
| @@ -170,14 +176,18 @@ func convertPushHock(hook *gitlab.PushEvent) (*model.Repo, *model.Build, error) | ||||
| 			if len(build.Email) != 0 { | ||||
| 				build.Avatar = getUserAvatar(build.Email) | ||||
| 			} | ||||
| 			break | ||||
| 		} | ||||
|  | ||||
| 		files = append(files, cm.Added...) | ||||
| 		files = append(files, cm.Removed...) | ||||
| 		files = append(files, cm.Modified...) | ||||
| 	} | ||||
| 	build.ChangedFiles = utils.DedupStrings(files) | ||||
|  | ||||
| 	return repo, build, nil | ||||
| } | ||||
|  | ||||
| func convertTagHock(hook *gitlab.TagEvent) (*model.Repo, *model.Build, error) { | ||||
| func convertTagHook(hook *gitlab.TagEvent) (*model.Repo, *model.Build, error) { | ||||
| 	repo := &model.Repo{} | ||||
| 	build := &model.Build{} | ||||
|  | ||||
|   | ||||
| @@ -31,7 +31,9 @@ import ( | ||||
| 	"github.com/woodpecker-ci/woodpecker/server/model" | ||||
| 	"github.com/woodpecker-ci/woodpecker/server/remote" | ||||
| 	"github.com/woodpecker-ci/woodpecker/server/remote/common" | ||||
| 	"github.com/woodpecker-ci/woodpecker/server/store" | ||||
| 	"github.com/woodpecker-ci/woodpecker/shared/oauth2" | ||||
| 	"github.com/woodpecker-ci/woodpecker/shared/utils" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| @@ -524,7 +526,7 @@ func (g *Gitlab) Branches(ctx context.Context, user *model.User, repo *model.Rep | ||||
|  | ||||
| // Hook parses the post-commit hook from the Request body | ||||
| // and returns the required data in a standard format. | ||||
| func (g *Gitlab) Hook(req *http.Request) (*model.Repo, *model.Build, error) { | ||||
| func (g *Gitlab) Hook(ctx context.Context, req *http.Request) (*model.Repo, *model.Build, error) { | ||||
| 	defer req.Body.Close() | ||||
| 	payload, err := ioutil.ReadAll(req.Body) | ||||
| 	if err != nil { | ||||
| @@ -538,12 +540,62 @@ func (g *Gitlab) Hook(req *http.Request) (*model.Repo, *model.Build, error) { | ||||
|  | ||||
| 	switch event := parsed.(type) { | ||||
| 	case *gitlab.MergeEvent: | ||||
| 		return convertMergeRequestHock(event, req) | ||||
| 		mergeIID, repo, build, err := convertMergeRequestHook(event, req) | ||||
| 		if err != nil { | ||||
| 			return nil, nil, err | ||||
| 		} | ||||
|  | ||||
| 		if build, err = g.loadChangedFilesFromMergeRequest(ctx, repo, build, mergeIID); err != nil { | ||||
| 			return nil, nil, err | ||||
| 		} | ||||
|  | ||||
| 		return repo, build, nil | ||||
| 	case *gitlab.PushEvent: | ||||
| 		return convertPushHock(event) | ||||
| 		return convertPushHook(event) | ||||
| 	case *gitlab.TagEvent: | ||||
| 		return convertTagHock(event) | ||||
| 		return convertTagHook(event) | ||||
| 	default: | ||||
| 		return nil, nil, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (g *Gitlab) loadChangedFilesFromMergeRequest(ctx context.Context, tmpRepo *model.Repo, build *model.Build, mergeIID int) (*model.Build, error) { | ||||
| 	_store, ok := store.TryFromContext(ctx) | ||||
| 	if !ok { | ||||
| 		log.Error().Msg("could not get store from context") | ||||
| 		return build, nil | ||||
| 	} | ||||
|  | ||||
| 	repo, err := _store.GetRepoName(tmpRepo.Owner + "/" + tmpRepo.Name) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	user, err := _store.GetUser(repo.UserID) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	client, err := newClient(g.URL, user.Token, g.SkipVerify) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	_repo, err := g.getProject(ctx, client, repo.Owner, repo.Name) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	changes, _, err := client.MergeRequests.GetMergeRequestChanges(_repo.ID, mergeIID, &gitlab.GetMergeRequestChangesOptions{}, gitlab.WithContext(ctx)) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	files := make([]string, 0, len(changes.Changes)*2) | ||||
| 	for _, file := range changes.Changes { | ||||
| 		files = append(files, file.NewPath, file.OldPath) | ||||
| 	} | ||||
| 	build.ChangedFiles = utils.DedupStrings(files) | ||||
|  | ||||
| 	return build, nil | ||||
| } | ||||
|   | ||||
| @@ -51,7 +51,7 @@ func load(t *testing.T, config string) *Gitlab { | ||||
| } | ||||
|  | ||||
| func Test_Gitlab(t *testing.T) { | ||||
| 	// setup a dummy github server | ||||
| 	// setup a dummy gitlab server | ||||
| 	server := testdata.NewServer(t) | ||||
| 	defer server.Close() | ||||
|  | ||||
| @@ -169,7 +169,7 @@ func Test_Gitlab(t *testing.T) { | ||||
| 					) | ||||
| 					req.Header = testdata.ServiceHookHeaders | ||||
|  | ||||
| 					hookRepo, build, err := client.Hook(req) | ||||
| 					hookRepo, build, err := client.Hook(ctx, req) | ||||
| 					assert.NoError(t, err) | ||||
| 					if assert.NotNil(t, hookRepo) && assert.NotNil(t, build) { | ||||
| 						assert.Equal(t, build.Event, model.EventPush) | ||||
| @@ -178,6 +178,7 @@ func Test_Gitlab(t *testing.T) { | ||||
| 						assert.Equal(t, "http://example.com/uploads/project/avatar/555/Outh-20-Logo.jpg", hookRepo.Avatar) | ||||
| 						assert.Equal(t, "develop", hookRepo.Branch) | ||||
| 						assert.Equal(t, "refs/heads/master", build.Ref) | ||||
| 						assert.Equal(t, []string{"cmd/cli/main.go"}, build.ChangedFiles) | ||||
| 					} | ||||
| 				}) | ||||
| 			}) | ||||
| @@ -191,7 +192,7 @@ func Test_Gitlab(t *testing.T) { | ||||
| 					) | ||||
| 					req.Header = testdata.ServiceHookHeaders | ||||
|  | ||||
| 					hookRepo, build, err := client.Hook(req) | ||||
| 					hookRepo, build, err := client.Hook(ctx, req) | ||||
| 					assert.NoError(t, err) | ||||
| 					if assert.NotNil(t, hookRepo) && assert.NotNil(t, build) { | ||||
| 						assert.Equal(t, "test", hookRepo.Owner) | ||||
| @@ -199,6 +200,7 @@ func Test_Gitlab(t *testing.T) { | ||||
| 						assert.Equal(t, "http://example.com/uploads/project/avatar/555/Outh-20-Logo.jpg", hookRepo.Avatar) | ||||
| 						assert.Equal(t, "develop", hookRepo.Branch) | ||||
| 						assert.Equal(t, "refs/tags/v22", build.Ref) | ||||
| 						assert.Len(t, build.ChangedFiles, 0) | ||||
| 					} | ||||
| 				}) | ||||
| 			}) | ||||
| @@ -208,18 +210,20 @@ func Test_Gitlab(t *testing.T) { | ||||
| 					req, _ := http.NewRequest( | ||||
| 						testdata.ServiceHookMethod, | ||||
| 						testdata.ServiceHookURL.String(), | ||||
| 						bytes.NewReader(testdata.ServiceHookMergeRequestBody), | ||||
| 						bytes.NewReader(testdata.WebhookMergeRequestBody), | ||||
| 					) | ||||
| 					req.Header = testdata.ServiceHookHeaders | ||||
|  | ||||
| 					hookRepo, build, err := client.Hook(req) | ||||
| 					// TODO: insert fake store into context to retrieve user & repo, this will activate fetching of ChangedFiles | ||||
| 					hookRepo, build, err := client.Hook(ctx, req) | ||||
| 					assert.NoError(t, err) | ||||
| 					if assert.NotNil(t, hookRepo) && assert.NotNil(t, build) { | ||||
| 						assert.Equal(t, "http://example.com/uploads/project/avatar/555/Outh-20-Logo.jpg", hookRepo.Avatar) | ||||
| 						assert.Equal(t, "develop", hookRepo.Branch) | ||||
| 						assert.Equal(t, "test", hookRepo.Owner) | ||||
| 						assert.Equal(t, "main", hookRepo.Branch) | ||||
| 						assert.Equal(t, "anbraten", hookRepo.Owner) | ||||
| 						assert.Equal(t, "woodpecker", hookRepo.Name) | ||||
| 						assert.Equal(t, "Update client.go 🎉", build.Title) | ||||
| 						assert.Len(t, build.ChangedFiles, 0) // see L217 | ||||
| 					} | ||||
| 				}) | ||||
| 			}) | ||||
|   | ||||
							
								
								
									
										169
									
								
								server/remote/gitlab/testdata/hooks.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										169
									
								
								server/remote/gitlab/testdata/hooks.go
									
									
									
									
										vendored
									
									
								
							| @@ -169,45 +169,45 @@ var ServiceHookTagPushBody = []byte(`{ | ||||
|   } | ||||
| }`) | ||||
|  | ||||
| // ServiceHookMergeRequestBody is payload of ServiceHook: MergeRequest | ||||
| var ServiceHookMergeRequestBody = []byte(`{ | ||||
| // WebhookMergeRequestBody is payload of MergeEvent | ||||
| var WebhookMergeRequestBody = []byte(`{ | ||||
|   "object_kind": "merge_request", | ||||
|   "event_type": "merge_request", | ||||
|   "user": { | ||||
|     "id": 2, | ||||
|     "name": "the test", | ||||
|     "username": "test", | ||||
|     "avatar_url": "https://www.gravatar.com/avatar/dd46a756faad4727fb679320751f6dea?s=80&d=identicon", | ||||
|     "email": "test@test.test" | ||||
|     "id": 2251488, | ||||
|     "name": "Anbraten", | ||||
|     "username": "anbraten", | ||||
|     "avatar_url": "https://secure.gravatar.com/avatar/fc9b6fe77c6b732a02925a62a81f05a0?s=80&d=identicon", | ||||
|     "email": "some@mail.info" | ||||
|   }, | ||||
|   "project": { | ||||
|     "id": 2, | ||||
|     "name": "Woodpecker", | ||||
|     "id": 32059612, | ||||
|     "name": "woodpecker", | ||||
|     "description": "", | ||||
|     "web_url": "http://10.40.8.5:3200/test/woodpecker", | ||||
|     "avatar_url": null, | ||||
|     "git_ssh_url": "git@10.40.8.5:test/woodpecker.git", | ||||
|     "git_http_url": "http://10.40.8.5:3200/test/woodpecker.git", | ||||
|     "namespace": "the test", | ||||
|     "web_url": "https://gitlab.com/anbraten/woodpecker", | ||||
|     "avatar_url": "http://example.com/uploads/project/avatar/555/Outh-20-Logo.jpg", | ||||
|     "git_ssh_url": "git@gitlab.com:anbraten/woodpecker.git", | ||||
|     "git_http_url": "https://gitlab.com/anbraten/woodpecker.git", | ||||
|     "namespace": "Anbraten", | ||||
|     "visibility_level": 20, | ||||
|     "path_with_namespace": "test/woodpecker", | ||||
|     "default_branch": "master", | ||||
|     "ci_config_path": null, | ||||
|     "homepage": "http://10.40.8.5:3200/test/woodpecker", | ||||
|     "url": "git@10.40.8.5:test/woodpecker.git", | ||||
|     "ssh_url": "git@10.40.8.5:test/woodpecker.git", | ||||
|     "http_url": "http://10.40.8.5:3200/test/woodpecker.git" | ||||
|     "path_with_namespace": "anbraten/woodpecker", | ||||
|     "default_branch": "main", | ||||
|     "ci_config_path": "", | ||||
|     "homepage": "https://gitlab.com/anbraten/woodpecker", | ||||
|     "url": "git@gitlab.com:anbraten/woodpecker.git", | ||||
|     "ssh_url": "git@gitlab.com:anbraten/woodpecker.git", | ||||
|     "http_url": "https://gitlab.com/anbraten/woodpecker.git" | ||||
|   }, | ||||
|   "object_attributes": { | ||||
|     "assignee_id": null, | ||||
|     "author_id": 2, | ||||
|     "created_at": "2021-09-27 05:00:01 UTC", | ||||
|     "assignee_id": 2251488, | ||||
|     "author_id": 2251488, | ||||
|     "created_at": "2022-01-10 15:23:41 UTC", | ||||
|     "description": "", | ||||
|     "head_pipeline_id": 5, | ||||
|     "id": 2, | ||||
|     "iid": 2, | ||||
|     "last_edited_at": null, | ||||
|     "last_edited_by_id": null, | ||||
|     "head_pipeline_id": 449733536, | ||||
|     "id": 134400602, | ||||
|     "iid": 3, | ||||
|     "last_edited_at": "2022-01-17 15:46:23 UTC", | ||||
|     "last_edited_by_id": 2251488, | ||||
|     "merge_commit_sha": null, | ||||
|     "merge_error": null, | ||||
|     "merge_params": { | ||||
| @@ -217,61 +217,61 @@ var ServiceHookMergeRequestBody = []byte(`{ | ||||
|     "merge_user_id": null, | ||||
|     "merge_when_pipeline_succeeds": false, | ||||
|     "milestone_id": null, | ||||
|     "source_branch": "masterfdsafds", | ||||
|     "source_project_id": 2, | ||||
|     "source_branch": "anbraten-main-patch-05373", | ||||
|     "source_project_id": 32059612, | ||||
|     "state_id": 1, | ||||
|     "target_branch": "master", | ||||
|     "target_project_id": 2, | ||||
|     "target_branch": "main", | ||||
|     "target_project_id": 32059612, | ||||
|     "time_estimate": 0, | ||||
|     "title": "Update client.go 🎉", | ||||
|     "updated_at": "2021-09-27 05:01:21 UTC", | ||||
|     "updated_by_id": null, | ||||
|     "url": "http://10.40.8.5:3200/test/woodpecker/-/merge_requests/2", | ||||
|     "updated_at": "2022-01-17 15:47:39 UTC", | ||||
|     "updated_by_id": 2251488, | ||||
|     "url": "https://gitlab.com/anbraten/woodpecker/-/merge_requests/3", | ||||
|     "source": { | ||||
|       "id": 2, | ||||
|       "name": "Woodpecker", | ||||
|       "id": 32059612, | ||||
|       "name": "woodpecker", | ||||
|       "description": "", | ||||
|       "web_url": "http://10.40.8.5:3200/test/woodpecker", | ||||
|       "avatar_url": "http://example.com/uploads/project/avatar/555/Outh-20-Logo.jpg", | ||||
|       "git_ssh_url": "git@10.40.8.5:test/woodpecker.git", | ||||
|       "git_http_url": "http://10.40.8.5:3200/test/woodpecker.git", | ||||
|       "namespace": "the test", | ||||
|       "web_url": "https://gitlab.com/anbraten/woodpecker", | ||||
|       "avatar_url": null, | ||||
|       "git_ssh_url": "git@gitlab.com:anbraten/woodpecker.git", | ||||
|       "git_http_url": "https://gitlab.com/anbraten/woodpecker.git", | ||||
|       "namespace": "Anbraten", | ||||
|       "visibility_level": 20, | ||||
|       "path_with_namespace": "test/woodpecker", | ||||
|       "default_branch": "develop", | ||||
|       "ci_config_path": null, | ||||
|       "homepage": "http://10.40.8.5:3200/test/woodpecker", | ||||
|       "url": "git@10.40.8.5:test/woodpecker.git", | ||||
|       "ssh_url": "git@10.40.8.5:test/woodpecker.git", | ||||
|       "http_url": "http://10.40.8.5:3200/test/woodpecker.git" | ||||
|       "path_with_namespace": "anbraten/woodpecker", | ||||
|       "default_branch": "main", | ||||
|       "ci_config_path": "", | ||||
|       "homepage": "https://gitlab.com/anbraten/woodpecker", | ||||
|       "url": "git@gitlab.com:anbraten/woodpecker.git", | ||||
|       "ssh_url": "git@gitlab.com:anbraten/woodpecker.git", | ||||
|       "http_url": "https://gitlab.com/anbraten/woodpecker.git" | ||||
|     }, | ||||
|     "target": { | ||||
|       "id": 2, | ||||
|       "name": "Woodpecker", | ||||
|       "id": 32059612, | ||||
|       "name": "woodpecker", | ||||
|       "description": "", | ||||
|       "web_url": "http://10.40.8.5:3200/test/woodpecker", | ||||
|       "web_url": "https://gitlab.com/anbraten/woodpecker", | ||||
|       "avatar_url": "http://example.com/uploads/project/avatar/555/Outh-20-Logo.jpg", | ||||
|       "git_ssh_url": "git@10.40.8.5:test/woodpecker.git", | ||||
|       "git_http_url": "http://10.40.8.5:3200/test/woodpecker.git", | ||||
|       "namespace": "the test", | ||||
|       "git_ssh_url": "git@gitlab.com:anbraten/woodpecker.git", | ||||
|       "git_http_url": "https://gitlab.com/anbraten/woodpecker.git", | ||||
|       "namespace": "Anbraten", | ||||
|       "visibility_level": 20, | ||||
|       "path_with_namespace": "test/woodpecker", | ||||
|       "default_branch": "develop", | ||||
|       "ci_config_path": null, | ||||
|       "homepage": "http://10.40.8.5:3200/test/woodpecker", | ||||
|       "url": "git@10.40.8.5:test/woodpecker.git", | ||||
|       "ssh_url": "git@10.40.8.5:test/woodpecker.git", | ||||
|       "http_url": "http://10.40.8.5:3200/test/woodpecker.git" | ||||
|       "path_with_namespace": "anbraten/woodpecker", | ||||
|       "default_branch": "main", | ||||
|       "ci_config_path": "", | ||||
|       "homepage": "https://gitlab.com/anbraten/woodpecker", | ||||
|       "url": "git@gitlab.com:anbraten/woodpecker.git", | ||||
|       "ssh_url": "git@gitlab.com:anbraten/woodpecker.git", | ||||
|       "http_url": "https://gitlab.com/anbraten/woodpecker.git" | ||||
|     }, | ||||
|     "last_commit": { | ||||
|       "id": "0ab96a10266b95b4b533dcfd98738015fbe70889", | ||||
|       "message": "Update state.go", | ||||
|       "title": "Update state.go", | ||||
|       "timestamp": "2021-09-27T05:01:20+00:00", | ||||
|       "url": "http://10.40.8.5:3200/test/woodpecker/-/commit/0ab96a10266b95b4b533dcfd98738015fbe70889", | ||||
|       "id": "c136499ec574e1034b24c5d306de9acda3005367", | ||||
|       "message": "Update folder/todo.txt", | ||||
|       "title": "Update folder/todo.txt", | ||||
|       "timestamp": "2022-01-17T15:47:38+00:00", | ||||
|       "url": "https://gitlab.com/anbraten/woodpecker/-/commit/c136499ec574e1034b24c5d306de9acda3005367", | ||||
|       "author": { | ||||
|         "name": "the test", | ||||
|         "email": "test@test.test" | ||||
|         "name": "Anbraten", | ||||
|         "email": "some@mail.info" | ||||
|       } | ||||
|     }, | ||||
|     "work_in_progress": false, | ||||
| @@ -281,25 +281,36 @@ var ServiceHookMergeRequestBody = []byte(`{ | ||||
|     "human_time_change": null, | ||||
|     "human_time_estimate": null, | ||||
|     "assignee_ids": [ | ||||
|  | ||||
|       2251488 | ||||
|     ], | ||||
|     "state": "opened", | ||||
|     "blocking_discussions_resolved": true, | ||||
|     "action": "update", | ||||
|     "oldrev": "6ef047571374c96a2bf13c361efd1fb008b0063e" | ||||
|     "oldrev": "8b641937b7340066d882b9d8a8cc5b0573a207de" | ||||
|   }, | ||||
|   "labels": [ | ||||
|  | ||||
|   ], | ||||
|   "changes": { | ||||
|     "updated_at": { | ||||
|       "previous": "2021-09-27 05:00:01 UTC", | ||||
|       "current": "2021-09-27 05:01:21 UTC" | ||||
|       "previous": "2022-01-17 15:46:23 UTC", | ||||
|       "current": "2022-01-17 15:47:39 UTC" | ||||
|     } | ||||
|   }, | ||||
|   "repository": { | ||||
|     "name": "Woodpecker", | ||||
|     "url": "git@10.40.8.5:test/woodpecker.git", | ||||
|     "name": "woodpecker", | ||||
|     "url": "git@gitlab.com:anbraten/woodpecker.git", | ||||
|     "description": "", | ||||
|     "homepage": "http://10.40.8.5:3200/test/woodpecker" | ||||
|   } | ||||
| }`) | ||||
|     "homepage": "https://gitlab.com/anbraten/woodpecker" | ||||
|   }, | ||||
|   "assignees": [ | ||||
|     { | ||||
|       "id": 2251488, | ||||
|       "name": "Anbraten", | ||||
|       "username": "anbraten", | ||||
|       "avatar_url": "https://secure.gravatar.com/avatar/fc9b6fe77c6b732a02925a62a81f05a0?s=80&d=identicon", | ||||
|       "email": "some@mail.info" | ||||
|     } | ||||
|   ] | ||||
| } | ||||
| `) | ||||
|   | ||||
| @@ -264,7 +264,7 @@ func (c *client) Branches(ctx context.Context, u *model.User, r *model.Repo) ([] | ||||
|  | ||||
| // Hook parses the incoming Gogs hook and returns the Repository and Build | ||||
| // details. If the hook is unsupported nil values are returned. | ||||
| func (c *client) Hook(r *http.Request) (*model.Repo, *model.Build, error) { | ||||
| func (c *client) Hook(ctx context.Context, r *http.Request) (*model.Repo, *model.Build, error) { | ||||
| 	return parseHook(r) | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -136,13 +136,13 @@ func (_m *Remote) File(ctx context.Context, u *model.User, r *model.Repo, b *mod | ||||
| 	return r0, r1 | ||||
| } | ||||
|  | ||||
| // Hook provides a mock function with given fields: r | ||||
| func (_m *Remote) Hook(r *http.Request) (*model.Repo, *model.Build, error) { | ||||
| 	ret := _m.Called(r) | ||||
| // Hook provides a mock function with given fields: ctx, r | ||||
| func (_m *Remote) Hook(ctx context.Context, r *http.Request) (*model.Repo, *model.Build, error) { | ||||
| 	ret := _m.Called(ctx, r) | ||||
|  | ||||
| 	var r0 *model.Repo | ||||
| 	if rf, ok := ret.Get(0).(func(*http.Request) *model.Repo); ok { | ||||
| 		r0 = rf(r) | ||||
| 	if rf, ok := ret.Get(0).(func(context.Context, *http.Request) *model.Repo); ok { | ||||
| 		r0 = rf(ctx, r) | ||||
| 	} else { | ||||
| 		if ret.Get(0) != nil { | ||||
| 			r0 = ret.Get(0).(*model.Repo) | ||||
| @@ -150,8 +150,8 @@ func (_m *Remote) Hook(r *http.Request) (*model.Repo, *model.Build, error) { | ||||
| 	} | ||||
|  | ||||
| 	var r1 *model.Build | ||||
| 	if rf, ok := ret.Get(1).(func(*http.Request) *model.Build); ok { | ||||
| 		r1 = rf(r) | ||||
| 	if rf, ok := ret.Get(1).(func(context.Context, *http.Request) *model.Build); ok { | ||||
| 		r1 = rf(ctx, r) | ||||
| 	} else { | ||||
| 		if ret.Get(1) != nil { | ||||
| 			r1 = ret.Get(1).(*model.Build) | ||||
| @@ -159,8 +159,8 @@ func (_m *Remote) Hook(r *http.Request) (*model.Repo, *model.Build, error) { | ||||
| 	} | ||||
|  | ||||
| 	var r2 error | ||||
| 	if rf, ok := ret.Get(2).(func(*http.Request) error); ok { | ||||
| 		r2 = rf(r) | ||||
| 	if rf, ok := ret.Get(2).(func(context.Context, *http.Request) error); ok { | ||||
| 		r2 = rf(ctx, r) | ||||
| 	} else { | ||||
| 		r2 = ret.Error(2) | ||||
| 	} | ||||
|   | ||||
| @@ -75,7 +75,7 @@ type Remote interface { | ||||
|  | ||||
| 	// Hook parses the post-commit hook from the Request body and returns the | ||||
| 	// required data in a standard format. | ||||
| 	Hook(r *http.Request) (*model.Repo, *model.Build, error) | ||||
| 	Hook(ctx context.Context, r *http.Request) (*model.Repo, *model.Build, error) | ||||
| } | ||||
|  | ||||
| // FileMeta represents a file in version control | ||||
|   | ||||
| @@ -30,6 +30,12 @@ func FromContext(c context.Context) Store { | ||||
| 	return c.Value(key).(Store) | ||||
| } | ||||
|  | ||||
| // TryFromContext try to return the Store associated with this context. | ||||
| func TryFromContext(c context.Context) (Store, bool) { | ||||
| 	store, ok := c.Value(key).(Store) | ||||
| 	return store, ok | ||||
| } | ||||
|  | ||||
| // ToContext adds the Store to this context if it supports | ||||
| // the Setter interface. | ||||
| func ToContext(c Setter, store Store) { | ||||
|   | ||||
							
								
								
									
										32
									
								
								shared/utils/strings.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								shared/utils/strings.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| // Copyright 2022 Woodpecker Authors | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //      http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package utils | ||||
|  | ||||
| // DedupStrings deduplicate string list, empty items are dropped | ||||
| func DedupStrings(list []string) []string { | ||||
| 	m := make(map[string]struct{}, len(list)) | ||||
|  | ||||
| 	for i := range list { | ||||
| 		if s := list[i]; len(s) > 0 { | ||||
| 			m[list[i]] = struct{}{} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	newList := make([]string, 0, len(m)) | ||||
| 	for k := range m { | ||||
| 		newList = append(newList, k) | ||||
| 	} | ||||
| 	return newList | ||||
| } | ||||
							
								
								
									
										48
									
								
								shared/utils/strings_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								shared/utils/strings_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| // Copyright 2022 Woodpecker Authors | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //      http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package utils | ||||
|  | ||||
| import ( | ||||
| 	"sort" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
|  | ||||
| func TestDedupStrings(t *testing.T) { | ||||
| 	tests := []struct { | ||||
| 		in  []string | ||||
| 		out []string | ||||
| 	}{{ | ||||
| 		in:  []string{"", "ab", "12", "ab"}, | ||||
| 		out: []string{"12", "ab"}, | ||||
| 	}, { | ||||
| 		in:  nil, | ||||
| 		out: nil, | ||||
| 	}, { | ||||
| 		in:  []string{""}, | ||||
| 		out: nil, | ||||
| 	}} | ||||
|  | ||||
| 	for _, tc := range tests { | ||||
| 		result := DedupStrings(tc.in) | ||||
| 		sort.Strings(result) | ||||
| 		if len(tc.out) == 0 { | ||||
| 			assert.Len(t, result, 0) | ||||
| 		} else { | ||||
| 			assert.EqualValues(t, tc.out, result, "could not correctly process input '%#v'", tc.in) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
		Reference in New Issue
	
	Block a user