diff --git a/cli/exec/flags.go b/cli/exec/flags.go index 10518d32b85..b8e92e9442d 100644 --- a/cli/exec/flags.go +++ b/cli/exec/flags.go @@ -270,11 +270,6 @@ var flags = []cli.Flag{ Name: "commit-author-name", Usage: "Set the metadata environment variable \"CI_COMMIT_AUTHOR\".", }, - &cli.StringFlag{ - Sources: cli.EnvVars("CI_COMMIT_AUTHOR_AVATAR"), - Name: "commit-author-avatar", - Usage: "Set the metadata environment variable \"CI_COMMIT_AUTHOR_AVATAR\".", - }, &cli.StringFlag{ Sources: cli.EnvVars("CI_COMMIT_AUTHOR_EMAIL"), Name: "commit-author-email", @@ -365,11 +360,6 @@ var flags = []cli.Flag{ Name: "prev-commit-author-name", Usage: "Set the metadata environment variable \"CI_PREV_COMMIT_AUTHOR\".", }, - &cli.StringFlag{ - Sources: cli.EnvVars("CI_PREV_COMMIT_AUTHOR_AVATAR"), - Name: "prev-commit-author-avatar", - Usage: "Set the metadata environment variable \"CI_PREV_COMMIT_AUTHOR_AVATAR\".", - }, &cli.StringFlag{ Sources: cli.EnvVars("CI_PREV_COMMIT_AUTHOR_EMAIL"), Name: "prev-commit-author-email", diff --git a/cli/exec/metadata.go b/cli/exec/metadata.go index 8664ec2c4f4..4ee188d8175 100644 --- a/cli/exec/metadata.go +++ b/cli/exec/metadata.go @@ -106,7 +106,6 @@ func metadataFromContext(_ context.Context, c *cli.Command, axis matrix.Axis, w metadataFileAndOverrideOrDefault(c, "commit-message", func(s string) { m.Curr.Commit.Message = s }, c.String) metadataFileAndOverrideOrDefault(c, "commit-author-name", func(s string) { m.Curr.Commit.Author.Name = s }, c.String) metadataFileAndOverrideOrDefault(c, "commit-author-email", func(s string) { m.Curr.Commit.Author.Email = s }, c.String) - metadataFileAndOverrideOrDefault(c, "commit-author-avatar", func(s string) { m.Curr.Commit.Author.Avatar = s }, c.String) metadataFileAndOverrideOrDefault(c, "commit-pull-labels", func(sl []string) { m.Curr.Commit.PullRequestLabels = sl }, c.StringSlice) metadataFileAndOverrideOrDefault(c, "commit-release-is-pre", func(b bool) { m.Curr.Commit.IsPrerelease = b }, c.Bool) @@ -128,7 +127,6 @@ func metadataFromContext(_ context.Context, c *cli.Command, axis matrix.Axis, w metadataFileAndOverrideOrDefault(c, "prev-commit-message", func(s string) { m.Prev.Commit.Message = s }, c.String) metadataFileAndOverrideOrDefault(c, "prev-commit-author-name", func(s string) { m.Prev.Commit.Author.Name = s }, c.String) metadataFileAndOverrideOrDefault(c, "prev-commit-author-email", func(s string) { m.Prev.Commit.Author.Email = s }, c.String) - metadataFileAndOverrideOrDefault(c, "prev-commit-author-avatar", func(s string) { m.Prev.Commit.Author.Avatar = s }, c.String) // Workflow metadataFileAndOverrideOrDefault(c, "workflow-name", func(s string) { m.Workflow.Name = s }, c.String) diff --git a/cli/pipeline/pipeline.go b/cli/pipeline/pipeline.go index efcae855311..ab42a239dec 100644 --- a/cli/pipeline/pipeline.go +++ b/cli/pipeline/pipeline.go @@ -89,6 +89,10 @@ func pipelineOutput(c *cli.Command, pipelines []*woodpecker.Pipeline, fd ...io.W if !noHeader { table.WriteHeader(cols) } + table.AddFieldFn("message", func(obj any) string { + pl, _ := obj.(*woodpecker.Pipeline) + return pl.Commit.Message + }) for _, resource := range pipelines { if err := table.Write(cols, resource); err != nil { return err diff --git a/cli/pipeline/pipeline_test.go b/cli/pipeline/pipeline_test.go index 5238a19fe91..50ce80bbff4 100644 --- a/cli/pipeline/pipeline_test.go +++ b/cli/pipeline/pipeline_test.go @@ -49,12 +49,14 @@ func TestPipelineOutput(t *testing.T) { pipelines := []*woodpecker.Pipeline{ { - Number: 1, - Status: "success", - Event: "push", - Branch: "main", - Message: "message\nmultiline", - Author: "John Doe\n", + Number: 1, + Status: "success", + Event: "push", + Branch: "main", + Commit: &woodpecker.Commit{ + Message: "message\nmultiline", + }, + Author: "John Doe\n", }, } diff --git a/cmd/server/openapi/docs.go b/cmd/server/openapi/docs.go index cbe3ed13dfa..89cbebce7fe 100644 --- a/cmd/server/openapi/docs.go +++ b/cmd/server/openapi/docs.go @@ -4705,18 +4705,18 @@ const docTemplate = `{ "author_avatar": { "type": "string" }, - "author_email": { - "type": "string" - }, "branch": { "type": "string" }, "commit": { - "type": "string" + "$ref": "#/definitions/model.Commit" }, "created": { "type": "integer" }, + "deployment": { + "$ref": "#/definitions/model.Deployment" + }, "event": { "type": "string" }, @@ -4726,18 +4726,21 @@ const docTemplate = `{ "id": { "type": "integer" }, - "message": { - "type": "string" - }, "number": { "type": "integer" }, + "pull_request": { + "$ref": "#/definitions/PullRequest" + }, "ref": { "type": "string" }, "refspec": { "type": "string" }, + "release": { + "type": "string" + }, "repo_id": { "type": "integer" }, @@ -4746,9 +4749,6 @@ const docTemplate = `{ }, "status": { "type": "string" - }, - "title": { - "type": "string" } } }, @@ -4883,9 +4883,6 @@ const docTemplate = `{ "author_avatar": { "type": "string" }, - "author_email": { - "type": "string" - }, "branch": { "type": "string" }, @@ -4896,16 +4893,16 @@ const docTemplate = `{ } }, "commit": { - "type": "string" + "$ref": "#/definitions/model.Commit" }, "created": { "type": "integer" }, - "deploy_task": { + "cron": { "type": "string" }, - "deploy_to": { - "type": "string" + "deployment": { + "$ref": "#/definitions/model.Deployment" }, "errors": { "type": "array", @@ -4922,29 +4919,20 @@ const docTemplate = `{ "forge_url": { "type": "string" }, - "from_fork": { - "type": "boolean" - }, "id": { "type": "integer" }, "is_prerelease": { "type": "boolean" }, - "message": { - "type": "string" - }, "number": { "type": "integer" }, "parent": { "type": "integer" }, - "pr_labels": { - "type": "array", - "items": { - "type": "string" - } + "pull_request": { + "$ref": "#/definitions/PullRequest" }, "ref": { "type": "string" @@ -4952,28 +4940,21 @@ const docTemplate = `{ "refspec": { "type": "string" }, + "release": { + "type": "string" + }, "reviewed": { "type": "integer" }, "reviewed_by": { "type": "string" }, - "sender": { - "description": "uses reported user for webhooks and name of cron for cron pipelines", - "type": "string" - }, "started": { "type": "integer" }, "status": { "$ref": "#/definitions/StatusValue" }, - "timestamp": { - "type": "integer" - }, - "title": { - "type": "string" - }, "updated": { "type": "integer" }, @@ -5008,9 +4989,18 @@ const docTemplate = `{ "PullRequest": { "type": "object", "properties": { + "from_fork": { + "type": "boolean" + }, "index": { "type": "string" }, + "labels": { + "type": "array", + "items": { + "type": "string" + } + }, "title": { "type": "string" } @@ -5504,9 +5494,6 @@ const docTemplate = `{ "metadata.Author": { "type": "object", "properties": { - "avatar": { - "type": "string" - }, "email": { "type": "string" }, @@ -5620,6 +5607,9 @@ const docTemplate = `{ "parent": { "type": "integer" }, + "release": { + "type": "string" + }, "started": { "type": "integer" }, @@ -5752,6 +5742,48 @@ const docTemplate = `{ "RequireApprovalAllEvents" ] }, + "model.Commit": { + "type": "object", + "properties": { + "author": { + "$ref": "#/definitions/model.CommitAuthor" + }, + "forge_url": { + "type": "string" + }, + "message": { + "type": "string" + }, + "sha": { + "type": "string" + } + } + }, + "model.CommitAuthor": { + "type": "object", + "properties": { + "author": { + "type": "string" + }, + "email": { + "type": "string" + } + } + }, + "model.Deployment": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "target": { + "type": "string" + }, + "task": { + "type": "string" + } + } + }, "model.ForgeType": { "type": "string", "enum": [ diff --git a/docs/docs/20-usage/50-environment.md b/docs/docs/20-usage/50-environment.md index 746cb7634dd..47f784ca4f1 100644 --- a/docs/docs/20-usage/50-environment.md +++ b/docs/docs/20-usage/50-environment.md @@ -77,7 +77,6 @@ This is the reference list of all environment variables available to your pipeli | `CI_COMMIT_MESSAGE` | commit message | `Initial commit` | | `CI_COMMIT_AUTHOR` | commit author username | `john-doe` | | `CI_COMMIT_AUTHOR_EMAIL` | commit author email address | `john-doe@example.com` | -| `CI_COMMIT_AUTHOR_AVATAR` | commit author avatar | `https://git.example.com/avatars/5dcbcadbce6f87f8abef` | | `CI_COMMIT_PRERELEASE` | release is a pre-release (empty if event is not `release`) | `false` | | | **Current pipeline** | | | `CI_PIPELINE_NUMBER` | pipeline number | `8` | @@ -108,7 +107,6 @@ This is the reference list of all environment variables available to your pipeli | `CI_PREV_COMMIT_MESSAGE` | previous commit message | `test` | | `CI_PREV_COMMIT_AUTHOR` | previous commit author username | `john-doe` | | `CI_PREV_COMMIT_AUTHOR_EMAIL` | previous commit author email address | `john-doe@example.com` | -| `CI_PREV_COMMIT_AUTHOR_AVATAR` | previous commit author avatar | `https://git.example.com/avatars/12` | | | **Previous pipeline** | | | `CI_PREV_PIPELINE_NUMBER` | previous pipeline number | `7` | | `CI_PREV_PIPELINE_PARENT` | previous pipeline number of parent pipeline | `0` | diff --git a/docs/src/pages/migrations.md b/docs/src/pages/migrations.md index a403e43f798..a76d5095af2 100644 --- a/docs/src/pages/migrations.md +++ b/docs/src/pages/migrations.md @@ -1,10 +1,16 @@ + + # Migrations To enhance the usability of Woodpecker and meet evolving security standards, occasional migrations are necessary. While we aim to minimize these changes, some are unavoidable. If you experience significant issues during a migration to a new version, please let us know so maintainers can reassess the updates. ## `next` -- No changes +### User-facing changes + +#### API changes + +- Changed the pipeline model to have different objects for different event metadata (e.g. pull request title) ## 3.0.0 @@ -65,6 +71,7 @@ The following built-in environment variables have been removed/replaced: - `CI_PIPELINE_FINISHED` as it was empty during execution - `CI_PIPELINE_STATUS` due to always being set to `success` - `CI_STEP_STATUS` due to always being set to `success` +- `CI_COMMIT_AUTHOR_AVATAR` and `CI_PREV_COMMIT_AUTHOR_AVATAR` as commit authors don't have an avatar - `WOODPECKER_WEBHOOK_HOST` in favor of `WOODPECKER_EXPERT_WEBHOOK_HOST` Environment variables which are empty after workflow parsing are not being injected into the build but filtered out beforehand ([#4193](https://github.com/woodpecker-ci/woodpecker/pull/4193)) @@ -83,6 +90,38 @@ The following syntax deprecations will now result in an error: - `platform:` ([#3916](https://github.com/woodpecker-ci/woodpecker/pull/3916)) - `branches:` ([#3916](https://github.com/woodpecker-ci/woodpecker/pull/3916)) +#### Workflow syntax changes + +- Grouping of steps via `steps.[name].group` should now be done using `steps.[name].depends_on` +- The `includes` and `excludes` event filter options have been removed +- Previously, env vars have been automatically sanitized to uppercase. + As this has been confusing, the type-case of the secret definition is now respected ([#3375](https://github.com/woodpecker-ci/woodpecker/pull/3375)). +- `secrets` have been entirely removed in favor of `environment` combined with the `from_secret` syntax. + As `secrets` are just normal env vars which are masked, the goal was to allow them to be declared next to normal env vars and at the same time reduce the keyword syntax count. + Additionally, the `from_secret` syntax gives more flexibility in naming. + Whereas beforehand `secrets` where always named after their initial secret name, the `from_secret` reference can now be different. + Last, one can inject multiple different env vars from the same secret reference. + + 2.x: + + ```yaml + secrets: [my_token] + ``` + + 3.x: + + ```yaml + environment: + MY_TOKEN: + from_secret: my_token + ``` + +- The `environment` filter option has been removed in favor of `when.evaluate` + +#### API changes + +- Removed deprecated `registry/` endpoint. Use `registries`, `/authorize/token` + #### CLI changes The following restructuring was done to achieve a more consistent grouping: diff --git a/pipeline/frontend/metadata/drone_compatibility.go b/pipeline/frontend/metadata/drone_compatibility.go index d93d14d90ba..07519333032 100644 --- a/pipeline/frontend/metadata/drone_compatibility.go +++ b/pipeline/frontend/metadata/drone_compatibility.go @@ -42,7 +42,6 @@ func SetDroneEnviron(env map[string]string) { copyEnv("CI_COMMIT_AUTHOR", "DRONE_COMMIT_AUTHOR", env) copyEnv("CI_COMMIT_AUTHOR", "DRONE_COMMIT_AUTHOR_NAME", env) copyEnv("CI_COMMIT_AUTHOR_EMAIL", "DRONE_COMMIT_AUTHOR_EMAIL", env) - copyEnv("CI_COMMIT_AUTHOR_AVATAR", "DRONE_COMMIT_AUTHOR_AVATAR", env) // repo copyEnv("CI_REPO", "DRONE_REPO", env) copyEnv("CI_REPO_OWNER", "DRONE_REPO_OWNER", env) diff --git a/pipeline/frontend/metadata/drone_compatibility_test.go b/pipeline/frontend/metadata/drone_compatibility_test.go index e31d0a7ac1a..084b21ee8f4 100644 --- a/pipeline/frontend/metadata/drone_compatibility_test.go +++ b/pipeline/frontend/metadata/drone_compatibility_test.go @@ -26,7 +26,6 @@ import ( func TestSetDroneEnvironOnPull(t *testing.T) { woodpeckerVars := `CI=woodpecker CI_COMMIT_AUTHOR=6543 -CI_COMMIT_AUTHOR_AVATAR=https://codeberg.org/avatars/09a234c768cb9bca78f6b2f82d6af173 CI_COMMIT_BRANCH=main CI_COMMIT_MESSAGE=fix testscript CI_COMMIT_PULL_REQUEST=9 @@ -42,7 +41,6 @@ CI_PIPELINE_EVENT=pull_request CI_PIPELINE_NUMBER=41 CI_PIPELINE_STARTED=1685749339 CI_PREV_COMMIT_AUTHOR=6543 -CI_PREV_COMMIT_AUTHOR_AVATAR=https://codeberg.org/avatars/09a234c768cb9bca78f6b2f82d6af173 CI_PREV_COMMIT_BRANCH=main CI_PREV_COMMIT_MESSAGE=Print filename and linenuber on fail CI_PREV_COMMIT_REF=refs/pull/13/head @@ -77,7 +75,6 @@ DRONE_BUILD_STARTED=1685749339 DRONE_BUILD_STATUS=success DRONE_COMMIT=a778b069d9f5992786d2db9be493b43868cfce76 DRONE_COMMIT_AUTHOR=6543 -DRONE_COMMIT_AUTHOR_AVATAR=https://codeberg.org/avatars/09a234c768cb9bca78f6b2f82d6af173 DRONE_COMMIT_AUTHOR_NAME=6543 DRONE_COMMIT_BEFORE=e246aff5a9466df2e522efc9007823a7496d9d41 DRONE_COMMIT_BRANCH=main @@ -108,7 +105,6 @@ PULLREQUEST_DRONE_PULL_REQUEST=9` func TestSetDroneEnvironOnPush(t *testing.T) { woodpeckerVars := `CI_COMMIT_AUTHOR=test -CI_COMMIT_AUTHOR_AVATAR=http://1.2.3.4:3000/avatars/dd46a756faad4727fb679320751f6dea CI_COMMIT_AUTHOR_EMAIL=test@noreply.localhost CI_COMMIT_BRANCH=main CI_COMMIT_MESSAGE=revert 9b2aed1392fc097ef7b027712977722fb004d463 @@ -134,7 +130,6 @@ CI_PIPELINE_PARENT=23 CI_PIPELINE_STARTED=1721328737 CI_PIPELINE_URL=http://1.2.3.4:8000/repos/2/pipeline/24 CI_PREV_COMMIT_AUTHOR=test -CI_PREV_COMMIT_AUTHOR_AVATAR=http://1.2.3.4:3000/avatars/dd46a756faad4727fb679320751f6dea CI_PREV_COMMIT_AUTHOR_EMAIL=test@noreply.localhost CI_PREV_COMMIT_BRANCH=main CI_PREV_COMMIT_MESSAGE=revert 9b2aed1392fc097ef7b027712977722fb004d463 @@ -191,7 +186,6 @@ DRONE_BUILD_STARTED=1721328737 DRONE_BUILD_STATUS=success DRONE_COMMIT=8826c98181353075bbeee8f99b400496488e3523 DRONE_COMMIT_AUTHOR=test -DRONE_COMMIT_AUTHOR_AVATAR=http://1.2.3.4:3000/avatars/dd46a756faad4727fb679320751f6dea DRONE_COMMIT_AUTHOR_EMAIL=test@noreply.localhost DRONE_COMMIT_AUTHOR_NAME=test DRONE_COMMIT_BEFORE=8826c98181353075bbeee8f99b400496488e3523 diff --git a/pipeline/frontend/metadata/environment.go b/pipeline/frontend/metadata/environment.go index d95ac8762b3..71f87ce77d5 100644 --- a/pipeline/frontend/metadata/environment.go +++ b/pipeline/frontend/metadata/environment.go @@ -93,7 +93,6 @@ func (m *Metadata) Environ() map[string]string { setNonEmptyEnvVar(params, "CI_COMMIT_BRANCH", commit.Branch) setNonEmptyEnvVar(params, "CI_COMMIT_AUTHOR", commit.Author.Name) setNonEmptyEnvVar(params, "CI_COMMIT_AUTHOR_EMAIL", commit.Author.Email) - setNonEmptyEnvVar(params, "CI_COMMIT_AUTHOR_AVATAR", commit.Author.Avatar) if pipeline.Event == EventTag || pipeline.Event == EventRelease || strings.HasPrefix(pipeline.Commit.Ref, "refs/tags/") { setNonEmptyEnvVar(params, "CI_COMMIT_TAG", strings.TrimPrefix(pipeline.Commit.Ref, "refs/tags/")) } @@ -143,7 +142,6 @@ func (m *Metadata) Environ() map[string]string { setNonEmptyEnvVar(params, "CI_PREV_COMMIT_BRANCH", prevCommit.Branch) setNonEmptyEnvVar(params, "CI_PREV_COMMIT_AUTHOR", prevCommit.Author.Name) setNonEmptyEnvVar(params, "CI_PREV_COMMIT_AUTHOR_EMAIL", prevCommit.Author.Email) - setNonEmptyEnvVar(params, "CI_PREV_COMMIT_AUTHOR_AVATAR", prevCommit.Author.Avatar) if prevPipeline.Event == EventPull || prevPipeline.Event == EventPullClosed { prevSourceBranch, prevTargetBranch := getSourceTargetBranches(prevCommit.Refspec) setNonEmptyEnvVar(params, "CI_PREV_COMMIT_SOURCE_BRANCH", prevSourceBranch) diff --git a/pipeline/frontend/metadata/types.go b/pipeline/frontend/metadata/types.go index 7b1084f65b7..9985fe21ea2 100644 --- a/pipeline/frontend/metadata/types.go +++ b/pipeline/frontend/metadata/types.go @@ -55,6 +55,7 @@ type ( Commit Commit `json:"commit,omitempty"` Parent int64 `json:"parent,omitempty"` Cron string `json:"cron,omitempty"` + Release string `json:"release,omitempty"` } // Commit defines runtime metadata for a commit. @@ -72,9 +73,8 @@ type ( // Author defines runtime metadata for a commit author. Author struct { - Name string `json:"name,omitempty"` - Email string `json:"email,omitempty"` - Avatar string `json:"avatar,omitempty"` + Name string `json:"name,omitempty"` + Email string `json:"email,omitempty"` } // Workflow defines runtime metadata for a workflow. diff --git a/server/api/hook_test.go b/server/api/hook_test.go index 6620440f30e..60a3a640326 100644 --- a/server/api/hook_test.go +++ b/server/api/hook_test.go @@ -56,6 +56,7 @@ func TestHook(t *testing.T) { ID: 123, RepoID: repo.ID, Event: model.EventPush, + Commit: &model.Commit{}, } repoToken := token.New(token.HookToken) diff --git a/server/api/pipeline.go b/server/api/pipeline.go index ce3d1396f91..86d46195cca 100644 --- a/server/api/pipeline.go +++ b/server/api/pipeline.go @@ -85,19 +85,15 @@ func CreatePipeline(c *gin.Context) { func createTmpPipeline(event model.WebhookEvent, commit *model.Commit, user *model.User, opts *model.PipelineOptions) *model.Pipeline { return &model.Pipeline{ - Event: event, - Commit: commit.SHA, - Branch: opts.Branch, - Timestamp: time.Now().UTC().Unix(), - - Avatar: user.Avatar, - Message: "MANUAL PIPELINE @ " + opts.Branch, + Event: event, + Commit: commit, + Branch: opts.Branch, Ref: opts.Branch, AdditionalVariables: opts.Variables, Author: user.Login, - Email: user.Email, + Avatar: user.Avatar, ForgeURL: commit.ForgeURL, } @@ -622,7 +618,10 @@ func PostPipeline(c *gin.Context) { // make Deploy overridable // make Deploy task overridable - pl.DeployTask = c.DefaultQuery("deploy_task", pl.DeployTask) + if pl.Deployment == nil { + pl.Deployment = new(model.Deployment) + } + pl.Deployment.Task = c.DefaultQuery("deploy_task", pl.Deployment.Task) // make Event overridable to deploy // TODO: refactor to use own proper API for deploy @@ -638,7 +637,7 @@ func PostPipeline(c *gin.Context) { return } - pl.DeployTo = c.DefaultQuery("deploy_to", pl.DeployTo) + pl.Deployment.Target = c.DefaultQuery("deploy_to", pl.Deployment.Target) } // Read query string parameters into pipelineParams, exclude reserved params diff --git a/server/api/pipeline_test.go b/server/api/pipeline_test.go index 04c1ba52c7d..f73815b5d41 100644 --- a/server/api/pipeline_test.go +++ b/server/api/pipeline_test.go @@ -37,6 +37,7 @@ var fakePipeline = &model.Pipeline{ ID: 2, Number: 2, Status: model.StatusSuccess, + Commit: &model.Commit{}, } func TestGetPipelines(t *testing.T) { @@ -169,6 +170,7 @@ func TestGetPipelineMetadata(t *testing.T) { ID: 1, Number: 1, Status: model.StatusFailure, + Commit: &model.Commit{}, } fakeRepo := &model.Repo{ID: 1} diff --git a/server/cron/cron.go b/server/cron/cron.go index 3460fbaa432..e7880bae9dd 100644 --- a/server/cron/cron.go +++ b/server/cron/cron.go @@ -137,13 +137,11 @@ func CreatePipeline(ctx context.Context, store store.Store, cron *model.Cron) (* } return repo, &model.Pipeline{ - Event: model.EventCron, - Commit: commit.SHA, - Ref: "refs/heads/" + cron.Branch, - Branch: cron.Branch, - Message: cron.Name, - Timestamp: cron.NextExec, - Sender: cron.Name, - ForgeURL: commit.ForgeURL, + Event: model.EventCron, + Commit: commit, + Ref: "refs/heads/" + cron.Branch, + Branch: cron.Branch, + Cron: cron.Name, + ForgeURL: commit.ForgeURL, }, nil } diff --git a/server/cron/cron_test.go b/server/cron/cron_test.go index ca39839f136..a19da033707 100644 --- a/server/cron/cron_test.go +++ b/server/cron/cron_test.go @@ -62,13 +62,15 @@ func TestCreatePipeline(t *testing.T) { }) assert.NoError(t, err) assert.EqualValues(t, &model.Pipeline{ - Branch: "default", - Commit: "sha1", + Branch: "default", + Commit: &model.Commit{ + ForgeURL: "https://example.com/sha1", + SHA: "sha1", + }, Event: "cron", ForgeURL: "https://example.com/sha1", - Message: "test", Ref: "refs/heads/default", - Sender: "test", + Cron: "test", }, pipeline) } diff --git a/server/forge/bitbucket/bitbucket.go b/server/forge/bitbucket/bitbucket.go index bbb5ac43b81..5c771a365e0 100644 --- a/server/forge/bitbucket/bitbucket.go +++ b/server/forge/bitbucket/bitbucket.go @@ -22,7 +22,6 @@ import ( "net/http" "net/url" "path/filepath" - "strconv" "golang.org/x/oauth2" @@ -32,6 +31,7 @@ import ( "go.woodpecker-ci.org/woodpecker/v3/server/forge/common" forge_types "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/store" shared_utils "go.woodpecker-ci.org/woodpecker/v3/shared/utils" ) @@ -222,7 +222,7 @@ func (c *config) Repos(ctx context.Context, u *model.User) ([]*model.Repo, error // File fetches the file from the Bitbucket repository and returns its contents. func (c *config) File(ctx context.Context, u *model.User, r *model.Repo, p *model.Pipeline, f string) ([]byte, error) { - config, err := c.newClient(ctx, u).FindSource(r.Owner, r.Name, p.Commit, f) + config, err := c.newClient(ctx, u).FindSource(r.Owner, r.Name, p.Commit.SHA, f) if err != nil { var rspErr internal.Error if ok := errors.As(err, &rspErr); ok && rspErr.Status == http.StatusNotFound { @@ -241,7 +241,7 @@ func (c *config) Dir(ctx context.Context, u *model.User, r *model.Repo, p *model repoPathFiles := []*forge_types.FileMeta{} client := c.newClient(ctx, u) for { - filesResp, err := client.GetRepoFiles(r.Owner, r.Name, p.Commit, f, page) + filesResp, err := client.GetRepoFiles(r.Owner, r.Name, p.Commit.SHA, f, page) if err != nil { var rspErr internal.Error if ok := errors.As(err, &rspErr); ok && rspErr.Status == http.StatusNotFound { @@ -257,7 +257,7 @@ func (c *config) Dir(ctx context.Context, u *model.User, r *model.Repo, p *model Name: filename, } if file.Type == "commit_file" { - fileData, err := c.newClient(ctx, u).FindSource(r.Owner, r.Name, p.Commit, file.Path) + fileData, err := c.newClient(ctx, u).FindSource(r.Owner, r.Name, p.Commit.SHA, file.Path) if err != nil { return nil, err } @@ -297,7 +297,7 @@ func (c *config) Status(ctx context.Context, user *model.User, repo *model.Repo, Key: common.GetPipelineStatusContext(repo, pipeline, workflow), URL: common.GetPipelineStatusURL(repo, pipeline, nil), } - return c.newClient(ctx, user).CreateStatus(repo.Owner, repo.Name, pipeline.Commit, &status) + return c.newClient(ctx, user).CreateStatus(repo.Owner, repo.Name, pipeline.Commit.SHA, &status) } // Activate activates the repository by registering repository push hooks with @@ -375,6 +375,8 @@ func (c *config) BranchHead(ctx context.Context, u *model.User, r *model.Repo, b return &model.Commit{ SHA: commit.Hash, ForgeURL: commit.Links.HTML.Href, + Message: commit.Message, + Author: convertCommitAuthor(commit.Author.Raw), }, nil } @@ -387,18 +389,25 @@ func (c *config) PullRequests(ctx context.Context, u *model.User, r *model.Repo, } var result []*model.PullRequest for _, pullRequest := range pullRequests { - result = append(result, &model.PullRequest{ - Index: model.ForgeRemoteID(strconv.Itoa(int(pullRequest.ID))), - Title: pullRequest.Title, - }) + result = append(result, convertPullRequest(pullRequest)) } return result, nil } // Hook parses the incoming Bitbucket hook and returns the Repository and // Pipeline details. If the hook is unsupported nil values are returned. -func (c *config) Hook(_ context.Context, req *http.Request) (*model.Repo, *model.Pipeline, error) { - return parseHook(req) +func (c *config) Hook(ctx context.Context, req *http.Request) (*model.Repo, *model.Pipeline, error) { + repo, pipeline, err := parseHook(req) + + if pipeline != nil && (pipeline.Event == model.EventPull || pipeline.Event == model.EventPullClosed) { + commit, err := c.getCommit(ctx, repo, pipeline.Commit.SHA) + if err != nil { + return nil, nil, err + } + pipeline.Commit = commit + } + + return repo, pipeline, err } // OrgMembership returns if user is member of organization and if user @@ -458,6 +467,35 @@ func (c *config) newOAuth2Config() *oauth2.Config { } } +func (c *config) getCommit(ctx context.Context, repo *model.Repo, sha string) (*model.Commit, error) { + _store, ok := store.TryFromContext(ctx) + if !ok { + return nil, fmt.Errorf("could not get store from context") + } + + repo, err := _store.GetRepoNameFallback(repo.ForgeRemoteID, repo.FullName) + if err != nil { + return nil, err + } + + user, err := _store.GetUser(repo.UserID) + if err != nil { + return nil, err + } + + commit, err := c.newClient(ctx, user).GetCommit(repo.Owner, repo.Name, sha) + if err != nil { + return nil, err + } + + return &model.Commit{ + SHA: commit.Hash, + ForgeURL: commit.Links.HTML.Href, + Message: commit.Message, + Author: convertCommitAuthor(commit.Author.Raw), + }, nil +} + // helper function to return matching hooks. func matchingHooks(hooks []*internal.Hook, rawURL string) *internal.Hook { link, err := url.Parse(rawURL) diff --git a/server/forge/bitbucket/bitbucket_test.go b/server/forge/bitbucket/bitbucket_test.go index c074cfcf2de..ef8f4ec417e 100644 --- a/server/forge/bitbucket/bitbucket_test.go +++ b/server/forge/bitbucket/bitbucket_test.go @@ -206,7 +206,15 @@ func TestBitbucket(t *testing.T) { r, b, err := c.Hook(ctx, req) assert.NoError(t, err) assert.Equal(t, "martinherren1984/publictestrepo", r.FullName) - assert.Equal(t, "c14c1bb05dfb1fdcdf06b31485fff61b0ea44277", b.Commit) + assert.Equal(t, &model.Commit{ + SHA: "c14c1bb05dfb1fdcdf06b31485fff61b0ea44277", + Message: "a\n", + ForgeURL: "https://bitbucket.org/martinherren1984/publictestrepo/commits/c14c1bb05dfb1fdcdf06b31485fff61b0ea44277", + Author: model.CommitAuthor{ + Author: "Martin Herren", + Email: "martin.herren@yyy.com", + }, + }, b.Commit) } var ( @@ -270,7 +278,7 @@ var ( } fakePipeline = &model.Pipeline{ - Commit: "9ecad50", + Commit: &model.Commit{SHA: "9ecad50"}, } fakeWorkflow = &model.Workflow{ diff --git a/server/forge/bitbucket/convert.go b/server/forge/bitbucket/convert.go index b7b13c6b47e..1ff40d05493 100644 --- a/server/forge/bitbucket/convert.go +++ b/server/forge/bitbucket/convert.go @@ -165,25 +165,27 @@ func convertPullHook(from *internal.PullRequestHook) *model.Pipeline { } pipeline := &model.Pipeline{ - Event: event, - Commit: from.PullRequest.Source.Commit.Hash, - Ref: fmt.Sprintf("refs/pull-requests/%d/from", from.PullRequest.ID), + Event: event, + Commit: &model.Commit{ + SHA: from.PullRequest.Source.Commit.Hash, + }, + Ref: fmt.Sprintf("refs/pull-requests/%d/from", from.PullRequest.ID), Refspec: fmt.Sprintf("%s:%s", from.PullRequest.Source.Branch.Name, from.PullRequest.Dest.Branch.Name, ), - ForgeURL: from.PullRequest.Links.HTML.Href, - Branch: from.PullRequest.Source.Branch.Name, - Message: from.PullRequest.Title, - Avatar: from.Actor.Links.Avatar.Href, - Author: from.Actor.Login, - Sender: from.Actor.Login, - Timestamp: from.PullRequest.Updated.UTC().Unix(), - FromFork: from.PullRequest.Source.Repo.UUID != from.PullRequest.Dest.Repo.UUID, + ForgeURL: from.PullRequest.Links.HTML.Href, + Branch: from.PullRequest.Source.Branch.Name, + + Author: from.Actor.Login, + Avatar: from.Actor.Links.Avatar.Href, + PullRequest: convertPullRequest(&from.PullRequest), } if from.PullRequest.State == stateClosed { - pipeline.Commit = from.PullRequest.MergeCommit.Hash + pipeline.Commit = &model.Commit{ + SHA: from.PullRequest.MergeCommit.Hash, + } pipeline.Ref = fmt.Sprintf("refs/heads/%s", from.PullRequest.Dest.Branch.Name) pipeline.Branch = from.PullRequest.Dest.Branch.Name } @@ -195,37 +197,48 @@ func convertPullHook(from *internal.PullRequestHook) *model.Pipeline { // hook to the Woodpecker pipeline struct holding commit information. func convertPushHook(hook *internal.PushHook, change *internal.Change) *model.Pipeline { pipeline := &model.Pipeline{ - Commit: change.New.Target.Hash, - ForgeURL: change.New.Target.Links.HTML.Href, - Branch: change.New.Name, - Message: change.New.Target.Message, - Avatar: hook.Actor.Links.Avatar.Href, - Author: hook.Actor.Login, - Sender: hook.Actor.Login, - Timestamp: change.New.Target.Date.UTC().Unix(), + Commit: &model.Commit{ + SHA: change.New.Target.Hash, + ForgeURL: change.New.Target.Links.HTML.Href, + Message: change.New.Target.Message, + Author: convertCommitAuthor(change.New.Target.Author.Raw), + }, + ForgeURL: change.New.Target.Links.HTML.Href, + Branch: change.New.Name, + Author: hook.Actor.Login, + Avatar: hook.Actor.Links.Avatar.Href, } switch change.New.Type { case "tag", "annotated_tag", "bookmark": pipeline.Event = model.EventTag pipeline.Ref = fmt.Sprintf("refs/tags/%s", change.New.Name) + // TODO is the forge url correct on tags? default: pipeline.Event = model.EventPush pipeline.Ref = fmt.Sprintf("refs/heads/%s", change.New.Name) } - if len(change.New.Target.Author.Raw) != 0 { - pipeline.Email = extractEmail(change.New.Target.Author.Raw) - } return pipeline } // regex for git author fields (r.g. "name "). -var reGitMail = regexp.MustCompile("<(.*)>") +var reGitMail = regexp.MustCompile("(.*) <(.*)>") // extracts the email from a git commit author string. -func extractEmail(gitAuthor string) (author string) { +func convertCommitAuthor(gitAuthor string) model.CommitAuthor { matches := reGitMail.FindAllStringSubmatch(gitAuthor, -1) if len(matches) == 1 { - author = matches[0][1] + return model.CommitAuthor{ + Author: matches[0][1], + Email: matches[0][2], + } + } + return model.CommitAuthor{} +} + +func convertPullRequest(from *internal.PullRequest) *model.PullRequest { + return &model.PullRequest{ + FromFork: from.Source.Repo.UUID != from.Dest.Repo.UUID, + Index: model.ForgeRemoteID(fmt.Sprint(from.ID)), + Title: from.Title, } - return } diff --git a/server/forge/bitbucket/convert_test.go b/server/forge/bitbucket/convert_test.go index 5f450cc03d1..9b1eb81b3fd 100644 --- a/server/forge/bitbucket/convert_test.go +++ b/server/forge/bitbucket/convert_test.go @@ -121,20 +121,18 @@ func Test_convertPullHook(t *testing.T) { hook.PullRequest.Source.Commit.Hash = "c8411d7" hook.PullRequest.Links.HTML.Href = "https://bitbucket.org/foo/bar/pulls/5" hook.PullRequest.Title = "updated README" - hook.PullRequest.Updated = time.Now() hook.PullRequest.ID = 1 pipeline := convertPullHook(hook) assert.Equal(t, model.EventPull, pipeline.Event) assert.Equal(t, hook.Actor.Login, pipeline.Author) assert.Equal(t, hook.Actor.Links.Avatar.Href, pipeline.Avatar) - assert.Equal(t, hook.PullRequest.Source.Commit.Hash, pipeline.Commit) + assert.Equal(t, hook.PullRequest.Source.Commit.Hash, pipeline.Commit.SHA) assert.Equal(t, hook.PullRequest.Source.Branch.Name, pipeline.Branch) assert.Equal(t, hook.PullRequest.Links.HTML.Href, pipeline.ForgeURL) assert.Equal(t, "refs/pull-requests/1/from", pipeline.Ref) assert.Equal(t, "change:main", pipeline.Refspec) - assert.Equal(t, hook.PullRequest.Title, pipeline.Message) - assert.Equal(t, hook.PullRequest.Updated.Unix(), pipeline.Timestamp) + assert.Equal(t, hook.PullRequest.Title, pipeline.PullRequest.Title) } func Test_convertPushHook(t *testing.T) { @@ -152,15 +150,14 @@ func Test_convertPushHook(t *testing.T) { pipeline := convertPushHook(&hook, &change) assert.Equal(t, model.EventPush, pipeline.Event) - assert.Equal(t, "test@domain.tld", pipeline.Email) + assert.Equal(t, "test@domain.tld", pipeline.Commit.Author.Email) assert.Equal(t, hook.Actor.Login, pipeline.Author) assert.Equal(t, hook.Actor.Links.Avatar.Href, pipeline.Avatar) - assert.Equal(t, change.New.Target.Hash, pipeline.Commit) + assert.Equal(t, change.New.Target.Hash, pipeline.Commit.SHA) assert.Equal(t, change.New.Name, pipeline.Branch) assert.Equal(t, change.New.Target.Links.HTML.Href, pipeline.ForgeURL) assert.Equal(t, "refs/heads/main", pipeline.Ref) - assert.Equal(t, change.New.Target.Message, pipeline.Message) - assert.Equal(t, change.New.Target.Date.Unix(), pipeline.Timestamp) + assert.Equal(t, change.New.Target.Message, pipeline.Commit.Message) } func Test_convertPushHookTag(t *testing.T) { diff --git a/server/forge/bitbucket/internal/client.go b/server/forge/bitbucket/internal/client.go index e7a5a74b05e..6e2be9573e8 100644 --- a/server/forge/bitbucket/internal/client.go +++ b/server/forge/bitbucket/internal/client.go @@ -46,6 +46,7 @@ const ( pathOrgPerms = "%s/2.0/workspaces/%s/permissions?%s" pathPullRequests = "%s/2.0/repositories/%s/%s/pullrequests?%s" pathBranchCommits = "%s/2.0/repositories/%s/%s/commits/%s" + pathCommit = "%s/2.0/repositories/%s/%s/commits/%s" pathDir = "%s/2.0/repositories/%s/%s/src/%s/%s" pageSize = 100 ) @@ -206,6 +207,13 @@ func (c *Client) GetBranchHead(owner, name, branch string) (*Commit, error) { return out.Values[0], nil } +func (c *Client) GetCommit(owner, name, sha string) (*Commit, error) { + out := new(Commit) + uri := fmt.Sprintf(pathCommit, c.base, owner, name, sha) + _, err := c.do(uri, http.MethodGet, nil, out) + return out, err +} + func (c *Client) GetUserWorkspaceMembership(workspace, user string) (string, error) { out := new(WorkspaceMembershipResp) opts := &ListOpts{Page: 1, PageLen: pageSize} diff --git a/server/forge/bitbucket/internal/types.go b/server/forge/bitbucket/internal/types.go index bbb03ab1cae..0ae6bb5c257 100644 --- a/server/forge/bitbucket/internal/types.go +++ b/server/forge/bitbucket/internal/types.go @@ -151,45 +151,9 @@ type PushHook struct { } type PullRequestHook struct { - Actor Account `json:"actor"` - Repo Repo `json:"repository"` - PullRequest struct { - ID int `json:"id"` - Type string `json:"type"` - Reason string `json:"reason"` - Desc string `json:"description"` - Title string `json:"title"` - State string `json:"state"` - Links Links `json:"links"` - Created time.Time `json:"created_on"` - Updated time.Time `json:"updated_on"` - - MergeCommit struct { - Hash string `json:"hash"` - } `json:"merge_commit"` - - Source struct { - Repo Repo `json:"repository"` - Commit struct { - Hash string `json:"hash"` - Links Links `json:"links"` - } `json:"commit"` - Branch struct { - Name string `json:"name"` - } `json:"branch"` - } `json:"source"` - - Dest struct { - Repo Repo `json:"repository"` - Commit struct { - Hash string `json:"hash"` - Links Links `json:"links"` - } `json:"commit"` - Branch struct { - Name string `json:"name"` - } `json:"branch"` - } `json:"destination"` - } `json:"pullrequest"` + Actor Account `json:"actor"` + Repo Repo `json:"repository"` + PullRequest PullRequest `json:"pullrequest"` } type WorkspaceMembershipResp struct { @@ -281,6 +245,30 @@ type PullRequestResp struct { type PullRequest struct { ID uint `json:"id"` Title string `json:"title"` + State string `json:"state"` + Links Links `json:"links"` + + MergeCommit PullRequestCommit `json:"merge_commit"` + + Source struct { + Repo Repo `json:"repository"` + Commit PullRequestCommit `json:"commit"` + Branch struct { + Name string `json:"name"` + } `json:"branch"` + } `json:"source"` + + Dest struct { + Repo Repo `json:"repository"` + Commit PullRequestCommit `json:"commit"` + Branch struct { + Name string `json:"name"` + } `json:"branch"` + } `json:"destination"` +} + +type PullRequestCommit struct { + Hash string `json:"hash"` } type CommitsResp struct { @@ -288,7 +276,11 @@ type CommitsResp struct { } type Commit struct { - Hash string `json:"hash"` + Hash string `json:"hash"` + Message string `json:"message"` + Author struct { + Raw string `json:"raw"` + } Links struct { HTML struct { Href string `json:"href"` diff --git a/server/forge/bitbucket/parse_test.go b/server/forge/bitbucket/parse_test.go index f0afb510287..bf5e7d087ef 100644 --- a/server/forge/bitbucket/parse_test.go +++ b/server/forge/bitbucket/parse_test.go @@ -59,7 +59,7 @@ func Test_parseHook(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "user_name/repo_name", r.FullName) assert.Equal(t, model.EventPull, b.Event) - assert.Equal(t, "d3022fc0ca3d", b.Commit) + assert.Equal(t, "d3022fc0ca3d", b.Commit.SHA) }) t.Run("pull-request merged", func(t *testing.T) { @@ -72,7 +72,7 @@ func Test_parseHook(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "anbraten/test-2", r.FullName) assert.Equal(t, model.EventPullClosed, b.Event) - assert.Equal(t, "006704dbeab2", b.Commit) + assert.Equal(t, "006704dbeab2", b.Commit.SHA) }) t.Run("pull-request closed", func(t *testing.T) { @@ -85,7 +85,7 @@ func Test_parseHook(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "anbraten/test-2", r.FullName) assert.Equal(t, model.EventPullClosed, b.Event) - assert.Equal(t, "f90e18fc9d45", b.Commit) + assert.Equal(t, "f90e18fc9d45", b.Commit.SHA) }) t.Run("malformed push", func(t *testing.T) { @@ -120,7 +120,7 @@ func Test_parseHook(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "martinherren1984/publictestrepo", r.FullName) assert.Equal(t, "https://bitbucket.org/martinherren1984/publictestrepo", r.Clone) - assert.Equal(t, "c14c1bb05dfb1fdcdf06b31485fff61b0ea44277", b.Commit) - assert.Equal(t, "a\n", b.Message) + assert.Equal(t, "c14c1bb05dfb1fdcdf06b31485fff61b0ea44277", b.Commit.SHA) + assert.Equal(t, "a\n", b.Commit.Message) }) } diff --git a/server/forge/bitbucketdatacenter/bitbucketdatacenter.go b/server/forge/bitbucketdatacenter/bitbucketdatacenter.go index d3a5c69addf..0feb5b33f7a 100644 --- a/server/forge/bitbucketdatacenter/bitbucketdatacenter.go +++ b/server/forge/bitbucketdatacenter/bitbucketdatacenter.go @@ -257,7 +257,7 @@ func (c *client) File(ctx context.Context, u *model.User, r *model.Repo, p *mode return nil, fmt.Errorf("unable to create bitbucket client: %w", err) } - b, resp, err := bc.Projects.GetTextFileContent(ctx, r.Owner, r.Name, f, p.Commit) + b, resp, err := bc.Projects.GetTextFileContent(ctx, r.Owner, r.Name, f, p.Commit.SHA) if err != nil { if resp.StatusCode == http.StatusNotFound { // requested directory might not exist @@ -276,7 +276,7 @@ func (c *client) Dir(ctx context.Context, u *model.User, r *model.Repo, p *model return nil, fmt.Errorf("unable to create bitbucket client: %w", err) } - opts := &bb.FilesListOptions{At: p.Commit} + opts := &bb.FilesListOptions{At: p.Commit.SHA} all := make([]*forge_types.FileMeta, 0) for { list, resp, err := bc.Projects.ListFiles(ctx, r.Owner, r.Name, path, opts) @@ -317,7 +317,7 @@ func (c *client) Status(ctx context.Context, u *model.User, repo *model.Repo, pi Description: common.GetPipelineStatusDescription(pipeline.Status), Ref: pipeline.Ref, } - _, err = bc.Projects.CreateBuildStatus(ctx, repo.Owner, repo.Name, pipeline.Commit, status) + _, err = bc.Projects.CreateBuildStatus(ctx, repo.Owner, repo.Name, pipeline.Commit.SHA, status) return err } @@ -365,22 +365,28 @@ func (c *client) BranchHead(ctx context.Context, u *model.User, r *model.Repo, b if err != nil { return nil, fmt.Errorf("unable to create bitbucket client: %w", err) } - branches, _, err := bc.Projects.SearchBranches(ctx, r.Owner, r.Name, &bb.BranchSearchOptions{Filter: b}) + commits, _, err := bc.Projects.SearchCommits(ctx, r.Owner, r.Name, &bb.CommitSearchOptions{ + Until: b, + ListOptions: bb.ListOptions{Limit: 1}, + }) if err != nil { return nil, err } - if len(branches) == 0 { + if len(commits) == 0 { return nil, fmt.Errorf("no matching branches returned") } - for _, branch := range branches { - if branch.DisplayID == b { - return &model.Commit{ - SHA: branch.LatestCommit, - ForgeURL: fmt.Sprintf("%s/commits/%s", r.ForgeURL, branch.LatestCommit), - }, nil - } - } - return nil, fmt.Errorf("no matching branches found") + + cm := commits[0] + + return &model.Commit{ + SHA: cm.ID, + ForgeURL: fmt.Sprintf("%s/commits/%s", r.ForgeURL, cm.ID), + Message: cm.Message, + Author: model.CommitAuthor{ + Author: cm.Author.Name, + Email: cm.Author.Email, + }, + }, nil } func (c *client) PullRequests(ctx context.Context, u *model.User, r *model.Repo, p *model.ListOptions) ([]*model.PullRequest, error) { @@ -397,7 +403,7 @@ func (c *client) PullRequests(ctx context.Context, u *model.User, r *model.Repo, return nil, fmt.Errorf("unable to list pull-requests: %w", err) } for _, pr := range prs { - all = append(all, &model.PullRequest{Index: convertID(pr.ID), Title: pr.Title}) + all = append(all, convertPullRequest(pr)) } if !p.All || resp.LastPage { break @@ -547,15 +553,19 @@ func (c *client) updatePipelineFromCommit(ctx context.Context, u *model.User, r return nil, fmt.Errorf("unable to create bitbucket client: %w", err) } - commit, _, err := bc.Projects.GetCommit(ctx, r.Owner, r.Name, p.Commit) + commit, _, err := bc.Projects.GetCommit(ctx, r.Owner, r.Name, p.Commit.SHA) if err != nil { return nil, fmt.Errorf("unable to read commit: %w", err) } - p.Message = commit.Message + p.Commit.Message = commit.Message + p.Commit.Author = model.CommitAuthor{ + Author: commit.Author.Name, + Email: commit.Author.Email, + } opts := &bb.ListOptions{} for { - changes, resp, err := bc.Projects.ListChanges(ctx, r.Owner, r.Name, p.Commit, opts) + changes, resp, err := bc.Projects.ListChanges(ctx, r.Owner, r.Name, p.Commit.SHA, opts) if err != nil { return nil, fmt.Errorf("unable to list commit changes: %w", err) } diff --git a/server/forge/bitbucketdatacenter/convert.go b/server/forge/bitbucketdatacenter/convert.go index d1fa741d4ce..0dcc4094a1e 100644 --- a/server/forge/bitbucketdatacenter/convert.go +++ b/server/forge/bitbucketdatacenter/convert.go @@ -18,7 +18,6 @@ import ( "fmt" "net/url" "strings" - "time" bb "github.com/neticdk/go-bitbucket/bitbucket" "golang.org/x/oauth2" @@ -89,19 +88,21 @@ func convertRepositoryPushEvent(ev *bb.RepositoryPushEvent, baseURL string) *mod } pipeline := &model.Pipeline{ - Commit: change.ToHash, - Branch: change.Ref.DisplayID, - Message: "", - Avatar: bitbucketAvatarURL(baseURL, ev.Actor.Slug), - Author: authorLabel(ev.Actor.Name), - Email: ev.Actor.Email, - Timestamp: time.Time(ev.Date).UTC().Unix(), - Ref: ev.Changes[0].RefId, - ForgeURL: fmt.Sprintf("%s/projects/%s/repos/%s/commits/%s", baseURL, ev.Repository.Project.Key, ev.Repository.Slug, change.ToHash), + Commit: &model.Commit{ + // message is set later + SHA: change.ToHash, + ForgeURL: fmt.Sprintf("%s/projects/%s/repos/%s/commits/%s", baseURL, ev.Repository.Project.Key, ev.Repository.Slug, change.ToHash), + }, + Branch: change.Ref.DisplayID, + Avatar: bitbucketAvatarURL(baseURL, ev.Actor.Slug), + Author: ev.Actor.Name, + Ref: ev.Changes[0].RefId, + ForgeURL: fmt.Sprintf("%s/projects/%s/repos/%s/commits/%s", baseURL, ev.Repository.Project.Key, ev.Repository.Slug, change.ToHash), } if strings.HasPrefix(ev.Changes[0].RefId, "refs/tags/") { pipeline.Event = model.EventTag + pipeline.ForgeURL = fmt.Sprintf("%s/projects/%s/repos/%s/browse?at=%s", baseURL, ev.Repository.Project.Key, ev.Repository.Slug, url.QueryEscape(pipeline.Ref)) } else { pipeline.Event = model.EventPush } @@ -111,18 +112,18 @@ func convertRepositoryPushEvent(ev *bb.RepositoryPushEvent, baseURL string) *mod func convertPullRequestEvent(ev *bb.PullRequestEvent, baseURL string) *model.Pipeline { pipeline := &model.Pipeline{ - Commit: ev.PullRequest.Source.Latest, - Branch: ev.PullRequest.Source.DisplayID, - Title: ev.PullRequest.Title, - Message: "", - Avatar: bitbucketAvatarURL(baseURL, ev.Actor.Slug), - Author: authorLabel(ev.Actor.Name), - Email: ev.Actor.Email, - Timestamp: time.Time(ev.Date).UTC().Unix(), - Ref: fmt.Sprintf("refs/pull-requests/%d/from", ev.PullRequest.ID), - ForgeURL: fmt.Sprintf("%s/projects/%s/repos/%s/commits/%s", baseURL, ev.PullRequest.Source.Repository.Project.Key, ev.PullRequest.Source.Repository.Slug, ev.PullRequest.Source.Latest), - Refspec: fmt.Sprintf("%s:%s", ev.PullRequest.Source.DisplayID, ev.PullRequest.Target.DisplayID), - FromFork: ev.PullRequest.Source.Repository.ID != ev.PullRequest.Target.Repository.ID, + Commit: &model.Commit{ + // message is set later + SHA: ev.PullRequest.Source.Latest, + ForgeURL: fmt.Sprintf("%s/projects/%s/repos/%s/commits/%s", baseURL, ev.PullRequest.Source.Repository.Project.Key, ev.PullRequest.Source.Repository.Slug, ev.PullRequest.Source.Latest), + }, + Branch: ev.PullRequest.Source.DisplayID, + Avatar: bitbucketAvatarURL(baseURL, ev.Actor.Slug), + Author: ev.Actor.Name, + Ref: fmt.Sprintf("refs/pull-requests/%d/from", ev.PullRequest.ID), + PullRequest: convertPullRequest(&ev.PullRequest), + ForgeURL: fmt.Sprintf("%s/projects/%s/repos/%s/pull-requests/%d", baseURL, ev.PullRequest.Source.Repository.Project.Key, ev.PullRequest.Source.Repository.Slug, ev.PullRequest.ID), + Refspec: fmt.Sprintf("%s:%s", ev.PullRequest.Source.DisplayID, ev.PullRequest.Target.DisplayID), } if ev.EventKey == bb.EventKeyPullRequestMerged || ev.EventKey == bb.EventKeyPullRequestDeclined || ev.EventKey == bb.EventKeyPullRequestDeleted { @@ -134,19 +135,6 @@ func convertPullRequestEvent(ev *bb.PullRequestEvent, baseURL string) *model.Pip return pipeline } -func authorLabel(name string) string { - var result string - - const maxNameLength = 40 - - if len(name) > maxNameLength { - result = name[0:37] + "..." - } else { - result = name - } - return result -} - func convertUser(user *bb.User, baseURL string) *model.User { return &model.User{ ForgeRemoteID: model.ForgeRemoteID(fmt.Sprintf("%d", user.ID)), @@ -173,6 +161,14 @@ func updateUserCredentials(u *model.User, t *oauth2.Token) { u.Expiry = t.Expiry.UTC().Unix() } +func convertPullRequest(pr *bb.PullRequest) *model.PullRequest { + return &model.PullRequest{ + FromFork: pr.Source.Repository.ID != pr.Target.Repository.ID, + Title: pr.Title, + Index: model.ForgeRemoteID(fmt.Sprint(pr.ID)), + } +} + func convertProjectsToTeams(projects []*bb.Project, client *bb.Client) []*model.Team { teams := make([]*model.Team, 0) for _, project := range projects { diff --git a/server/forge/bitbucketdatacenter/convert_test.go b/server/forge/bitbucketdatacenter/convert_test.go index 74ee30b9854..b6b5e707635 100644 --- a/server/forge/bitbucketdatacenter/convert_test.go +++ b/server/forge/bitbucketdatacenter/convert_test.go @@ -152,16 +152,16 @@ func Test_convertRepositoryPushEvent(t *testing.T) { }, }, to: &model.Pipeline{ - Commit: "1234567890abcdef", - Branch: "branch", - Message: "", - Avatar: "https://base.url/users/john.doe_mail.com/avatar.png", - Author: "John Doe", - Email: "john.doe@mail.com", - Timestamp: now.UTC().Unix(), - Ref: "refs/head/branch", - ForgeURL: "https://base.url/projects/PRJ/repos/REPO/commits/1234567890abcdef", - Event: model.EventPush, + Commit: &model.Commit{ + SHA: "1234567890abcdef", + ForgeURL: "https://base.url/projects/PRJ/repos/REPO/commits/1234567890abcdef", + }, + Branch: "branch", + Avatar: "https://base.url/users/john.doe_mail.com/avatar.png", + Author: "John Doe", + Ref: "refs/head/branch", + ForgeURL: "https://base.url/projects/PRJ/repos/REPO/commits/1234567890abcdef", + Event: model.EventPush, }, }, } @@ -212,17 +212,21 @@ func Test_convertPullRequestEvent(t *testing.T) { } to := convertPullRequestEvent(from, "https://base.url") assert.Equal(t, &model.Pipeline{ - Commit: "1234567890abcdef", - Branch: "branch", - Avatar: "https://base.url/users/john.doe_mail.com/avatar.png", - Author: "John Doe", - Email: "john.doe@mail.com", - Timestamp: now.UTC().Unix(), - Ref: "refs/pull-requests/123/from", - ForgeURL: "https://base.url/projects/PRJ/repos/REPO/commits/1234567890abcdef", - Event: model.EventPull, - Refspec: "branch:main", - Title: "my title", + Commit: &model.Commit{ + SHA: "1234567890abcdef", + ForgeURL: "https://base.url/projects/PRJ/repos/REPO/commits/1234567890abcdef", + }, + Branch: "branch", + Avatar: "https://base.url/users/john.doe_mail.com/avatar.png", + Author: "John Doe", + Ref: "refs/pull-requests/123/from", + ForgeURL: "https://base.url/projects/PRJ/repos/REPO/commits/1234567890abcdef", + Event: model.EventPull, + Refspec: "branch:main", + PullRequest: &model.PullRequest{ + Index: "123", + Title: "my title", + }, }, to) } @@ -267,38 +271,23 @@ func Test_convertPullRequestCloseEvent(t *testing.T) { } to := convertPullRequestEvent(from, "https://base.url") assert.Equal(t, &model.Pipeline{ - Commit: "1234567890abcdef", - Branch: "branch", - Avatar: "https://base.url/users/john.doe_mail.com/avatar.png", - Author: "John Doe", - Email: "john.doe@mail.com", - Timestamp: now.UTC().Unix(), - Ref: "refs/pull-requests/123/from", - ForgeURL: "https://base.url/projects/PRJ/repos/REPO/commits/1234567890abcdef", - Event: model.EventPullClosed, - Refspec: "branch:main", - Title: "my title", - }, to) -} - -func Test_authorLabel(t *testing.T) { - tests := []struct { - from string - to string - }{ - { - from: "Some Short Author", - to: "Some Short Author", + Commit: &model.Commit{ + SHA: "1234567890abcdef", + ForgeURL: "https://base.url/projects/PRJ/repos/REPO/commits/1234567890abcdef", }, - { - from: "Some Very Long Author That May Include Multiple Names Here", - //nolint:misspell - to: "Some Very Long Author That May Includ...", + Branch: "branch", + Avatar: "https://base.url/users/john.doe_mail.com/avatar.png", + Author: "John Doe", + + Ref: "refs/pull-requests/123/from", + ForgeURL: "https://base.url/projects/PRJ/repos/REPO/commits/1234567890abcdef", + Event: model.EventPullClosed, + Refspec: "branch:main", + PullRequest: &model.PullRequest{ + Title: "my title", + Index: "123", }, - } - for _, tt := range tests { - assert.Equal(t, tt.to, authorLabel(tt.from)) - } + }, to) } func Test_convertUser(t *testing.T) { diff --git a/server/forge/forgejo/fixtures/handler.go b/server/forge/forgejo/fixtures/handler.go index 73c3a9eb7c9..0372d8af08b 100644 --- a/server/forge/forgejo/fixtures/handler.go +++ b/server/forge/forgejo/fixtures/handler.go @@ -36,6 +36,7 @@ func Handler() http.Handler { e.GET("/api/v1/repos/:owner/:name/pulls/:index/files", getPRFiles) e.GET("/api/v1/user/repos", getUserRepos) e.GET("/api/v1/version", getVersion) + e.GET("/api/v1/repos/:owner/:name/git/commits/:sha", getCommit) return e } @@ -137,6 +138,15 @@ func getPRFiles(c *gin.Context) { } } +func getCommit(c *gin.Context) { + switch c.Param("sha") { + case "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c": + c.String(http.StatusOK, commitPayload) + default: + c.String(http.StatusNotFound, "") + } +} + const listRepoHookPayloads = ` [ { @@ -208,3 +218,74 @@ const prFilesPayload = ` } ] ` + +const commitPayload = ` +{ + "url": "http://localhost:3000/api/v1/repos/qwerty287/woodpecker/git/commits/0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c", + "sha": "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c", + "created": "2025-01-05T12:31:42+02:00", + "html_url": "http://localhost:3000/qwerty287/woodpecker/commit/0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c", + "commit": { + "url": "http://localhost:3000/api/v1/repos/qwerty287/woodpecker/git/commits/0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c", + "author": { + "name": "qwerty287", + "email": "qwerty287@noreply.localhost", + "date": "2025-01-05T12:31:42+02:00" + }, + "committer": { + "name": "qwerty287", + "email": "qwerty287@noreply.localhost", + "date": "2025-01-05T12:31:42+02:00" + }, + "message": "README.md aktualisiert\n", + "tree": { + "url": "http://localhost:3000/api/v1/repos/qwerty287/woodpecker/git/trees/0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c", + "sha": "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c", + "created": "2025-01-05T12:31:42+02:00" + }, + "verification": { + "verified": false, + "reason": "gpg.error.not_signed_commit", + "signature": "", + "signer": null, + "payload": "" + } + }, + "author": { + "id": 1, + "login": "qwerty287", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "qwerty287@noreply.localhost", + "avatar_url": "http://localhost:3000/avatars/25a4ce9e2945c8583f82ce7b2ee8bc3c", + "html_url": "http://localhost:3000/qwerty287", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2023-04-12T18:52:45+03:00", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "qwerty287" + }, + "files": [ + { + "filename": "README.md", + "status": "modified" + } + ], + "stats": { + "total": 2, + "additions": 1, + "deletions": 1 + } +} +` diff --git a/server/forge/forgejo/fixtures/hooks.go b/server/forge/forgejo/fixtures/hooks.go index 9caf1a3d287..c1f27974b76 100644 --- a/server/forge/forgejo/fixtures/hooks.go +++ b/server/forge/forgejo/fixtures/hooks.go @@ -36,6 +36,19 @@ const HookPush = ` "modified": ["app/controller/application.rb"] } ], + "head_commit": { + "id": "ef98532add3b2feb7a137426bba1248724367df5", + "message": "bump\n", + "url": "http://forgejo.golang.org/gordon/hello-world/commit/ef98532add3b2feb7a137426bba1248724367df5", + "author": { + "name": "Gordon the Gopher", + "email": "gordon@golang.org", + "username": "gordon" + }, + "added": ["CHANGELOG.md"], + "removed": [], + "modified": ["app/controller/application.rb"] + }, "repository": { "id": 1, "name": "hello-world", @@ -475,6 +488,7 @@ const HookPullRequest = `{ "pull_request": { "html_url": "http://forgejo.golang.org/gordon/hello-world/pull/1", "state": "open", + "number": 1, "title": "Update the README with new information", "body": "please merge", "user": { diff --git a/server/forge/forgejo/forgejo.go b/server/forge/forgejo/forgejo.go index 1e5a0d41870..a7c28dc6576 100644 --- a/server/forge/forgejo/forgejo.go +++ b/server/forge/forgejo/forgejo.go @@ -272,7 +272,7 @@ func (c *Forgejo) File(ctx context.Context, u *model.User, r *model.Repo, b *mod return nil, err } - cfg, resp, err := client.GetFile(r.Owner, r.Name, b.Commit, f) + cfg, resp, err := client.GetFile(r.Owner, r.Name, b.Commit.SHA, f) if err != nil && resp != nil && resp.StatusCode == http.StatusNotFound { return nil, errors.Join(err, &forge_types.ErrConfigNotFound{Configs: []string{f}}) } @@ -288,7 +288,7 @@ func (c *Forgejo) Dir(ctx context.Context, u *model.User, r *model.Repo, b *mode } // List files in repository. Path from root - tree, _, err := client.GetTrees(r.Owner, r.Name, b.Commit, true) + tree, _, err := client.GetTrees(r.Owner, r.Name, b.Commit.SHA, true) if err != nil { return nil, err } @@ -326,7 +326,7 @@ func (c *Forgejo) Status(ctx context.Context, user *model.User, repo *model.Repo _, _, err = client.CreateStatus( repo.Owner, repo.Name, - pipeline.Commit, + pipeline.Commit.SHA, forgejo.CreateStatusOption{ State: getStatus(workflow.State), TargetURL: common.GetPipelineStatusURL(repo, pipeline, workflow), @@ -460,6 +460,11 @@ func (c *Forgejo) BranchHead(ctx context.Context, u *model.User, r *model.Repo, return &model.Commit{ SHA: b.Commit.ID, ForgeURL: b.Commit.URL, + Message: b.Commit.Message, + Author: model.CommitAuthor{ + Author: b.Commit.Author.Name, + Email: b.Commit.Author.Email, + }, }, nil } @@ -496,13 +501,31 @@ func (c *Forgejo) Hook(ctx context.Context, r *http.Request) (*model.Repo, *mode return nil, nil, err } - if pipeline != nil && pipeline.Event == model.EventRelease && pipeline.Commit == "" { - tagName := strings.Split(pipeline.Ref, "/")[2] - sha, err := c.getTagCommitSHA(ctx, repo, tagName) - if err != nil { - return nil, nil, err + if pipeline != nil { + if pipeline.Event == model.EventRelease && pipeline.Commit.SHA == "" { + tagName := strings.Split(pipeline.Ref, "/")[2] + sha, _, err := c.getTagMessage(ctx, repo, tagName) + if err != nil { + return nil, nil, err + } + pipeline.Commit = sha + } else if pipeline.Event == model.EventPull || pipeline.Event == model.EventPullClosed { + sha, err := c.getCommitFromSHAStore(ctx, repo, pipeline.Commit.SHA) + if err != nil { + return nil, nil, err + } + pipeline.Commit = sha + } else if pipeline.Event == model.EventTag { + tagName := strings.Split(pipeline.Ref, "/")[2] + sha, msg, err := c.getTagMessage(ctx, repo, tagName) + if err != nil { + return nil, nil, err + } + pipeline.Commit = sha + if pipeline.Commit.Message != msg { + pipeline.ReleaseTagTitle = msg + } } - pipeline.Commit = sha } if pipeline != nil && (pipeline.Event == model.EventPull || pipeline.Event == model.EventPullClosed) && len(pipeline.ChangedFiles) == 0 { @@ -650,34 +673,75 @@ func (c *Forgejo) getChangedFilesForPR(ctx context.Context, repo *model.Repo, in }, -1) } -func (c *Forgejo) getTagCommitSHA(ctx context.Context, repo *model.Repo, tagName string) (string, error) { +func (c *Forgejo) getTagMessage(ctx context.Context, repo *model.Repo, tagName string) (*model.Commit, string, error) { _store, ok := store.TryFromContext(ctx) if !ok { - log.Error().Msg("could not get store from context") - return "", nil + return nil, "", fmt.Errorf("could not get store from context") } repo, err := _store.GetRepoNameFallback(repo.ForgeRemoteID, repo.FullName) if err != nil { - return "", err + return nil, "", err } user, err := _store.GetUser(repo.UserID) if err != nil { - return "", err + return nil, "", err } client, err := c.newClientToken(ctx, user.AccessToken) if err != nil { - return "", err + return nil, "", err } tag, _, err := client.GetTag(repo.Owner, repo.Name, tagName) if err != nil { - return "", err + return nil, "", err } - return tag.Commit.SHA, nil + commit, err := c.getCommitFromSHA(ctx, user, repo, tag.Commit.SHA) + return commit, tag.Message, err +} + +func (c *Forgejo) getCommitFromSHAStore(ctx context.Context, repo *model.Repo, sha string) (*model.Commit, error) { + _store, ok := store.TryFromContext(ctx) + if !ok { + return nil, fmt.Errorf("could not get store from context") + } + + repo, err := _store.GetRepoNameFallback(repo.ForgeRemoteID, repo.FullName) + if err != nil { + return nil, err + } + + user, err := _store.GetUser(repo.UserID) + if err != nil { + return nil, err + } + + return c.getCommitFromSHA(ctx, user, repo, sha) +} + +func (c *Forgejo) getCommitFromSHA(ctx context.Context, user *model.User, repo *model.Repo, sha string) (*model.Commit, error) { + client, err := c.newClientToken(ctx, user.AccessToken) + if err != nil { + return nil, err + } + + commit, _, err := client.GetSingleCommit(repo.Owner, repo.Name, sha) + if err != nil { + return nil, err + } + + return &model.Commit{ + Message: commit.RepoCommit.Message, + Author: model.CommitAuthor{ + Author: commit.RepoCommit.Author.Name, + Email: commit.RepoCommit.Author.Email, + }, + ForgeURL: commit.HTMLURL, + SHA: commit.SHA, + }, nil } func (c *Forgejo) perPage(ctx context.Context) int { diff --git a/server/forge/forgejo/forgejo_test.go b/server/forge/forgejo/forgejo_test.go index 0dcbc49fb62..a909f87e8d0 100644 --- a/server/forge/forgejo/forgejo_test.go +++ b/server/forge/forgejo/forgejo_test.go @@ -161,7 +161,7 @@ var ( } fakePipeline = &model.Pipeline{ - Commit: "9ecad50", + Commit: &model.Commit{SHA: "9ecad50"}, } fakeWorkflow = &model.Workflow{ diff --git a/server/forge/forgejo/helper.go b/server/forge/forgejo/helper.go index 394f6a133e6..8f3f5bd240a 100644 --- a/server/forge/forgejo/helper.go +++ b/server/forge/forgejo/helper.go @@ -19,8 +19,8 @@ import ( "fmt" "io" "net/url" + "strconv" "strings" - "time" "codeberg.org/mvdkleijn/forgejo-sdk/forgejo" @@ -75,30 +75,27 @@ func pipelineFromPush(hook *pushHook) *model.Pipeline { fixMalformedAvatar(hook.Sender.AvatarURL), ) - var message string link := hook.Compare - if len(hook.Commits) > 0 { - message = hook.Commits[0].Message - if len(hook.Commits) == 1 { - link = hook.Commits[0].URL - } - } else { - message = hook.HeadCommit.Message + if hook.TotalCommits <= 1 { link = hook.HeadCommit.URL } return &model.Pipeline{ - Event: model.EventPush, - Commit: hook.After, + Event: model.EventPush, + Commit: &model.Commit{ + SHA: hook.After, + Message: hook.HeadCommit.Message, + ForgeURL: hook.HeadCommit.URL, + Author: model.CommitAuthor{ + Author: hook.HeadCommit.Author.Name, + Email: hook.HeadCommit.Author.Email, + }, + }, Ref: hook.Ref, ForgeURL: link, Branch: strings.TrimPrefix(hook.Ref, "refs/heads/"), - Message: message, - Avatar: avatar, Author: hook.Sender.UserName, - Email: hook.Sender.Email, - Timestamp: time.Now().UTC().Unix(), - Sender: hook.Sender.UserName, + Avatar: avatar, ChangedFiles: getChangedFilesFromPushHook(hook), } } @@ -128,16 +125,14 @@ func pipelineFromTag(hook *pushHook) *model.Pipeline { ref := strings.TrimPrefix(hook.Ref, "refs/tags/") return &model.Pipeline{ - Event: model.EventTag, - Commit: hook.Sha, - Ref: fmt.Sprintf("refs/tags/%s", ref), - ForgeURL: fmt.Sprintf("%s/src/tag/%s", hook.Repo.HTMLURL, ref), - Message: fmt.Sprintf("created tag %s", ref), - Avatar: avatar, - Author: hook.Sender.UserName, - Sender: hook.Sender.UserName, - Email: hook.Sender.Email, - Timestamp: time.Now().UTC().Unix(), + Event: model.EventTag, + Commit: &model.Commit{ + SHA: hook.Sha, + }, + Ref: fmt.Sprintf("refs/tags/%s", ref), + ForgeURL: fmt.Sprintf("%s/releases/tag/%s", hook.Repo.HTMLURL, ref), + Author: hook.Sender.UserName, + Avatar: avatar, } } @@ -145,7 +140,7 @@ func pipelineFromTag(hook *pushHook) *model.Pipeline { func pipelineFromPullRequest(hook *pullRequestHook) *model.Pipeline { avatar := expandAvatar( hook.Repo.HTMLURL, - fixMalformedAvatar(hook.PullRequest.Poster.AvatarURL), + fixMalformedAvatar(hook.Sender.AvatarURL), ) event := model.EventPull @@ -155,22 +150,17 @@ func pipelineFromPullRequest(hook *pullRequestHook) *model.Pipeline { pipeline := &model.Pipeline{ Event: event, - Commit: hook.PullRequest.Head.Sha, + Commit: &model.Commit{SHA: hook.PullRequest.Head.Sha}, ForgeURL: hook.PullRequest.HTMLURL, Ref: fmt.Sprintf("refs/pull/%d/head", hook.Number), Branch: hook.PullRequest.Base.Ref, - Message: hook.PullRequest.Title, - Author: hook.PullRequest.Poster.UserName, + Author: hook.Sender.UserName, Avatar: avatar, - Sender: hook.Sender.UserName, - Email: hook.Sender.Email, - Title: hook.PullRequest.Title, Refspec: fmt.Sprintf("%s:%s", hook.PullRequest.Head.Ref, hook.PullRequest.Base.Ref, ), - PullRequestLabels: convertLabels(hook.PullRequest.Labels), - FromFork: hook.PullRequest.Head.RepoID != hook.PullRequest.Base.RepoID, + PullRequest: convertPullRequests(hook.PullRequest), } return pipeline @@ -183,16 +173,14 @@ func pipelineFromRelease(hook *releaseHook) *model.Pipeline { ) return &model.Pipeline{ - Event: model.EventRelease, - Ref: fmt.Sprintf("refs/tags/%s", hook.Release.TagName), - ForgeURL: hook.Release.HTMLURL, - Branch: hook.Release.Target, - Message: fmt.Sprintf("created release %s", hook.Release.Title), - Avatar: avatar, - Author: hook.Sender.UserName, - Sender: hook.Sender.UserName, - Email: hook.Sender.Email, - IsPrerelease: hook.Release.IsPrerelease, + Event: model.EventRelease, + Ref: fmt.Sprintf("refs/tags/%s", hook.Release.TagName), + ForgeURL: hook.Release.HTMLURL, + Branch: hook.Release.Target, + ReleaseTagTitle: hook.Release.Title, + Author: hook.Sender.UserName, + Avatar: avatar, + IsPrerelease: hook.Release.IsPrerelease, } } @@ -268,6 +256,15 @@ func matchingHooks(hooks []*forgejo.Hook, rawURL string) *forgejo.Hook { return nil } +func convertPullRequests(from *forgejo.PullRequest) *model.PullRequest { + return &model.PullRequest{ + Index: model.ForgeRemoteID(strconv.Itoa(int(from.Index))), + Title: from.Title, + Labels: convertLabels(from.Labels), + FromFork: from.Head.RepoID != from.Base.RepoID, + } +} + func convertLabels(from []*forgejo.Label) []string { labels := make([]string, len(from)) for i, label := range from { diff --git a/server/forge/forgejo/helper_test.go b/server/forge/forgejo/helper_test.go index b7414a504f7..702a23519b6 100644 --- a/server/forge/forgejo/helper_test.go +++ b/server/forge/forgejo/helper_test.go @@ -66,11 +66,19 @@ func Test_parsePush(t *testing.T) { hook, _ := parsePush(buf) pipeline := pipelineFromPush(hook) assert.Equal(t, model.EventPush, pipeline.Event) - assert.Equal(t, hook.After, pipeline.Commit) + assert.Equal(t, &model.Commit{ + SHA: "ef98532add3b2feb7a137426bba1248724367df5", + Message: "bump\n", + ForgeURL: "http://forgejo.golang.org/gordon/hello-world/commit/ef98532add3b2feb7a137426bba1248724367df5", + Author: model.CommitAuthor{ + Author: "Gordon the Gopher", + Email: "gordon@golang.org", + }, + }, pipeline.Commit) assert.Equal(t, hook.Ref, pipeline.Ref) assert.Equal(t, hook.Commits[0].URL, pipeline.ForgeURL) assert.Equal(t, "main", pipeline.Branch) - assert.Equal(t, hook.Commits[0].Message, pipeline.Message) + assert.Equal(t, hook.Commits[0].Message, pipeline.Commit.Message) assert.Equal(t, "http://1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", pipeline.Avatar) assert.Equal(t, hook.Sender.UserName, pipeline.Author) assert.Equal(t, []string{"CHANGELOG.md", "app/controller/application.rb"}, pipeline.ChangedFiles) @@ -91,11 +99,10 @@ func Test_parsePush(t *testing.T) { hook, _ := parsePush(buf) pipeline := pipelineFromTag(hook) assert.Equal(t, model.EventTag, pipeline.Event) - assert.Equal(t, hook.Sha, pipeline.Commit) + assert.Equal(t, hook.Sha, pipeline.Commit.SHA) assert.Equal(t, "refs/tags/v1.0.0", pipeline.Ref) assert.Empty(t, pipeline.Branch) - assert.Equal(t, "http://forgejo.golang.org/gordon/hello-world/src/tag/v1.0.0", pipeline.ForgeURL) - assert.Equal(t, "created tag v1.0.0", pipeline.Message) + assert.Equal(t, "http://forgejo.golang.org/gordon/hello-world/releases/tag/v1.0.0", pipeline.ForgeURL) }) } @@ -131,13 +138,13 @@ func Test_parsePullRequest(t *testing.T) { hook, _ := parsePullRequest(buf) pipeline := pipelineFromPullRequest(hook) assert.Equal(t, model.EventPull, pipeline.Event) - assert.Equal(t, hook.PullRequest.Head.Sha, pipeline.Commit) + assert.Equal(t, hook.PullRequest.Head.Sha, pipeline.Commit.SHA) assert.Equal(t, "refs/pull/1/head", pipeline.Ref) assert.Equal(t, "http://forgejo.golang.org/gordon/hello-world/pull/1", pipeline.ForgeURL) assert.Equal(t, "main", pipeline.Branch) assert.Equal(t, "feature/changes:main", pipeline.Refspec) - assert.Equal(t, hook.PullRequest.Title, pipeline.Message) - assert.Equal(t, "http://1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", pipeline.Avatar) + assert.Equal(t, hook.PullRequest.Title, pipeline.PullRequest.Title) + assert.Equal(t, "https://secure.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", pipeline.Avatar) assert.Equal(t, hook.PullRequest.Poster.UserName, pipeline.Author) }) diff --git a/server/forge/forgejo/parse_test.go b/server/forge/forgejo/parse_test.go index a40777521b6..9a8ff1094e1 100644 --- a/server/forge/forgejo/parse_test.go +++ b/server/forge/forgejo/parse_test.go @@ -63,15 +63,20 @@ func TestForgejoParser(t *testing.T) { }, }, pipe: &model.Pipeline{ - Author: "6543", - Event: "push", - Commit: "28c3613ae62640216bea5e7dc71aa65356e4298b", + Event: "push", + Commit: &model.Commit{ + SHA: "28c3613ae62640216bea5e7dc71aa65356e4298b", + Message: "Delete '.woodpecker/.check.yml'\n", + Author: model.CommitAuthor{ + Author: "meisam", + Email: "meisam@noreply.codeberg.org", + }, + ForgeURL: "https://codeberg.org/meisam/woodpecktester/commit/28c3613ae62640216bea5e7dc71aa65356e4298b", + }, Branch: "fdsafdsa", Ref: "refs/heads/fdsafdsa", - Message: "Delete '.woodpecker/.check.yml'\n", - Sender: "6543", + Author: "6543", Avatar: "https://codeberg.org/avatars/09a234c768cb9bca78f6b2f82d6af173", - Email: "6543@obermui.de", ForgeURL: "https://codeberg.org/meisam/woodpecktester/commit/28c3613ae62640216bea5e7dc71aa65356e4298b", ChangedFiles: []string{".woodpecker/.check.yml"}, }, @@ -97,15 +102,20 @@ func TestForgejoParser(t *testing.T) { }, }, pipe: &model.Pipeline{ - Author: "gordon", - Event: "push", - Commit: "ef98532add3b2feb7a137426bba1248724367df5", + Event: "push", + Commit: &model.Commit{ + SHA: "ef98532add3b2feb7a137426bba1248724367df5", + Author: model.CommitAuthor{ + Author: "Gordon the Gopher", + Email: "gordon@golang.org", + }, + ForgeURL: "http://forgejo.golang.org/gordon/hello-world/commit/ef98532add3b2feb7a137426bba1248724367df5", + Message: "bump\n", + }, Branch: "main", Ref: "refs/heads/main", - Message: "bump\n", - Sender: "gordon", + Author: "gordon", Avatar: "http://1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", - Email: "gordon@golang.org", ForgeURL: "http://forgejo.golang.org/gordon/hello-world/commit/ef98532add3b2feb7a137426bba1248724367df5", ChangedFiles: []string{"CHANGELOG.md", "app/controller/application.rb"}, }, @@ -131,15 +141,20 @@ func TestForgejoParser(t *testing.T) { }, }, pipe: &model.Pipeline{ - Author: "test-user", - Event: "push", - Commit: "29be01c073851cf0db0c6a466e396b725a670453", + Event: "push", + Commit: &model.Commit{ + SHA: "29be01c073851cf0db0c6a466e396b725a670453", + Message: "add some text\n", + ForgeURL: "http://127.0.0.1:3000/Test-CI/multi-line-secrets/commit/29be01c073851cf0db0c6a466e396b725a670453", + Author: model.CommitAuthor{ + Author: "6543", + Email: "6543@obermui.de", + }, + }, Branch: "main", Ref: "refs/heads/main", - Message: "add some text\n", - Sender: "test-user", + Author: "test-user", Avatar: "http://127.0.0.1:3000/avatars/dd46a756faad4727fb679320751f6dea", - Email: "test@noreply.localhost", ForgeURL: "http://127.0.0.1:3000/Test-CI/multi-line-secrets/compare/6efcf5b7c98f3e7a491675164b7a2e7acac27941...29be01c073851cf0db0c6a466e396b725a670453", ChangedFiles: []string{"aaa", "aa"}, }, @@ -166,15 +181,12 @@ func TestForgejoParser(t *testing.T) { }, }, pipe: &model.Pipeline{ - Author: "gordon", Event: "tag", - Commit: "ef98532add3b2feb7a137426bba1248724367df5", + Commit: &model.Commit{SHA: "ef98532add3b2feb7a137426bba1248724367df5"}, Ref: "refs/tags/v1.0.0", - Message: "created tag v1.0.0", - Sender: "gordon", + Author: "gordon", Avatar: "https://secure.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", - Email: "gordon@golang.org", - ForgeURL: "http://forgejo.golang.org/gordon/hello-world/src/tag/v1.0.0", + ForgeURL: "http://forgejo.golang.org/gordon/hello-world/releases/tag/v1.0.0", }, }, { @@ -199,19 +211,19 @@ func TestForgejoParser(t *testing.T) { }, }, pipe: &model.Pipeline{ - Author: "gordon", - Event: "pull_request", - Commit: "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c", - Branch: "main", - Ref: "refs/pull/1/head", - Refspec: "feature/changes:main", - Title: "Update the README with new information", - Message: "Update the README with new information", - Sender: "gordon", - Avatar: "http://1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", - Email: "gordon@golang.org", - ForgeURL: "http://forgejo.golang.org/gordon/hello-world/pull/1", - PullRequestLabels: []string{}, + Event: "pull_request", + Commit: &model.Commit{SHA: "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c"}, + Branch: "main", + Ref: "refs/pull/1/head", + Refspec: "feature/changes:main", + Author: "gordon", + Avatar: "https://secure.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", + ForgeURL: "http://forgejo.golang.org/gordon/hello-world/pull/1", + PullRequest: &model.PullRequest{ + Labels: []string{}, + Index: "1", + Title: "Update the README with new information", + }, }, }, { @@ -237,21 +249,21 @@ func TestForgejoParser(t *testing.T) { }, }, pipe: &model.Pipeline{ - Author: "test", Event: "pull_request", - Commit: "788ed8d02d3b7fcfcf6386dbcbca696aa1d4dc25", + Commit: &model.Commit{SHA: "788ed8d02d3b7fcfcf6386dbcbca696aa1d4dc25"}, Branch: "main", Ref: "refs/pull/2/head", Refspec: "test-patch-1:main", - Title: "New Pull", - Message: "New Pull", - Sender: "test", + Author: "test", Avatar: "http://127.0.0.1:3000/avatars/dd46a756faad4727fb679320751f6dea", - Email: "test@noreply.localhost", ForgeURL: "http://127.0.0.1:3000/Test-CI/multi-line-secrets/pulls/2", - PullRequestLabels: []string{ - "Kind/Bug", - "Kind/Security", + PullRequest: &model.PullRequest{ + Labels: []string{ + "Kind/Bug", + "Kind/Security", + }, + Index: "2", + Title: "New Pull", }, }, }, @@ -277,19 +289,19 @@ func TestForgejoParser(t *testing.T) { }, }, pipe: &model.Pipeline{ - Author: "anbraten", - Event: "pull_request_closed", - Commit: "d555a5dd07f4d0148a58d4686ec381502ae6a2d4", - Branch: "main", - Ref: "refs/pull/1/head", - Refspec: "anbraten-patch-1:main", - Title: "Adjust file", - Message: "Adjust file", - Sender: "anbraten", - Avatar: "https://seccdn.libravatar.org/avatar/fc9b6fe77c6b732a02925a62a81f05a0?d=identicon", - Email: "anbraten@sender.forgejo.com", - ForgeURL: "https://forgejo.com/anbraten/test-repo/pulls/1", - PullRequestLabels: []string{}, + Event: "pull_request_closed", + Commit: &model.Commit{SHA: "d555a5dd07f4d0148a58d4686ec381502ae6a2d4"}, + Branch: "main", + Ref: "refs/pull/1/head", + Refspec: "anbraten-patch-1:main", + Author: "anbraten", + Avatar: "https://seccdn.libravatar.org/avatar/fc9b6fe77c6b732a02925a62a81f05a0?d=identicon", + ForgeURL: "https://forgejo.com/anbraten/test-repo/pulls/1", + PullRequest: &model.PullRequest{ + Labels: []string{}, + Index: "1", + Title: "Adjust file", + }, }, }, { @@ -314,19 +326,19 @@ func TestForgejoParser(t *testing.T) { }, }, pipe: &model.Pipeline{ - Author: "anbraten", - Event: "pull_request_closed", - Commit: "d555a5dd07f4d0148a58d4686ec381502ae6a2d4", - Branch: "main", - Ref: "refs/pull/1/head", - Refspec: "anbraten-patch-1:main", - Title: "Adjust file", - Message: "Adjust file", - Sender: "anbraten", - Avatar: "https://seccdn.libravatar.org/avatar/fc9b6fe77c6b732a02925a62a81f05a0?d=identicon", - Email: "anbraten@noreply.forgejo.com", - ForgeURL: "https://forgejo.com/anbraten/test-repo/pulls/1", - PullRequestLabels: []string{}, + Event: "pull_request_closed", + Commit: &model.Commit{SHA: "d555a5dd07f4d0148a58d4686ec381502ae6a2d4"}, + Branch: "main", + Ref: "refs/pull/1/head", + Refspec: "anbraten-patch-1:main", + Author: "anbraten", + Avatar: "https://seccdn.libravatar.org/avatar/fc9b6fe77c6b732a02925a62a81f05a0?d=identicon", + ForgeURL: "https://forgejo.com/anbraten/test-repo/pulls/1", + PullRequest: &model.PullRequest{ + Labels: []string{}, + Index: "1", + Title: "Adjust file", + }, }, }, { @@ -352,15 +364,13 @@ func TestForgejoParser(t *testing.T) { }, }, pipe: &model.Pipeline{ - Author: "anbraten", - Event: "release", - Branch: "main", - Ref: "refs/tags/0.0.5", - Message: "created release Version 0.0.5", - Sender: "anbraten", - Avatar: "https://git.xxx/user/avatar/anbraten/-1", - Email: "anbraten@noreply.xxx", - ForgeURL: "https://git.xxx/anbraten/demo/releases/tag/0.0.5", + Event: "release", + Branch: "main", + Ref: "refs/tags/0.0.5", + Author: "anbraten", + Avatar: "https://git.xxx/user/avatar/anbraten/-1", + ForgeURL: "https://git.xxx/anbraten/demo/releases/tag/0.0.5", + ReleaseTagTitle: "Version 0.0.5", }, }, } @@ -375,7 +385,6 @@ func TestForgejoParser(t *testing.T) { assert.ErrorIs(t, err, tc.err) } else if assert.NoError(t, err) { assert.EqualValues(t, tc.repo, r) - p.Timestamp = 0 assert.EqualValues(t, tc.pipe, p) } }) diff --git a/server/forge/forgejo/types.go b/server/forge/forgejo/types.go index 4e19f55d609..f49531faf86 100644 --- a/server/forge/forgejo/types.go +++ b/server/forge/forgejo/types.go @@ -28,7 +28,8 @@ type pushHook struct { Repo *forgejo.Repository `json:"repository"` - Commits []forgejo.PayloadCommit `json:"commits"` + TotalCommits int `json:"total_commits"` + Commits []forgejo.PayloadCommit `json:"commits"` HeadCommit forgejo.PayloadCommit `json:"head_commit"` diff --git a/server/forge/gitea/fixtures/handler.go b/server/forge/gitea/fixtures/handler.go index 7bc2bbb42ad..6d15d4670bb 100644 --- a/server/forge/gitea/fixtures/handler.go +++ b/server/forge/gitea/fixtures/handler.go @@ -36,6 +36,7 @@ func Handler() http.Handler { e.GET("/api/v1/repos/:owner/:name/pulls/:index/files", getPRFiles) e.GET("/api/v1/user/repos", getUserRepos) e.GET("/api/v1/version", getVersion) + e.GET("/api/v1/repos/:owner/:name/git/commits/:sha", getCommit) return e } @@ -137,6 +138,15 @@ func getPRFiles(c *gin.Context) { } } +func getCommit(c *gin.Context) { + switch c.Param("sha") { + case "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c": + c.String(http.StatusOK, commitPayload) + default: + c.String(http.StatusNotFound, "") + } +} + const listRepoHookPayloads = ` [ { @@ -208,3 +218,74 @@ const prFilesPayload = ` } ] ` + +const commitPayload = ` +{ + "url": "http://localhost:3000/api/v1/repos/qwerty287/woodpecker/git/commits/0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c", + "sha": "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c", + "created": "2025-01-05T12:31:42+02:00", + "html_url": "http://localhost:3000/qwerty287/woodpecker/commit/0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c", + "commit": { + "url": "http://localhost:3000/api/v1/repos/qwerty287/woodpecker/git/commits/0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c", + "author": { + "name": "qwerty287", + "email": "qwerty287@noreply.localhost", + "date": "2025-01-05T12:31:42+02:00" + }, + "committer": { + "name": "qwerty287", + "email": "qwerty287@noreply.localhost", + "date": "2025-01-05T12:31:42+02:00" + }, + "message": "README.md aktualisiert\n", + "tree": { + "url": "http://localhost:3000/api/v1/repos/qwerty287/woodpecker/git/trees/0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c", + "sha": "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c", + "created": "2025-01-05T12:31:42+02:00" + }, + "verification": { + "verified": false, + "reason": "gpg.error.not_signed_commit", + "signature": "", + "signer": null, + "payload": "" + } + }, + "author": { + "id": 1, + "login": "qwerty287", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "qwerty287@noreply.localhost", + "avatar_url": "http://localhost:3000/avatars/25a4ce9e2945c8583f82ce7b2ee8bc3c", + "html_url": "http://localhost:3000/qwerty287", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2023-04-12T18:52:45+03:00", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "qwerty287" + }, + "files": [ + { + "filename": "README.md", + "status": "modified" + } + ], + "stats": { + "total": 2, + "additions": 1, + "deletions": 1 + } +} +` diff --git a/server/forge/gitea/fixtures/hooks.go b/server/forge/gitea/fixtures/hooks.go index 2b151a62ce3..92f055d3995 100644 --- a/server/forge/gitea/fixtures/hooks.go +++ b/server/forge/gitea/fixtures/hooks.go @@ -36,6 +36,19 @@ const HookPush = ` "modified": ["app/controller/application.rb"] } ], + "head_commit": { + "id": "ef98532add3b2feb7a137426bba1248724367df5", + "message": "bump\n", + "url": "http://gitea.golang.org/gordon/hello-world/commit/ef98532add3b2feb7a137426bba1248724367df5", + "author": { + "name": "Gordon the Gopher", + "email": "gordon@golang.org", + "username": "gordon" + }, + "added": ["CHANGELOG.md"], + "removed": [], + "modified": ["app/controller/application.rb"] + }, "repository": { "id": 1, "name": "hello-world", @@ -475,6 +488,7 @@ const HookPullRequest = `{ "pull_request": { "html_url": "http://gitea.golang.org/gordon/hello-world/pull/1", "state": "open", + "number": 1, "title": "Update the README with new information", "body": "please merge", "user": { diff --git a/server/forge/gitea/gitea.go b/server/forge/gitea/gitea.go index 7b3788d0652..afc28aec87c 100644 --- a/server/forge/gitea/gitea.go +++ b/server/forge/gitea/gitea.go @@ -274,7 +274,7 @@ func (c *Gitea) File(ctx context.Context, u *model.User, r *model.Repo, b *model return nil, err } - cfg, resp, err := client.GetFile(r.Owner, r.Name, b.Commit, f) + cfg, resp, err := client.GetFile(r.Owner, r.Name, b.Commit.SHA, f) if err != nil && resp != nil && resp.StatusCode == http.StatusNotFound { return nil, errors.Join(err, &forge_types.ErrConfigNotFound{Configs: []string{f}}) } @@ -290,7 +290,7 @@ func (c *Gitea) Dir(ctx context.Context, u *model.User, r *model.Repo, b *model. } // List files in repository. Path from root - tree, _, err := client.GetTrees(r.Owner, r.Name, b.Commit, true) + tree, _, err := client.GetTrees(r.Owner, r.Name, b.Commit.SHA, true) if err != nil { return nil, err } @@ -328,7 +328,7 @@ func (c *Gitea) Status(ctx context.Context, user *model.User, repo *model.Repo, _, _, err = client.CreateStatus( repo.Owner, repo.Name, - pipeline.Commit, + pipeline.Commit.SHA, gitea.CreateStatusOption{ State: getStatus(workflow.State), TargetURL: common.GetPipelineStatusURL(repo, pipeline, workflow), @@ -462,6 +462,11 @@ func (c *Gitea) BranchHead(ctx context.Context, u *model.User, r *model.Repo, br return &model.Commit{ SHA: b.Commit.ID, ForgeURL: b.Commit.URL, + Message: b.Commit.Message, + Author: model.CommitAuthor{ + Author: b.Commit.Author.Name, + Email: b.Commit.Author.Email, + }, }, nil } @@ -487,10 +492,7 @@ func (c *Gitea) PullRequests(ctx context.Context, u *model.User, r *model.Repo, result := make([]*model.PullRequest, len(pullRequests)) for i := range pullRequests { - result[i] = &model.PullRequest{ - Index: model.ForgeRemoteID(strconv.Itoa(int(pullRequests[i].Index))), - Title: pullRequests[i].Title, - } + result[i] = convertPullRequests(pullRequests[i]) } return result, err } @@ -503,13 +505,31 @@ func (c *Gitea) Hook(ctx context.Context, r *http.Request) (*model.Repo, *model. return nil, nil, err } - if pipeline != nil && pipeline.Event == model.EventRelease && pipeline.Commit == "" { - tagName := strings.Split(pipeline.Ref, "/")[2] - sha, err := c.getTagCommitSHA(ctx, repo, tagName) - if err != nil { - return nil, nil, err + if pipeline != nil { + if pipeline.Event == model.EventRelease && pipeline.Commit.SHA == "" { + tagName := strings.Split(pipeline.Ref, "/")[2] + sha, _, err := c.getTagMessages(ctx, repo, tagName) + if err != nil { + return nil, nil, err + } + pipeline.Commit = sha + } else if pipeline.Event == model.EventPull || pipeline.Event == model.EventPullClosed { + sha, err := c.getCommitFromSHAStore(ctx, repo, pipeline.Commit.SHA) + if err != nil { + return nil, nil, err + } + pipeline.Commit = sha + } else if pipeline.Event == model.EventTag { + tagName := strings.Split(pipeline.Ref, "/")[2] + sha, msg, err := c.getTagMessages(ctx, repo, tagName) + if err != nil { + return nil, nil, err + } + pipeline.Commit = sha + if pipeline.Commit.Message != msg { + pipeline.ReleaseTagTitle = msg + } } - pipeline.Commit = sha } if pipeline != nil && (pipeline.Event == model.EventPull || pipeline.Event == model.EventPullClosed) && len(pipeline.ChangedFiles) == 0 { @@ -657,34 +677,75 @@ func (c *Gitea) getChangedFilesForPR(ctx context.Context, repo *model.Repo, inde }, -1) } -func (c *Gitea) getTagCommitSHA(ctx context.Context, repo *model.Repo, tagName string) (string, error) { +func (c *Gitea) getTagMessages(ctx context.Context, repo *model.Repo, tagName string) (*model.Commit, string, error) { _store, ok := store.TryFromContext(ctx) if !ok { - log.Error().Msg("could not get store from context") - return "", nil + return nil, "", fmt.Errorf("could not get store from context") } repo, err := _store.GetRepoNameFallback(repo.ForgeRemoteID, repo.FullName) if err != nil { - return "", err + return nil, "", err } user, err := _store.GetUser(repo.UserID) if err != nil { - return "", err + return nil, "", err } client, err := c.newClientToken(ctx, user.AccessToken) if err != nil { - return "", err + return nil, "", err } tag, _, err := client.GetTag(repo.Owner, repo.Name, tagName) if err != nil { - return "", err + return nil, "", err + } + + commit, err := c.getCommitFromSHA(ctx, user, repo, tag.Commit.SHA) + return commit, tag.Message, err +} + +func (c *Gitea) getCommitFromSHAStore(ctx context.Context, repo *model.Repo, sha string) (*model.Commit, error) { + _store, ok := store.TryFromContext(ctx) + if !ok { + return nil, fmt.Errorf("could not get store from context") + } + + repo, err := _store.GetRepoNameFallback(repo.ForgeRemoteID, repo.FullName) + if err != nil { + return nil, err + } + + user, err := _store.GetUser(repo.UserID) + if err != nil { + return nil, err } - return tag.Commit.SHA, nil + return c.getCommitFromSHA(ctx, user, repo, sha) +} + +func (c *Gitea) getCommitFromSHA(ctx context.Context, user *model.User, repo *model.Repo, sha string) (*model.Commit, error) { + client, err := c.newClientToken(ctx, user.AccessToken) + if err != nil { + return nil, err + } + + commit, _, err := client.GetSingleCommit(repo.Owner, repo.Name, sha) + if err != nil { + return nil, err + } + + return &model.Commit{ + Message: commit.RepoCommit.Message, + Author: model.CommitAuthor{ + Author: commit.RepoCommit.Author.Name, + Email: commit.RepoCommit.Author.Email, + }, + ForgeURL: commit.HTMLURL, + SHA: commit.SHA, + }, nil } func (c *Gitea) perPage(ctx context.Context) int { diff --git a/server/forge/gitea/gitea_test.go b/server/forge/gitea/gitea_test.go index 1892c5fcc93..8185f58bb9b 100644 --- a/server/forge/gitea/gitea_test.go +++ b/server/forge/gitea/gitea_test.go @@ -162,7 +162,7 @@ var ( } fakePipeline = &model.Pipeline{ - Commit: "9ecad50", + Commit: &model.Commit{SHA: "9ecad50"}, } fakeWorkflow = &model.Workflow{ diff --git a/server/forge/gitea/helper.go b/server/forge/gitea/helper.go index 9420d5fffc5..e6776726bb5 100644 --- a/server/forge/gitea/helper.go +++ b/server/forge/gitea/helper.go @@ -20,8 +20,8 @@ import ( "fmt" "io" "net/url" + "strconv" "strings" - "time" "code.gitea.io/sdk/gitea" @@ -76,30 +76,27 @@ func pipelineFromPush(hook *pushHook) *model.Pipeline { fixMalformedAvatar(hook.Sender.AvatarURL), ) - var message string link := hook.Compare - if len(hook.Commits) > 0 { - message = hook.Commits[0].Message - if len(hook.Commits) == 1 { - link = hook.Commits[0].URL - } - } else { - message = hook.HeadCommit.Message + if hook.TotalCommits <= 1 { link = hook.HeadCommit.URL } return &model.Pipeline{ - Event: model.EventPush, - Commit: hook.After, + Event: model.EventPush, + Commit: &model.Commit{ + SHA: hook.After, + Message: hook.HeadCommit.Message, + ForgeURL: hook.HeadCommit.URL, + Author: model.CommitAuthor{ + Author: hook.HeadCommit.Author.Name, + Email: hook.HeadCommit.Author.Email, + }, + }, Ref: hook.Ref, ForgeURL: link, Branch: strings.TrimPrefix(hook.Ref, "refs/heads/"), - Message: message, - Avatar: avatar, Author: hook.Sender.UserName, - Email: hook.Sender.Email, - Timestamp: time.Now().UTC().Unix(), - Sender: hook.Sender.UserName, + Avatar: avatar, ChangedFiles: getChangedFilesFromPushHook(hook), } } @@ -129,16 +126,14 @@ func pipelineFromTag(hook *pushHook) *model.Pipeline { ref := strings.TrimPrefix(hook.Ref, "refs/tags/") return &model.Pipeline{ - Event: model.EventTag, - Commit: hook.Sha, - Ref: fmt.Sprintf("refs/tags/%s", ref), - ForgeURL: fmt.Sprintf("%s/src/tag/%s", hook.Repo.HTMLURL, ref), - Message: fmt.Sprintf("created tag %s", ref), - Avatar: avatar, - Author: hook.Sender.UserName, - Sender: hook.Sender.UserName, - Email: hook.Sender.Email, - Timestamp: time.Now().UTC().Unix(), + Event: model.EventTag, + Commit: &model.Commit{ + SHA: hook.Sha, + }, + Ref: fmt.Sprintf("refs/tags/%s", ref), + ForgeURL: fmt.Sprintf("%s/src/releases/tag/%s", hook.Repo.HTMLURL, ref), + Author: hook.Sender.UserName, + Avatar: avatar, } } @@ -146,7 +141,7 @@ func pipelineFromTag(hook *pushHook) *model.Pipeline { func pipelineFromPullRequest(hook *pullRequestHook) *model.Pipeline { avatar := expandAvatar( hook.Repo.HTMLURL, - fixMalformedAvatar(hook.PullRequest.Poster.AvatarURL), + fixMalformedAvatar(hook.Sender.AvatarURL), ) event := model.EventPull @@ -155,23 +150,20 @@ func pipelineFromPullRequest(hook *pullRequestHook) *model.Pipeline { } pipeline := &model.Pipeline{ - Event: event, - Commit: hook.PullRequest.Head.Sha, + Event: event, + Commit: &model.Commit{ + SHA: hook.PullRequest.Head.Sha, + }, ForgeURL: hook.PullRequest.HTMLURL, Ref: fmt.Sprintf("refs/pull/%d/head", hook.Number), Branch: hook.PullRequest.Base.Ref, - Message: hook.PullRequest.Title, - Author: hook.PullRequest.Poster.UserName, + Author: hook.Sender.UserName, Avatar: avatar, - Sender: hook.Sender.UserName, - Email: hook.Sender.Email, - Title: hook.PullRequest.Title, Refspec: fmt.Sprintf("%s:%s", hook.PullRequest.Head.Ref, hook.PullRequest.Base.Ref, ), - PullRequestLabels: convertLabels(hook.PullRequest.Labels), - FromFork: hook.PullRequest.Head.RepoID != hook.PullRequest.Base.RepoID, + PullRequest: convertPullRequests(hook.PullRequest), } return pipeline @@ -184,16 +176,14 @@ func pipelineFromRelease(hook *releaseHook) *model.Pipeline { ) return &model.Pipeline{ - Event: model.EventRelease, - Ref: fmt.Sprintf("refs/tags/%s", hook.Release.TagName), - ForgeURL: hook.Release.HTMLURL, - Branch: hook.Release.Target, - Message: fmt.Sprintf("created release %s", hook.Release.Title), - Avatar: avatar, - Author: hook.Sender.UserName, - Sender: hook.Sender.UserName, - Email: hook.Sender.Email, - IsPrerelease: hook.Release.IsPrerelease, + Event: model.EventRelease, + Ref: fmt.Sprintf("refs/tags/%s", hook.Release.TagName), + ForgeURL: hook.Release.HTMLURL, + Branch: hook.Release.Target, + ReleaseTagTitle: hook.Release.Title, + Author: hook.Sender.UserName, + Avatar: avatar, + IsPrerelease: hook.Release.IsPrerelease, } } @@ -267,6 +257,15 @@ func matchingHooks(hooks []*gitea.Hook, rawURL string) *gitea.Hook { return nil } +func convertPullRequests(from *gitea.PullRequest) *model.PullRequest { + return &model.PullRequest{ + Index: model.ForgeRemoteID(strconv.Itoa(int(from.Index))), + Title: from.Title, + Labels: convertLabels(from.Labels), + FromFork: from.Head.RepoID != from.Base.RepoID, + } +} + func convertLabels(from []*gitea.Label) []string { labels := make([]string, len(from)) for i, label := range from { diff --git a/server/forge/gitea/helper_test.go b/server/forge/gitea/helper_test.go index b728699ae64..1ddd79c041a 100644 --- a/server/forge/gitea/helper_test.go +++ b/server/forge/gitea/helper_test.go @@ -67,11 +67,19 @@ func Test_parsePush(t *testing.T) { hook, _ := parsePush(buf) pipeline := pipelineFromPush(hook) assert.Equal(t, model.EventPush, pipeline.Event) - assert.Equal(t, hook.After, pipeline.Commit) + assert.Equal(t, &model.Commit{ + SHA: "ef98532add3b2feb7a137426bba1248724367df5", + Message: "bump\n", + ForgeURL: "http://gitea.golang.org/gordon/hello-world/commit/ef98532add3b2feb7a137426bba1248724367df5", + Author: model.CommitAuthor{ + Author: "Gordon the Gopher", + Email: "gordon@golang.org", + }, + }, pipeline.Commit) assert.Equal(t, hook.Ref, pipeline.Ref) assert.Equal(t, hook.Commits[0].URL, pipeline.ForgeURL) assert.Equal(t, "main", pipeline.Branch) - assert.Equal(t, hook.Commits[0].Message, pipeline.Message) + assert.Equal(t, hook.Commits[0].Message, pipeline.Commit.Message) assert.Equal(t, "http://1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", pipeline.Avatar) assert.Equal(t, hook.Sender.UserName, pipeline.Author) assert.Equal(t, []string{"CHANGELOG.md", "app/controller/application.rb"}, pipeline.ChangedFiles) @@ -92,11 +100,10 @@ func Test_parsePush(t *testing.T) { hook, _ := parsePush(buf) pipeline := pipelineFromTag(hook) assert.Equal(t, model.EventTag, pipeline.Event) - assert.Equal(t, hook.Sha, pipeline.Commit) + assert.Equal(t, hook.Sha, pipeline.Commit.SHA) assert.Equal(t, "refs/tags/v1.0.0", pipeline.Ref) assert.Empty(t, pipeline.Branch) - assert.Equal(t, "http://gitea.golang.org/gordon/hello-world/src/tag/v1.0.0", pipeline.ForgeURL) - assert.Equal(t, "created tag v1.0.0", pipeline.Message) + assert.Equal(t, "http://gitea.golang.org/gordon/hello-world/src/releases/tag/v1.0.0", pipeline.ForgeURL) }) } @@ -132,13 +139,13 @@ func Test_parsePullRequest(t *testing.T) { hook, _ := parsePullRequest(buf) pipeline := pipelineFromPullRequest(hook) assert.Equal(t, model.EventPull, pipeline.Event) - assert.Equal(t, hook.PullRequest.Head.Sha, pipeline.Commit) + assert.Equal(t, hook.PullRequest.Head.Sha, pipeline.Commit.SHA) assert.Equal(t, "refs/pull/1/head", pipeline.Ref) assert.Equal(t, "http://gitea.golang.org/gordon/hello-world/pull/1", pipeline.ForgeURL) assert.Equal(t, "main", pipeline.Branch) assert.Equal(t, "feature/changes:main", pipeline.Refspec) - assert.Equal(t, hook.PullRequest.Title, pipeline.Message) - assert.Equal(t, "http://1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", pipeline.Avatar) + assert.Equal(t, hook.PullRequest.Title, pipeline.PullRequest.Title) + assert.Equal(t, "https://secure.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", pipeline.Avatar) assert.Equal(t, hook.PullRequest.Poster.UserName, pipeline.Author) }) diff --git a/server/forge/gitea/parse_test.go b/server/forge/gitea/parse_test.go index 437376611ff..18e59235074 100644 --- a/server/forge/gitea/parse_test.go +++ b/server/forge/gitea/parse_test.go @@ -64,15 +64,21 @@ func TestGiteaParser(t *testing.T) { }, }, pipe: &model.Pipeline{ - Author: "6543", - Event: "push", - Commit: "28c3613ae62640216bea5e7dc71aa65356e4298b", - Branch: "fdsafdsa", - Ref: "refs/heads/fdsafdsa", - Message: "Delete '.woodpecker/.check.yml'\n", - Sender: "6543", - Avatar: "https://codeberg.org/avatars/09a234c768cb9bca78f6b2f82d6af173", - Email: "6543@obermui.de", + Event: "push", + Commit: &model.Commit{ + SHA: "28c3613ae62640216bea5e7dc71aa65356e4298b", + Message: "Delete '.woodpecker/.check.yml'\n", + ForgeURL: "https://codeberg.org/meisam/woodpecktester/commit/28c3613ae62640216bea5e7dc71aa65356e4298b", + Author: model.CommitAuthor{ + Author: "meisam", + Email: "meisam@noreply.codeberg.org", + }, + }, + Branch: "fdsafdsa", + Ref: "refs/heads/fdsafdsa", + Author: "6543", + Avatar: "https://codeberg.org/avatars/09a234c768cb9bca78f6b2f82d6af173", + ForgeURL: "https://codeberg.org/meisam/woodpecktester/commit/28c3613ae62640216bea5e7dc71aa65356e4298b", ChangedFiles: []string{".woodpecker/.check.yml"}, }, @@ -98,15 +104,20 @@ func TestGiteaParser(t *testing.T) { }, }, pipe: &model.Pipeline{ - Author: "gordon", - Event: "push", - Commit: "ef98532add3b2feb7a137426bba1248724367df5", + Event: "push", + Commit: &model.Commit{ + SHA: "ef98532add3b2feb7a137426bba1248724367df5", + Message: "bump\n", + ForgeURL: "http://gitea.golang.org/gordon/hello-world/commit/ef98532add3b2feb7a137426bba1248724367df5", + Author: model.CommitAuthor{ + Author: "Gordon the Gopher", + Email: "gordon@golang.org", + }, + }, Branch: "main", Ref: "refs/heads/main", - Message: "bump\n", - Sender: "gordon", + Author: "gordon", Avatar: "http://1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", - Email: "gordon@golang.org", ForgeURL: "http://gitea.golang.org/gordon/hello-world/commit/ef98532add3b2feb7a137426bba1248724367df5", ChangedFiles: []string{"CHANGELOG.md", "app/controller/application.rb"}, }, @@ -132,15 +143,20 @@ func TestGiteaParser(t *testing.T) { }, }, pipe: &model.Pipeline{ - Author: "test-user", - Event: "push", - Commit: "29be01c073851cf0db0c6a466e396b725a670453", + Event: "push", + Commit: &model.Commit{ + SHA: "29be01c073851cf0db0c6a466e396b725a670453", + Message: "add some text\n", + ForgeURL: "http://127.0.0.1:3000/Test-CI/multi-line-secrets/commit/29be01c073851cf0db0c6a466e396b725a670453", + Author: model.CommitAuthor{ + Author: "6543", + Email: "6543@obermui.de", + }, + }, Branch: "main", Ref: "refs/heads/main", - Message: "add some text\n", - Sender: "test-user", + Author: "test-user", Avatar: "http://127.0.0.1:3000/avatars/dd46a756faad4727fb679320751f6dea", - Email: "test@noreply.localhost", ForgeURL: "http://127.0.0.1:3000/Test-CI/multi-line-secrets/compare/6efcf5b7c98f3e7a491675164b7a2e7acac27941...29be01c073851cf0db0c6a466e396b725a670453", ChangedFiles: []string{"aaa", "aa"}, }, @@ -167,15 +183,12 @@ func TestGiteaParser(t *testing.T) { }, }, pipe: &model.Pipeline{ - Author: "gordon", Event: "tag", - Commit: "ef98532add3b2feb7a137426bba1248724367df5", + Commit: &model.Commit{SHA: "ef98532add3b2feb7a137426bba1248724367df5"}, Ref: "refs/tags/v1.0.0", - Message: "created tag v1.0.0", - Sender: "gordon", + Author: "gordon", Avatar: "https://secure.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", - Email: "gordon@golang.org", - ForgeURL: "http://gitea.golang.org/gordon/hello-world/src/tag/v1.0.0", + ForgeURL: "http://gitea.golang.org/gordon/hello-world/src/releases/tag/v1.0.0", }, }, { @@ -200,19 +213,19 @@ func TestGiteaParser(t *testing.T) { }, }, pipe: &model.Pipeline{ - Author: "gordon", - Event: "pull_request", - Commit: "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c", - Branch: "main", - Ref: "refs/pull/1/head", - Refspec: "feature/changes:main", - Title: "Update the README with new information", - Message: "Update the README with new information", - Sender: "gordon", - Avatar: "http://1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", - Email: "gordon@golang.org", - ForgeURL: "http://gitea.golang.org/gordon/hello-world/pull/1", - PullRequestLabels: []string{}, + Event: "pull_request", + Commit: &model.Commit{SHA: "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c"}, + Branch: "main", + Ref: "refs/pull/1/head", + Refspec: "feature/changes:main", + Author: "gordon", + Avatar: "https://secure.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", + ForgeURL: "http://gitea.golang.org/gordon/hello-world/pull/1", + PullRequest: &model.PullRequest{ + Labels: []string{}, + Index: "1", + Title: "Update the README with new information", + }, }, }, { @@ -238,21 +251,21 @@ func TestGiteaParser(t *testing.T) { }, }, pipe: &model.Pipeline{ - Author: "test", Event: "pull_request", - Commit: "788ed8d02d3b7fcfcf6386dbcbca696aa1d4dc25", + Commit: &model.Commit{SHA: "788ed8d02d3b7fcfcf6386dbcbca696aa1d4dc25"}, Branch: "main", Ref: "refs/pull/2/head", Refspec: "test-patch-1:main", - Title: "New Pull", - Message: "New Pull", - Sender: "test", + Author: "test", Avatar: "http://127.0.0.1:3000/avatars/dd46a756faad4727fb679320751f6dea", - Email: "test@noreply.localhost", ForgeURL: "http://127.0.0.1:3000/Test-CI/multi-line-secrets/pulls/2", - PullRequestLabels: []string{ - "Kind/Bug", - "Kind/Security", + PullRequest: &model.PullRequest{ + Labels: []string{ + "Kind/Bug", + "Kind/Security", + }, + Index: "2", + Title: "New Pull", }, }, }, @@ -278,19 +291,19 @@ func TestGiteaParser(t *testing.T) { }, }, pipe: &model.Pipeline{ - Author: "anbraten", - Event: "pull_request_closed", - Commit: "d555a5dd07f4d0148a58d4686ec381502ae6a2d4", - Branch: "main", - Ref: "refs/pull/1/head", - Refspec: "anbraten-patch-1:main", - Title: "Adjust file", - Message: "Adjust file", - Sender: "anbraten", - Avatar: "https://seccdn.libravatar.org/avatar/fc9b6fe77c6b732a02925a62a81f05a0?d=identicon", - Email: "anbraten@sender.gitea.com", - ForgeURL: "https://gitea.com/anbraten/test-repo/pulls/1", - PullRequestLabels: []string{}, + Event: "pull_request_closed", + Commit: &model.Commit{SHA: "d555a5dd07f4d0148a58d4686ec381502ae6a2d4"}, + Branch: "main", + Ref: "refs/pull/1/head", + Refspec: "anbraten-patch-1:main", + Author: "anbraten", + Avatar: "https://seccdn.libravatar.org/avatar/fc9b6fe77c6b732a02925a62a81f05a0?d=identicon", + ForgeURL: "https://gitea.com/anbraten/test-repo/pulls/1", + PullRequest: &model.PullRequest{ + Labels: []string{}, + Index: "1", + Title: "Adjust file", + }, }, }, { @@ -315,19 +328,19 @@ func TestGiteaParser(t *testing.T) { }, }, pipe: &model.Pipeline{ - Author: "anbraten", - Event: "pull_request_closed", - Commit: "d555a5dd07f4d0148a58d4686ec381502ae6a2d4", - Branch: "main", - Ref: "refs/pull/1/head", - Refspec: "anbraten-patch-1:main", - Title: "Adjust file", - Message: "Adjust file", - Sender: "anbraten", - Avatar: "https://seccdn.libravatar.org/avatar/fc9b6fe77c6b732a02925a62a81f05a0?d=identicon", - Email: "anbraten@noreply.gitea.com", - ForgeURL: "https://gitea.com/anbraten/test-repo/pulls/1", - PullRequestLabels: []string{}, + Event: "pull_request_closed", + Commit: &model.Commit{SHA: "d555a5dd07f4d0148a58d4686ec381502ae6a2d4"}, + Branch: "main", + Ref: "refs/pull/1/head", + Refspec: "anbraten-patch-1:main", + Author: "anbraten", + Avatar: "https://seccdn.libravatar.org/avatar/fc9b6fe77c6b732a02925a62a81f05a0?d=identicon", + ForgeURL: "https://gitea.com/anbraten/test-repo/pulls/1", + PullRequest: &model.PullRequest{ + Labels: []string{}, + Index: "1", + Title: "Adjust file", + }, }, }, { @@ -353,15 +366,13 @@ func TestGiteaParser(t *testing.T) { }, }, pipe: &model.Pipeline{ - Author: "anbraten", - Event: "release", - Branch: "main", - Ref: "refs/tags/0.0.5", - Message: "created release Version 0.0.5", - Sender: "anbraten", - Avatar: "https://git.xxx/user/avatar/anbraten/-1", - Email: "anbraten@noreply.xxx", - ForgeURL: "https://git.xxx/anbraten/demo/releases/tag/0.0.5", + Event: "release", + Branch: "main", + Ref: "refs/tags/0.0.5", + ReleaseTagTitle: "Version 0.0.5", + Author: "anbraten", + Avatar: "https://git.xxx/user/avatar/anbraten/-1", + ForgeURL: "https://git.xxx/anbraten/demo/releases/tag/0.0.5", }, }, } @@ -376,7 +387,6 @@ func TestGiteaParser(t *testing.T) { assert.ErrorIs(t, err, tc.err) } else if assert.NoError(t, err) { assert.EqualValues(t, tc.repo, r) - p.Timestamp = 0 assert.EqualValues(t, tc.pipe, p) } }) diff --git a/server/forge/gitea/types.go b/server/forge/gitea/types.go index 2c27a324b90..4a617c2dc3c 100644 --- a/server/forge/gitea/types.go +++ b/server/forge/gitea/types.go @@ -29,7 +29,8 @@ type pushHook struct { Repo *gitea.Repository `json:"repository"` - Commits []gitea.PayloadCommit `json:"commits"` + TotalCommits int `json:"total_commits"` + Commits []gitea.PayloadCommit `json:"commits"` HeadCommit gitea.PayloadCommit `json:"head_commit"` diff --git a/server/forge/github/convert.go b/server/forge/github/convert.go index 90bb9b05d98..358687407c4 100644 --- a/server/forge/github/convert.go +++ b/server/forge/github/convert.go @@ -17,6 +17,7 @@ package github import ( "fmt" + "strconv" "github.com/google/go-github/v69/github" @@ -159,6 +160,15 @@ func convertRepoHook(eventRepo *github.PushEventRepository) *model.Repo { return repo } +func convertPullRequest(pr *github.PullRequest) *model.PullRequest { + return &model.PullRequest{ + Index: model.ForgeRemoteID(strconv.Itoa(pr.GetNumber())), + Title: pr.GetTitle(), + Labels: convertLabels(pr.Labels), + FromFork: pr.GetHead().GetRepo().GetID() != pr.GetBase().GetRepo().GetID(), + } +} + // convertLabels is a helper function used to convert a GitHub label list to // the common Woodpecker label structure. func convertLabels(from []*github.Label) []string { @@ -168,3 +178,15 @@ func convertLabels(from []*github.Label) []string { } return labels } + +func convertCommit(from *github.Commit) *model.Commit { + return &model.Commit{ + SHA: from.GetSHA(), + ForgeURL: from.GetHTMLURL(), + Message: from.GetMessage(), + Author: model.CommitAuthor{ + Author: from.GetAuthor().GetName(), + Email: from.GetAuthor().GetEmail(), + }, + } +} diff --git a/server/forge/github/convert_test.go b/server/forge/github/convert_test.go index 763ae2cd8fc..6da473b69b1 100644 --- a/server/forge/github/convert_test.go +++ b/server/forge/github/convert_test.go @@ -182,7 +182,8 @@ func Test_parsePullHook(t *testing.T) { AvatarURL: github.Ptr("https://avatars1.githubusercontent.com/u/583231"), }, }, Sender: &github.User{ - Login: github.Ptr("octocat"), + Login: github.Ptr("octocat"), + AvatarURL: github.Ptr("https://avatars1.githubusercontent.com/u/583231"), }, } pull, _, pipeline, err := parsePullHook(from, true) @@ -192,12 +193,11 @@ func Test_parsePullHook(t *testing.T) { assert.Equal(t, *from.PullRequest.Base.Ref, pipeline.Branch) assert.Equal(t, "refs/pull/42/merge", pipeline.Ref) assert.Equal(t, "changes:main", pipeline.Refspec) - assert.Equal(t, *from.PullRequest.Head.SHA, pipeline.Commit) - assert.Equal(t, *from.PullRequest.Title, pipeline.Message) - assert.Equal(t, *from.PullRequest.Title, pipeline.Title) + assert.Equal(t, *from.PullRequest.Head.SHA, pipeline.Commit.SHA) + assert.Equal(t, *from.PullRequest.Title, pipeline.PullRequest.Title) assert.Equal(t, *from.PullRequest.User.Login, pipeline.Author) assert.Equal(t, *from.PullRequest.User.AvatarURL, pipeline.Avatar) - assert.Equal(t, *from.Sender.Login, pipeline.Sender) + assert.Equal(t, *from.Sender.Login, pipeline.Author) } func Test_parseDeployHook(t *testing.T) { @@ -216,8 +216,8 @@ func Test_parseDeployHook(t *testing.T) { assert.Equal(t, model.EventDeploy, pipeline.Event) assert.Equal(t, "main", pipeline.Branch) assert.Equal(t, "refs/heads/main", pipeline.Ref) - assert.Equal(t, *from.Deployment.SHA, pipeline.Commit) - assert.Equal(t, *from.Deployment.Description, pipeline.Message) + assert.Equal(t, *from.Deployment.SHA, pipeline.Commit.SHA) + assert.Equal(t, *from.Deployment.Description, pipeline.Deployment.Description) assert.Equal(t, *from.Deployment.URL, pipeline.ForgeURL) assert.Equal(t, *from.Sender.Login, pipeline.Author) assert.Equal(t, *from.Sender.AvatarURL, pipeline.Avatar) @@ -239,40 +239,22 @@ func Test_parsePushHook(t *testing.T) { assert.Equal(t, model.EventPush, pipeline.Event) assert.Equal(t, "main", pipeline.Branch) assert.Equal(t, "refs/heads/main", pipeline.Ref) - assert.Equal(t, *from.HeadCommit.ID, pipeline.Commit) - assert.Equal(t, *from.HeadCommit.Message, pipeline.Message) - assert.Equal(t, *from.HeadCommit.URL, pipeline.ForgeURL) - assert.Equal(t, *from.Sender.Login, pipeline.Author) - assert.Equal(t, *from.Sender.AvatarURL, pipeline.Avatar) - assert.Equal(t, *from.HeadCommit.Author.Email, pipeline.Email) + assert.Equal(t, &model.Commit{SHA: "f72fc19", Message: "updated README.md", ForgeURL: "https://github.com/octocat/hello-world", Author: model.CommitAuthor{Author: "", Email: "github.Ptr(octocat@github.com"}}, pipeline.Commit) + assert.Equal(t, from.GetHeadCommit().GetMessage(), pipeline.Commit.Message) + assert.Equal(t, from.GetHeadCommit().GetURL(), pipeline.ForgeURL) + assert.Equal(t, from.GetSender().GetLogin(), pipeline.Author) + assert.Equal(t, from.GetSender().GetAvatarURL(), pipeline.Avatar) + assert.Equal(t, from.GetHeadCommit().GetAuthor().GetEmail(), pipeline.Commit.Author.Email) }) t.Run("convert tag from webhook", func(t *testing.T) { from := &github.PushEvent{} from.Ref = github.Ptr("refs/tags/v1.0.0") - - _, pipeline := parsePushHook(from) - assert.Equal(t, model.EventTag, pipeline.Event) - assert.Equal(t, "refs/tags/v1.0.0", pipeline.Ref) - }) - - t.Run("convert tag's base branch to pipeline's branch ", func(t *testing.T) { - from := &github.PushEvent{} - from.Ref = github.Ptr("refs/tags/v1.0.0") from.BaseRef = github.Ptr("refs/heads/main") _, pipeline := parsePushHook(from) assert.Equal(t, model.EventTag, pipeline.Event) - assert.Equal(t, "main", pipeline.Branch) - }) - - t.Run("not convert tag's base_ref from webhook if not prefixed with 'ref/heads/'", func(t *testing.T) { - from := &github.PushEvent{} - from.Ref = github.Ptr("refs/tags/v1.0.0") - from.BaseRef = github.Ptr("refs/refs/main") - - _, pipeline := parsePushHook(from) - assert.Equal(t, model.EventTag, pipeline.Event) - assert.Equal(t, "refs/tags/v1.0.0", pipeline.Branch) + assert.Equal(t, "refs/tags/v1.0.0", pipeline.Ref) + assert.Empty(t, pipeline.Branch) }) } diff --git a/server/forge/github/github.go b/server/forge/github/github.go index 7e09144a414..7785ab8e60b 100644 --- a/server/forge/github/github.go +++ b/server/forge/github/github.go @@ -252,7 +252,7 @@ func (c *client) File(ctx context.Context, u *model.User, r *model.Repo, b *mode client := c.newClientToken(ctx, u.AccessToken) opts := new(github.RepositoryContentGetOptions) - opts.Ref = b.Commit + opts.Ref = b.Commit.SHA content, _, resp, err := client.Repositories.GetContents(ctx, r.Owner, r.Name, f, opts) if resp != nil && resp.StatusCode == http.StatusNotFound { return nil, errors.Join(err, &forge_types.ErrConfigNotFound{Configs: []string{f}}) @@ -271,7 +271,7 @@ func (c *client) Dir(ctx context.Context, u *model.User, r *model.Repo, b *model client := c.newClientToken(ctx, u.AccessToken) opts := new(github.RepositoryContentGetOptions) - opts.Ref = b.Commit + opts.Ref = b.Commit.SHA _, data, resp, err := client.Repositories.GetContents(ctx, r.Owner, r.Name, f, opts) if resp != nil && resp.StatusCode == http.StatusNotFound { return nil, errors.Join(err, &forge_types.ErrConfigNotFound{Configs: []string{f}}) @@ -331,10 +331,7 @@ func (c *client) PullRequests(ctx context.Context, u *model.User, r *model.Repo, result := make([]*model.PullRequest, len(pullRequests)) for i := range pullRequests { - result[i] = &model.PullRequest{ - Index: model.ForgeRemoteID(strconv.Itoa(pullRequests[i].GetNumber())), - Title: pullRequests[i].GetTitle(), - } + result[i] = convertPullRequest(pullRequests[i]) } return result, err } @@ -537,7 +534,7 @@ func (c *client) Status(ctx context.Context, user *model.User, repo *model.Repo, return err } - _, _, err := client.Repositories.CreateStatus(ctx, repo.Owner, repo.Name, pipeline.Commit, &github.RepoStatus{ + _, _, err := client.Repositories.CreateStatus(ctx, repo.Owner, repo.Name, pipeline.Commit.SHA, &github.RepoStatus{ Context: github.Ptr(common.GetPipelineStatusContext(repo, pipeline, workflow)), State: github.Ptr(convertStatus(workflow.State)), Description: github.Ptr(common.GetPipelineStatusDescription(workflow.State)), @@ -595,10 +592,7 @@ func (c *client) BranchHead(ctx context.Context, u *model.User, r *model.Repo, b if err != nil { return nil, err } - return &model.Commit{ - SHA: b.GetCommit().GetSHA(), - ForgeURL: b.GetCommit().GetHTMLURL(), - }, nil + return convertCommit(b.GetCommit().GetCommit()), nil } // Hook parses the post-commit hook from the Request body @@ -609,13 +603,21 @@ func (c *client) Hook(ctx context.Context, r *http.Request) (*model.Repo, *model return nil, nil, err } - if pipeline != nil && pipeline.Event == model.EventRelease && pipeline.Commit == "" { - tagName := strings.Split(pipeline.Ref, "/")[2] - sha, err := c.getTagCommitSHA(ctx, repo, tagName) - if err != nil { - return nil, nil, err + if pipeline != nil { + if pipeline.Event == model.EventRelease && pipeline.Commit.SHA == "" { + tagName := strings.Split(pipeline.Ref, "/")[2] + sha, err := c.getTagCommitSHA(ctx, repo, tagName) + if err != nil { + return nil, nil, err + } + pipeline.Commit = sha + } else if pipeline.Event == model.EventDeploy || pipeline.Event == model.EventPull || pipeline.Event == model.EventPullClosed { + sha, err := c.getCommitFromSHA(ctx, repo, pipeline.Commit.SHA) + if err != nil { + return nil, nil, err + } + pipeline.Commit = sha } - pipeline.Commit = sha } if pull != nil && len(pipeline.ChangedFiles) == 0 { @@ -666,21 +668,20 @@ func (c *client) loadChangedFilesFromPullRequest(ctx context.Context, pull *gith return pipeline, err } -func (c *client) getTagCommitSHA(ctx context.Context, repo *model.Repo, tagName string) (string, error) { +func (c *client) getTagCommitSHA(ctx context.Context, repo *model.Repo, tagName string) (*model.Commit, error) { _store, ok := store.TryFromContext(ctx) if !ok { - log.Error().Msg("could not get store from context") - return "", nil + return nil, fmt.Errorf("could not get store from context") } repo, err := _store.GetRepoNameFallback(repo.ForgeRemoteID, repo.FullName) if err != nil { - return "", err + return nil, err } user, err := _store.GetUser(repo.UserID) if err != nil { - return "", err + return nil, err } gh := c.newClientToken(ctx, user.AccessToken) @@ -690,7 +691,7 @@ func (c *client) getTagCommitSHA(ctx context.Context, repo *model.Repo, tagName for { tags, _, err := gh.Repositories.ListTags(ctx, repo.Owner, repo.Name, &github.ListOptions{Page: page}) if err != nil { - return "", err + return nil, err } for _, t := range tags { @@ -704,7 +705,33 @@ func (c *client) getTagCommitSHA(ctx context.Context, repo *model.Repo, tagName } } if tag == nil { - return "", fmt.Errorf("could not find tag %s", tagName) + return nil, fmt.Errorf("could not find tag %s", tagName) + } + return convertCommit(tag.GetCommit()), nil +} + +func (c *client) getCommitFromSHA(ctx context.Context, repo *model.Repo, sha string) (*model.Commit, error) { + _store, ok := store.TryFromContext(ctx) + if !ok { + return nil, fmt.Errorf("could not get store from context") + } + + repo, err := _store.GetRepoNameFallback(repo.ForgeRemoteID, repo.FullName) + if err != nil { + return nil, err + } + + user, err := _store.GetUser(repo.UserID) + if err != nil { + return nil, err } - return tag.GetCommit().GetSHA(), nil + + gh := c.newClientToken(ctx, user.AccessToken) + + commit, _, err := gh.Repositories.GetCommit(ctx, repo.Owner, repo.Name, sha, nil) + if err != nil { + return nil, err + } + + return convertCommit(commit.GetCommit()), nil } diff --git a/server/forge/github/parse.go b/server/forge/github/parse.go index 519e57f7c85..ff385732f73 100644 --- a/server/forge/github/parse.go +++ b/server/forge/github/parse.go @@ -85,32 +85,28 @@ func parsePushHook(hook *github.PushEvent) (*model.Repo, *model.Pipeline) { } pipeline := &model.Pipeline{ - Event: model.EventPush, - Commit: hook.GetHeadCommit().GetID(), + Event: model.EventPush, + Commit: &model.Commit{ + SHA: hook.GetHeadCommit().GetID(), + Author: convertCommitAuthor(hook.GetHeadCommit().GetAuthor()), + Message: hook.GetHeadCommit().GetMessage(), + ForgeURL: hook.GetHeadCommit().GetURL(), + }, Ref: hook.GetRef(), ForgeURL: hook.GetHeadCommit().GetURL(), Branch: strings.ReplaceAll(hook.GetRef(), "refs/heads/", ""), - Message: hook.GetHeadCommit().GetMessage(), - Email: hook.GetHeadCommit().GetAuthor().GetEmail(), Avatar: hook.GetSender().GetAvatarURL(), Author: hook.GetSender().GetLogin(), - Sender: hook.GetSender().GetLogin(), ChangedFiles: getChangedFilesFromCommits(hook.Commits), } - if len(pipeline.Author) == 0 { - pipeline.Author = hook.GetHeadCommit().GetAuthor().GetLogin() - } if strings.HasPrefix(pipeline.Ref, "refs/tags/") { // just kidding, this is actually a tag event. Why did this come as a push // event we'll never know! pipeline.Event = model.EventTag pipeline.ChangedFiles = nil - // For tags, if the base_ref (tag's base branch) is set, we're using it - // as pipeline's branch so that we can filter events base on it - if strings.HasPrefix(hook.GetBaseRef(), "refs/heads/") { - pipeline.Branch = strings.ReplaceAll(hook.GetBaseRef(), "refs/heads/", "") - } + pipeline.Branch = "" + pipeline.ForgeURL = fmt.Sprintf("%s/releases/tag/%s", hook.GetRepo().GetURL(), strings.TrimPrefix(pipeline.Ref, "refs/tags/")) } return convertRepoHook(hook.GetRepo()), pipeline @@ -120,20 +116,23 @@ func parsePushHook(hook *github.PushEvent) (*model.Repo, *model.Pipeline) { // If the commit type is unsupported nil values are returned. func parseDeployHook(hook *github.DeploymentEvent) (*model.Repo, *model.Pipeline) { pipeline := &model.Pipeline{ - Event: model.EventDeploy, - Commit: hook.GetDeployment().GetSHA(), - ForgeURL: hook.GetDeployment().GetURL(), - Message: hook.GetDeployment().GetDescription(), - Ref: hook.GetDeployment().GetRef(), - Branch: hook.GetDeployment().GetRef(), - Avatar: hook.GetSender().GetAvatarURL(), - Author: hook.GetSender().GetLogin(), - Sender: hook.GetSender().GetLogin(), - DeployTo: hook.GetDeployment().GetEnvironment(), - DeployTask: hook.GetDeployment().GetTask(), + Event: model.EventDeploy, + Commit: &model.Commit{ + SHA: hook.GetDeployment().GetSHA(), + }, + ForgeURL: hook.GetDeployment().GetURL(), + Ref: hook.GetDeployment().GetRef(), + Branch: hook.GetDeployment().GetRef(), + Avatar: hook.GetSender().GetAvatarURL(), + Author: hook.GetSender().GetLogin(), + Deployment: &model.Deployment{ + Target: hook.GetDeployment().GetEnvironment(), + Task: hook.GetDeployment().GetTask(), + Description: hook.GetDeployment().GetDescription(), + }, } // if the ref is a sha or short sha we need to manually construct the ref. - if strings.HasPrefix(pipeline.Commit, pipeline.Ref) || pipeline.Commit == pipeline.Ref { + if strings.HasPrefix(pipeline.Commit.SHA, pipeline.Ref) || pipeline.Commit.SHA == pipeline.Ref { pipeline.Branch = hook.GetRepo().GetDefaultBranch() pipeline.Ref = fmt.Sprintf("refs/heads/%s", pipeline.Branch) } @@ -157,25 +156,21 @@ func parsePullHook(hook *github.PullRequestEvent, merge bool) (*github.PullReque event = model.EventPullClosed } - fromFork := hook.GetPullRequest().GetHead().GetRepo().GetID() != hook.GetPullRequest().GetBase().GetRepo().GetID() - pipeline := &model.Pipeline{ - Event: event, - Commit: hook.GetPullRequest().GetHead().GetSHA(), + Event: event, + Commit: &model.Commit{ + SHA: hook.GetPullRequest().GetHead().GetSHA(), + }, ForgeURL: 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(), + Avatar: hook.GetSender().GetAvatarURL(), + Author: hook.GetSender().GetLogin(), Refspec: fmt.Sprintf(refSpec, hook.GetPullRequest().GetHead().GetRef(), hook.GetPullRequest().GetBase().GetRef(), ), - PullRequestLabels: convertLabels(hook.GetPullRequest().Labels), - FromFork: fromFork, + PullRequest: convertPullRequest(hook.GetPullRequest()), } if merge { pipeline.Ref = fmt.Sprintf(mergeRefs, hook.GetPullRequest().GetNumber()) @@ -197,15 +192,14 @@ func parseReleaseHook(hook *github.ReleaseEvent) (*model.Repo, *model.Pipeline) } pipeline := &model.Pipeline{ - Event: model.EventRelease, - ForgeURL: hook.GetRelease().GetHTMLURL(), - Ref: fmt.Sprintf("refs/tags/%s", hook.GetRelease().GetTagName()), - Branch: hook.GetRelease().GetTargetCommitish(), // cspell:disable-line - Message: fmt.Sprintf("created release %s", name), - Author: hook.GetRelease().GetAuthor().GetLogin(), - Avatar: hook.GetRelease().GetAuthor().GetAvatarURL(), - Sender: hook.GetSender().GetLogin(), - IsPrerelease: hook.GetRelease().GetPrerelease(), + Event: model.EventRelease, + ForgeURL: hook.GetRelease().GetHTMLURL(), + Ref: fmt.Sprintf("refs/tags/%s", hook.GetRelease().GetTagName()), + Branch: hook.GetRelease().GetTargetCommitish(), // cspell:disable-line + ReleaseTagTitle: name, + Avatar: hook.GetSender().GetAvatarURL(), + Author: hook.GetSender().GetLogin(), + IsPrerelease: hook.GetRelease().GetPrerelease(), } return convertRepo(hook.GetRepo()), pipeline @@ -221,3 +215,10 @@ func getChangedFilesFromCommits(commits []*github.HeadCommit) []string { } return utils.DeduplicateStrings(files) } + +func convertCommitAuthor(u *github.CommitAuthor) model.CommitAuthor { + return model.CommitAuthor{ + Author: u.GetName(), + Email: u.GetEmail(), + } +} diff --git a/server/forge/github/parse_test.go b/server/forge/github/parse_test.go index a48f0ff5abd..3b3220b8b9b 100644 --- a/server/forge/github/parse_test.go +++ b/server/forge/github/parse_test.go @@ -111,8 +111,8 @@ func Test_parseHook(t *testing.T) { assert.NotNil(t, b) assert.Nil(t, p) assert.Equal(t, model.EventDeploy, b.Event) - assert.Equal(t, "production", b.DeployTo) - assert.Equal(t, "deploy", b.DeployTask) + assert.Equal(t, "production", b.Deployment.Target) + assert.Equal(t, "deploy", b.Deployment.Task) }) t.Run("release hook", func(t *testing.T) { diff --git a/server/forge/gitlab/convert.go b/server/forge/gitlab/convert.go index c8ccdc119ca..472b01513ca 100644 --- a/server/forge/gitlab/convert.go +++ b/server/forge/gitlab/convert.go @@ -19,6 +19,7 @@ import ( "encoding/hex" "fmt" "net/http" + "strconv" "strings" "gitlab.com/gitlab-org/api/client-go" @@ -119,26 +120,28 @@ func convertMergeRequestHook(hook *gitlab.MergeEvent, req *http.Request) (int, * lastCommit := obj.LastCommit - pipeline.Message = lastCommit.Message - pipeline.Commit = lastCommit.ID + pipeline.Commit = &model.Commit{ + Message: lastCommit.Message, + SHA: lastCommit.ID, + Author: model.CommitAuthor{ + Author: lastCommit.Author.Name, + Email: lastCommit.Author.Email, + }, + ForgeURL: lastCommit.URL, + } pipeline.Ref = fmt.Sprintf(mergeRefs, obj.IID) pipeline.Branch = obj.SourceBranch pipeline.Refspec = fmt.Sprintf("%s:%s", obj.SourceBranch, obj.TargetBranch) - - author := lastCommit.Author - - pipeline.Author = author.Name - pipeline.Email = author.Email - - if len(pipeline.Email) != 0 { - pipeline.Avatar = getUserAvatar(pipeline.Email) - } - - pipeline.Title = obj.Title + pipeline.Author = hook.User.Username + pipeline.Avatar = hook.User.AvatarURL pipeline.ForgeURL = obj.URL - pipeline.PullRequestLabels = convertLabels(hook.Labels) - pipeline.FromFork = target.PathWithNamespace != source.PathWithNamespace + pipeline.PullRequest = &model.PullRequest{ + Labels: convertLabels(hook.Labels), + FromFork: target.PathWithNamespace != source.PathWithNamespace, + Title: obj.Title, + Index: model.ForgeRemoteID(strconv.Itoa(obj.IID)), + } return obj.IID, repo, pipeline, nil } @@ -170,21 +173,19 @@ func convertPushHook(hook *gitlab.PushEvent) (*model.Repo, *model.Pipeline, erro } pipeline.Event = model.EventPush - pipeline.Commit = hook.After + pipeline.Commit = &model.Commit{SHA: hook.After} pipeline.Branch = strings.TrimPrefix(hook.Ref, "refs/heads/") pipeline.Ref = hook.Ref + pipeline.Author = hook.UserUsername + pipeline.Avatar = hook.UserAvatar // 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 { - pipeline.Author = cm.Author.Name - pipeline.Email = cm.Author.Email - pipeline.Message = cm.Message - pipeline.Timestamp = cm.Timestamp.Unix() - if len(pipeline.Email) != 0 { - pipeline.Avatar = getUserAvatar(pipeline.Email) - } + pipeline.Commit.Author = model.CommitAuthor{Author: cm.Author.Name, Email: cm.Author.Email} + pipeline.Commit.Message = cm.Message + pipeline.Commit.ForgeURL = cm.URL } files = append(files, cm.Added...) @@ -223,19 +224,21 @@ func convertTagHook(hook *gitlab.TagEvent) (*model.Repo, *model.Pipeline, error) } pipeline.Event = model.EventTag - pipeline.Commit = hook.After + pipeline.Commit = &model.Commit{ + SHA: hook.After, + } pipeline.Branch = strings.TrimPrefix(hook.Ref, "refs/heads/") pipeline.Ref = hook.Ref + pipeline.Author = hook.UserUsername + pipeline.Avatar = hook.UserAvatar + pipeline.ForgeURL = fmt.Sprintf("%s/-/tags/%s", repo.ForgeURL, strings.TrimPrefix(hook.Ref, "refs/tags/")) + // TODO does hook.Commits always contain hook.After? for _, cm := range hook.Commits { if hook.After == cm.ID { - pipeline.Author = cm.Author.Name - pipeline.Email = cm.Author.Email - pipeline.Message = cm.Message - pipeline.Timestamp = cm.Timestamp.Unix() - if len(pipeline.Email) != 0 { - pipeline.Avatar = getUserAvatar(pipeline.Email) - } + pipeline.Commit.Author = model.CommitAuthor{Author: cm.Author.Name, Email: cm.Author.Email} + pipeline.Commit.Message = cm.Message + pipeline.Commit.ForgeURL = cm.URL break } } @@ -264,21 +267,22 @@ func convertReleaseHook(hook *gitlab.ReleaseEvent) (*model.Repo, *model.Pipeline repo.IsSCMPrivate = hook.Project.VisibilityLevel > VisibilityLevelInternal pipeline := &model.Pipeline{ - Event: model.EventRelease, - Commit: hook.Commit.ID, - ForgeURL: hook.URL, - Message: fmt.Sprintf("created release %s", hook.Name), - Sender: hook.Commit.Author.Name, - Author: hook.Commit.Author.Name, - Email: hook.Commit.Author.Email, - + Event: model.EventRelease, + Commit: &model.Commit{ + SHA: hook.Commit.ID, + Author: model.CommitAuthor{ + Author: hook.Commit.Author.Name, + Email: hook.Commit.Author.Email, + }, + Message: hook.Commit.Message, + ForgeURL: hook.Commit.URL, + }, + ForgeURL: hook.URL, + ReleaseTagTitle: hook.Name, // Tag name here is the ref. We should add the refs/tags, so // it is known it's a tag (git-plugin looks for it) Ref: "refs/tags/" + hook.Tag, } - if len(pipeline.Email) != 0 { - pipeline.Avatar = getUserAvatar(pipeline.Email) - } return repo, pipeline, nil } diff --git a/server/forge/gitlab/gitlab.go b/server/forge/gitlab/gitlab.go index fbe14500237..a38ba29592c 100644 --- a/server/forge/gitlab/gitlab.go +++ b/server/forge/gitlab/gitlab.go @@ -357,8 +357,10 @@ func (g *GitLab) PullRequests(ctx context.Context, u *model.User, r *model.Repo, result := make([]*model.PullRequest, len(pullRequests)) for i := range pullRequests { result[i] = &model.PullRequest{ - Index: model.ForgeRemoteID(strconv.Itoa(pullRequests[i].ID)), - Title: pullRequests[i].Title, + Index: model.ForgeRemoteID(strconv.Itoa(pullRequests[i].ID)), + Title: pullRequests[i].Title, + Labels: pullRequests[i].Labels, + FromFork: pullRequests[i].TargetProjectID != pullRequests[i].SourceProjectID, } } return result, err @@ -374,7 +376,7 @@ func (g *GitLab) File(ctx context.Context, user *model.User, repo *model.Repo, p if err != nil { return nil, err } - file, resp, err := client.RepositoryFiles.GetRawFile(_repo.ID, fileName, &gitlab.GetRawFileOptions{Ref: &pipeline.Commit}, gitlab.WithContext(ctx)) + file, resp, err := client.RepositoryFiles.GetRawFile(_repo.ID, fileName, &gitlab.GetRawFileOptions{Ref: &pipeline.Commit.SHA}, gitlab.WithContext(ctx)) if resp != nil && resp.StatusCode == http.StatusNotFound { return nil, errors.Join(err, &forge_types.ErrConfigNotFound{Configs: []string{fileName}}) } @@ -397,7 +399,7 @@ func (g *GitLab) Dir(ctx context.Context, user *model.User, repo *model.Repo, pi opts := &gitlab.ListTreeOptions{ ListOptions: gitlab.ListOptions{PerPage: perPage}, Path: &path, - Ref: &pipeline.Commit, + Ref: &pipeline.Commit.SHA, Recursive: gitlab.Ptr(false), } @@ -445,7 +447,7 @@ func (g *GitLab) Status(ctx context.Context, user *model.User, repo *model.Repo, return err } - _, _, err = client.Commits.SetCommitStatus(_repo.ID, pipeline.Commit, &gitlab.SetCommitStatusOptions{ + _, _, err = client.Commits.SetCommitStatus(_repo.ID, pipeline.Commit.SHA, &gitlab.SetCommitStatusOptions{ State: getStatus(workflow.State), Description: gitlab.Ptr(common.GetPipelineStatusDescription(workflow.State)), TargetURL: gitlab.Ptr(common.GetPipelineStatusURL(repo, pipeline, workflow)), @@ -621,6 +623,8 @@ func (g *GitLab) BranchHead(ctx context.Context, u *model.User, r *model.Repo, b return &model.Commit{ SHA: b.Commit.ID, ForgeURL: b.Commit.WebURL, + Message: b.Commit.Message, + Author: model.CommitAuthor{Author: b.Commit.AuthorName, Email: b.Commit.AuthorEmail}, }, nil } @@ -663,7 +667,16 @@ func (g *GitLab) Hook(ctx context.Context, req *http.Request) (*model.Repo, *mod case *gitlab.TagEvent: return convertTagHook(event) case *gitlab.ReleaseEvent: - return convertReleaseHook(event) + repo, pipeline, err := convertReleaseHook(event) + if err != nil { + return nil, nil, err + } + + if pipeline, err = g.loadReleaseAuthor(ctx, repo, pipeline); err != nil { + return nil, nil, err + } + + return repo, pipeline, nil default: return nil, nil, &forge_types.ErrIgnoreEvent{Event: string(eventType)} } @@ -815,3 +828,41 @@ func (g *GitLab) loadChangedFilesFromMergeRequest(ctx context.Context, tmpRepo * return pipeline, nil } + +func (g *GitLab) loadReleaseAuthor(ctx context.Context, tmpRepo *model.Repo, pipeline *model.Pipeline) (*model.Pipeline, error) { + _store, ok := store.TryFromContext(ctx) + if !ok { + log.Error().Msg("could not get store from context") + return pipeline, nil + } + + repo, err := _store.GetRepoNameFallback(tmpRepo.ForgeRemoteID, tmpRepo.FullName) + if err != nil { + return nil, err + } + + user, err := _store.GetUser(repo.UserID) + if err != nil { + return nil, err + } + + client, err := newClient(g.url, user.AccessToken, g.SkipVerify) + if err != nil { + return nil, err + } + + _repo, err := g.getProject(ctx, client, repo.ForgeRemoteID, repo.Owner, repo.Name) + if err != nil { + return nil, err + } + + release, _, err := client.Releases.GetRelease(_repo.ID, strings.TrimPrefix(pipeline.Ref, "refs/tags/"), gitlab.WithContext(ctx)) + if err != nil { + return nil, err + } + + pipeline.Author = release.Author.Username + pipeline.Avatar = release.Author.AvatarURL + + return pipeline, nil +} diff --git a/server/forge/gitlab/gitlab_test.go b/server/forge/gitlab/gitlab_test.go index 876696ca7e4..445d9a658c2 100644 --- a/server/forge/gitlab/gitlab_test.go +++ b/server/forge/gitlab/gitlab_test.go @@ -162,6 +162,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", pipeline.Ref) + assert.Equal(t, "http://10.40.8.5:3200/test/woodpecker/-/tags/v22", pipeline.ForgeURL) assert.Len(t, pipeline.ChangedFiles, 0) assert.Equal(t, model.EventTag, pipeline.Event) } @@ -183,7 +184,7 @@ func Test_GitLab(t *testing.T) { assert.Equal(t, "main", hookRepo.Branch) assert.Equal(t, "anbraten", hookRepo.Owner) assert.Equal(t, "woodpecker", hookRepo.Name) - assert.Equal(t, "Update client.go 🎉", pipeline.Title) + assert.Equal(t, "Update client.go 🎉", pipeline.PullRequest.Title) assert.Len(t, pipeline.ChangedFiles, 0) // see L217 assert.Equal(t, model.EventPull, pipeline.Event) } @@ -234,7 +235,7 @@ func Test_GitLab(t *testing.T) { assert.Equal(t, "main", hookRepo.Branch) assert.Equal(t, "anbraten", hookRepo.Owner) assert.Equal(t, "woodpecker-test", hookRepo.Name) - assert.Equal(t, "Add new file", pipeline.Title) + assert.Equal(t, "Add new file", pipeline.PullRequest.Title) assert.Len(t, pipeline.ChangedFiles, 0) // see L217 assert.Equal(t, model.EventPullClosed, pipeline.Event) } @@ -255,7 +256,7 @@ func Test_GitLab(t *testing.T) { assert.Equal(t, "main", hookRepo.Branch) assert.Equal(t, "anbraten", hookRepo.Owner) assert.Equal(t, "woodpecker-test", hookRepo.Name) - assert.Equal(t, "Add new file", pipeline.Title) + assert.Equal(t, "Add new file", pipeline.PullRequest.Title) assert.Len(t, pipeline.ChangedFiles, 0) // see L217 assert.Equal(t, model.EventPullClosed, pipeline.Event) } @@ -274,7 +275,7 @@ func Test_GitLab(t *testing.T) { if assert.NotNil(t, hookRepo) && assert.NotNil(t, pipeline) { assert.Equal(t, "refs/tags/0.0.2", pipeline.Ref) assert.Equal(t, "ci", hookRepo.Name) - assert.Equal(t, "created release Awesome version 0.0.2", pipeline.Message) + assert.Equal(t, "Awesome version 0.0.2", pipeline.ReleaseTagTitle) assert.Equal(t, model.EventRelease, pipeline.Event) } }) diff --git a/server/model/commit.go b/server/model/commit.go index 2de74fc44ba..dee6812485b 100644 --- a/server/model/commit.go +++ b/server/model/commit.go @@ -1,6 +1,13 @@ package model type Commit struct { - SHA string - ForgeURL string + SHA string `json:"sha"` + Message string `json:"message"` + ForgeURL string `json:"forge_url"` + Author CommitAuthor `json:"author"` +} + +type CommitAuthor struct { + Author string `json:"author"` + Email string `json:"email"` } diff --git a/server/model/feed.go b/server/model/feed.go index 04357fc4ec2..44b30ee623a 100644 --- a/server/model/feed.go +++ b/server/model/feed.go @@ -17,21 +17,21 @@ package model // Feed represents an item in the user's feed or timeline. type Feed struct { - RepoID int64 `json:"repo_id" xorm:"repo_id"` - ID int64 `json:"id,omitempty" xorm:"pipeline_id"` - Number int64 `json:"number,omitempty" xorm:"pipeline_number"` - Event string `json:"event,omitempty" xorm:"pipeline_event"` - Status string `json:"status,omitempty" xorm:"pipeline_status"` - Created int64 `json:"created,omitempty" xorm:"pipeline_created"` - Started int64 `json:"started,omitempty" xorm:"pipeline_started"` - Finished int64 `json:"finished,omitempty" xorm:"pipeline_finished"` - Commit string `json:"commit,omitempty" xorm:"pipeline_commit"` - Branch string `json:"branch,omitempty" xorm:"pipeline_branch"` - Ref string `json:"ref,omitempty" xorm:"pipeline_ref"` - Refspec string `json:"refspec,omitempty" xorm:"pipeline_refspec"` - Title string `json:"title,omitempty" xorm:"pipeline_title"` - Message string `json:"message,omitempty" xorm:"pipeline_message"` - Author string `json:"author,omitempty" xorm:"pipeline_author"` - Avatar string `json:"author_avatar,omitempty" xorm:"pipeline_avatar"` - Email string `json:"author_email,omitempty" xorm:"pipeline_email"` + RepoID int64 `json:"repo_id" xorm:"repo_id"` + ID int64 `json:"id,omitempty" xorm:"pipeline_id"` + Number int64 `json:"number,omitempty" xorm:"pipeline_number"` + Event string `json:"event,omitempty" xorm:"pipeline_event"` + Status string `json:"status,omitempty" xorm:"pipeline_status"` + Created int64 `json:"created,omitempty" xorm:"pipeline_created"` + Started int64 `json:"started,omitempty" xorm:"pipeline_started"` + Finished int64 `json:"finished,omitempty" xorm:"pipeline_finished"` + Branch string `json:"branch,omitempty" xorm:"pipeline_branch"` + Ref string `json:"ref,omitempty" xorm:"pipeline_ref"` + Refspec string `json:"refspec,omitempty" xorm:"pipeline_refspec"` + Deployment *Deployment `json:"deployment" xorm:"json 'pipeline_deployment'"` + PullRequest *PullRequest `json:"pull_request,omitempty" xorm:"json 'pipeline_pr'"` + ReleaseTagTitle string `json:"release_tag_title,omitempty" xorm:"pipeline_release_tag_title"` + Commit *Commit `json:"commit,omitempty" xorm:"json 'pipeline_commit'"` + Author string `json:"author,omitempty" xorm:"pipeline_author"` + Avatar string `json:"author_avatar,omitempty" xorm:"pipeline_avatar"` } // @name Feed diff --git a/server/model/pipeline.go b/server/model/pipeline.go index b74e8184ac6..dd2c7b7dae1 100644 --- a/server/model/pipeline.go +++ b/server/model/pipeline.go @@ -20,39 +20,37 @@ import ( ) type Pipeline struct { - ID int64 `json:"id" xorm:"pk autoincr 'id'"` - RepoID int64 `json:"-" xorm:"UNIQUE(s) INDEX 'repo_id'"` - Number int64 `json:"number" xorm:"UNIQUE(s) 'number'"` - Author string `json:"author" xorm:"INDEX 'author'"` - Parent int64 `json:"parent" xorm:"parent"` - Event WebhookEvent `json:"event" xorm:"event"` - Status StatusValue `json:"status" xorm:"INDEX 'status'"` - Errors []*types.PipelineError `json:"errors" xorm:"json 'errors'"` - Created int64 `json:"created" xorm:"'created' NOT NULL DEFAULT 0 created"` - Updated int64 `json:"updated" xorm:"'updated' NOT NULL DEFAULT 0 updated"` - Started int64 `json:"started" xorm:"started"` - Finished int64 `json:"finished" xorm:"finished"` - DeployTo string `json:"deploy_to" xorm:"deploy"` - DeployTask string `json:"deploy_task" xorm:"deploy_task"` - Commit string `json:"commit" xorm:"commit"` - Branch string `json:"branch" xorm:"branch"` - Ref string `json:"ref" xorm:"ref"` - Refspec string `json:"refspec" xorm:"refspec"` - Title string `json:"title" xorm:"title"` - Message string `json:"message" xorm:"TEXT 'message'"` - Timestamp int64 `json:"timestamp" xorm:"'timestamp'"` - Sender string `json:"sender" xorm:"sender"` // uses reported user for webhooks and name of cron for cron pipelines - Avatar string `json:"author_avatar" xorm:"varchar(500) avatar"` - Email string `json:"author_email" xorm:"varchar(500) email"` - ForgeURL string `json:"forge_url" xorm:"forge_url"` - Reviewer string `json:"reviewed_by" xorm:"reviewer"` - Reviewed int64 `json:"reviewed" xorm:"reviewed"` - Workflows []*Workflow `json:"workflows,omitempty" xorm:"-"` - ChangedFiles []string `json:"changed_files,omitempty" xorm:"LONGTEXT 'changed_files'"` - AdditionalVariables map[string]string `json:"variables,omitempty" xorm:"json 'additional_variables'"` - PullRequestLabels []string `json:"pr_labels,omitempty" xorm:"json 'pr_labels'"` - IsPrerelease bool `json:"is_prerelease,omitempty" xorm:"is_prerelease"` - FromFork bool `json:"from_fork,omitempty" xorm:"from_fork"` + ID int64 `json:"id" xorm:"pk autoincr 'id'"` + RepoID int64 `json:"-" xorm:"UNIQUE(s) INDEX 'repo_id'"` + Number int64 `json:"number" xorm:"UNIQUE(s) 'number'"` + Parent int64 `json:"parent" xorm:"parent"` + Status StatusValue `json:"status" xorm:"INDEX 'status'"` + Errors []*types.PipelineError `json:"errors" xorm:"json 'errors'"` + Created int64 `json:"created" xorm:"'created' NOT NULL DEFAULT 0 created"` + Updated int64 `json:"updated" xorm:"'updated' NOT NULL DEFAULT 0 updated"` + Started int64 `json:"started" xorm:"started"` + Finished int64 `json:"finished" xorm:"finished"` + Reviewer string `json:"reviewed_by" xorm:"reviewer"` + Reviewed int64 `json:"reviewed" xorm:"reviewed"` + Workflows []*Workflow `json:"workflows,omitempty" xorm:"-"` + AdditionalVariables map[string]string `json:"variables,omitempty" xorm:"json 'additional_variables'"` + + // event related + + Event WebhookEvent `json:"event" xorm:"event"` + Commit *Commit `json:"commit" xorm:"json 'commit'"` + Branch string `json:"branch" xorm:"branch"` + Ref string `json:"ref" xorm:"ref"` + Refspec string `json:"refspec" xorm:"refspec"` + ForgeURL string `json:"forge_url" xorm:"forge_url"` + Author string `json:"author" xorm:"author"` + Avatar string `json:"author_avatar" xorm:"varchar(500) 'avatar'"` + ChangedFiles []string `json:"changed_files,omitempty" xorm:"LONGTEXT 'changed_files'"` + Deployment *Deployment `json:"deployment,omitempty" xorm:"json 'deployment'"` + IsPrerelease bool `json:"is_prerelease,omitempty" xorm:"is_prerelease"` + PullRequest *PullRequest `json:"pull_request,omitempty" xorm:"json 'pr'"` + Cron string `json:"cron,omitempty" xorm:"cron"` + ReleaseTagTitle string `json:"release_tag_title,omitempty" xorm:"release_tag_title"` } // @name Pipeline // TableName return database table name for xorm. @@ -78,3 +76,9 @@ type PipelineOptions struct { Branch string `json:"branch"` Variables map[string]string `json:"variables"` } // @name PipelineOptions + +type Deployment struct { + Target string `json:"target"` + Task string `json:"task"` + Description string `json:"description"` +} diff --git a/server/model/pull_request.go b/server/model/pull_request.go index e5ed5971751..512c3e8c36a 100644 --- a/server/model/pull_request.go +++ b/server/model/pull_request.go @@ -15,6 +15,8 @@ package model type PullRequest struct { - Index ForgeRemoteID `json:"index"` - Title string `json:"title"` + Index ForgeRemoteID `json:"index"` + Title string `json:"title"` + Labels []string `json:"labels,omitempty"` + FromFork bool `json:"from_fork,omitempty"` } // @name PullRequest diff --git a/server/pipeline/create.go b/server/pipeline/create.go index 2a5ebb6da16..4d40907f29b 100644 --- a/server/pipeline/create.go +++ b/server/pipeline/create.go @@ -42,13 +42,9 @@ func Create(ctx context.Context, _store store.Store, repo *model.Repo, pipeline } if pipeline.Event == model.EventPush || pipeline.Event == model.EventPull || pipeline.Event == model.EventPullClosed { - skipMatch := skipPipelineRegex.FindString(pipeline.Message) + skipMatch := skipPipelineRegex.FindString(pipeline.Commit.Message) if len(skipMatch) > 0 { - ref := pipeline.Commit - if len(ref) == 0 { - ref = pipeline.Ref - } - log.Debug().Str("repo", repo.FullName).Msgf("ignoring pipeline as skip-ci was found in the commit (%s) message '%s'", ref, pipeline.Message) + log.Debug().Str("repo", repo.FullName).Msgf("ignoring pipeline as skip-ci was found in the commit (%s) message '%s'", pipeline.Commit.SHA, pipeline.Commit.Message) return nil, ErrFiltered } } diff --git a/server/pipeline/gated.go b/server/pipeline/gated.go index 8804b8c2b93..150c7a23518 100644 --- a/server/pipeline/gated.go +++ b/server/pipeline/gated.go @@ -48,7 +48,7 @@ func needsApproval(repo *model.Repo, pipeline *model.Pipeline) bool { // repository requires approval for pull requests from forks case model.RequireApprovalForks: - if pipeline.Event == model.EventPull && pipeline.FromFork { + if pipeline.Event == model.EventPull && pipeline.PullRequest.FromFork { return true } diff --git a/server/pipeline/gated_test.go b/server/pipeline/gated_test.go index c429c9a0393..868f7701bca 100644 --- a/server/pipeline/gated_test.go +++ b/server/pipeline/gated_test.go @@ -43,8 +43,8 @@ func TestSetGatedState(t *testing.T) { RequireApproval: model.RequireApprovalForks, }, pipeline: &model.Pipeline{ - Event: model.EventPull, - FromFork: true, + Event: model.EventPull, + PullRequest: &model.PullRequest{FromFork: true}, }, expectBlocked: true, }, @@ -54,8 +54,8 @@ func TestSetGatedState(t *testing.T) { RequireApproval: model.RequireApprovalPullRequests, }, pipeline: &model.Pipeline{ - Event: model.EventPull, - FromFork: false, + Event: model.EventPull, + PullRequest: &model.PullRequest{FromFork: false}, }, expectBlocked: true, }, diff --git a/server/pipeline/stepbuilder/metadata.go b/server/pipeline/stepbuilder/metadata.go index 8ceb0dbcd00..cbbd3c275d6 100644 --- a/server/pipeline/stepbuilder/metadata.go +++ b/server/pipeline/stepbuilder/metadata.go @@ -100,42 +100,45 @@ func metadataPipelineFromModelPipeline(pipeline *model.Pipeline, includeParent b return metadata.Pipeline{} } - cron := "" - if pipeline.Event == model.EventCron { - cron = pipeline.Sender - } - parent := int64(0) if includeParent { parent = pipeline.Parent } - return metadata.Pipeline{ - Number: pipeline.Number, - Parent: parent, - Created: pipeline.Created, - Started: pipeline.Started, - Finished: pipeline.Finished, - Status: string(pipeline.Status), - Event: string(pipeline.Event), - ForgeURL: pipeline.ForgeURL, - DeployTo: pipeline.DeployTo, - DeployTask: pipeline.DeployTask, + metadata := metadata.Pipeline{ + Number: pipeline.Number, + Parent: parent, + Created: pipeline.Created, + Started: pipeline.Started, + Finished: pipeline.Finished, + Status: string(pipeline.Status), + Event: string(pipeline.Event), + ForgeURL: pipeline.ForgeURL, Commit: metadata.Commit{ - Sha: pipeline.Commit, + Sha: pipeline.Commit.SHA, Ref: pipeline.Ref, Refspec: pipeline.Refspec, Branch: pipeline.Branch, - Message: pipeline.Message, + Message: pipeline.Commit.Message, Author: metadata.Author{ - Name: pipeline.Author, - Email: pipeline.Email, - Avatar: pipeline.Avatar, + Name: pipeline.Commit.Author.Author, + Email: pipeline.Commit.Author.Email, }, - ChangedFiles: pipeline.ChangedFiles, - PullRequestLabels: pipeline.PullRequestLabels, - IsPrerelease: pipeline.IsPrerelease, + ChangedFiles: pipeline.ChangedFiles, + + IsPrerelease: pipeline.IsPrerelease, }, - Cron: cron, + Release: pipeline.ReleaseTagTitle, + Cron: pipeline.Cron, } + + if pipeline.PullRequest != nil { + metadata.Commit.PullRequestLabels = pipeline.PullRequest.Labels + } + if pipeline.Deployment != nil { + metadata.DeployTo = pipeline.Deployment.Target + metadata.DeployTask = pipeline.Deployment.Task + } + + return metadata } diff --git a/server/pipeline/stepbuilder/metadata_test.go b/server/pipeline/stepbuilder/metadata_test.go index c63680d9495..c91b2e64dcf 100644 --- a/server/pipeline/stepbuilder/metadata_test.go +++ b/server/pipeline/stepbuilder/metadata_test.go @@ -41,6 +41,8 @@ func TestMetadataFromStruct(t *testing.T) { }{ { name: "Test with empty info", + pipeline: &model.Pipeline{Commit: &model.Commit{}}, + prev: &model.Pipeline{Commit: &model.Commit{}}, expectedMetadata: metadata.Metadata{Sys: metadata.System{Name: "woodpecker"}}, expectedEnviron: map[string]string{ "CI": "woodpecker", @@ -58,8 +60,8 @@ func TestMetadataFromStruct(t *testing.T) { name: "Test with forge", forge: forge, repo: &model.Repo{FullName: "testUser/testRepo", ForgeURL: "https://gitea.com/testUser/testRepo", Clone: "https://gitea.com/testUser/testRepo.git", CloneSSH: "git@gitea.com:testUser/testRepo.git", Branch: "main", IsSCMPrivate: true}, - pipeline: &model.Pipeline{Number: 3, ChangedFiles: []string{"test.go", "markdown file.md"}}, - prev: &model.Pipeline{Number: 2}, + pipeline: &model.Pipeline{Number: 3, ChangedFiles: []string{"test.go", "markdown file.md"}, Commit: &model.Commit{}}, + prev: &model.Pipeline{Number: 2, Commit: &model.Commit{}}, workflow: &model.Workflow{Name: "hello"}, sysURL: "https://example.com", expectedMetadata: metadata.Metadata{ diff --git a/server/pipeline/stepbuilder/stepBuilder_test.go b/server/pipeline/stepbuilder/stepBuilder_test.go index bca38e79833..547bf8a2cd9 100644 --- a/server/pipeline/stepbuilder/stepBuilder_test.go +++ b/server/pipeline/stepbuilder/stepBuilder_test.go @@ -39,10 +39,12 @@ func TestGlobalEnvsubst(t *testing.T) { }, Repo: &model.Repo{}, Curr: &model.Pipeline{ - Message: "aaa", - Event: model.EventPush, + Commit: &model.Commit{ + Message: "aaa", + }, + Event: model.EventPush, }, - Prev: &model.Pipeline{}, + Prev: &model.Pipeline{Commit: &model.Commit{}}, Netrc: &model.Netrc{}, Secs: []*model.Secret{}, Regs: []*model.Registry{}, @@ -78,10 +80,12 @@ func TestMissingGlobalEnvsubst(t *testing.T) { }, Repo: &model.Repo{}, Curr: &model.Pipeline{ - Message: "aaa", - Event: model.EventPush, + Commit: &model.Commit{ + Message: "aaa", + }, + Event: model.EventPush, }, - Prev: &model.Pipeline{}, + Prev: &model.Pipeline{Commit: &model.Commit{}}, Netrc: &model.Netrc{}, Secs: []*model.Secret{}, Regs: []*model.Registry{}, @@ -113,10 +117,11 @@ func TestMultilineEnvsubst(t *testing.T) { Forge: getMockForge(t), Repo: &model.Repo{}, Curr: &model.Pipeline{ - Message: `aaa -bbb`, + Commit: &model.Commit{ + Message: "aaa\nbbb", + }, }, - Prev: &model.Pipeline{}, + Prev: &model.Pipeline{Commit: &model.Commit{}}, Netrc: &model.Netrc{}, Secs: []*model.Secret{}, Regs: []*model.Registry{}, @@ -157,9 +162,10 @@ func TestMultiPipeline(t *testing.T) { Forge: getMockForge(t), Repo: &model.Repo{}, Curr: &model.Pipeline{ - Event: model.EventPush, + Event: model.EventPush, + Commit: &model.Commit{}, }, - Prev: &model.Pipeline{}, + Prev: &model.Pipeline{Commit: &model.Commit{}}, Netrc: &model.Netrc{}, Secs: []*model.Secret{}, Regs: []*model.Registry{}, @@ -198,9 +204,10 @@ func TestDependsOn(t *testing.T) { Forge: getMockForge(t), Repo: &model.Repo{}, Curr: &model.Pipeline{ - Event: model.EventPush, + Event: model.EventPush, + Commit: &model.Commit{}, }, - Prev: &model.Pipeline{}, + Prev: &model.Pipeline{Commit: &model.Commit{}}, Netrc: &model.Netrc{}, Secs: []*model.Secret{}, Regs: []*model.Registry{}, @@ -253,9 +260,10 @@ func TestRunsOn(t *testing.T) { Forge: getMockForge(t), Repo: &model.Repo{}, Curr: &model.Pipeline{ - Event: model.EventPush, + Event: model.EventPush, + Commit: &model.Commit{}, }, - Prev: &model.Pipeline{}, + Prev: &model.Pipeline{Commit: &model.Commit{}}, Netrc: &model.Netrc{}, Secs: []*model.Secret{}, Regs: []*model.Registry{}, @@ -294,9 +302,10 @@ func TestPipelineName(t *testing.T) { Forge: getMockForge(t), Repo: &model.Repo{Config: ".woodpecker"}, Curr: &model.Pipeline{ - Event: model.EventPush, + Event: model.EventPush, + Commit: &model.Commit{}, }, - Prev: &model.Pipeline{}, + Prev: &model.Pipeline{Commit: &model.Commit{}}, Netrc: &model.Netrc{}, Secs: []*model.Secret{}, Regs: []*model.Registry{}, @@ -338,8 +347,9 @@ func TestBranchFilter(t *testing.T) { Curr: &model.Pipeline{ Branch: "dev", Event: model.EventPush, + Commit: &model.Commit{}, }, - Prev: &model.Pipeline{}, + Prev: &model.Pipeline{Commit: &model.Commit{}}, Netrc: &model.Netrc{}, Secs: []*model.Secret{}, Regs: []*model.Registry{}, @@ -381,8 +391,8 @@ func TestRootWhenFilter(t *testing.T) { b := StepBuilder{ Forge: getMockForge(t), Repo: &model.Repo{}, - Curr: &model.Pipeline{Event: "tag"}, - Prev: &model.Pipeline{}, + Curr: &model.Pipeline{Event: "tag", Commit: &model.Commit{}}, + Prev: &model.Pipeline{Commit: &model.Commit{}}, Netrc: &model.Netrc{}, Secs: []*model.Secret{}, Regs: []*model.Registry{}, @@ -426,13 +436,14 @@ func TestZeroSteps(t *testing.T) { pipeline := &model.Pipeline{ Branch: "dev", Event: model.EventPush, + Commit: &model.Commit{}, } b := StepBuilder{ Forge: getMockForge(t), Repo: &model.Repo{}, Curr: pipeline, - Prev: &model.Pipeline{}, + Prev: &model.Pipeline{Commit: &model.Commit{}}, Netrc: &model.Netrc{}, Secs: []*model.Secret{}, Regs: []*model.Registry{}, @@ -466,13 +477,14 @@ func TestZeroStepsAsMultiPipelineDeps(t *testing.T) { pipeline := &model.Pipeline{ Branch: "dev", Event: model.EventPush, + Commit: &model.Commit{}, } b := StepBuilder{ Forge: getMockForge(t), Repo: &model.Repo{}, Curr: pipeline, - Prev: &model.Pipeline{}, + Prev: &model.Pipeline{Commit: &model.Commit{}}, Netrc: &model.Netrc{}, Secs: []*model.Secret{}, Regs: []*model.Registry{}, @@ -524,13 +536,14 @@ func TestZeroStepsAsMultiPipelineTransitiveDeps(t *testing.T) { pipeline := &model.Pipeline{ Branch: "dev", Event: model.EventPush, + Commit: &model.Commit{}, } b := StepBuilder{ Forge: getMockForge(t), Repo: &model.Repo{}, Curr: pipeline, - Prev: &model.Pipeline{}, + Prev: &model.Pipeline{Commit: &model.Commit{}}, Netrc: &model.Netrc{}, Secs: []*model.Secret{}, Regs: []*model.Registry{}, diff --git a/server/services/config/combined_test.go b/server/services/config/combined_test.go index 6097f980512..a953e5168da 100644 --- a/server/services/config/combined_test.go +++ b/server/services/config/combined_test.go @@ -222,7 +222,7 @@ func TestFetchFromConfigService(t *testing.T) { f, &model.User{AccessToken: "xxx"}, repo, - &model.Pipeline{Commit: "89ab7b2d6bfb347144ac7c557e638ab402848fee"}, + &model.Pipeline{Commit: &model.Commit{SHA: "89ab7b2d6bfb347144ac7c557e638ab402848fee"}}, []*forge_types.FileMeta{}, false, ) diff --git a/server/services/config/forge_test.go b/server/services/config/forge_test.go index 898d511e4e2..4f65a4fba43 100644 --- a/server/services/config/forge_test.go +++ b/server/services/config/forge_test.go @@ -314,7 +314,7 @@ func TestFetch(t *testing.T) { f, &model.User{AccessToken: "xxx"}, repo, - &model.Pipeline{Commit: "89ab7b2d6bfb347144ac7c557e638ab402848fee"}, + &model.Pipeline{Commit: &model.Commit{SHA: "89ab7b2d6bfb347144ac7c557e638ab402848fee"}}, nil, false, ) diff --git a/server/store/datastore/config_test.go b/server/store/datastore/config_test.go index 81a0114d0ef..fbeaa76fac7 100644 --- a/server/store/datastore/config_test.go +++ b/server/store/datastore/config_test.go @@ -53,7 +53,7 @@ func TestConfig(t *testing.T) { pipeline := &model.Pipeline{ RepoID: repo.ID, Status: model.StatusRunning, - Commit: "85f8c029b902ed9400bc600bac301a0aadb144ac", + Commit: &model.Commit{SHA: "85f8c029b902ed9400bc600bac301a0aadb144ac"}, } assert.NoError(t, store.CreatePipeline(pipeline)) diff --git a/server/store/datastore/feed.go b/server/store/datastore/feed.go index 698507c5ad3..9e04d04e346 100644 --- a/server/store/datastore/feed.go +++ b/server/store/datastore/feed.go @@ -35,10 +35,10 @@ pipelines.%s as pipeline_commit, pipelines.branch as pipeline_branch, pipelines.ref as pipeline_ref, pipelines.refspec as pipeline_refspec, -pipelines.title as pipeline_title, -pipelines.message as pipeline_message, +pipelines.deployment as pipeline_deployment, +pipelines.pr as pipeline_pr, +pipelines.release_tag_title as pipeline_release_tag_title, pipelines.author as pipeline_author, -pipelines.email as pipeline_email, pipelines.avatar as pipeline_avatar` return fmt.Sprintf(feedTemplate, s.quoteIdentifier("commit")) diff --git a/server/store/datastore/feed_test.go b/server/store/datastore/feed_test.go index 9f26b228905..65aeb1d42fc 100644 --- a/server/store/datastore/feed_test.go +++ b/server/store/datastore/feed_test.go @@ -51,14 +51,18 @@ func TestGetPipelineQueue(t *testing.T) { RepoID: repo1.ID, Status: model.StatusPending, Number: 1, - Event: "push", - Commit: "abc123", + Event: model.EventPush, + Commit: &model.Commit{ + SHA: "abc123", + Message: "Initial commit", + Author: model.CommitAuthor{ + Author: "joe", + Email: "joe@example.com", + }, + }, Branch: "main", Ref: "refs/heads/main", - Message: "Initial commit", Author: "joe", - Email: "foo@bar.com", - Title: "First pipeline", } assert.NoError(t, store.CreatePipeline(pipeline1)) @@ -75,10 +79,11 @@ func TestGetPipelineQueue(t *testing.T) { assert.Equal(t, pipeline1.Commit, feedItem.Commit) assert.Equal(t, pipeline1.Branch, feedItem.Branch) assert.Equal(t, pipeline1.Ref, feedItem.Ref) - assert.Equal(t, pipeline1.Title, feedItem.Title) - assert.Equal(t, pipeline1.Message, feedItem.Message) + assert.Equal(t, pipeline1.Commit.Message, feedItem.Commit.Message) + assert.Equal(t, pipeline1.Commit.SHA, feedItem.Commit.SHA) + assert.Equal(t, pipeline1.Commit.Author.Email, feedItem.Commit.Author.Email) + assert.Equal(t, pipeline1.Commit.Author.Author, feedItem.Commit.Author.Author) assert.Equal(t, pipeline1.Author, feedItem.Author) - assert.Equal(t, pipeline1.Email, feedItem.Email) } func TestUserFeed(t *testing.T) { diff --git a/server/store/datastore/migration/024_update_pipeline_messages.go b/server/store/datastore/migration/024_update_pipeline_messages.go new file mode 100644 index 00000000000..5ea22a004c7 --- /dev/null +++ b/server/store/datastore/migration/024_update_pipeline_messages.go @@ -0,0 +1,159 @@ +// Copyright 2024 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 migration + +import ( + "strings" + + "src.techknowlogick.com/xormigrate" + "xorm.io/xorm" + + "go.woodpecker-ci.org/woodpecker/v3/server/model" +) + +// perPage024 set the size of the slice to read per page. +var perPage024 = 100 + +var updatePipelineMessages = xormigrate.Migration{ + ID: "update-pipeline-messages", + MigrateSession: func(sess *xorm.Session) error { + type commitAuthor struct { + Author string `json:"author"` + Email string `json:"email"` + } + type commit struct { + SHA string `json:"sha"` + Message string `json:"message"` + ForgeURL string `json:"forge_url"` + Author commitAuthor `json:"author"` + } + type pullRequest struct { + Index model.ForgeRemoteID `json:"index"` + Title string `json:"title"` + Labels []string `json:"labels,omitempty"` + FromFork bool `json:"from_fork,omitempty"` + } + + type deployment struct { + Target string `json:"target"` + Task string `json:"task"` + Description string `json:"description"` + } + + type pipelines struct { + ID int64 `xorm:"pk autoincr 'id'"` + Event model.WebhookEvent `xorm:"event"` + Author string `xorm:"INDEX 'author'"` + ForgeURL string `xorm:"forge_url"` + Ref string `xorm:"ref"` + + Commit string `xorm:"commit"` + Title string `xorm:"title"` + Message string `xorm:"TEXT 'message'"` + Sender string `xorm:"sender"` // uses reported user for webhooks and name of cron for cron pipelines + DeployTo string `xorm:"deploy"` + DeployTask string `xorm:"deploy_task"` + PullRequestLabels []string `xorm:"json 'pr_labels'"` + FromFork bool `xorm:"from_fork"` + + // new fields + CommitNew *commit `xorm:"json 'commit_new'"` + Deployment *deployment `xorm:"json 'deployment'"` + PullRequest *pullRequest `xorm:"json 'pr'"` + Cron string `xorm:"cron"` + ReleaseTagTitle string `xorm:"release_tag_title"` + + // removed without replacement + Timestamp int64 `xorm:"'timestamp'"` + Email string `xorm:"varchar(500) email"` + } + + if err := sess.Sync(new(pipelines)); err != nil { + return err + } + + page := 0 + oldPipelines := make([]*pipelines, 0, perPage024) + + for { + oldPipelines = oldPipelines[:0] + + err := sess.Limit(perPage024, page*perPage024).Cols("id", "event", "author", "forge_url", "commit", "title", "message", "sender", "deploy", "deploy_task", "pr_labels", "from_fork", "email").Find(&oldPipelines) + if err != nil { + return err + } + + for _, oldPipeline := range oldPipelines { + var newPipeline pipelines + newPipeline.ID = oldPipeline.ID + newPipeline.CommitNew = &commit{ + SHA: oldPipeline.Commit, + Message: oldPipeline.Message, + ForgeURL: oldPipeline.ForgeURL, + Author: commitAuthor{ + Author: oldPipeline.Author, + Email: oldPipeline.Email, + }, + } + + switch oldPipeline.Event { + case model.EventRelease: + newPipeline.ReleaseTagTitle = strings.TrimPrefix(oldPipeline.Message, "created release ") + case model.EventCron: + newPipeline.Cron = oldPipeline.Sender + case model.EventPull, model.EventPullClosed: + newPipeline.PullRequest = &pullRequest{ + Title: oldPipeline.Title, + Index: model.ForgeRemoteID( + strings.TrimSuffix( + strings.TrimSuffix( + strings.TrimPrefix( + strings.TrimPrefix(oldPipeline.Ref, "refs/pull/"), + "refs/merge-requests/", + ), + "/merge"), + "/head", + ), + ), + FromFork: oldPipeline.FromFork, + Labels: oldPipeline.PullRequestLabels, + } + case model.EventDeploy: + newPipeline.Deployment = &deployment{ + Description: oldPipeline.Message, + Target: oldPipeline.DeployTo, + Task: oldPipeline.DeployTask, + } + } + + if _, err := sess.ID(oldPipeline.ID).Cols("commit_new", "deployment", "pr", "cron", "release_tag_title").Update(newPipeline); err != nil { + return err + } + } + + if len(oldPipelines) < perPage024 { + break + } + + page++ + } + + if err := dropTableColumns(sess, "pipelines", "email", "timestamp", "sender", "commit", "title", "message", "deploy", "deploy_task", "pr_labels", "from_fork"); err != nil { + return err + } + + return renameColumn(sess, "pipelines", "commit_new", "commit") + }, +} diff --git a/server/store/datastore/migration/migration.go b/server/store/datastore/migration/migration.go index d056aad56fa..1e9732b6500 100644 --- a/server/store/datastore/migration/migration.go +++ b/server/store/datastore/migration/migration.go @@ -52,6 +52,7 @@ var migrationTasks = []*xormigrate.Migration{ &renameTokenFields, &setNewDefaultsForRequireApproval, &removeRepoScm, + &updatePipelineMessages, } var allBeans = []any{ diff --git a/server/store/datastore/pipeline_test.go b/server/store/datastore/pipeline_test.go index fed95149b2e..a47fc57ff68 100644 --- a/server/store/datastore/pipeline_test.go +++ b/server/store/datastore/pipeline_test.go @@ -54,14 +54,14 @@ func TestPipelines(t *testing.T) { pipeline = model.Pipeline{ RepoID: repo.ID, Status: model.StatusSuccess, - Commit: "85f8c029b902ed9400bc600bac301a0aadb144ac", + Commit: &model.Commit{SHA: "85f8c029b902ed9400bc600bac301a0aadb144ac"}, Branch: "some-branch", } err = store.CreatePipeline(&pipeline) assert.NoError(t, err) assert.NotZero(t, pipeline.ID) assert.EqualValues(t, 1, pipeline.Number) - assert.Equal(t, "85f8c029b902ed9400bc600bac301a0aadb144ac", pipeline.Commit) + assert.Equal(t, "85f8c029b902ed9400bc600bac301a0aadb144ac", pipeline.Commit.SHA) count, err = store.GetPipelineCount() assert.NoError(t, err) @@ -109,7 +109,7 @@ func TestPipelines(t *testing.T) { RepoID: repo.ID, Status: model.StatusRunning, Branch: "main", - Commit: "85f8c029b902ed9400bc600bac301a0aadb144aa", + Commit: &model.Commit{SHA: "85f8c029b902ed9400bc600bac301a0aadb144ac"}, } err1 = store.CreatePipeline(pipeline3, []*model.Step{}...) assert.NoError(t, err1) diff --git a/web/eslint.config.js b/web/eslint.config.js index ac3bf082785..b6fb3dafed1 100644 --- a/web/eslint.config.js +++ b/web/eslint.config.js @@ -92,7 +92,8 @@ export default antfu( order: ['template', 'script', 'style'], }, ], - 'vue/singleline-html-element-content-newline': ['off'], + 'vue/singleline-html-element-content-newline': 'off', + 'vue/html-indent': 'off', }, }, diff --git a/web/src/components/repo/pipeline/PipelineItem.vue b/web/src/components/repo/pipeline/PipelineItem.vue index c17b6d0e547..f1d0d53c83d 100644 --- a/web/src/components/repo/pipeline/PipelineItem.vue +++ b/web/src/components/repo/pipeline/PipelineItem.vue @@ -23,17 +23,32 @@ -
- - - - - +
+ + + + + + {{ shortMessage }} + +
+ +
- {{ shortMessage }} - + + + + + + {{ shortContext }} +
- {{ pipeline.commit.slice(0, 10) }} + {{ pipeline.commit.sha.slice(0, 10) }}
@@ -90,7 +105,7 @@ const props = defineProps<{ const { t } = useI18n(); const pipeline = toRef(props, 'pipeline'); -const { since, duration, message, shortMessage, prettyRef, created } = usePipeline(pipeline); +const { since, duration, message, shortMessage, context, shortContext, prettyRef, created } = usePipeline(pipeline); const pipelineEventTitle = computed(() => { switch (pipeline.value.event) { diff --git a/web/src/components/repo/pipeline/PipelineStepList.vue b/web/src/components/repo/pipeline/PipelineStepList.vue index ac57697847e..9358d843d22 100644 --- a/web/src/components/repo/pipeline/PipelineStepList.vue +++ b/web/src/components/repo/pipeline/PipelineStepList.vue @@ -8,44 +8,49 @@
- {{ pipeline.author }} + {{ pipeline.event === 'cron' ? pipeline.cron : pipeline.author }} - + + {{ prettyRef }} + {{ prettyRef }} -
- - - {{ prettyRef }} -
- - {{ pipeline.commit.slice(0, 10) }} + {{ pipeline.commit.sha.slice(0, 10) }}
diff --git a/web/src/compositions/usePipeline.ts b/web/src/compositions/usePipeline.ts index 27a158c7112..05c9c17a7bf 100644 --- a/web/src/compositions/usePipeline.ts +++ b/web/src/compositions/usePipeline.ts @@ -75,34 +75,36 @@ export default (pipeline: Ref) => { return prettyDuration(durationElapsed.value); }); - const message = computed(() => emojify(pipeline.value?.message ?? '')); + const message = computed(() => emojify(pipeline.value?.commit.message ?? '')); const shortMessage = computed(() => message.value.split('\n')[0]); - const prTitleWithDescription = computed(() => emojify(pipeline.value?.title ?? '')); - const prTitle = computed(() => prTitleWithDescription.value.split('\n')[0]); - - const prettyRef = computed(() => { - if (pipeline.value?.event === 'push' || pipeline.value?.event === 'deployment') { - return pipeline.value.branch; - } - - if (pipeline.value?.event === 'cron') { - return pipeline.value.ref.replaceAll('refs/heads/', ''); + const context = computed(() => { + let context = ''; + if (pipeline.value?.event === 'pull_request' || pipeline.value?.event === 'pull_request_closed') { + context = pipeline.value.pull_request!.title; + } else if (pipeline.value?.event === 'deployment') { + context = pipeline.value.deployment!.description; + } else if (pipeline.value?.event === 'release' || pipeline.value?.event === 'tag') { + context = pipeline.value.release_tag_title || ''; } + return emojify(context); + }); + const shortContext = computed(() => context.value.split('\n')[0]); - if (pipeline.value?.event === 'tag') { + const prettyRef = computed(() => { + if (pipeline.value?.event === 'tag' || pipeline.value?.event === 'release') { return pipeline.value.ref.replaceAll('refs/tags/', ''); } if (pipeline.value?.event === 'pull_request' || pipeline.value?.event === 'pull_request_closed') { - return `#${pipeline.value.ref - .replaceAll('refs/pull/', '') - .replaceAll('refs/merge-requests/', '') - .replaceAll('/merge', '') - .replaceAll('/head', '')}`; + return `#${pipeline.value.pull_request!.index}`; + } + + if (!pipeline.value) { + return ''; } - return pipeline.value?.ref; + return pipeline.value.branch || pipeline.value.ref; }); const created = computed(() => { @@ -115,5 +117,5 @@ export default (pipeline: Ref) => { return toLocaleString(new Date(start * 1000)); }); - return { since, duration, message, shortMessage, prTitle, prTitleWithDescription, prettyRef, created }; + return { since, duration, message, shortMessage, shortContext, context, prettyRef, created }; }; diff --git a/web/src/lib/api/types/pipeline.ts b/web/src/lib/api/types/pipeline.ts index 2ca9ccf0d84..fc0c95f6830 100644 --- a/web/src/lib/api/types/pipeline.ts +++ b/web/src/lib/api/types/pipeline.ts @@ -1,3 +1,4 @@ +import type { PullRequest } from './pull_request'; import type { WebhookEvents } from './webhook'; export interface PipelineError { @@ -36,43 +37,24 @@ export interface Pipeline { // When the pipeline was finished. finished: number; - // Where the deployment should go. - deploy_to: string; - // The commit for the pipeline. - commit: string; + commit: PipelineCommit; // The branch the commit was pushed to. branch: string; - // The commit message. - message: string; - - // When the commit was created. - timestamp: number; - // The alias for the commit. ref: string; // The mapping from the local repository to a branch in the forge. refspec: string; - // The clone URL of the forge repository. - clone_url: string; - - title: string; - - sender: string; - // The login for the author of the commit. author: string; // The avatar for the author of the commit. author_avatar: string; - // email for the author of the commit. - author_email: string; - // This url will point to the repository state associated with the pipeline's commit. forge_url: string; @@ -80,6 +62,14 @@ export interface Pipeline { reviewed: number; + pull_request?: PullRequest; + + deployment?: PipelineDeployment; + + release_tag_title?: string; + + cron: string; + // The steps associated with this pipeline. // A pipeline will have multiple steps if a matrix pipeline was used or if a rebuild was requested. workflows?: PipelineWorkflow[]; @@ -128,6 +118,22 @@ export interface PipelineStep { type?: StepType; } +export interface PipelineCommit { + sha: string; + message: string; + forge_url: string; + author: PipelineCommitAuthor; +} + +export interface PipelineCommitAuthor { + author: string; + email: string; +} + +export interface PipelineDeployment { + description: string; +} + export interface PipelineLog { id: number; step_id: number; diff --git a/woodpecker-go/woodpecker/types.go b/woodpecker-go/woodpecker/types.go index 3e36b1dfc25..a069e3e5e39 100644 --- a/woodpecker-go/woodpecker/types.go +++ b/woodpecker-go/woodpecker/types.go @@ -96,32 +96,54 @@ type ( // Pipeline defines a pipeline object. Pipeline struct { - ID int64 `json:"id"` - Number int64 `json:"number"` - Parent int64 `json:"parent"` - Event string `json:"event"` - Status string `json:"status"` - Errors []*PipelineError `json:"errors"` - Created int64 `json:"created_at"` - Updated int64 `json:"updated_at"` - Started int64 `json:"started_at"` - Finished int64 `json:"finished_at"` - Deploy string `json:"deploy_to"` - Commit string `json:"commit"` - Branch string `json:"branch"` - Ref string `json:"ref"` - Refspec string `json:"refspec"` - Title string `json:"title"` - Message string `json:"message"` - Timestamp int64 `json:"timestamp"` - Sender string `json:"sender"` - Author string `json:"author"` - Avatar string `json:"author_avatar"` - Email string `json:"author_email"` - ForgeURL string `json:"forge_url"` - Reviewer string `json:"reviewed_by"` - Reviewed int64 `json:"reviewed_at"` - Workflows []*Workflow `json:"workflows,omitempty"` + ID int64 `json:"id"` + Number int64 `json:"number"` + Parent int64 `json:"parent"` + Event string `json:"event"` + Status string `json:"status"` + Errors []*PipelineError `json:"errors"` + Created int64 `json:"created_at"` + Updated int64 `json:"updated_at"` + Started int64 `json:"started_at"` + Finished int64 `json:"finished_at"` + Deployment *Deployment `json:"deployment"` + Commit *Commit `json:"commit"` + Branch string `json:"branch"` + Ref string `json:"ref"` + Refspec string `json:"refspec"` + PullRequest *PullRequest `json:"pull_request,omitempty"` + Author string `json:"author"` + Avatar string `json:"author_avatar"` + ForgeURL string `json:"forge_url"` + Reviewer string `json:"reviewed_by"` + Reviewed int64 `json:"reviewed_at"` + Workflows []*Workflow `json:"workflows,omitempty"` + } + + Commit struct { + SHA string `json:"sha"` + Message string `json:"message"` + ForgeURL string `json:"forge_url"` + Author *CommitAuthor `json:"author"` + } + + CommitAuthor struct { + Author string `json:"author"` + Email string `json:"email"` + Avatar string `json:"avatar"` + } + + Deployment struct { + Target string `json:"target"` + Task string `json:"task"` + Description string `json:"description"` + } + + PullRequest struct { + Index string `json:"index"` + Title string `json:"title"` + PullRequestLabels []string `json:"pr_labels,omitempty"` + FromFork bool `json:"from_fork,omitempty"` } // Workflow represents a workflow in the pipeline.