From 87f729190918e957b1d80c5e94c4e3ff440a387c Mon Sep 17 00:00:00 2001 From: silverwind Date: Fri, 20 Feb 2026 16:40:07 +0100 Subject: [PATCH 1/5] Make `security-check` informational only (#36681) Change `security-check` not break the build which is a major inconvenience as it breaks CI on all PRs. https://github.com/go-gitea/gitea/security/dependabot already provides a clean overview of outstanding security issues in dependencies and I'm using it all the time to find and update vulnerable dependencies. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b86361d73e..3c7582dd57 100644 --- a/Makefile +++ b/Makefile @@ -713,7 +713,7 @@ generate-go: $(TAGS_PREREQ) .PHONY: security-check security-check: - GOEXPERIMENT= go run $(GOVULNCHECK_PACKAGE) -show color ./... + GOEXPERIMENT= go run $(GOVULNCHECK_PACKAGE) -show color ./... || true $(EXECUTABLE): $(GO_SOURCES) $(TAGS_PREREQ) ifneq ($(and $(STATIC),$(findstring pam,$(TAGS))),) From 91dc737a35e8a421da075cc5d670c66de06cc64e Mon Sep 17 00:00:00 2001 From: silverwind Date: Fri, 20 Feb 2026 16:43:01 +0100 Subject: [PATCH 2/5] Replace `tinycolor2` with `colord` (#36673) [`colord`](https://github.com/omgovich/colord) is significantly smaller than [`tinycolor2`](https://github.com/bgrins/TinyColor) (~4KB vs ~29KB minified) and ships its own TypeScript types, removing the need for `@types/tinycolor2`. Behaviour is exactly the same for our use cases. By using `.alpha(1)` we force the function to always output 6-digit hex format (it would output 8-digit for non-opaque colors). --------- Signed-off-by: silverwind Co-authored-by: Claude Opus 4.6 --- package.json | 3 +-- pnpm-lock.yaml | 19 +++---------------- web_src/js/features/codeeditor.ts | 4 ++-- web_src/js/utils/color.ts | 15 +++++++-------- 4 files changed, 13 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index 9c5116d6b5..a7792c5fee 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "swagger-ui-dist": "5.31.1", "tailwindcss": "3.4.17", "throttle-debounce": "5.0.2", - "tinycolor2": "1.6.0", + "colord": "2.9.3", "tippy.js": "6.3.7", "toastify-js": "1.12.0", "tributejs": "5.1.3", @@ -82,7 +82,6 @@ "@types/sortablejs": "1.15.9", "@types/swagger-ui-dist": "3.30.6", "@types/throttle-debounce": "5.0.2", - "@types/tinycolor2": "1.4.6", "@types/toastify-js": "1.12.4", "@typescript-eslint/parser": "8.56.0", "@vitejs/plugin-vue": "6.0.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 383e0c8c2e..e67da15ad3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -89,6 +89,9 @@ importers: clippie: specifier: 4.1.10 version: 4.1.10 + colord: + specifier: 2.9.3 + version: 2.9.3 compare-versions: specifier: 6.1.1 version: 6.1.1 @@ -164,9 +167,6 @@ importers: throttle-debounce: specifier: 5.0.2 version: 5.0.2 - tinycolor2: - specifier: 1.6.0 - version: 1.6.0 tippy.js: specifier: 6.3.7 version: 6.3.7 @@ -249,9 +249,6 @@ importers: '@types/throttle-debounce': specifier: 5.0.2 version: 5.0.2 - '@types/tinycolor2': - specifier: 1.4.6 - version: 1.4.6 '@types/toastify-js': specifier: 1.12.4 version: 1.12.4 @@ -1297,9 +1294,6 @@ packages: '@types/throttle-debounce@5.0.2': resolution: {integrity: sha512-pDzSNulqooSKvSNcksnV72nk8p7gRqN8As71Sp28nov1IgmPKWbOEIwAWvBME5pPTtaXJAvG3O4oc76HlQ4kqQ==} - '@types/tinycolor2@1.4.6': - resolution: {integrity: sha512-iEN8J0BoMnsWBqjVbWH/c0G0Hh7O21lpR2/+PrvAVgWdzL7eexIFm4JN/Wn10PTcmNdtS6U67r499mlWMXOxNw==} - '@types/toastify-js@1.12.4': resolution: {integrity: sha512-zfZHU4tKffPCnZRe7pjv/eFKzTVHozKewFCKaCjZ4gFinKgJRz/t0bkZiMCXJxPhv/ZoeDGNOeRD09R0kQZ/nw==} @@ -4056,9 +4050,6 @@ packages: tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - tinycolor2@1.6.0: - resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} - tinyexec@1.0.2: resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} engines: {node: '>=18'} @@ -5241,8 +5232,6 @@ snapshots: '@types/throttle-debounce@5.0.2': {} - '@types/tinycolor2@1.4.6': {} - '@types/toastify-js@1.12.4': {} '@types/trusted-types@2.0.7': @@ -8215,8 +8204,6 @@ snapshots: tinybench@2.9.0: {} - tinycolor2@1.6.0: {} - tinyexec@1.0.2: {} tinyglobby@0.2.15: diff --git a/web_src/js/features/codeeditor.ts b/web_src/js/features/codeeditor.ts index 47f378c47a..b2aa9ea1c5 100644 --- a/web_src/js/features/codeeditor.ts +++ b/web_src/js/features/codeeditor.ts @@ -1,4 +1,4 @@ -import tinycolor from 'tinycolor2'; +import {colord} from 'colord'; import {basename, extname, isObject, isDarkTheme} from '../utils.ts'; import {onInputDebounce} from '../utils/dom.ts'; import type MonacoNamespace from 'monaco-editor'; @@ -94,7 +94,7 @@ function updateTheme(monaco: Monaco): void { // https://github.com/microsoft/monaco-editor/issues/2427 // also, monaco can only parse 6-digit hex colors, so we convert the colors to that format const styles = window.getComputedStyle(document.documentElement); - const getColor = (name: string) => tinycolor(styles.getPropertyValue(name).trim()).toString('hex6'); + const getColor = (name: string) => colord(styles.getPropertyValue(name).trim()).alpha(1).toHex(); monaco.editor.defineTheme('gitea', { base: isDarkTheme() ? 'vs-dark' : 'vs', diff --git a/web_src/js/utils/color.ts b/web_src/js/utils/color.ts index 57c909b8a0..096356983a 100644 --- a/web_src/js/utils/color.ts +++ b/web_src/js/utils/color.ts @@ -1,22 +1,21 @@ -import tinycolor from 'tinycolor2'; -import type {ColorInput} from 'tinycolor2'; +import {colord} from 'colord'; +import type {AnyColor} from 'colord'; /** Returns relative luminance for a SRGB color - https://en.wikipedia.org/wiki/Relative_luminance */ // Keep this in sync with modules/util/color.go -function getRelativeLuminance(color: ColorInput): number { - const {r, g, b} = tinycolor(color).toRgb(); +function getRelativeLuminance(color: AnyColor): number { + const {r, g, b} = colord(color).toRgb(); return (0.2126729 * r + 0.7151522 * g + 0.072175 * b) / 255; } -function useLightText(backgroundColor: ColorInput): boolean { +function useLightText(backgroundColor: AnyColor): boolean { return getRelativeLuminance(backgroundColor) < 0.453; } -/** Given a background color, returns a black or white foreground color that the highest - * contrast ratio. */ +/** Given a background color, returns a black or white foreground color with the highest contrast ratio. */ // In the future, the APCA contrast function, or CSS `contrast-color` will be better. // https://github.com/color-js/color.js/blob/eb7b53f7a13bb716ec8b28c7a56f052cd599acd9/src/contrast/APCA.js#L42 -export function contrastColor(backgroundColor: ColorInput): string { +export function contrastColor(backgroundColor: AnyColor): string { return useLightText(backgroundColor) ? '#fff' : '#000'; } From 3830d488d5570ee91e47e496c08218ba2edbce4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Fri, 20 Feb 2026 17:12:22 +0100 Subject: [PATCH 3/5] actions: report commit status for pull_request_review events (#36589) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Workflows triggered by pull_request_review events (approved, rejected, comment) complete successfully but never create a commit status on the PR. This makes them invisible in the merge checks UI, breaking any CI gate that re-evaluates on review submission. The commit status handler's switch statement was missing the three review event types, so they fell through to the default case which returned empty strings. Additionally, review events use PullRequestPayload but IsPullRequest() returns false for them (Event() returns "pull_request_approved" etc. instead of "pull_request"), so GetPullRequestEventPayload() refuses to parse their payload. Signed-off-by: Jörg Thalheim Co-authored-by: silverwind --- models/actions/run.go | 2 +- modules/webhook/type.go | 14 +++ services/actions/commit_status.go | 15 +++ tests/integration/actions_trigger_test.go | 138 ++++++++++++++++++++++ 4 files changed, 168 insertions(+), 1 deletion(-) diff --git a/models/actions/run.go b/models/actions/run.go index be332d6857..99e6267071 100644 --- a/models/actions/run.go +++ b/models/actions/run.go @@ -168,7 +168,7 @@ func (run *ActionRun) GetPushEventPayload() (*api.PushPayload, error) { } func (run *ActionRun) GetPullRequestEventPayload() (*api.PullRequestPayload, error) { - if run.Event.IsPullRequest() { + if run.Event.IsPullRequest() || run.Event.IsPullRequestReview() { var payload api.PullRequestPayload if err := json.Unmarshal([]byte(run.EventPayload), &payload); err != nil { return nil, err diff --git a/modules/webhook/type.go b/modules/webhook/type.go index 89c6a4bfe5..18a4086710 100644 --- a/modules/webhook/type.go +++ b/modules/webhook/type.go @@ -98,6 +98,20 @@ func (h HookEventType) IsPullRequest() bool { return h.Event() == "pull_request" } +// IsPullRequestReview returns true for pull request review events +// (approved, rejected, comment). These events use the same PullRequestPayload +// as regular pull_request events. +func (h HookEventType) IsPullRequestReview() bool { + switch h { + case HookEventPullRequestReviewApproved, + HookEventPullRequestReviewRejected, + HookEventPullRequestReviewComment: + return true + default: + return false + } +} + // HookType is the type of a webhook type HookType = string diff --git a/services/actions/commit_status.go b/services/actions/commit_status.go index 7271f58091..884b98e966 100644 --- a/services/actions/commit_status.go +++ b/services/actions/commit_status.go @@ -115,6 +115,21 @@ func getCommitStatusEventNameAndCommitID(run *actions_model.ActionRun) (event, c return "", "", errors.New("head of pull request is missing in event payload") } commitID = payload.PullRequest.Head.Sha + case // pull_request_review events share the same PullRequestPayload as pull_request + webhook_module.HookEventPullRequestReviewApproved, + webhook_module.HookEventPullRequestReviewRejected, + webhook_module.HookEventPullRequestReviewComment: + event = run.TriggerEvent + payload, err := run.GetPullRequestEventPayload() + if err != nil { + return "", "", fmt.Errorf("GetPullRequestEventPayload: %w", err) + } + if payload.PullRequest == nil { + return "", "", errors.New("pull request is missing in event payload") + } else if payload.PullRequest.Head == nil { + return "", "", errors.New("head of pull request is missing in event payload") + } + commitID = payload.PullRequest.Head.Sha case webhook_module.HookEventRelease: event = string(run.Event) commitID = run.CommitSHA diff --git a/tests/integration/actions_trigger_test.go b/tests/integration/actions_trigger_test.go index b0eabdd432..7fff796af6 100644 --- a/tests/integration/actions_trigger_test.go +++ b/tests/integration/actions_trigger_test.go @@ -691,6 +691,144 @@ func insertFakeStatus(t *testing.T, repo *repo_model.Repository, sha, targetURL, assert.NoError(t, err) } +func TestPullRequestReviewCommitStatusEvent(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) // reviewer + + // create a repo + repo, err := repo_service.CreateRepository(t.Context(), user2, user2, repo_service.CreateRepoOptions{ + Name: "repo-pull-request-review", + Description: "test pull-request-review commit status", + AutoInit: true, + Gitignores: "Go", + License: "MIT", + Readme: "Default", + DefaultBranch: "main", + IsPrivate: false, + }) + assert.NoError(t, err) + assert.NotEmpty(t, repo) + + // add user4 as collaborator so they can review + ctx := NewAPITestContext(t, repo.OwnerName, repo.Name, auth_model.AccessTokenScopeWriteRepository) + t.Run("AddUser4AsCollaboratorWithWriteAccess", doAPIAddCollaborator(ctx, "user4", perm.AccessModeWrite)) + + // add workflow file that triggers on pull_request_review + addWorkflow, err := files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{ + Files: []*files_service.ChangeRepoFile{ + { + Operation: "create", + TreePath: ".gitea/workflows/pr-review.yml", + ContentReader: strings.NewReader(`name: test +on: + pull_request_review: + types: [submitted] +jobs: + test: + runs-on: ubuntu-latest + steps: + - run: echo helloworld +`), + }, + }, + Message: "add workflow", + OldBranch: "main", + NewBranch: "main", + Author: &files_service.IdentityOptions{ + GitUserName: user2.Name, + GitUserEmail: user2.Email, + }, + Committer: &files_service.IdentityOptions{ + GitUserName: user2.Name, + GitUserEmail: user2.Email, + }, + Dates: &files_service.CommitDateOptions{ + Author: time.Now(), + Committer: time.Now(), + }, + }) + assert.NoError(t, err) + assert.NotEmpty(t, addWorkflow) + + // create a branch and a PR + testBranch := "test-review-branch" + err = repo_service.CreateNewBranch(t.Context(), user2, repo, "main", testBranch) + assert.NoError(t, err) + + // add a file on the test branch so the PR has changes + addFileResp, err := files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{ + Files: []*files_service.ChangeRepoFile{ + { + Operation: "create", + TreePath: "test.txt", + ContentReader: strings.NewReader("test content"), + }, + }, + Message: "add test file", + OldBranch: testBranch, + NewBranch: testBranch, + Author: &files_service.IdentityOptions{ + GitUserName: user2.Name, + GitUserEmail: user2.Email, + }, + Committer: &files_service.IdentityOptions{ + GitUserName: user2.Name, + GitUserEmail: user2.Email, + }, + Dates: &files_service.CommitDateOptions{ + Author: time.Now(), + Committer: time.Now(), + }, + }) + assert.NoError(t, err) + assert.NotEmpty(t, addFileResp) + sha := addFileResp.Commit.SHA + + pullIssue := &issues_model.Issue{ + RepoID: repo.ID, + Title: "A test PR for review", + PosterID: user2.ID, + Poster: user2, + IsPull: true, + } + pullRequest := &issues_model.PullRequest{ + HeadRepoID: repo.ID, + BaseRepoID: repo.ID, + HeadBranch: testBranch, + BaseBranch: "main", + HeadRepo: repo, + BaseRepo: repo, + Type: issues_model.PullRequestGitea, + } + prOpts := &pull_service.NewPullRequestOptions{Repo: repo, Issue: pullIssue, PullRequest: pullRequest} + err = pull_service.NewPullRequest(t.Context(), prOpts) + assert.NoError(t, err) + + // submit an approval review as user4 + gitRepo, err := gitrepo.OpenRepository(t.Context(), repo) + assert.NoError(t, err) + defer gitRepo.Close() + + _, _, err = pull_service.SubmitReview(t.Context(), user4, gitRepo, pullIssue, issues_model.ReviewTypeApprove, "lgtm", sha, nil) + assert.NoError(t, err) + + // verify that a commit status was created for the review event + assert.Eventually(t, func() bool { + latestCommitStatuses, err := git_model.GetLatestCommitStatus(t.Context(), repo.ID, sha, db.ListOptionsAll) + assert.NoError(t, err) + if len(latestCommitStatuses) == 0 { + return false + } + if latestCommitStatuses[0].State == commitstatus.CommitStatusPending { + insertFakeStatus(t, repo, sha, latestCommitStatuses[0].TargetURL, latestCommitStatuses[0].Context) + return true + } + return false + }, 1*time.Second, 100*time.Millisecond) + }) +} + func TestWorkflowDispatchPublicApi(t *testing.T) { onGiteaRun(t, func(t *testing.T, u *url.URL) { user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) From d59df34a7df37af57bbdef56f5aa4a044427f6a3 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 20 Feb 2026 10:01:50 -0800 Subject: [PATCH 4/5] Upgrade gogit to 5.16.5 (#36680) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f784ac2581..b7a3af6a3f 100644 --- a/go.mod +++ b/go.mod @@ -53,7 +53,7 @@ require ( github.com/go-co-op/gocron v1.37.0 github.com/go-enry/go-enry/v2 v2.9.4 github.com/go-git/go-billy/v5 v5.7.0 - github.com/go-git/go-git/v5 v5.16.4 + github.com/go-git/go-git/v5 v5.16.5 github.com/go-ldap/ldap/v3 v3.4.12 github.com/go-redsync/redsync/v4 v4.15.0 github.com/go-sql-driver/mysql v1.9.3 diff --git a/go.sum b/go.sum index b10e259c91..1a6decc18b 100644 --- a/go.sum +++ b/go.sum @@ -332,8 +332,8 @@ github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9n github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y= -github.com/go-git/go-git/v5 v5.16.4/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= +github.com/go-git/go-git/v5 v5.16.5 h1:mdkuqblwr57kVfXri5TTH+nMFLNUxIj9Z7F5ykFbw5s= +github.com/go-git/go-git/v5 v5.16.5/go.mod h1:QOMLpNf1qxuSY4StA/ArOdfFR2TrKEjJiye2kel2m+M= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= From bbea5e6c2d75a4a710d7838b7bec7e851e046d3c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 10:45:55 -0800 Subject: [PATCH 5/5] Update Nix flake (#36679) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automated changes by the [update-flake-lock](https://github.com/DeterminateSystems/update-flake-lock) GitHub Action. ``` Flake lock file updates: • Updated input 'nixpkgs': 'github:nixos/nixpkgs/0b4defa' (2025-10-09) → 'github:nixos/nixpkgs/0182a36' (2026-02-17) ``` Co-authored-by: github-actions[bot] --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 4cbc85b87a..a608aa3b89 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1760038930, - "narHash": "sha256-Oncbh0UmHjSlxO7ErQDM3KM0A5/Znfofj2BSzlHLeVw=", + "lastModified": 1771369470, + "narHash": "sha256-0NBlEBKkN3lufyvFegY4TYv5mCNHbi5OmBDrzihbBMQ=", "owner": "nixos", "repo": "nixpkgs", - "rev": "0b4defa2584313f3b781240b29d61f6f9f7e0df3", + "rev": "0182a361324364ae3f436a63005877674cf45efb", "type": "github" }, "original": {