From 4df2e411508dce4bcc7024f28fbaeb5373ed947f Mon Sep 17 00:00:00 2001 From: qwerty287 <80460567+qwerty287@users.noreply.github.com> Date: Sun, 31 May 2026 12:23:53 +0200 Subject: [PATCH] Add commit timestamp env vars (#6670) Co-authored-by: Akash Kumar --- cli/exec/flags.go | 10 ++ cli/exec/metadata.go | 2 + cmd/server/openapi/docs.go | 3 + docs/docs/20-usage/50-environment.md | 2 + pipeline/frontend/metadata/environment.go | 2 + .../frontend/metadata/environment_test.go | 6 +- pipeline/frontend/metadata/types.go | 1 + server/pipeline/metadata/metadata.go | 11 +- server/pipeline/metadata/metadata_test.go | 115 +++++++++++++----- 9 files changed, 116 insertions(+), 36 deletions(-) diff --git a/cli/exec/flags.go b/cli/exec/flags.go index dafe9f81d4..1c517b19d2 100644 --- a/cli/exec/flags.go +++ b/cli/exec/flags.go @@ -284,6 +284,11 @@ var flags = []cli.Flag{ Name: "commit-message", Usage: "Set the metadata environment variable \"CI_COMMIT_MESSAGE\".", }, + &cli.Int64Flag{ + Sources: cli.EnvVars("CI_COMMIT_TIMESTAMP"), + Name: "commit-timestamp", + Usage: "Set the metadata environment variable \"CI_COMMIT_TIMESTAMP\".", + }, &cli.StringFlag{ Sources: cli.EnvVars("CI_COMMIT_AUTHOR"), Name: "commit-author-name", @@ -387,6 +392,11 @@ var flags = []cli.Flag{ Name: "prev-commit-message", Usage: "Set the metadata environment variable \"CI_PREV_COMMIT_MESSAGE\".", }, + &cli.Int64Flag{ + Sources: cli.EnvVars("CI_PREV_COMMIT_TIMESTAMP"), + Name: "prev-commit-message", + Usage: "Set the metadata environment variable \"CI_PREV_COMMIT_TIMESTAMP\".", + }, &cli.StringFlag{ Sources: cli.EnvVars("CI_PREV_COMMIT_AUTHOR"), Name: "prev-commit-author-name", diff --git a/cli/exec/metadata.go b/cli/exec/metadata.go index ba3fbb82a0..5b3f84e4cc 100644 --- a/cli/exec/metadata.go +++ b/cli/exec/metadata.go @@ -104,6 +104,7 @@ func metadataFromContext(_ context.Context, c *cli.Command, axis matrix.Axis) (* metadataFileAndOverrideOrDefault(c, "commit-refspec", func(s string) { m.Curr.Commit.Refspec = s }, c.String) metadataFileAndOverrideOrDefault(c, "commit-branch", func(s string) { m.Curr.Commit.Branch = s }, c.String) metadataFileAndOverrideOrDefault(c, "commit-message", func(s string) { m.Curr.Commit.Message = s }, c.String) + metadataFileAndOverrideOrDefault(c, "commit-timestamp", func(i int64) { m.Curr.Commit.Timestamp = i }, c.Int64) 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) // TODO remove in next major @@ -128,6 +129,7 @@ func metadataFromContext(_ context.Context, c *cli.Command, axis matrix.Axis) (* metadataFileAndOverrideOrDefault(c, "prev-commit-refspec", func(s string) { m.Prev.Commit.Refspec = s }, c.String) metadataFileAndOverrideOrDefault(c, "prev-commit-branch", func(s string) { m.Prev.Commit.Branch = s }, c.String) metadataFileAndOverrideOrDefault(c, "prev-commit-message", func(s string) { m.Prev.Commit.Message = s }, c.String) + metadataFileAndOverrideOrDefault(c, "prev-commit-timestamp", func(i int64) { m.Prev.Commit.Timestamp = i }, c.Int64) 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) // TODO remove in next major diff --git a/cmd/server/openapi/docs.go b/cmd/server/openapi/docs.go index 20962fafe7..5da5adcdf0 100644 --- a/cmd/server/openapi/docs.go +++ b/cmd/server/openapi/docs.go @@ -5914,6 +5914,9 @@ const docTemplate = `{ }, "sha": { "type": "string" + }, + "timestamp": { + "type": "integer" } } }, diff --git a/docs/docs/20-usage/50-environment.md b/docs/docs/20-usage/50-environment.md index 794348de22..f072deca5b 100644 --- a/docs/docs/20-usage/50-environment.md +++ b/docs/docs/20-usage/50-environment.md @@ -76,6 +76,7 @@ This is the reference list of all environment variables available to your pipeli | `CI_COMMIT_PULL_REQUEST_LABELS` | labels assigned to pull request (set only for pull request events) | `server` | | `CI_COMMIT_PULL_REQUEST_MILESTONE` | milestone assigned to pull request (set only for `pull_request` and `pull_request_closed` events) | `summer-sprint` | | `CI_COMMIT_MESSAGE` | commit message | `Initial commit` | +| `CI_COMMIT_TIMESTAMP` | commit UNIX timestamp | `1722617519` | | `CI_COMMIT_AUTHOR` | commit author username | `john-doe` | | `CI_COMMIT_AUTHOR_EMAIL` | commit author email address | `john-doe@example.com` | | `CI_COMMIT_PRERELEASE` | release is a pre-release (empty if event is not `release`) | `false` | @@ -112,6 +113,7 @@ This is the reference list of all environment variables available to your pipeli | `CI_PREV_COMMIT_TARGET_BRANCH` | previous commit target branch (set only for pull request events) | `main` | | `CI_PREV_COMMIT_URL` | previous commit link in forge | `https://git.example.com/john-doe/my-repo/commit/15784117e4e103f36cba75a9e29da48046eb82c4` | | `CI_PREV_COMMIT_MESSAGE` | previous commit message | `test` | +| `CI_PREV_COMMIT_TIMESTAMP` | previous commit UNIX timestamp | `1722617519` | | `CI_PREV_COMMIT_AUTHOR` | previous commit author username | `john-doe` | | `CI_PREV_COMMIT_AUTHOR_EMAIL` | previous commit author email address | `john-doe@example.com` | | | **Previous pipeline** | | diff --git a/pipeline/frontend/metadata/environment.go b/pipeline/frontend/metadata/environment.go index 205d7a87bb..f8d367e19e 100644 --- a/pipeline/frontend/metadata/environment.go +++ b/pipeline/frontend/metadata/environment.go @@ -97,6 +97,7 @@ func (m *Metadata) Environ() map[string]string { setNonEmptyEnvVar(params, "CI_COMMIT_REFSPEC", commit.Refspec) setNonEmptyEnvVar(params, "CI_COMMIT_MESSAGE", commit.Message) setNonEmptyEnvVar(params, "CI_COMMIT_BRANCH", commit.Branch) + setNonEmptyEnvVar(params, "CI_COMMIT_TIMESTAMP", strconv.FormatInt(commit.Timestamp, 10)) setNonEmptyEnvVar(params, "CI_COMMIT_AUTHOR", commit.Author.Name) setNonEmptyEnvVar(params, "CI_COMMIT_AUTHOR_EMAIL", commit.Author.Email) if p, f := strings.CutPrefix(pipeline.Commit.Ref, "refs/tags/"); f { @@ -150,6 +151,7 @@ func (m *Metadata) Environ() map[string]string { setNonEmptyEnvVar(params, "CI_PREV_COMMIT_REFSPEC", prevCommit.Refspec) setNonEmptyEnvVar(params, "CI_PREV_COMMIT_MESSAGE", prevCommit.Message) setNonEmptyEnvVar(params, "CI_PREV_COMMIT_BRANCH", prevCommit.Branch) + setNonEmptyEnvVar(params, "CI_PREV_COMMIT_TIMESTAMP", strconv.FormatInt(prevCommit.Timestamp, 10)) setNonEmptyEnvVar(params, "CI_PREV_COMMIT_AUTHOR", prevCommit.Author.Name) setNonEmptyEnvVar(params, "CI_PREV_COMMIT_AUTHOR_EMAIL", prevCommit.Author.Email) if prevPipeline.Event.IsPull() { diff --git a/pipeline/frontend/metadata/environment_test.go b/pipeline/frontend/metadata/environment_test.go index 154aa76a5e..6a15ca3ae9 100644 --- a/pipeline/frontend/metadata/environment_test.go +++ b/pipeline/frontend/metadata/environment_test.go @@ -27,13 +27,15 @@ func TestEnviron(t *testing.T) { Event: EventRelease, Commit: Commit{ Ref: "refs/tags/v1.2.3", + Timestamp: 1722617519, IsPrerelease: true, }, }, Prev: Pipeline{ Event: EventPullMetadata, Commit: Commit{ - Refspec: "branch-a:branch-b", + Refspec: "branch-a:branch-b", + Timestamp: 1722610173, }, }, } @@ -47,6 +49,8 @@ func TestEnviron(t *testing.T) { assert.Equal(t, "branch-b", envs["CI_PREV_COMMIT_TARGET_BRANCH"]) assert.Equal(t, "[]", envs["CI_PIPELINE_FILES"]) assert.Equal(t, "v1.2.3", envs["CI_COMMIT_TAG"]) + assert.Equal(t, "1722617519", envs["CI_COMMIT_TIMESTAMP"]) + assert.Equal(t, "1722610173", envs["CI_PREV_COMMIT_TIMESTAMP"]) m = Metadata{ Sys: System{Name: "wp"}, diff --git a/pipeline/frontend/metadata/types.go b/pipeline/frontend/metadata/types.go index 43fba4716d..5ef7598e4d 100644 --- a/pipeline/frontend/metadata/types.go +++ b/pipeline/frontend/metadata/types.go @@ -69,6 +69,7 @@ type ( Refspec string `json:"refspec,omitempty"` Branch string `json:"branch,omitempty"` Message string `json:"message,omitempty"` + Timestamp int64 `json:"timestamp,omitempty"` Author Author `json:"author"` ChangedFiles []string `json:"changed_files,omitempty"` PullRequestLabels []string `json:"labels,omitempty"` diff --git a/server/pipeline/metadata/metadata.go b/server/pipeline/metadata/metadata.go index 6d9764b285..172a984160 100644 --- a/server/pipeline/metadata/metadata.go +++ b/server/pipeline/metadata/metadata.go @@ -140,11 +140,12 @@ func metadataPipelineFromModelPipeline(pipeline *model.Pipeline, includeParent b DeployTo: pipeline.DeployTo, DeployTask: pipeline.DeployTask, Commit: metadata.Commit{ - Sha: pipeline.Commit, - Ref: pipeline.Ref, - Refspec: pipeline.Refspec, - Branch: pipeline.Branch, - Message: pipeline.Message, + Sha: pipeline.Commit, + Ref: pipeline.Ref, + Refspec: pipeline.Refspec, + Branch: pipeline.Branch, + Message: pipeline.Message, + Timestamp: pipeline.Timestamp, Author: metadata.Author{ Name: pipeline.Author, Email: pipeline.Email, diff --git a/server/pipeline/metadata/metadata_test.go b/server/pipeline/metadata/metadata_test.go index 6675b38c2f..3721102efa 100644 --- a/server/pipeline/metadata/metadata_test.go +++ b/server/pipeline/metadata/metadata_test.go @@ -44,23 +44,38 @@ func TestGetWorkflowMetadata(t *testing.T) { name: "Test with empty info", expectedMetadata: metadata.Metadata{Sys: metadata.System{Name: "woodpecker"}}, expectedEnviron: map[string]string{ - "CI": "woodpecker", - "CI_PIPELINE_CREATED": "0", "CI_PIPELINE_FILES": "[]", "CI_PIPELINE_NUMBER": "0", - "CI_PIPELINE_PARENT": "0", "CI_PIPELINE_STARTED": "0", "CI_PIPELINE_URL": "/repos/0/pipeline/0", + "CI": "woodpecker", + "CI_PIPELINE_CREATED": "0", + "CI_PIPELINE_FILES": "[]", + "CI_PIPELINE_NUMBER": "0", + "CI_PIPELINE_PARENT": "0", + "CI_PIPELINE_STARTED": "0", + "CI_PIPELINE_URL": "/repos/0/pipeline/0", "CI_PREV_PIPELINE_CREATED": "0", - "CI_PREV_PIPELINE_FINISHED": "0", "CI_PREV_PIPELINE_NUMBER": "0", "CI_PREV_PIPELINE_PARENT": "0", - "CI_PREV_PIPELINE_STARTED": "0", "CI_PREV_PIPELINE_URL": "/repos/0/pipeline/0", - "CI_REPO_PRIVATE": "false", "CI_REPO_TRUSTED": "false", "CI_REPO_TRUSTED_NETWORK": "false", "CI_REPO_TRUSTED_SECURITY": "false", "CI_REPO_TRUSTED_VOLUMES": "false", - "CI_STEP_NUMBER": "0", "CI_STEP_URL": "/repos/0/pipeline/0", "CI_SYSTEM_NAME": "woodpecker", - "CI_WORKFLOW_NUMBER": "0", + "CI_COMMIT_TIMESTAMP": "0", + "CI_PREV_COMMIT_TIMESTAMP": "0", + "CI_PREV_PIPELINE_FINISHED": "0", + "CI_PREV_PIPELINE_NUMBER": "0", + "CI_PREV_PIPELINE_PARENT": "0", + "CI_PREV_PIPELINE_STARTED": "0", + "CI_PREV_PIPELINE_URL": "/repos/0/pipeline/0", + "CI_REPO_PRIVATE": "false", + "CI_REPO_TRUSTED": "false", + "CI_REPO_TRUSTED_NETWORK": "false", + "CI_REPO_TRUSTED_SECURITY": "false", + "CI_REPO_TRUSTED_VOLUMES": "false", + "CI_STEP_NUMBER": "0", + "CI_STEP_URL": "/repos/0/pipeline/0", + "CI_SYSTEM_NAME": "woodpecker", + "CI_WORKFLOW_NUMBER": "0", }, }, { 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, Timestamp: 1722617519, ChangedFiles: []string{"test.go", "markdown file.md"}}, + prev: &model.Pipeline{Number: 2, Timestamp: 1722610173}, workflow: &builder.Workflow{Name: "hello"}, sysURL: "https://example.com", expectedMetadata: metadata.Metadata{ @@ -69,23 +84,48 @@ func TestGetWorkflowMetadata(t *testing.T) { Repo: metadata.Repo{Owner: "testUser", Name: "testRepo", ForgeURL: "https://gitea.com/testUser/testRepo", CloneURL: "https://gitea.com/testUser/testRepo.git", CloneSSHURL: "git@gitea.com:testUser/testRepo.git", Branch: "main", Private: true}, Curr: metadata.Pipeline{ Number: 3, - Commit: metadata.Commit{ChangedFiles: []string{"test.go", "markdown file.md"}}, + Commit: metadata.Commit{Timestamp: 1722617519, ChangedFiles: []string{"test.go", "markdown file.md"}}, }, - Prev: metadata.Pipeline{Number: 2}, + Prev: metadata.Pipeline{Number: 2, Commit: metadata.Commit{Timestamp: 1722610173}}, Workflow: metadata.Workflow{Name: "hello"}, }, expectedEnviron: map[string]string{ - "CI": "woodpecker", - "CI_FORGE_TYPE": "gitea", "CI_FORGE_URL": "https://gitea.com", - "CI_PIPELINE_CREATED": "0", "CI_PIPELINE_FILES": `["test.go","markdown file.md"]`, - "CI_PIPELINE_NUMBER": "3", "CI_PIPELINE_PARENT": "0", "CI_PIPELINE_STARTED": "0", "CI_PIPELINE_URL": "https://example.com/repos/0/pipeline/3", + "CI": "woodpecker", + "CI_FORGE_TYPE": "gitea", + "CI_FORGE_URL": "https://gitea.com", + "CI_COMMIT_TIMESTAMP": "1722617519", + "CI_PIPELINE_CREATED": "0", + "CI_PIPELINE_FILES": `["test.go","markdown file.md"]`, + "CI_PIPELINE_NUMBER": "3", + "CI_PIPELINE_PARENT": "0", + "CI_PIPELINE_STARTED": "0", + "CI_PIPELINE_URL": "https://example.com/repos/0/pipeline/3", + "CI_PREV_COMMIT_TIMESTAMP": "1722610173", "CI_PREV_PIPELINE_CREATED": "0", - "CI_PREV_PIPELINE_FINISHED": "0", "CI_PREV_PIPELINE_NUMBER": "2", "CI_PREV_PIPELINE_PARENT": "0", - "CI_PREV_PIPELINE_STARTED": "0", "CI_PREV_PIPELINE_URL": "https://example.com/repos/0/pipeline/2", "CI_REPO": "testUser/testRepo", "CI_REPO_CLONE_URL": "https://gitea.com/testUser/testRepo.git", "CI_REPO_CLONE_SSH_URL": "git@gitea.com:testUser/testRepo.git", - "CI_REPO_DEFAULT_BRANCH": "main", "CI_REPO_NAME": "testRepo", "CI_REPO_OWNER": "testUser", "CI_REPO_PRIVATE": "true", - "CI_REPO_TRUSTED": "false", "CI_REPO_TRUSTED_NETWORK": "false", "CI_REPO_TRUSTED_SECURITY": "false", "CI_REPO_TRUSTED_VOLUMES": "false", - "CI_REPO_URL": "https://gitea.com/testUser/testRepo", "CI_STEP_NUMBER": "0", "CI_STEP_URL": "https://example.com/repos/0/pipeline/3", "CI_SYSTEM_HOST": "example.com", - "CI_SYSTEM_NAME": "woodpecker", "CI_SYSTEM_URL": "https://example.com", "CI_WORKFLOW_NAME": "hello", "CI_WORKFLOW_NUMBER": "0", + "CI_PREV_PIPELINE_FINISHED": "0", + "CI_PREV_PIPELINE_NUMBER": "2", + "CI_PREV_PIPELINE_PARENT": "0", + "CI_PREV_PIPELINE_STARTED": "0", + "CI_PREV_PIPELINE_URL": "https://example.com/repos/0/pipeline/2", + "CI_REPO": "testUser/testRepo", + "CI_REPO_CLONE_URL": "https://gitea.com/testUser/testRepo.git", + "CI_REPO_CLONE_SSH_URL": "git@gitea.com:testUser/testRepo.git", + "CI_REPO_DEFAULT_BRANCH": "main", + "CI_REPO_NAME": "testRepo", + "CI_REPO_OWNER": "testUser", + "CI_REPO_PRIVATE": "true", + "CI_REPO_TRUSTED": "false", + "CI_REPO_TRUSTED_NETWORK": "false", + "CI_REPO_TRUSTED_SECURITY": "false", + "CI_REPO_TRUSTED_VOLUMES": "false", + "CI_REPO_URL": "https://gitea.com/testUser/testRepo", + "CI_STEP_NUMBER": "0", + "CI_STEP_URL": "https://example.com/repos/0/pipeline/3", + "CI_SYSTEM_HOST": "example.com", + "CI_SYSTEM_NAME": "woodpecker", + "CI_SYSTEM_URL": "https://example.com", + "CI_WORKFLOW_NAME": "hello", + "CI_WORKFLOW_NUMBER": "0", }, }, { @@ -97,16 +137,31 @@ func TestGetWorkflowMetadata(t *testing.T) { RerunCount: 1, }}, expectedEnviron: map[string]string{ - "CI": "woodpecker", - "CI_PIPELINE_CREATED": "0", "CI_PIPELINE_FILES": "[]", "CI_PIPELINE_NUMBER": "3", - "CI_PIPELINE_PARENT": "2", "CI_PIPELINE_STARTED": "0", "CI_PIPELINE_URL": "/repos/0/pipeline/3", + "CI": "woodpecker", + "CI_PIPELINE_CREATED": "0", + "CI_PIPELINE_FILES": "[]", + "CI_PIPELINE_NUMBER": "3", + "CI_PIPELINE_PARENT": "2", + "CI_PIPELINE_STARTED": "0", + "CI_PIPELINE_URL": "/repos/0/pipeline/3", "CI_PIPELINE_RERUNS": "1", + "CI_COMMIT_TIMESTAMP": "0", + "CI_PREV_COMMIT_TIMESTAMP": "0", "CI_PREV_PIPELINE_CREATED": "0", - "CI_PREV_PIPELINE_FINISHED": "0", "CI_PREV_PIPELINE_NUMBER": "0", "CI_PREV_PIPELINE_PARENT": "0", - "CI_PREV_PIPELINE_STARTED": "0", "CI_PREV_PIPELINE_URL": "/repos/0/pipeline/0", - "CI_REPO_PRIVATE": "false", "CI_REPO_TRUSTED": "false", "CI_REPO_TRUSTED_NETWORK": "false", "CI_REPO_TRUSTED_SECURITY": "false", "CI_REPO_TRUSTED_VOLUMES": "false", - "CI_STEP_NUMBER": "0", "CI_STEP_URL": "/repos/0/pipeline/3", "CI_SYSTEM_NAME": "woodpecker", - "CI_WORKFLOW_NUMBER": "0", + "CI_PREV_PIPELINE_FINISHED": "0", + "CI_PREV_PIPELINE_NUMBER": "0", + "CI_PREV_PIPELINE_PARENT": "0", + "CI_PREV_PIPELINE_STARTED": "0", + "CI_PREV_PIPELINE_URL": "/repos/0/pipeline/0", + "CI_REPO_PRIVATE": "false", + "CI_REPO_TRUSTED": "false", + "CI_REPO_TRUSTED_NETWORK": "false", + "CI_REPO_TRUSTED_SECURITY": "false", + "CI_REPO_TRUSTED_VOLUMES": "false", + "CI_STEP_NUMBER": "0", + "CI_STEP_URL": "/repos/0/pipeline/3", + "CI_SYSTEM_NAME": "woodpecker", + "CI_WORKFLOW_NUMBER": "0", }, }, }