From 1ad9e996be722a6da7d25f062fffd3c4ce1bcf2b Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 7 Apr 2026 20:33:38 -0700 Subject: [PATCH 01/48] Changelog for 1.26.0-rc0 (#37134) --- CHANGELOG.md | 379 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 379 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b662cb4ad5..0fbdf6d9f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,385 @@ This changelog goes through the changes that have been made in each release without substantial changes to our git log; to see the highlights of what has been added to each release, please refer to the [blog](https://blog.gitea.com). +## [1.26.0-rc0](https://github.com/go-gitea/gitea/releases/tag/v1.26.0-rc0) - 2026-04-07 + +* BREAKING + * Correct swagger annotations for enums, status codes, and notification state (#37030) + * Remove GET API registration-token (#36801) + * Support Actions `concurrency` syntax (#32751) + * Make PUBLIC_URL_DETECTION default to "auto" (#36955) +* SECURITY + * Bound PageSize in `ListUnadoptedRepositories` (#36884) +* FEATURES + * Support Actions `concurrency` syntax (#32751) + * Add terraform state registry (#36710) + * Instance-wide (global) info banner and maintenance mode (#36571) + * Support rendering OpenAPI spec (#36449) + * Add keyboard shortcuts for repository file and code search (#36416) + * Add support for archive-upload rpc (#36391) + * Add ability to download subpath archive (#36371) + * Add workflow dependencies visualization (#26062) (#36248) & Restyle Workflow Graph (#36912) + * Automatic generation of release notes (#35977) + * Add "Go to file", "Delete Directory" to repo file list page (#35911) + * Introduce "config edit-ini" sub command to help maintaining INI config file (#35735) + * Add button to re-run failed jobs in Actions (#36924) + * Support actions and reusable workflows from private repos (#32562) + * Add summary to action runs view (#36883) + * Add user badges (#36752) + * Add configurable permissions for Actions automatic tokens (#36173) + * Add per-runner “Disable/Pause” (#36776) +* PERFORMANCE + * WorkflowDispatch API optionally return runid (#36706) + * Add render cache for SVG icons (#36863) + * Load `mentionValues` asynchronously (#36739) + * Lazy-load some Vue components, fix heatmap chunk loading on every page (#36719) + * Load heatmap data asynchronously (#36622) + * Use prev/next pagination for user profile activities page to speed up (#36642) + * Refactor cat-file batch operations and support `--batch-command` approach (#35775) + * Use merge tree to detect conflicts when possible (#36400) +* ENHANCEMENTS + * Adds option to force update new branch in contents routes (#35592) + * Add viewer controller for mermaid (zoom, drag) (#36557) + * Add code editor setting dropdowns (#36534) + * Add `elk` layout support to mermaid (#36486) + * Add resolve/unresolve review comment API endpoints (#36441) + * Allow configuring default PR base branch (fixes #36412) (#36425) + * Add support for RPM Errata (updateinfo.xml) (#37125) + * Require additional user confirmation for making repo private (#36959) + * Feature non-zipped actions artifacts (action v7 / nodejs / npm v6.2.0) (#36786) + * Add `actions.WORKFLOW_DIRS` setting (#36619) + * Avoid opening new tab when downloading actions logs (#36740) + * Implements OIDC RP-Initiated Logout (#36724) + * Show workflow link (#37070) + * Desaturate dark theme background colors (#37056) + * Refactor "org teams" page and help new users to "add member" to an org (#37051) + * Add webhook name field to improve webhook identification (#37025) (#37040) + * Make task list checkboxes clickable in the preview tab (#37010) + * Improve severity labels in Actions logs and tweak colors (#36993) + * Linkify URLs in Actions workflow logs (#36986) + * Allow text selection on checkbox labels (#36970) + * Support dark/light theme images in markdown (#36922) + * Enable native dark mode for swagger-ui (#36899) + * Rework checkbox styling, remove `input` border hover effect (#36870) + * Refactor storage content-type handling of ServeDirectURL (#36804) + * Use "Enable Gravatar" but not "Disable" (#36771) + * Use case-insensitive matching for Git error "Not a valid object name" (#36728) + * Add “Copy Source” to markup comment menu (#36726) + * Change image transparency grid to CSS (#36711) + * Add "Run" prefix for unnamed action steps (#36624) + * Persist actions log time display settings in `localStorage` (#36623) + * Use first commit title for multi-commit PRs and fix auto-focus title field (#36606) + * Improve BuildCaseInsensitiveLike with lowercase (#36598) + * Improve diff highlighting (#36583) + * Exclude cancelled runs from failure-only email notifications (#36569) + * Use full-file highlighting for diff sections (#36561) + * Color command/error logs in Actions log (#36538) + * Add paging headers (#36521) + * Improve timeline entries for WIP prefix changes in pull requests (#36518) + * Add FOLDER_ICON_THEME configuration option (#36496) + * Normalize guessed languages for code highlighting (#36450) + * Add chunked transfer encoding support for LFS uploads (#36380) + * Indicate when only optional checks failed (#36367) + * Add 'allow_maintainer_edit' API option for creating a pull request (#36283) + * Support closing keywords with URL references (#36221) + * Improve diff file headers (#36215) + * Fix and enhance comment editor monospace toggle (#36181) + * Add git.DIFF_RENAME_SIMILARITY_THRESHOLD option (#36164) + * Add matching pair insertion to markdown textarea (#36121) + * Add sorting/filtering to admin user search API endpoint (#36112) + * Allow action user have read permission in public repo like other user (#36095) + * Disable matchBrackets in monaco (#36089) + * Use GitHub-style commit message for squash merge (#35987) + * Make composer registry support tar.gz and tar.bz2 and fix bugs (#35958) + * Add GITEA_PR_INDEX env variable to githooks (#35938) + * Add proper error message if session provider can not be created (#35520) + * Add button to copy file name in PR files (#35509) + * Move `X_FRAME_OPTIONS` setting from `cors` to `security` section (#30256) + * Add placeholder content for empty content page (#37114) + * Add `DEFAULT_DELETE_BRANCH_AFTER_MERGE` setting (#36917) + * Redirect to the only OAuth2 provider when no other login methods and fix various problems (#36901) + * Add admin badge to navbar avatar (#36790) + * Add `never` option to `PUBLIC_URL_DETECTION` configuration (#36785) + * Add background and run count to actions list page (#36707) + * Add icon to buttons "Close with Comment", "Close Pull Request", "Close Issue" (#36654) + * Add support for in_progress event in workflow_run webhook (#36979) + * Report commit status for pull_request_review events (#36589) + * Render merged pull request title as such in dashboard feed (#36479) + * Feature to be able to filter project boards by milestones (#36321) + * Use user id in noreply emails (#36550) + * Enable pagination on GiteaDownloader.getIssueReactions() (#36549) + * Remove striped tables in UI (#36509) + * Improve control char rendering and escape button styling (#37094) + * Support legacy run/job index-based URLs and refactor migration 326 (#37008) + * Add date to "No Contributions" tooltip (#36190) + * Show edit page confirmation dialog on tree view file change (#36130) + * Mention proc-receive in text for dashboard.resync_all_hooks func (#35991) + * Reuse selectable style for wiki (#35990) + * Support blue yellow colorblind theme (#35910) + * Support selecting theme on the footer (#35741) + * Improve online runner check (#35722) + * Add quick approve button on PR page (#35678) + * Enable commenting on expanded lines in PR diffs (#35662) + * Print PR-Title into tooltip for actions (#35579) + * Use explicit, stronger defaults for newly generated repo signing keys for Debian (#36236) + * Improve the compare page (#36261) + * Unify repo names in system notices (#36491) + * Move package settings to package instead of being tied to version (#37026) + * Add Actions API rerun endpoints for runs and jobs (#36768) + * Add branch_count to repository API (#35351) (#36743) + * Add created_by filter to SearchIssues (#36670) + * Allow admins to rename non-local users (#35970) + * Support updating branch via API (#35951) + * Add an option to automatically verify SSH keys from LDAP (#35927) + * Make "update file" API can create a new file when SHA is not set (#35738) + * Update issue.go with labels documentation (labels content, not ids) (#35522) + * Expose content_version for optimistic locking on issue and PR edits (#37035) + * Pass ServeHeaderOptions by value instead of pointer, fine tune httplib tests (#36982) +* BUGFIXES + * Fix API not persisting pull request unit config when has_pull_requests is not set (#36718) + * Rename CSS variables and improve colorblind themes (#36353) + * Hide `add-matcher` and `remove-matcher` from actions job logs (#36520) + * Prevent navigation keys from triggering actions during IME composition (#36540) + * Fix vertical alignment of `.commit-sign-badge` children (#36570) + * Fix duplicate startup warnings in admin panel (#36641) + * Fix CODEOWNERS review request attribution using comment metadata (#36348) + * Fix HTML tags appearing in wiki table of contents (#36284) + * Fix various bugs (#37096) + * Fix various legacy problems (#37092) + * Fix RPM Registry 404 when package name contains 'package' (#37087) + * Merge some standalone Vite entries into index.js (#37085) + * Fix various problems (#37077) + * Fix issue label deletion with Actions tokens (#37013) + * Hide delete branch or tag buttons in mirror or archived repositories. (#37006) + * Fix org contact email not clearable once set (#36975) + * Fix a bug when forking a repository in an organization (#36950) + * Preserve sort order of exclusive labels from template repo (#36931) + * Make container registry support Apple Container (basic auth) (#36920) + * Fix the wrong push commits in the pull request when force push (#36914) + * Add class "list-header-filters" to the div for projects (#36889) + * Fix dbfs error handling (#36844) + * Fix incorrect viewed files counter if reverted change was viewed (#36819) + * Refactor avatar package, support default avatar fallback (#36788) + * Fix README symlink resolution in subdirectories like .github (#36775) + * Fix CSS stacking context issue in actions log (#36749) + * Add gpg signing for merge rebase and update by rebase (#36701) + * Delete non-exist branch should return 404 (#36694) + * Fix `TestActionsCollaborativeOwner` (#36657) + * Fix multi-arch Docker build SIGILL by splitting frontend stage (#36646) + * Fix linguist-detectable attribute being ignored for configuration files (#36640) + * Fix state desync in ComboMarkdownEditor (#36625) + * Unify DEFAULT_SHOW_FULL_NAME output in templates and dropdown (#36597) + * Pull Request Pusher should be the author of the merge (#36581) + * Fix various version parsing problems (#36553) + * Fix highlight diff result (#36539) + * Fix mirror sync parser and fix mirror messages (#36504) + * Fix bug when list pull request commits (#36485) + * Fix various bugs (#36446) + * Fix issue filter menu layout (#36426) + * Restrict branch naming when new change matches with protection rules (#36405) + * Fix link/origin referrer and login redirect (#36279) + * Generate IDs for HTML headings without id attribute (#36233) + * Use a migration test instead of a wrong test which populated the meta test repositories and fix a migration bug (#36160) + * Fix issue close timeline icon (#36138) + * Fix diff blob excerpt expansion (#35922) + * Fix external render (#35727) + * Fix review request webhook bug (#35339) (#35723) + * Fix shutdown waitgroup panic (#35676) + * Cleanup ActionRun creation (#35624) + * Fix possible bug when migrating issues/pull requests (#33487) + * Various fixes (#36697) + * Apply notify/register mail flags during install load (#37120) + * Repair duration display for bad stopped timestamps (#37121) + * Fix(upgrade.sh): use HTTPS for GPG key import and restore SELinux context after upgrade (#36930) + * Fix various trivial problems (#36921) + * Fix various trivial problems (#36953) + * Fix NuGet package upload error handling (#37074) + * Fix CodeQL code scanning alerts (#36858) + * Refactor issue sidebar and fix various problems (#37045) + * Fix various problems (#37029) + * Fix relative-time RangeError (#37021) + * Fix chroma lexer mapping (#36629) + * Fix typos and grammar in English locale (#36751) + * Fix milestone/project text overflow in issue sidebar (#36741) + * Fix `no-content` message not rendering after comment edit (#36733) + * Fix theme loading in development (#36605) + * Fix workflow run jobs API returning null steps (#36603) + * Fix timeline event layout overflow with long content (#36595) + * Fix minor UI issues in runner edit page (#36590) + * Fix incorrect vendored detections (#36508) + * Fix editorconfig not respected in PR Conversation view (#36492) + * Don't create self-references in merged PRs (#36490) + * Fix potential incorrect runID in run status update (#36437) + * Fix file-tree ui error when adding files to repo without commits (#36312) + * Improve image captcha contrast for dark mode (#36265) + * Fix panic in blame view when a file has only a single commit (#36230) + * Fix spelling error in migrate-storage cmd utility (#36226) + * Fix code highlighting on blame page (#36157) + * Fix nilnil in onedev downloader (#36154) + * Fix actions lint (#36029) + * Fix oauth2 session gob register (#36017) + * Fix Arch repo pacman.conf snippet (#35825) + * Fix a number of `strictNullChecks`-related issues (#35795) + * Fix URLJoin, markup render link reoslving, sign-in/up/linkaccount page common data (#36861) + * Hide delete directory button for mirror or archive repository and disable the menu item if user have no permission (#36384) + * Update message severity colors, fix navbar double border (#37019) + * Inline and lazy-load EasyMDE CSS, fix border colors (#36714) + * Closed milestones with no issues now show as 100% completed (#36220) + * Add test for ExtendCommentTreePathLength migration and fix bugs (#35791) + * Only turn links to current instance into hash links (#36237) + * Fix typos in code comments: doesnt, dont, wont (#36890) +* REFACTOR + * Replace Monaco with CodeMirror (#36764) + * Replace CSRF cookie with `CrossOriginProtection` (#36183) + * Replace index with id in actions routes (#36842) + * Remove unnecessary function parameter (#35765) + * Move jobparser from act repository to Gitea (#36699) + * Refactor compare router param parse (#36105) + * Optimize 'refreshAccesses' to perform update without removing then adding (#35702) + * Clean up checkbox cursor styles (#37016) + * Remove undocumented support of signing key in the repository git configuration file (#36143) + * Switch `cmd/` to use constructor functions. (#36962) + * Use `relative-time` to render absolute dates (#36238) + * Some refactors about GetMergeBase (#36186) + * Some small refactors (#36163) + * Use gitRepo as parameter instead of repopath when invoking sign functions (#36162) + * Move blame to gitrepo (#36161) + * Move some functions to gitrepo package to reduce RepoPath reference directly (#36126) + * Use gitrepo's clone and push when possible (#36093) + * Remove mermaid margin workaround (#35732) + * Move some functions to gitrepo package (#35543) + * Move GetDiverging functions to gitrepo (#35524) + * Use global lock instead of status pool for cron lock (#35507) + * Use explicit mux instead of DefaultServeMux (#36276) + * Use gitrepo's push function (#36245) + * Pass request context to generateAdditionalHeadersForIssue (#36274) + * Move assign project when creating pull request to the same database transaction (#36244) + * Move catfile batch to a sub package of git module (#36232) + * Use gitrepo.Repository instead of wikipath (#35398) + * Use experimental go json v2 library (#35392) + * Refactor template render (#36438) + * Refactor GetRepoRawDiffForFile to avoid unnecessary pipe or goroutine (#36434) + * Refactor text utility classes to Tailwind CSS (#36703) + * Refactor git command stdio pipe (#36422) + * Refactor git command context & pipeline (#36406) + * Refactor git command stdio pipe (#36393) + * Remove unused functions (#36672) + * Refactor Actions Token Access (#35688) + * Move commit related functions to gitrepo package (#35600) + * Move archive function to repo_model and gitrepo (#35514) + * Move some functions to gitrepo package (#35503) + * Use git model to detect whether branch exist instead of gitrepo method (#35459) + * Some refactor for repo path (#36251) + * Extract helper functions from SearchIssues (#36158) + * Refactor merge conan and container auth preserve actions taskID (#36560) + * Refactor Nuget Auth to reuse Basic Auth Token Validation (#36558) + * Refactor ActionsTaskID (#36503) + * Refactor auth middleware (#36848) + * Refactor code render and render control chars (#37078) + * Clean up AppURL, remove legacy origin-url webcomponent (#37090) + * Remove `util.URLJoin` and replace all callers with direct path concatenation (#36867) + * Replace legacy tw-flex utility classes with flex-text-block/inline (#36778) + * Mark unused&immature activitypub as "not implemented" (#36789) +* TESTING + * Add e2e tests for server push events (#36879) + * Rework e2e tests (#36634) + * Add e2e reaction test, improve accessibility, enable parallel testing (#37081) + * Increase e2e test timeouts on CI to fix flaky tests (#37053) +* BUILD + * Convert locale files from ini to json format (#35489) + * Bump golangci-lint to 2.7.2, enable modernize stringsbuilder (#36180) + * Port away from `flake-utils` (#35675) + * Remove nolint (#36252) + * Update the Unlicense copy to latest version (#36636) + * Update to go 1.26.0 and golangci-lint 2.9.0 (#36588) + * Replace `google/go-licenses` with custom generation (#36575) + * Update go dependencies (#36548) + * Bump appleboy/git-push-action from 1.0.0 to 1.2.0 (#36306) + * Remove fomantic form module (#36222) + * Bump setup-node to v6, re-enable cache (#36207) + * Bump crowdin/github-action from 1 to 2 (#36204) + * Revert "Bump alpine to 3.23 (#36185)" (#36202) + * Update chroma to v2.21.1 (#36201) + * Bump astral-sh/setup-uv from 6 to 7 (#36198) + * Bump docker/build-push-action from 5 to 6 (#36197) + * Bump aws-actions/configure-aws-credentials from 4 to 5 (#36196) + * Bump dev-hanz-ops/install-gh-cli-action from 0.1.0 to 0.2.1 (#36195) + * Add JSON linting (#36192) + * Enable dependabot for actions (#36191) + * Bump alpine to 3.23 (#36185) + * Update chroma to v2.21.0 (#36171) + * Update JS deps and eslint enhancements (#36147) + * Update JS deps (#36091) + * update golangci-lint to v2.7.0 (#36079) + * Update JS deps, fix deprecations (#36040) + * Update JS deps (#35978) + * Add toolchain directive to go.mod (#35901) + * Move `gitea-vet` to use `go tool` (#35878) + * Update to go 1.25.4 (#35877) + * Enable TypeScript `strictNullChecks` (#35843) + * Enable `vue/require-typed-ref` eslint rule (#35764) + * Update JS dependencies (#35759) + * Move `codeformat` folder to tools (#35758) + * Update dependencies (#35733) + * Bump happy-dom from 20.0.0 to 20.0.2 (#35677) + * Bump setup-go to v6 (#35660) + * Update JS deps, misc tweaks (#35643) + * Bump happy-dom from 19.0.2 to 20.0.0 (#35625) + * Use bundled version of spectral (#35573) + * Update JS and PY deps (#35565) + * Bump github.com/wneessen/go-mail from 0.6.2 to 0.7.1 (#35557) + * Migrate from webpack to vite (#37002) + * Update JS dependencies and misc tweaks (#37064) + * Update to eslint 10 (#36925) + * Optimize Docker build with dependency layer caching (#36864) + * Update JS deps (#36850) + * Update tool dependencies and fix new lint issues (#36702) + * Remove redundant linter rules (#36658) + * Move Fomantic dropdown CSS to custom module (#36530) + * Remove and forbid `@ts-expect-error` (#36513) + * Refactor git command stderr handling (#36402) + * Enable gocheckcompilerdirectives linter (#36156) + * Replace `lint-go-gopls` with additional `govet` linters (#36028) + * Update golangci-lint to v2.6.0 (#35801) + * Misc tool tweaks (#35734) + * Add cache to container build (#35697) + * Upgrade vite (#37126) + * Update `setup-uv` to v8.0.0 (#37101) + * Upgrade `go-git` to v5.17.2 and related dependencies (#37060) + * Raise minimum Node.js version to 22.18.0 (#37058) + * Upgrade `golang.org/x/image` to v0.38.0 (#37054) + * Update minimum go version to 1.26.1, golangci-lint to 2.11.2, fix test style (#36876) + * Enable eslint concurrency (#36878) + * Vendor relative-time-element as local web component (#36853) + * Update material-icon-theme v5.32.0 (#36832) + * Update Go dependencies (#36781) + * Upgrade minimatch (#36760) + * Remove i18n backport tool at the moment because of translation format changed (#36643) + * Update emoji data for Unicode 16 (#36596) + * Update JS dependencies, adjust webpack config, misc fixes (#36431) + * Update material-icon-theme to v5.31.0 (#36427) + * Update JS and PY deps (#36383) + * Bump alpine to 3.23, add platforms to `docker-dryrun` (#36379) + * Update JS deps (#36354) + * Update goldmark to v1.7.16 (#36343) + * Update chroma to v2.22.0 (#36342) +* DOCS + * Update AI Contribution Policy (#37022) + * Update AGENTS.md with additional guidelines (#37018) + * Add missing cron tasks to example ini (#37012) + * Add AI Contribution Policy to CONTRIBUTING.md (#36651) + * Minor punctuation improvement in CONTRIBUTING.md (#36291) + * Add documentation for markdown anchor post-processing (#36443) +* MISC + * Correct spelling (#36783) + * Update Nix flake (#37110) + * Update Nix flake (#37024) + * Add valid github scopes (#36977) + * Update Nix flake (#36943) + * Update Nix flake (#36902) + * Update Nix flake (#36857) + * Update Nix flake (#36787) + ## [1.25.5](https://github.com/go-gitea/gitea/releases/tag/v1.25.5) - 2026-03-10 * SECURITY -- 2.52.0 From 3e6b9e5312ef1c64ebcb89efde3b6da2c677c81a Mon Sep 17 00:00:00 2001 From: Giteabot Date: Thu, 9 Apr 2026 00:27:32 +0800 Subject: [PATCH 02/48] Bump min go version to 1.26.2 (#37139) (#37143) Backport #37139 by @silverwind Update Go from 1.26.1 to 1.26.2 to fix 6 stdlib vulnerabilities: - GO-2026-4947: `crypto/x509` chain building - GO-2026-4946: `crypto/x509` policy validation - GO-2026-4870: `crypto/tls` KeyUpdate DoS - GO-2026-4869: `archive/tar` unbounded allocation - GO-2026-4866: `crypto/x509` name constraints bypass - GO-2026-4865: `html/template` XSS Co-authored-by: silverwind Co-authored-by: Claude (Opus 4.6) --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 22de5ba1ba..0bccbe87b9 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module code.gitea.io/gitea -go 1.26.1 +go 1.26.2 // rfc5280 said: "The serial number is an integer assigned by the CA to each certificate." // But some CAs use negative serial number, just relax the check. related: -- 2.52.0 From a2283a0c03380f64ebdaaf84431f9f7967727410 Mon Sep 17 00:00:00 2001 From: Giteabot Date: Thu, 9 Apr 2026 20:35:07 +0800 Subject: [PATCH 03/48] Clean up and improve non-gitea js error filter (#37148) (#37155) Backport #37148 by @silverwind 1. Filter out errors that contain `chrome-extension://` etc protocols 2. Extract filtering into its own function and test it 3. Fix the `window.config.assetUrlPrefix` mock, guaranteed to end with `/assets` 4. Remove useless `??` and `?.` for properties that always exist --- This PR was written with the help of Claude Opus 4.6 Co-authored-by: silverwind Co-authored-by: Claude (Opus 4.6) --- web_src/js/modules/errors.test.ts | 17 ++++++++++++++++- web_src/js/modules/errors.ts | 21 ++++++++++++++------- web_src/js/vitest.setup.ts | 2 +- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/web_src/js/modules/errors.test.ts b/web_src/js/modules/errors.test.ts index 8ac0e262b2..efa114a88d 100644 --- a/web_src/js/modules/errors.test.ts +++ b/web_src/js/modules/errors.test.ts @@ -1,4 +1,19 @@ -import {showGlobalErrorMessage} from './errors.ts'; +import {isGiteaError, showGlobalErrorMessage} from './errors.ts'; + +test('isGiteaError', () => { + expect(isGiteaError('', '')).toBe(true); + expect(isGiteaError('moz-extension://abc/content.js', '')).toBe(false); + expect(isGiteaError('safari-extension://abc/content.js', '')).toBe(false); + expect(isGiteaError('safari-web-extension://abc/content.js', '')).toBe(false); + expect(isGiteaError('chrome-extension://abc/content.js', '')).toBe(false); + expect(isGiteaError('https://other-site.com/script.js', '')).toBe(false); + expect(isGiteaError('http://localhost:3000/some/page', '')).toBe(true); + expect(isGiteaError('http://localhost:3000/assets/js/index.abc123.js', '')).toBe(true); + expect(isGiteaError('', `Error\n at chrome-extension://abc/content.js:1:1`)).toBe(false); + expect(isGiteaError('', `Error\n at https://other-site.com/script.js:1:1`)).toBe(false); + expect(isGiteaError('', `Error\n at http://localhost:3000/assets/js/index.abc123.js:1:1`)).toBe(true); + expect(isGiteaError('http://localhost:3000/assets/js/index.js', `Error\n at chrome-extension://abc/content.js:1:1`)).toBe(false); +}); test('showGlobalErrorMessage', () => { document.body.innerHTML = '
'; diff --git a/web_src/js/modules/errors.ts b/web_src/js/modules/errors.ts index 882da36977..ddda0ebe42 100644 --- a/web_src/js/modules/errors.ts +++ b/web_src/js/modules/errors.ts @@ -23,11 +23,19 @@ export function showGlobalErrorMessage(msg: string, msgType: Intent = 'error') { msgContainer.prepend(msgDiv); } +// Detect whether an error originated from Gitea's own scripts, not from +// browser extensions or other external scripts. +const extensionRe = /(chrome|moz|safari(-web)?)-extension:\/\//; +export function isGiteaError(filename: string, stack: string): boolean { + if (extensionRe.test(filename) || extensionRe.test(stack)) return false; + const assetBaseUrl = new URL(`${window.config.assetUrlPrefix}/`, window.location.origin).href; + if (filename && !filename.startsWith(assetBaseUrl) && !filename.startsWith(window.location.origin)) return false; + if (stack && !stack.includes(assetBaseUrl)) return false; + return true; +} + export function processWindowErrorEvent({error, reason, message, type, filename, lineno, colno}: ErrorEvent & PromiseRejectionEvent) { const err = error ?? reason; - const assetBaseUrl = String(new URL(`${window.config?.assetUrlPrefix ?? '/assets'}/`, window.location.origin)); - const {runModeIsProd} = window.config ?? {}; - // `error` and `reason` are not guaranteed to be errors. If the value is falsy, it is likely a // non-critical event from the browser. We log them but don't show them to users. Examples: // - https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver#observation_errors @@ -35,12 +43,11 @@ export function processWindowErrorEvent({error, reason, message, type, filename, // - https://github.com/go-gitea/gitea/issues/20240 if (!err) { if (message) console.error(new Error(message)); - if (runModeIsProd) return; + if (window.config.runModeIsProd) return; } - // If the error stack trace does not include the base URL of our script assets, it likely came - // from a browser extension or inline script. Do not show such errors in production. - if (err instanceof Error && !err.stack?.includes(assetBaseUrl) && runModeIsProd) return; + // Filter out errors from browser extensions or other non-Gitea scripts. + if (!isGiteaError(filename ?? '', err?.stack ?? '')) return; let msg = err?.message ?? message; if (lineno) msg += ` (${filename} @ ${lineno}:${colno})`; diff --git a/web_src/js/vitest.setup.ts b/web_src/js/vitest.setup.ts index 5623075a27..a08325bcba 100644 --- a/web_src/js/vitest.setup.ts +++ b/web_src/js/vitest.setup.ts @@ -13,7 +13,7 @@ await import('./globals.ts'); window.config = { appUrl: 'http://localhost:3000/', appSubUrl: '', - assetUrlPrefix: '', + assetUrlPrefix: '/assets', sharedWorkerUri: '', runModeIsProd: true, customEmojis: {}, -- 2.52.0 From 4eca71d6d40ea1cf0f9154a97d595553153f70d1 Mon Sep 17 00:00:00 2001 From: Giteabot Date: Fri, 10 Apr 2026 04:57:04 +0800 Subject: [PATCH 04/48] Report structurally invalid workflows to users (#37116) (#37164) Backport #37116 by @bircni `model.ReadWorkflow` succeeds for YAML that is syntactically valid but fails deeper parsing in `jobparser.Parse` (e.g. blank lines inside `run: |` blocks cause a SetJob round-trip error). Add `ValidateWorkflowContent` which runs the full `jobparser.Parse` to catch these cases, and use it in the file view, the actions workflow list, and the workflow detection loop so users see the error instead of silently getting a 500 or a dropped workflow. Fixes #37115 Signed-off-by: Nicolas Co-authored-by: Nicolas Co-authored-by: Claude Sonnet 4.6 Co-authored-by: Zettat123 Co-authored-by: wxiaoguang --- modules/actions/workflows.go | 10 ++++++++++ modules/actions/workflows_test.go | 22 ++++++++++++++++------ routers/web/repo/actions/actions.go | 9 +++++++++ routers/web/repo/view_file.go | 5 +---- 4 files changed, 36 insertions(+), 10 deletions(-) diff --git a/modules/actions/workflows.go b/modules/actions/workflows.go index 4ac06def4d..ba1aee7d72 100644 --- a/modules/actions/workflows.go +++ b/modules/actions/workflows.go @@ -103,10 +103,20 @@ func GetEventsFromContent(content []byte) ([]*jobparser.Event, error) { if err != nil { return nil, err } + if err := ValidateWorkflowContent(content); err != nil { + return nil, err + } return events, nil } +// ValidateWorkflowContent catches structural errors (e.g. blank lines in run: | blocks) +// that model.ReadWorkflow alone does not detect. +func ValidateWorkflowContent(content []byte) error { + _, err := jobparser.Parse(content) + return err +} + func DetectWorkflows( gitRepo *git.Repository, commit *git.Commit, diff --git a/modules/actions/workflows_test.go b/modules/actions/workflows_test.go index ea027366f7..cda2de13e2 100644 --- a/modules/actions/workflows_test.go +++ b/modules/actions/workflows_test.go @@ -9,16 +9,26 @@ import ( "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/test" webhook_module "code.gitea.io/gitea/modules/webhook" "github.com/stretchr/testify/assert" ) +func fullWorkflowContent(part string) []byte { + return []byte(` +name: test +` + part + ` +jobs: + test: + runs-on: ubuntu-latest + steps: + - run: echo hello +`) +} + func TestIsWorkflow(t *testing.T) { - oldDirs := setting.Actions.WorkflowDirs - defer func() { - setting.Actions.WorkflowDirs = oldDirs - }() + defer test.MockVariableValue(&setting.Actions.WorkflowDirs)() tests := []struct { name string @@ -218,7 +228,7 @@ func TestDetectMatched(t *testing.T) { for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { - evts, err := GetEventsFromContent([]byte(tc.yamlOn)) + evts, err := GetEventsFromContent(fullWorkflowContent(tc.yamlOn)) assert.NoError(t, err) assert.Len(t, evts, 1) assert.Equal(t, tc.expected, detectMatched(nil, tc.commit, tc.triggedEvent, tc.payload, evts[0])) @@ -373,7 +383,7 @@ func TestMatchIssuesEvent(t *testing.T) { for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { - evts, err := GetEventsFromContent([]byte(tc.yamlOn)) + evts, err := GetEventsFromContent(fullWorkflowContent(tc.yamlOn)) assert.NoError(t, err) assert.Len(t, evts, 1) diff --git a/routers/web/repo/actions/actions.go b/routers/web/repo/actions/actions.go index 988d2d0a99..644a53f28a 100644 --- a/routers/web/repo/actions/actions.go +++ b/routers/web/repo/actions/actions.go @@ -151,6 +151,11 @@ func prepareWorkflowTemplate(ctx *context.Context, commit *git.Commit) (workflow workflows = append(workflows, workflow) continue } + if err := actions.ValidateWorkflowContent(content); err != nil { + workflow.ErrMsg = ctx.Locale.TrString("actions.runs.invalid_workflow_helper", err.Error()) + workflows = append(workflows, workflow) + continue + } workflow.Workflow = wf // The workflow must contain at least one job without "needs". Otherwise, a deadlock will occur and no jobs will be able to run. hasJobWithoutNeeds := false @@ -315,6 +320,10 @@ func prepareWorkflowList(ctx *context.Context, workflows []WorkflowInfo) { if !job.Status.IsWaiting() { continue } + if err := actions.ValidateWorkflowContent(job.WorkflowPayload); err != nil { + runErrors[run.ID] = ctx.Locale.TrString("actions.runs.invalid_workflow_helper", err.Error()) + break + } hasOnlineRunner := false for _, runner := range runners { if !runner.IsDisabled && runner.CanMatchLabels(job.RunsOn) { diff --git a/routers/web/repo/view_file.go b/routers/web/repo/view_file.go index 3ae0dab25b..65fcb8adba 100644 --- a/routers/web/repo/view_file.go +++ b/routers/web/repo/view_file.go @@ -25,8 +25,6 @@ import ( "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/context" issue_service "code.gitea.io/gitea/services/issue" - - "github.com/nektos/act/pkg/model" ) func prepareLatestCommitInfo(ctx *context.Context) bool { @@ -184,8 +182,7 @@ func prepareFileView(ctx *context.Context, entry *git.TreeEntry) { if err != nil { log.Error("actions.GetContentFromEntry: %v", err) } - _, workFlowErr := model.ReadWorkflow(bytes.NewReader(content)) - if workFlowErr != nil { + if workFlowErr := actions.ValidateWorkflowContent(content); workFlowErr != nil { ctx.Data["FileError"] = ctx.Locale.Tr("actions.runs.invalid_workflow_helper", workFlowErr.Error()) } } else if issue_service.IsCodeOwnerFile(ctx.Repo.TreePath) { -- 2.52.0 From d0a39bc3a4469becfbefaf532cf48fa50928e174 Mon Sep 17 00:00:00 2001 From: silverwind Date: Fri, 10 Apr 2026 12:47:11 +0200 Subject: [PATCH 05/48] Replace `rollup-plugin-license` with `rolldown-license-plugin` (#37130) (#37158) Backport #37130. Only one merge conflict in lockfile. --- This PR was written with the help of Claude Opus 4.6 Co-authored-by: Claude (Opus 4.6) --- build/generate-go-licenses.go | 1 + package.json | 5 +- pnpm-lock.yaml | 369 +--------------------------------- tests/e2e/licenses.test.ts | 9 + vite.config.ts | 51 ++--- 5 files changed, 41 insertions(+), 394 deletions(-) create mode 100644 tests/e2e/licenses.test.ts diff --git a/build/generate-go-licenses.go b/build/generate-go-licenses.go index b710fdb841..057e6a6e49 100644 --- a/build/generate-go-licenses.go +++ b/build/generate-go-licenses.go @@ -19,6 +19,7 @@ import ( // regexp is based on go-license, excluding README and NOTICE // https://github.com/google/go-licenses/blob/master/licenses/find.go +// also defined in vite.config.ts var licenseRe = regexp.MustCompile(`^(?i)((UN)?LICEN(S|C)E|COPYING).*$`) // primaryLicenseRe matches exact primary license filenames without suffixes. diff --git a/package.json b/package.json index ebfc1456c8..791b1e5f59 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "pdfobject": "2.3.1", "perfect-debounce": "2.1.0", "postcss": "8.5.8", - "rollup-plugin-license": "3.7.0", + "rolldown-license-plugin": "2.2.0", "sortablejs": "1.15.7", "swagger-ui-dist": "5.32.1", "tailwindcss": "3.4.19", @@ -73,8 +73,7 @@ "vite-string-plugin": "2.0.2", "vue": "3.5.31", "vue-bar-graph": "2.2.0", - "vue-chartjs": "5.3.3", - "wrap-ansi": "10.0.0" + "vue-chartjs": "5.3.3" }, "devDependencies": { "@eslint-community/eslint-plugin-eslint-comments": "4.7.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3831e591b6..3690d0e56f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -185,9 +185,9 @@ importers: postcss: specifier: 8.5.8 version: 8.5.8 - rollup-plugin-license: - specifier: 3.7.0 - version: 3.7.0(picomatch@4.0.4)(rollup@4.60.1) + rolldown-license-plugin: + specifier: 2.2.0 + version: 2.2.0 sortablejs: specifier: 1.15.7 version: 1.15.7 @@ -230,9 +230,6 @@ importers: vue-chartjs: specifier: 5.3.3 version: 5.3.3(chart.js@4.5.1)(vue@3.5.31(typescript@6.0.2)) - wrap-ansi: - specifier: 10.0.0 - version: 10.0.0 devDependencies: '@eslint-community/eslint-plugin-eslint-comments': specifier: 4.7.1 @@ -1235,144 +1232,6 @@ packages: '@rolldown/pluginutils@1.0.0-rc.2': resolution: {integrity: sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==} - '@rollup/rollup-android-arm-eabi@4.60.1': - resolution: {integrity: sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==} - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm64@4.60.1': - resolution: {integrity: sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==} - cpu: [arm64] - os: [android] - - '@rollup/rollup-darwin-arm64@4.60.1': - resolution: {integrity: sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==} - cpu: [arm64] - os: [darwin] - - '@rollup/rollup-darwin-x64@4.60.1': - resolution: {integrity: sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==} - cpu: [x64] - os: [darwin] - - '@rollup/rollup-freebsd-arm64@4.60.1': - resolution: {integrity: sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==} - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-x64@4.60.1': - resolution: {integrity: sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==} - cpu: [x64] - os: [freebsd] - - '@rollup/rollup-linux-arm-gnueabihf@4.60.1': - resolution: {integrity: sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==} - cpu: [arm] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-arm-musleabihf@4.60.1': - resolution: {integrity: sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==} - cpu: [arm] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-arm64-gnu@4.60.1': - resolution: {integrity: sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-arm64-musl@4.60.1': - resolution: {integrity: sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-loong64-gnu@4.60.1': - resolution: {integrity: sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==} - cpu: [loong64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-loong64-musl@4.60.1': - resolution: {integrity: sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==} - cpu: [loong64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-ppc64-gnu@4.60.1': - resolution: {integrity: sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-ppc64-musl@4.60.1': - resolution: {integrity: sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==} - cpu: [ppc64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-riscv64-gnu@4.60.1': - resolution: {integrity: sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-riscv64-musl@4.60.1': - resolution: {integrity: sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==} - cpu: [riscv64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-s390x-gnu@4.60.1': - resolution: {integrity: sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-x64-gnu@4.60.1': - resolution: {integrity: sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-x64-musl@4.60.1': - resolution: {integrity: sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==} - cpu: [x64] - os: [linux] - libc: [musl] - - '@rollup/rollup-openbsd-x64@4.60.1': - resolution: {integrity: sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==} - cpu: [x64] - os: [openbsd] - - '@rollup/rollup-openharmony-arm64@4.60.1': - resolution: {integrity: sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==} - cpu: [arm64] - os: [openharmony] - - '@rollup/rollup-win32-arm64-msvc@4.60.1': - resolution: {integrity: sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==} - cpu: [arm64] - os: [win32] - - '@rollup/rollup-win32-ia32-msvc@4.60.1': - resolution: {integrity: sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-gnu@4.60.1': - resolution: {integrity: sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==} - cpu: [x64] - os: [win32] - - '@rollup/rollup-win32-x64-msvc@4.60.1': - resolution: {integrity: sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==} - cpu: [x64] - os: [win32] - '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} @@ -1892,10 +1751,6 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} - ansi-styles@6.2.3: - resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} - engines: {node: '>=12'} - ansi_up@6.0.6: resolution: {integrity: sha512-yIa1x3Ecf8jWP4UWEunNjqNX6gzE4vg2gGz+xqRGY+TBSucnYp6RRdPV4brmtg6bQ1ljD48mZ5iGSEj7QEpRKA==} @@ -1916,10 +1771,6 @@ packages: resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} engines: {node: '>= 0.4'} - array-find-index@1.0.2: - resolution: {integrity: sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==} - engines: {node: '>=0.10.0'} - asciinema-player@3.15.1: resolution: {integrity: sha512-agVYeNlPxthLyAb92l9AS7ypW0uhesqOuQzyR58Q4Sj+MvesQztZBgx86lHqNJkB8rQ6EP0LeA9czGytQUBpYw==} @@ -2113,9 +1964,6 @@ packages: resolution: {integrity: sha512-ObxuY6vnbWTN6Od72xfwN9DbzC7Y2vv8u1Soi9ahRKL37gb6y1qk6/dgjs+3JWuXJHWvsg3BXIwzd/rkmAwavg==} engines: {node: '>= 12.0.0'} - commenting@1.1.0: - resolution: {integrity: sha512-YeNK4tavZwtH7jEgK1ZINXzLKm6DZdEMfsaaieOsCAN0S8vsY7UeuO3Q7d/M018EFgE+IeUAuBOKkFccBZsUZA==} - compare-versions@6.1.1: resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} @@ -3385,9 +3233,6 @@ packages: mlly@1.8.2: resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} - moment@2.30.1: - resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} - moo@0.5.3: resolution: {integrity: sha512-m2fmM2dDm7GZQsY7KK2cme8agi+AAljILjQnof7p1ZMDe6dQ4bdnSMx0cPppudoeNv5hEFQirN6u+O4fDE0IWA==} @@ -3478,10 +3323,6 @@ packages: package-manager-detector@1.6.0: resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} - package-name-regex@2.0.6: - resolution: {integrity: sha512-gFL35q7kbE/zBaPA3UKhp2vSzcPYx2ecbYuwv1ucE9Il6IIgBDweBlH8D68UFGZic2MkllKa2KHCfC1IQBQUYA==} - engines: {node: '>=12'} - parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -3706,22 +3547,14 @@ packages: robust-predicates@3.0.3: resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==} + rolldown-license-plugin@2.2.0: + resolution: {integrity: sha512-7a/v9/9o5/pCpPtx4WSX68/xHC8wmmR/cxkofWQ7I7ep5Tvhjb9KkIUdTyuKc52SHiGSz2PxrS0qm/z2PjJyiQ==} + rolldown@1.0.0-rc.12: resolution: {integrity: sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - rollup-plugin-license@3.7.0: - resolution: {integrity: sha512-RvvOIF+GH3fBR3wffgc/vmjQn6qOn72WjppWVDp/v+CLpT0BbcRBdSkPeeIOL6U5XccdYgSIMjUyXgxlKEEFcw==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.0 - - rollup@4.60.1: - resolution: {integrity: sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - roughjs@4.6.6: resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} @@ -3805,27 +3638,6 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} - spdx-compare@1.0.0: - resolution: {integrity: sha512-C1mDZOX0hnu0ep9dfmuoi03+eOdDoz2yvK79RxbcrVEG1NO1Ph35yW102DHWKN4pk80nwCgeMmSY5L25VE4D9A==} - - spdx-exceptions@2.5.0: - resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} - - spdx-expression-parse@3.0.1: - resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} - - spdx-expression-validate@2.0.0: - resolution: {integrity: sha512-b3wydZLM+Tc6CFvaRDBOF9d76oGIHNCLYFeHbftFXUWjnfZWganmDmvtM5sm1cRwJc/VDBMLyGGrsLFd1vOxbg==} - - spdx-license-ids@3.0.23: - resolution: {integrity: sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==} - - spdx-ranges@2.1.1: - resolution: {integrity: sha512-mcdpQFV7UDAgLpXEE/jOMqvK4LBoO0uTQg0uvXUewmEFhpiZx5yJSZITHB8w1ZahKdhfZqP5GPEOKLyEq5p8XA==} - - spdx-satisfies@5.0.1: - resolution: {integrity: sha512-Nwor6W6gzFp8XX4neaKQ7ChV4wmpSh2sSDemMFSzHxpTw460jxFYeOn+jq4ybnSSw/5sc3pjka9MQPouksQNpw==} - spectral-cli-bundle@1.0.7: resolution: {integrity: sha512-vIUC0nwv9tYxWV1xHdR3CTVDOEEtLKaDCcQpARZgO0Db7VmSpSWJ4xrnVPNSmO59hBtGwW2CVzHf0OimJBaKAA==} engines: {node: '>=20'} @@ -4256,10 +4068,6 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} - wrap-ansi@10.0.0: - resolution: {integrity: sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==} - engines: {node: '>=20'} - write-file-atomic@7.0.1: resolution: {integrity: sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg==} engines: {node: ^20.17.0 || >=22.9.0} @@ -5205,81 +5013,6 @@ snapshots: '@rolldown/pluginutils@1.0.0-rc.2': {} - '@rollup/rollup-android-arm-eabi@4.60.1': - optional: true - - '@rollup/rollup-android-arm64@4.60.1': - optional: true - - '@rollup/rollup-darwin-arm64@4.60.1': - optional: true - - '@rollup/rollup-darwin-x64@4.60.1': - optional: true - - '@rollup/rollup-freebsd-arm64@4.60.1': - optional: true - - '@rollup/rollup-freebsd-x64@4.60.1': - optional: true - - '@rollup/rollup-linux-arm-gnueabihf@4.60.1': - optional: true - - '@rollup/rollup-linux-arm-musleabihf@4.60.1': - optional: true - - '@rollup/rollup-linux-arm64-gnu@4.60.1': - optional: true - - '@rollup/rollup-linux-arm64-musl@4.60.1': - optional: true - - '@rollup/rollup-linux-loong64-gnu@4.60.1': - optional: true - - '@rollup/rollup-linux-loong64-musl@4.60.1': - optional: true - - '@rollup/rollup-linux-ppc64-gnu@4.60.1': - optional: true - - '@rollup/rollup-linux-ppc64-musl@4.60.1': - optional: true - - '@rollup/rollup-linux-riscv64-gnu@4.60.1': - optional: true - - '@rollup/rollup-linux-riscv64-musl@4.60.1': - optional: true - - '@rollup/rollup-linux-s390x-gnu@4.60.1': - optional: true - - '@rollup/rollup-linux-x64-gnu@4.60.1': - optional: true - - '@rollup/rollup-linux-x64-musl@4.60.1': - optional: true - - '@rollup/rollup-openbsd-x64@4.60.1': - optional: true - - '@rollup/rollup-openharmony-arm64@4.60.1': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.60.1': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.60.1': - optional: true - - '@rollup/rollup-win32-x64-gnu@4.60.1': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.60.1': - optional: true - '@rtsao/scc@1.1.0': {} '@scarf/scarf@1.4.0': {} @@ -5925,8 +5658,6 @@ snapshots: dependencies: color-convert: 2.0.1 - ansi-styles@6.2.3: {} - ansi_up@6.0.6: {} any-promise@1.3.0: {} @@ -5942,8 +5673,6 @@ snapshots: aria-query@5.3.2: {} - array-find-index@1.0.2: {} - asciinema-player@3.15.1: dependencies: '@babel/runtime': 7.29.2 @@ -6112,8 +5841,6 @@ snapshots: comment-parser@1.4.6: {} - commenting@1.1.0: {} - compare-versions@6.1.1: {} concat-map@0.0.1: {} @@ -7556,8 +7283,6 @@ snapshots: pkg-types: 1.3.1 ufo: 1.6.3 - moment@2.30.1: {} - moo@0.5.3: {} ms@2.1.3: {} @@ -7627,8 +7352,6 @@ snapshots: package-manager-detector@1.6.0: {} - package-name-regex@2.0.6: {} - parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -7815,6 +7538,8 @@ snapshots: robust-predicates@3.0.3: {} + rolldown-license-plugin@2.2.0: {} + rolldown@1.0.0-rc.12(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1): dependencies: '@oxc-project/types': 0.122.0 @@ -7839,51 +7564,6 @@ snapshots: - '@emnapi/core' - '@emnapi/runtime' - rollup-plugin-license@3.7.0(picomatch@4.0.4)(rollup@4.60.1): - dependencies: - commenting: 1.1.0 - fdir: 6.5.0(picomatch@4.0.4) - lodash: 4.17.23 - magic-string: 0.30.21 - moment: 2.30.1 - package-name-regex: 2.0.6 - rollup: 4.60.1 - spdx-expression-validate: 2.0.0 - spdx-satisfies: 5.0.1 - transitivePeerDependencies: - - picomatch - - rollup@4.60.1: - dependencies: - '@types/estree': 1.0.8 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.60.1 - '@rollup/rollup-android-arm64': 4.60.1 - '@rollup/rollup-darwin-arm64': 4.60.1 - '@rollup/rollup-darwin-x64': 4.60.1 - '@rollup/rollup-freebsd-arm64': 4.60.1 - '@rollup/rollup-freebsd-x64': 4.60.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.60.1 - '@rollup/rollup-linux-arm-musleabihf': 4.60.1 - '@rollup/rollup-linux-arm64-gnu': 4.60.1 - '@rollup/rollup-linux-arm64-musl': 4.60.1 - '@rollup/rollup-linux-loong64-gnu': 4.60.1 - '@rollup/rollup-linux-loong64-musl': 4.60.1 - '@rollup/rollup-linux-ppc64-gnu': 4.60.1 - '@rollup/rollup-linux-ppc64-musl': 4.60.1 - '@rollup/rollup-linux-riscv64-gnu': 4.60.1 - '@rollup/rollup-linux-riscv64-musl': 4.60.1 - '@rollup/rollup-linux-s390x-gnu': 4.60.1 - '@rollup/rollup-linux-x64-gnu': 4.60.1 - '@rollup/rollup-linux-x64-musl': 4.60.1 - '@rollup/rollup-openbsd-x64': 4.60.1 - '@rollup/rollup-openharmony-arm64': 4.60.1 - '@rollup/rollup-win32-arm64-msvc': 4.60.1 - '@rollup/rollup-win32-ia32-msvc': 4.60.1 - '@rollup/rollup-win32-x64-gnu': 4.60.1 - '@rollup/rollup-win32-x64-msvc': 4.60.1 - fsevents: 2.3.3 - roughjs@4.6.6: dependencies: hachure-fill: 0.5.2 @@ -7958,33 +7638,6 @@ snapshots: source-map-js@1.2.1: {} - spdx-compare@1.0.0: - dependencies: - array-find-index: 1.0.2 - spdx-expression-parse: 3.0.1 - spdx-ranges: 2.1.1 - - spdx-exceptions@2.5.0: {} - - spdx-expression-parse@3.0.1: - dependencies: - spdx-exceptions: 2.5.0 - spdx-license-ids: 3.0.23 - - spdx-expression-validate@2.0.0: - dependencies: - spdx-expression-parse: 3.0.1 - - spdx-license-ids@3.0.23: {} - - spdx-ranges@2.1.1: {} - - spdx-satisfies@5.0.1: - dependencies: - spdx-compare: 1.0.0 - spdx-expression-parse: 3.0.1 - spdx-ranges: 2.1.1 - spectral-cli-bundle@1.0.7: optionalDependencies: fsevents: 2.3.3 @@ -8453,12 +8106,6 @@ snapshots: word-wrap@1.2.5: {} - wrap-ansi@10.0.0: - dependencies: - ansi-styles: 6.2.3 - string-width: 8.2.0 - strip-ansi: 7.2.0 - write-file-atomic@7.0.1: dependencies: signal-exit: 4.1.0 diff --git a/tests/e2e/licenses.test.ts b/tests/e2e/licenses.test.ts new file mode 100644 index 0000000000..b347ad95e2 --- /dev/null +++ b/tests/e2e/licenses.test.ts @@ -0,0 +1,9 @@ +import {test, expect} from '@playwright/test'; + +test('licenses.txt', async ({page}) => { + const resp = await page.goto('/assets/licenses.txt'); + expect(resp?.status()).toBe(200); + const content = await resp!.text(); + expect(content).toContain('@vue/'); + expect(content).toContain('code.gitea.io/'); +}); diff --git a/vite.config.ts b/vite.config.ts index cc446f6558..d2d3778509 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,13 +1,12 @@ import {build, defineConfig} from 'vite'; import vuePlugin from '@vitejs/plugin-vue'; import {stringPlugin} from 'vite-string-plugin'; +import {licensePlugin, wrap} from 'rolldown-license-plugin'; import {readFileSync, writeFileSync, mkdirSync, unlinkSync, globSync} from 'node:fs'; import path, {basename, join, parse} from 'node:path'; import {env} from 'node:process'; import tailwindcss from 'tailwindcss'; import tailwindConfig from './tailwind.config.ts'; -import wrapAnsi from 'wrap-ansi'; -import licensePlugin from 'rollup-plugin-license'; import type {InlineConfig, Plugin, Rolldown} from 'vite'; import {camelize} from 'vue'; @@ -39,10 +38,6 @@ const webComponents = new Set([ 'text-expander', ]); -function formatLicenseText(licenseText: string) { - return wrapAnsi(licenseText || '', 80).trim(); -} - const commonRolldownOptions: Rolldown.RolldownOptions = { checks: { eval: false, // htmx needs eval @@ -314,33 +309,29 @@ export default defineConfig(commonViteOpts({ }, }), isProduction ? licensePlugin({ - thirdParty: { - output: { - file: join(import.meta.dirname, 'public/assets/licenses.txt'), - template(deps) { - const line = '-'.repeat(80); - const goJson = readFileSync(join(import.meta.dirname, 'assets/go-licenses.json'), 'utf8'); - const goModules = JSON.parse(goJson).map(({name, licenseText}: {name: string, licenseText: string}) => { - return {name, body: formatLicenseText(licenseText)}; - }); - const jsModules = deps.map((dep) => { - return {name: dep.name, version: dep.version, body: formatLicenseText(dep.licenseText ?? '')}; - }); - const modules = [...goModules, ...jsModules].sort((a, b) => a.name.localeCompare(b.name)); - return modules.map(({name, version, body}: {name: string, version?: string, body: string}) => { - const title = version ? `${name}@${version}` : name; - return `${line}\n${title}\n${line}\n${body}`; - }).join('\n'); - }, - }, - allow(dependency) { - if (dependency.name === 'khroma') return true; // MIT: https://github.com/fabiospampinato/khroma/pull/33 - return /(Apache-2\.0|0BSD|BSD-2-Clause|BSD-3-Clause|MIT|ISC|CPAL-1\.0|Unlicense|EPL-1\.0|EPL-2\.0)/.test(dependency.license ?? ''); - }, + done(deps, context) { + const line = '-'.repeat(80); + const goLicenses = JSON.parse(readFileSync(join(import.meta.dirname, 'assets/go-licenses.json'), 'utf8')); + const combined: Record = {}; + for (const {name, licenseText} of goLicenses) { + combined[name] = wrap(licenseText || '', 80).trim(); + } + for (const {name, version, licenseText} of deps) { + combined[`${name}@${version}`] = wrap(licenseText, 80).trim(); + } + const content = Object.entries(combined) + .sort(([a], [b]) => a.localeCompare(b)) + .map(([title, body]) => `${line}\n${title}\n${line}\n${body}`).join('\n'); + context.emitFile({type: 'asset', fileName: 'licenses.txt', source: content}); + }, + match: /^((UN)?LICEN(S|C)E|COPYING).*$/i, // also defined in build/generate-go-licenses.go + allow(dep) { + if (dep.name === 'khroma') return true; // MIT: https://github.com/fabiospampinato/khroma/pull/33 + return /(Apache-2\.0|0BSD|BSD-2-Clause|BSD-3-Clause|MIT|ISC|CPAL-1\.0|Unlicense|EPL-1\.0|EPL-2\.0)/.test(dep.license); }, }) : { name: 'dev-licenses-stub', - closeBundle() { + configureServer() { writeFileSync(join(outDir, 'licenses.txt'), 'Licenses are disabled during development'); }, }, -- 2.52.0 From fc5e0ec8776497c84c7417ebecfa4ff79881ac4c Mon Sep 17 00:00:00 2001 From: Giteabot Date: Fri, 10 Apr 2026 22:18:18 +0800 Subject: [PATCH 06/48] Add missing `//nolint:depguard` (#37162) (#37170) Backport #37162 by @silverwind When running `golangci-lint` without `GOEXPERIMENT=jsonv2`, a lint error `import 'encoding/json' is not allowed` is seen. All other files in the module that import `encodings/json` have `//nolint` already, so add it. --- This PR was written with the help of Claude Opus 4.6 Co-authored-by: silverwind Co-authored-by: Claude (Opus 4.6) --- modules/json/jsonlegacy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/json/jsonlegacy.go b/modules/json/jsonlegacy.go index 83eabad452..99875b96f1 100644 --- a/modules/json/jsonlegacy.go +++ b/modules/json/jsonlegacy.go @@ -6,7 +6,7 @@ package json import ( - "encoding/json" + "encoding/json" //nolint:depguard // this package wraps it "io" ) -- 2.52.0 From 7a7376dfc88e64e043f915e906b0b59c71c40b84 Mon Sep 17 00:00:00 2001 From: Giteabot Date: Sat, 11 Apr 2026 00:49:55 +0800 Subject: [PATCH 07/48] Implement logout redirection for reverse proxy auth setups (#36085) (#37171) Backport #36085 by @eliroca When authentication is handled externally by a reverse proxy SSO provider, users can be redirected to an external logout URL or relative path defined on the reverse proxy. Co-authored-by: Elisei Roca Co-authored-by: wxiaoguang --- custom/conf/app.example.ini | 5 +++++ modules/setting/security.go | 2 ++ routers/web/auth/auth.go | 7 ++++++- tests/integration/signout_test.go | 31 ++++++++++++++++++++++++------- 4 files changed, 37 insertions(+), 8 deletions(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 26c512b6f6..c714ed9660 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -461,6 +461,11 @@ INTERNAL_TOKEN = ;; Name of cookie used to store authentication information. ;COOKIE_REMEMBER_NAME = gitea_incredible ;; +;; URL or path that Gitea should redirect users to *after* performing its own logout. +;; Use this, if needed, when authentication is handled by a reverse proxy or SSO. +;; For example: "/my-sso/logout?return=/my-sso/home" +;REVERSE_PROXY_LOGOUT_REDIRECT = +;; ;; Reverse proxy authentication header name of user name, email, and full name ;REVERSE_PROXY_AUTHENTICATION_USER = X-WEBAUTH-USER ;REVERSE_PROXY_AUTHENTICATION_EMAIL = X-WEBAUTH-EMAIL diff --git a/modules/setting/security.go b/modules/setting/security.go index a1fd0bce2e..152bcffd9f 100644 --- a/modules/setting/security.go +++ b/modules/setting/security.go @@ -31,6 +31,7 @@ var ( ReverseProxyAuthEmail string ReverseProxyAuthFullName string ReverseProxyLimit int + ReverseProxyLogoutRedirect string ReverseProxyTrustedProxies []string MinPasswordLength int ImportLocalPaths bool @@ -124,6 +125,7 @@ func loadSecurityFrom(rootCfg ConfigProvider) { ReverseProxyAuthFullName = sec.Key("REVERSE_PROXY_AUTHENTICATION_FULL_NAME").MustString("X-WEBAUTH-FULLNAME") ReverseProxyLimit = sec.Key("REVERSE_PROXY_LIMIT").MustInt(1) + ReverseProxyLogoutRedirect = sec.Key("REVERSE_PROXY_LOGOUT_REDIRECT").String() ReverseProxyTrustedProxies = sec.Key("REVERSE_PROXY_TRUSTED_PROXIES").Strings(",") if len(ReverseProxyTrustedProxies) == 0 { ReverseProxyTrustedProxies = []string{"127.0.0.0/8", "::1/128"} diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go index e5e30667f0..d705062b3d 100644 --- a/routers/web/auth/auth.go +++ b/routers/web/auth/auth.go @@ -493,12 +493,17 @@ func SignOut(ctx *context.Context) { } func buildSignOutRedirectURL(ctx *context.Context) string { - // TODO: can also support REVERSE_PROXY_AUTHENTICATION logout URL in the future if ctx.Doer != nil && ctx.Doer.LoginType == auth.OAuth2 { if s := buildOIDCEndSessionURL(ctx, ctx.Doer); s != "" { return s } } + + // The assumption is: if reverse proxy auth is enabled, then the users should only sign-in via reverse proxy auth. + // TODO: in the future, if we need to distinguish different sign-in methods, we need to save the sign-in method in session and check here + if setting.Service.EnableReverseProxyAuth && setting.ReverseProxyLogoutRedirect != "" { + return setting.ReverseProxyLogoutRedirect + } return setting.AppSubURL + "/" } diff --git a/tests/integration/signout_test.go b/tests/integration/signout_test.go index 0c0ac5dd87..ff35dcf7a5 100644 --- a/tests/integration/signout_test.go +++ b/tests/integration/signout_test.go @@ -7,6 +7,7 @@ import ( "net/http" "testing" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/tests" @@ -16,13 +17,29 @@ import ( func TestSignOut(t *testing.T) { defer tests.PrepareTestEnv(t)() - session := loginUser(t, "user2") + t.Run("NormalLogout", func(t *testing.T) { + session := loginUser(t, "user2") - req := NewRequest(t, "GET", "/user/logout") - resp := session.MakeRequest(t, req, http.StatusSeeOther) - assert.Equal(t, "/", test.RedirectURL(resp)) + req := NewRequest(t, "GET", "/user/logout") + resp := session.MakeRequest(t, req, http.StatusSeeOther) + assert.Equal(t, "/", resp.Header().Get("Location")) - // try to view a private repo, should fail - req = NewRequest(t, "GET", "/user2/repo2") - session.MakeRequest(t, req, http.StatusNotFound) + // logged out, try to view a private repo, should fail + req = NewRequest(t, "GET", "/user2/repo2") + session.MakeRequest(t, req, http.StatusNotFound) + }) + + t.Run("ReverseProxyLogoutRedirect", func(t *testing.T) { + defer test.MockVariableValue(&setting.Service.EnableReverseProxyAuth, true)() + defer test.MockVariableValue(&setting.ReverseProxyLogoutRedirect, "/my-sso/logout?return_to=/my-sso/home")() + + session := loginUser(t, "user2") + req := NewRequest(t, "GET", "/user/logout") + resp := session.MakeRequest(t, req, http.StatusSeeOther) + assert.Equal(t, "/my-sso/logout?return_to=/my-sso/home", resp.Header().Get("Location")) + + // logged out, try to view a private repo, should fail + req = NewRequest(t, "GET", "/user2/repo2") + session.MakeRequest(t, req, http.StatusNotFound) + }) } -- 2.52.0 From 0112ec9b3477a52ac0dc4e04607f4f89ac295d90 Mon Sep 17 00:00:00 2001 From: Giteabot Date: Sat, 11 Apr 2026 19:14:35 +0800 Subject: [PATCH 08/48] Fix flaky `TestCatFileBatch/QueryTerminated` test (#37159) (#37178) Backport #37159 by @silverwind `TestCatFileBatch/QueryTerminated` relied on timing to distinguish `os.ErrClosed` vs `io.EOF` error paths. Replace `time.Sleep`-based synchronization with a channel-based hook on pipe close, making both error paths fully deterministic regardless of CI runner speed. Ref: https://github.com/go-gitea/gitea/actions/runs/24193070536/job/70615366804 --- This PR was written with the help of Claude Opus 4.6 Co-authored-by: silverwind Co-authored-by: Claude (Opus 4.6) Co-authored-by: wxiaoguang --- modules/git/catfile_batch_reader.go | 65 +++++++++++++++-------------- modules/git/catfile_batch_test.go | 45 ++++++++++---------- 2 files changed, 57 insertions(+), 53 deletions(-) diff --git a/modules/git/catfile_batch_reader.go b/modules/git/catfile_batch_reader.go index 0c8fc740be..5727c4a8ac 100644 --- a/modules/git/catfile_batch_reader.go +++ b/modules/git/catfile_batch_reader.go @@ -13,63 +13,45 @@ import ( "strconv" "strings" "sync/atomic" - "time" "code.gitea.io/gitea/modules/git/gitcmd" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/util" ) -var catFileBatchDebugWaitClose atomic.Int64 - type catFileBatchCommunicator struct { - closeFunc func(err error) + closeFunc atomic.Pointer[func(err error)] reqWriter io.Writer respReader *bufio.Reader debugGitCmd *gitcmd.Command } -func (b *catFileBatchCommunicator) Close() { - if b.closeFunc != nil { - b.closeFunc(nil) - b.closeFunc = nil +func (b *catFileBatchCommunicator) Close(err ...error) { + if fn := b.closeFunc.Swap(nil); fn != nil { + (*fn)(util.OptionalArg(err)) } } -// newCatFileBatch opens git cat-file --batch in the provided repo and returns a stdin pipe, a stdout reader and cancel function -func newCatFileBatch(ctx context.Context, repoPath string, cmdCatFile *gitcmd.Command) (ret *catFileBatchCommunicator) { +// newCatFileBatch opens git cat-file --batch/--batch-check/--batch-command command and prepares the stdin/stdout pipes for communication. +func newCatFileBatch(ctx context.Context, repoPath string, cmdCatFile *gitcmd.Command) *catFileBatchCommunicator { ctx, ctxCancel := context.WithCancelCause(ctx) - - // We often want to feed the commits in order into cat-file --batch, followed by their trees and subtrees as necessary. stdinWriter, stdoutReader, stdPipeClose := cmdCatFile.MakeStdinStdoutPipe() - pipeClose := func() { - if delay := catFileBatchDebugWaitClose.Load(); delay > 0 { - time.Sleep(time.Duration(delay)) // for testing purpose only - } - stdPipeClose() - } - closeFunc := func(err error) { - ctxCancel(err) - pipeClose() - } - return newCatFileBatchWithCloseFunc(ctx, repoPath, cmdCatFile, stdinWriter, stdoutReader, closeFunc) -} - -func newCatFileBatchWithCloseFunc(ctx context.Context, repoPath string, cmdCatFile *gitcmd.Command, - stdinWriter gitcmd.PipeWriter, stdoutReader gitcmd.PipeReader, closeFunc func(err error), -) *catFileBatchCommunicator { ret := &catFileBatchCommunicator{ debugGitCmd: cmdCatFile, - closeFunc: closeFunc, reqWriter: stdinWriter, respReader: bufio.NewReaderSize(stdoutReader, 32*1024), // use a buffered reader for rich operations } + ret.closeFunc.Store(new(func(err error) { + ctxCancel(err) + stdPipeClose() + })) err := cmdCatFile.WithDir(repoPath).StartWithStderr(ctx) if err != nil { log.Error("Unable to start git command %v: %v", cmdCatFile.LogString(), err) // ideally here it should return the error, but it would require refactoring all callers // so just return a dummy communicator that does nothing, almost the same behavior as before, not bad - closeFunc(err) + ret.Close(err) return ret } @@ -78,12 +60,33 @@ func newCatFileBatchWithCloseFunc(ctx context.Context, repoPath string, cmdCatFi if err != nil && !errors.Is(err, context.Canceled) { log.Error("cat-file --batch command failed in repo %s, error: %v", repoPath, err) } - closeFunc(err) + ret.Close(err) }() return ret } +func (b *catFileBatchCommunicator) debugKill() (ret struct { + beforeClose chan struct{} + blockClose chan struct{} + afterClose chan struct{} +}, +) { + ret.beforeClose = make(chan struct{}) + ret.blockClose = make(chan struct{}) + ret.afterClose = make(chan struct{}) + oldCloseFunc := b.closeFunc.Load() + b.closeFunc.Store(new(func(err error) { + b.closeFunc.Store(nil) + close(ret.beforeClose) + <-ret.blockClose + (*oldCloseFunc)(err) + close(ret.afterClose) + })) + b.debugGitCmd.DebugKill() + return ret +} + // catFileBatchParseInfoLine reads the header line from cat-file --batch // We expect: SP SP LF // then leaving the rest of the stream " LF" to be read diff --git a/modules/git/catfile_batch_test.go b/modules/git/catfile_batch_test.go index 69662ffc1a..782d34d249 100644 --- a/modules/git/catfile_batch_test.go +++ b/modules/git/catfile_batch_test.go @@ -7,9 +7,7 @@ import ( "io" "os" "path/filepath" - "sync" "testing" - "time" "code.gitea.io/gitea/modules/test" @@ -39,13 +37,22 @@ func testCatFileBatch(t *testing.T) { require.Error(t, err) }) - simulateQueryTerminated := func(pipeCloseDelay, pipeReadDelay time.Duration) (errRead error) { - catFileBatchDebugWaitClose.Store(int64(pipeCloseDelay)) - defer catFileBatchDebugWaitClose.Store(0) + simulateQueryTerminated := func(t *testing.T, errBeforePipeClose, errAfterPipeClose error) { + readError := func(t *testing.T, r io.Reader, expectedErr error) { + if expectedErr == nil { + return // expectedErr == nil means this read should be skipped + } + n, err := r.Read(make([]byte, 100)) + assert.Zero(t, n) + assert.ErrorIs(t, err, expectedErr) + } + batch, err := NewBatch(t.Context(), filepath.Join(testReposDir, "repo1_bare")) require.NoError(t, err) defer batch.Close() - _, _ = batch.QueryInfo("e2129701f1a4d54dc44f03c93bca0a2aec7c5449") + _, err = batch.QueryInfo("e2129701f1a4d54dc44f03c93bca0a2aec7c5449") + require.NoError(t, err) + var c *catFileBatchCommunicator switch b := batch.(type) { case *catFileBatchLegacy: @@ -58,24 +65,18 @@ func testCatFileBatch(t *testing.T) { t.FailNow() } - wg := sync.WaitGroup{} - wg.Go(func() { - time.Sleep(pipeReadDelay) - var n int - n, errRead = c.respReader.Read(make([]byte, 100)) - assert.Zero(t, n) - }) - time.Sleep(10 * time.Millisecond) - c.debugGitCmd.DebugKill() - wg.Wait() - return errRead - } + require.NotEqual(t, errBeforePipeClose == nil, errAfterPipeClose == nil, "must set exactly one of the expected errors") + inceptor := c.debugKill() + <-inceptor.beforeClose // wait for the command's Close to be called, the pipe is not closed yet + readError(t, c.respReader, errBeforePipeClose) // then caller will read on an open pipe which will be closed soon + close(inceptor.blockClose) // continue to close the pipe + <-inceptor.afterClose // wait for the pipe to be closed + readError(t, c.respReader, errAfterPipeClose) // then caller will read on a closed pipe + } t.Run("QueryTerminated", func(t *testing.T) { - err := simulateQueryTerminated(0, 20*time.Millisecond) - assert.ErrorIs(t, err, os.ErrClosed) // pipes are closed faster - err = simulateQueryTerminated(40*time.Millisecond, 20*time.Millisecond) - assert.ErrorIs(t, err, io.EOF) // reader is faster + simulateQueryTerminated(t, io.EOF, nil) // reader is faster + simulateQueryTerminated(t, nil, os.ErrClosed) // pipes are closed faster }) batch, err := NewBatch(t.Context(), filepath.Join(testReposDir, "repo1_bare")) -- 2.52.0 From f9b808a8d29af38f9126f39d90f0fc5cfcce949f Mon Sep 17 00:00:00 2001 From: Giteabot Date: Sat, 11 Apr 2026 19:16:20 +0800 Subject: [PATCH 09/48] Remove dead CSS rules (#37173) (#37177) Backport #37173 by @silverwind Remove CSS rules whose HTML classes/IDs are no longer referenced in any template, Go source, or JavaScript/TypeScript file: - `.archived-icon`: removed from templates in c85bb62635 - `.bottom-line`: removed from blame rendering in 9c6aeb47f7 - `.commit-status-link`: removed from templates in f3c4baa84b - `.instruct-toggle`: removed from templates in 75e85c25c1 - `.runner-new-text`, `#runner-new`: never referenced outside CSS - `.ap-terminal`: stale, asciinema-player uses `.ap-term`, still not needed - `.scrolling.dimmable.dimmed`: dimmer stand-in never adds this class - `.markup span.align-center/align-right/float-left/float-right`: never produced by any renderer, sanitizer strips class attributes - `.markup ul.no-list`, `.markup ol.no-list`: same as above --- This PR was written with the help of Claude Opus 4.6 Signed-off-by: silverwind Co-authored-by: silverwind Co-authored-by: Claude (Opus 4.6) Co-authored-by: wxiaoguang --- web_src/css/actions.css | 8 ---- web_src/css/base.css | 13 ------ web_src/css/markup/asciicast.css | 4 +- web_src/css/markup/content.css | 69 -------------------------------- web_src/css/modules/modal.css | 8 ---- web_src/css/repo.css | 9 ----- 6 files changed, 3 insertions(+), 108 deletions(-) diff --git a/web_src/css/actions.css b/web_src/css/actions.css index c43ebe21a0..14cf65f273 100644 --- a/web_src/css/actions.css +++ b/web_src/css/actions.css @@ -6,14 +6,6 @@ overflow-x: auto; } -.runner-container .runner-new-text { - color: var(--color-white); -} - -.runner-container #runner-new:hover .runner-new-text { - color: var(--color-white) !important; -} - .runner-container .task-status-success { background-color: var(--color-green); color: var(--color-white); diff --git a/web_src/css/base.css b/web_src/css/base.css index a8d9dea2a2..c278132a00 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -643,10 +643,6 @@ overflow-menu .ui.label { color: var(--color-primary-contrast); } -.archived-icon { - color: var(--color-secondary-dark-2) !important; -} - .oauth2-authorize-application-box { margin-top: 3em !important; } @@ -670,10 +666,6 @@ overflow-menu .ui.label { min-width: 50px; } -.lines-num span.bottom-line::after { - border-bottom: 1px solid var(--color-secondary); -} - .lines-num span::after { content: attr(data-line-number); line-height: var(--line-height-code) !important; @@ -783,11 +775,6 @@ tr.top-line-blame:first-of-type { border-top: none; /* merge code lines belonging to the same commit into one block */ } -.lines-code .bottom-line, -.lines-commit .bottom-line { - border-bottom: 1px solid var(--color-secondary); -} - .migrate .svg.gitea-git { color: var(--color-git); } diff --git a/web_src/css/markup/asciicast.css b/web_src/css/markup/asciicast.css index 89696bc710..a45daaa8e8 100644 --- a/web_src/css/markup/asciicast.css +++ b/web_src/css/markup/asciicast.css @@ -3,6 +3,8 @@ height: auto; } -.ap-terminal { +/* Related: https://github.com/asciinema/asciinema-player/blob/develop/src/components/Terminal.js :
+Old PR: Fix UI regression of asciinema player https://github.com/go-gitea/gitea/pull/26159 */ +.ap-term { overflow: hidden !important; } diff --git a/web_src/css/markup/content.css b/web_src/css/markup/content.css index e7a967a7c6..efa6947ef1 100644 --- a/web_src/css/markup/content.css +++ b/web_src/css/markup/content.css @@ -154,12 +154,6 @@ In markup content, we always use bottom margin for all elements */ padding-inline-start: 2em; } -.markup ul.no-list, -.markup ol.no-list { - padding: 0; - list-style-type: none; -} - .markup .task-list-item { list-style-type: none; } @@ -357,69 +351,6 @@ html[data-gitea-theme-dark="false"] .markup img[src*="#gh-dark-mode-only"] { color: var(--color-text); } -.markup span.align-center { - display: block; - overflow: hidden; - clear: both; -} - -.markup span.align-center > span { - display: block; - margin: 13px auto 0; - overflow: hidden; - text-align: center; -} - -.markup span.align-center span img, -.markup span.align-center span video { - margin: 0 auto; - text-align: center; -} - -.markup span.align-right { - display: block; - overflow: hidden; - clear: both; -} - -.markup span.align-right > span { - display: block; - margin: 13px 0 0; - overflow: hidden; - text-align: right; -} - -.markup span.align-right span img, -.markup span.align-right span video { - margin: 0; - text-align: right; -} - -.markup span.float-left { - display: block; - float: left; - margin-inline-end: 13px; - overflow: hidden; -} - -.markup span.float-left span { - margin: 13px 0 0; -} - -.markup span.float-right { - display: block; - float: right; - margin-inline-start: 13px; - overflow: hidden; -} - -.markup span.float-right > span { - display: block; - margin: 13px auto 0; - overflow: hidden; - text-align: right; -} - .markup code, .markup tt { padding: 0.2em 0.4em; diff --git a/web_src/css/modules/modal.css b/web_src/css/modules/modal.css index 5d686746cb..d45e54b947 100644 --- a/web_src/css/modules/modal.css +++ b/web_src/css/modules/modal.css @@ -159,19 +159,11 @@ display: block; } -.scrolling.dimmable.dimmed { - overflow: hidden; -} - .scrolling.dimmable > .dimmer { justify-content: flex-start; position: fixed; } -.scrolling.dimmable.dimmed > .dimmer { - overflow: auto; -} - .modals.dimmer .ui.scrolling.modal { margin: 2rem auto; } diff --git a/web_src/css/repo.css b/web_src/css/repo.css index 95d6ca2169..923aef04ba 100644 --- a/web_src/css/repo.css +++ b/web_src/css/repo.css @@ -287,10 +287,6 @@ td .commit-summary { min-width: 100px; } -.repository.view.issue .instruct-toggle { - display: inline-block; -} - /* issue title & meta & edit */ .issue-title-header { width: 100%; @@ -1463,11 +1459,6 @@ tbody.commit-list { } } -.commit-list .commit-status-link { - display: inline-block; - vertical-align: middle; -} - .commit-body { margin: 0.25em 0; white-space: pre-wrap; -- 2.52.0 From b37e098ff01fc1984099a4efc5e7802bd1cebdbb Mon Sep 17 00:00:00 2001 From: Giteabot Date: Mon, 13 Apr 2026 09:53:09 +0800 Subject: [PATCH 10/48] Indicate form field readonly via background, fix RunUser config (#37175, #37180) (#37184) Backport #37175 by @silverwind The `Run As Username` field on the install page was a `readonly` input that looked editable but wasn't, confusing users. Style `readonly` inputs with a subtle background, matching other frameworks. image Backport #37180 The comment "so just use current one if config says default" is not right anymore: "git" isn't the "default" value of RunUser (Comment out app.example.ini #15807). The RunUser's value is from current session's username. Fixes #37174 --------- Signed-off-by: wxiaoguang Signed-off-by: silverwind Co-authored-by: silverwind Co-authored-by: Claude (Opus 4.6) Co-authored-by: wxiaoguang --- custom/conf/app.example.ini | 4 +- modules/setting/setting.go | 2 +- options/locale/locale_en-US.json | 3 +- routers/install/install.go | 18 +---- templates/devtest/form-fields.tmpl | 109 +++++++++++++++++++++++++++++ templates/install.tmpl | 2 +- web_src/css/modules/form.css | 8 ++- 7 files changed, 122 insertions(+), 24 deletions(-) create mode 100644 templates/devtest/form-fields.tmpl diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index c714ed9660..ef276e4da5 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -41,10 +41,10 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; App name that shows in every page title -APP_NAME = ; Gitea: Git with a cup of tea +;APP_NAME = Gitea: Git with a cup of tea ;; ;; RUN_USER will automatically detect the current user - but you can set it here change it if you run locally -RUN_USER = ; git +;RUN_USER = ;; ;; Application run mode, affects performance and debugging: "dev" or "prod", default is "prod" ;; Mode "dev" makes Gitea easier to develop and debug, values other than "dev" are treated as "prod" which is for production use. diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 2009be0bbd..3c1ad14428 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -201,7 +201,7 @@ func mustCurrentRunUserMatch(rootCfg ConfigProvider) { if HasInstallLock(rootCfg) { currentUser, match := IsRunUserMatchCurrentUser(RunUser) if !match { - log.Fatal("Expect user '%s' but current user is: %s", RunUser, currentUser) + log.Fatal("Expect user '%s' (RUN_USER in app.ini) but current user is: %s", RunUser, currentUser) } } } diff --git a/options/locale/locale_en-US.json b/options/locale/locale_en-US.json index 9d61e3f1d7..b9d5247b3d 100644 --- a/options/locale/locale_en-US.json +++ b/options/locale/locale_en-US.json @@ -269,7 +269,7 @@ "install.lfs_path": "Git LFS Root Path", "install.lfs_path_helper": "Files tracked by Git LFS will be stored in this directory. Leave empty to disable.", "install.run_user": "Run As Username", - "install.run_user_helper": "The operating system username that Gitea runs as. Note that this user must have access to the repository root path.", + "install.run_user_helper": "The operating system username that Gitea runs as, it must have write access to the data paths. This value is auto-detected and cannot be changed here. To use a different user, restart Gitea under that account.", "install.domain": "Server Domain", "install.domain_helper": "Domain or host address for the server.", "install.ssh_port": "SSH Server Port", @@ -316,7 +316,6 @@ "install.invalid_db_table": "The database table \"%s\" is invalid: %v", "install.invalid_repo_path": "The repository root path is invalid: %v", "install.invalid_app_data_path": "The app data path is invalid: %v", - "install.run_user_not_match": "The 'run as' username is not the current username: %s -> %s", "install.internal_token_failed": "Failed to generate internal token: %v", "install.secret_key_failed": "Failed to generate secret key: %v", "install.save_config_failed": "Failed to save configuration: %v", diff --git a/routers/install/install.go b/routers/install/install.go index dec0b31e5c..a0f32fb939 100644 --- a/routers/install/install.go +++ b/routers/install/install.go @@ -26,7 +26,6 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/timeutil" - "code.gitea.io/gitea/modules/user" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web/middleware" "code.gitea.io/gitea/routers/common" @@ -87,15 +86,7 @@ func Install(ctx *context.Context) { form.AppName = setting.AppName form.RepoRootPath = setting.RepoRootPath form.LFSRootPath = setting.LFS.Storage.Path - - // Note(unknown): it's hard for Windows users change a running user, - // so just use current one if config says default. - if setting.IsWindows && setting.RunUser == "git" { - form.RunUser = user.CurrentUsername() - } else { - form.RunUser = setting.RunUser - } - + form.RunUser = setting.RunUser form.Domain = setting.Domain form.SSHPort = setting.SSH.Port form.HTTPPort = setting.HTTPPort @@ -272,13 +263,6 @@ func SubmitInstall(ctx *context.Context) { return } - currentUser, match := setting.IsRunUserMatchCurrentUser(form.RunUser) - if !match { - ctx.Data["Err_RunUser"] = true - ctx.RenderWithErrDeprecated(ctx.Tr("install.run_user_not_match", form.RunUser, currentUser), tplInstall, &form) - return - } - // Check logic loophole between disable self-registration and no admin account. if form.DisableRegistration && len(form.AdminName) == 0 { ctx.Data["Err_Services"] = true diff --git a/templates/devtest/form-fields.tmpl b/templates/devtest/form-fields.tmpl new file mode 100644 index 0000000000..ee6df2e813 --- /dev/null +++ b/templates/devtest/form-fields.tmpl @@ -0,0 +1,109 @@ +{{template "devtest/devtest-header"}} +
+
+

Input

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +

Textarea

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +

Dropdown

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +

Required

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+{{template "devtest/devtest-footer"}} diff --git a/templates/install.tmpl b/templates/install.tmpl index 45f14d5c57..bc6fed08e9 100644 --- a/templates/install.tmpl +++ b/templates/install.tmpl @@ -117,7 +117,7 @@ {{ctx.Locale.Tr "install.lfs_path_helper"}}
-
+
{{ctx.Locale.Tr "install.run_user_helper"}} diff --git a/web_src/css/modules/form.css b/web_src/css/modules/form.css index 2d315786c6..2999f64cf6 100644 --- a/web_src/css/modules/form.css +++ b/web_src/css/modules/form.css @@ -99,6 +99,13 @@ textarea:focus, color: var(--color-input-text); } +.ui.form input:not([type="checkbox"], [type="radio"])[readonly], +.ui.form textarea[readonly], +.ui.form select[readonly], +.ui.form .ui.selection.dropdown[readonly] { + background: var(--color-secondary-bg); +} + .ui.input { color: var(--color-input-text); } @@ -198,7 +205,6 @@ textarea:focus, background-color: var(--color-error-bg); border-color: var(--color-error-border); color: var(--color-error-text); - border-radius: 0; } .ui.form .field.error textarea:focus, .ui.form .field.error select:focus, -- 2.52.0 From 789a3d3a4db7a33464aeb0f91b28c9e2dfe52443 Mon Sep 17 00:00:00 2001 From: Giteabot Date: Tue, 14 Apr 2026 02:01:44 +0800 Subject: [PATCH 11/48] fix(api): handle fork-only commits in compare API (#37185) (#37199) Backport #37185 by @Mohit25022005 Fix 500 error when comparing branches across fork repositories ## Problem The compare API returns a 500 Internal Server Error when comparing branches where the head commit exists only in the fork repository. ## Cause The API was using the base repository's GitRepo and repository context when converting commits. This fails when the commit does not exist in the base repository, resulting in a "fatal: bad object" error. ## Solution Use the head repository and HeadGitRepo when available to ensure commits are resolved in the correct repository context. ## Result * Fixes "fatal: bad object" error * Enables proper comparison between base and fork repositories * Prevents 500 Internal Server Error Fixes #37168 Co-authored-by: Mohit Swarnkar Co-authored-by: wxiaoguang --- routers/api/v1/repo/compare.go | 11 +++- tests/integration/api_repo_compare_test.go | 58 ++++++++++++++-------- 2 files changed, 45 insertions(+), 24 deletions(-) diff --git a/routers/api/v1/repo/compare.go b/routers/api/v1/repo/compare.go index 6285138c27..69dceb6db9 100644 --- a/routers/api/v1/repo/compare.go +++ b/routers/api/v1/repo/compare.go @@ -62,13 +62,20 @@ func CompareDiff(ctx *context.APIContext) { apiCommits := make([]*api.Commit, 0, len(compareInfo.Commits)) userCache := make(map[string]*user_model.User) + for i := 0; i < len(compareInfo.Commits); i++ { - apiCommit, err := convert.ToCommit(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, compareInfo.Commits[i], userCache, + apiCommit, err := convert.ToCommit( + ctx, + compareInfo.HeadRepo, + compareInfo.HeadGitRepo, + compareInfo.Commits[i], + userCache, convert.ToCommitOptions{ Stat: true, Verification: verification, Files: files, - }) + }, + ) if err != nil { ctx.APIErrorInternal(err) return diff --git a/tests/integration/api_repo_compare_test.go b/tests/integration/api_repo_compare_test.go index 9565e4d209..8aa0035b0a 100644 --- a/tests/integration/api_repo_compare_test.go +++ b/tests/integration/api_repo_compare_test.go @@ -5,46 +5,60 @@ package integration import ( "net/http" + "net/url" "testing" auth_model "code.gitea.io/gitea/models/auth" + repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestAPICompareBranches(t *testing.T) { - defer tests.PrepareTestEnv(t)() + onGiteaRun(t, func(t *testing.T, _ *url.URL) { + session2 := loginUser(t, "user2") + token2 := getTokenForLoggedInUser(t, session2, auth_model.AccessTokenScopeWriteRepository) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - // Login as User2. - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + t.Run("CompareBranches", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - t.Run("CompareBranches", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - req := NewRequestf(t, "GET", "/api/v1/repos/user2/repo20/compare/add-csv...remove-files-b").AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) + req := NewRequestf(t, "GET", "/api/v1/repos/user2/repo20/compare/add-csv...remove-files-b").AddTokenAuth(token2) + resp := MakeRequest(t, req, http.StatusOK) + apiResp := DecodeJSON(t, resp, &api.Compare{}) + assert.Equal(t, 2, apiResp.TotalCommits) + assert.Len(t, apiResp.Commits, 2) + }) - var apiResp *api.Compare - DecodeJSON(t, resp, &apiResp) + t.Run("CompareCommits", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - assert.Equal(t, 2, apiResp.TotalCommits) - assert.Len(t, apiResp.Commits, 2) - }) + req := NewRequestf(t, "GET", "/api/v1/repos/user2/repo20/compare/808038d2f71b0ab02099...c8e31bc7688741a5287f").AddTokenAuth(token2) + resp := MakeRequest(t, req, http.StatusOK) + apiResp := DecodeJSON(t, resp, &api.Compare{}) + assert.Equal(t, 1, apiResp.TotalCommits) + assert.Len(t, apiResp.Commits, 1) + }) - t.Run("CompareCommits", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - req := NewRequestf(t, "GET", "/api/v1/repos/user2/repo20/compare/808038d2f71b0ab02099...c8e31bc7688741a5287f").AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) + t.Run("CompareForkOnlyCommit", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - var apiResp *api.Compare - DecodeJSON(t, resp, &apiResp) + user13 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 13}) + repo11 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 11}) + user13Sess := loginUser(t, "user13") + user13Token := getTokenForLoggedInUser(t, user13Sess, auth_model.AccessTokenScopeWriteRepository) - assert.Equal(t, 1, apiResp.TotalCommits) - assert.Len(t, apiResp.Commits, 1) + _, err := createFileInBranch(user13, repo11, createFileInBranchOptions{OldBranch: "master", NewBranch: "new-branch"}, map[string]string{"file.txt": "content"}) + require.NoError(t, err) + req := NewRequestf(t, "GET", "/api/v1/repos/user12/repo10/compare/master...user13:new-branch").AddTokenAuth(user13Token) + resp := MakeRequest(t, req, http.StatusOK) + apiResp := DecodeJSON(t, resp, &api.Compare{}) + assert.Equal(t, 1, apiResp.TotalCommits) + assert.Len(t, apiResp.Commits, 1) + }) }) } -- 2.52.0 From 8687faaf3af6c7801597cba0d38e6c278b4e6f68 Mon Sep 17 00:00:00 2001 From: Giteabot Date: Tue, 14 Apr 2026 02:42:56 +0800 Subject: [PATCH 12/48] Always show owner/repo name in compare page dropdowns (#37172) (#37200) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backport #37172 by @xingxing21 Fixes: https://github.com/go-gitea/gitea/issues/36677 The fix is a template-only change in [templates/repo/diff/compare.tmpl:16-25](vscode-webview://1ca9j6f1e3qtaf59o0cr4ind65ulf8mevvbbbq88int1gg2lncar/templates/repo/diff/compare.tmpl#L16-L25). Before, the display names were built with conditional logic: All four variables defaulted to just the owner name (Examples) $HeadCompareName was only upgraded to owner/repo format in two narrow cases: Same org, different repos A root repo exists with the same owner as the head repo All other cases (same-repo PRs, cross-org PRs) got owner-only labels After, all four variables are unconditionally set to owner/repo format using printf "%s/%s": ``` - {{$BaseCompareName := printf "%s/%s" $.BaseName $.Repository.Name -}} - {{- $HeadCompareName := printf "%s/%s" $.HeadRepo.OwnerName $.HeadRepo.Name -}} - {{- $OwnForkCompareName := "" -}} - {{- if .OwnForkRepo -}} - {{- $OwnForkCompareName = printf "%s/%s" .OwnForkRepo.OwnerName .OwnForkRepo.Name -}} - {{- end -}} - {{- $RootRepoCompareName := "" -}} - {{- if .RootRepo -}} - {{- $RootRepoCompareName = printf "%s/%s" .RootRepo.OwnerName .RootRepo.Name -}} - {{- end -}} + {{$BaseCompareName := $.Repository.FullName -}} + {{$HeadCompareName := $.HeadRepo.FullName -}} + {{$OwnForkCompareName := "" -}} + {{if $.OwnForkRepo -}} + {{$OwnForkCompareName = $.OwnForkRepo.FullName -}} + {{end -}} + {{$RootRepoCompareName := "" -}} + {{if $.RootRepo -}} + {{$RootRepoCompareName = $.RootRepo.FullName -}} + {{end -}} ``` These variables drive the labels in the base and head branch selector buttons and their dropdown items. No backend changes were needed — $.BaseName, $.Repository.Name, $.HeadRepo.OwnerName, and $.HeadRepo.Name were already available in the template context. Co-authored-by: Xing Hong <39619359+xingxing21@users.noreply.github.com> Co-authored-by: wxiaoguang --- templates/repo/diff/compare.tmpl | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/templates/repo/diff/compare.tmpl b/templates/repo/diff/compare.tmpl index 87c783f446..afd44f26a4 100644 --- a/templates/repo/diff/compare.tmpl +++ b/templates/repo/diff/compare.tmpl @@ -13,22 +13,16 @@ {{ctx.Locale.Tr "action.compare_commits_general"}} {{end}} - {{$BaseCompareName := $.BaseName -}} - {{- $HeadCompareName := $.HeadRepo.OwnerName -}} - {{- if and (eq $.BaseName $.HeadRepo.OwnerName) (ne $.Repository.Name $.HeadRepo.Name) -}} - {{- $HeadCompareName = printf "%s/%s" $.HeadRepo.OwnerName $.HeadRepo.Name -}} - {{- end -}} - {{- $OwnForkCompareName := "" -}} - {{- if .OwnForkRepo -}} - {{- $OwnForkCompareName = .OwnForkRepo.OwnerName -}} - {{- end -}} - {{- $RootRepoCompareName := "" -}} - {{- if .RootRepo -}} - {{- $RootRepoCompareName = .RootRepo.OwnerName -}} - {{- if eq $.HeadRepo.OwnerName .RootRepo.OwnerName -}} - {{- $HeadCompareName = printf "%s/%s" $.HeadRepo.OwnerName $.HeadRepo.Name -}} - {{- end -}} - {{- end -}} + {{$BaseCompareName := $.Repository.FullName -}} + {{$HeadCompareName := $.HeadRepo.FullName -}} + {{$OwnForkCompareName := "" -}} + {{if $.OwnForkRepo -}} + {{$OwnForkCompareName = $.OwnForkRepo.FullName -}} + {{end -}} + {{$RootRepoCompareName := "" -}} + {{if $.RootRepo -}} + {{$RootRepoCompareName = $.RootRepo.FullName -}} + {{end -}}
{{svg "octicon-git-compare"}} -- 2.52.0 From d1be5c3612125767d58c6b398f25b8a0ed5ec79a Mon Sep 17 00:00:00 2001 From: Giteabot Date: Tue, 14 Apr 2026 04:26:32 +0800 Subject: [PATCH 13/48] Fix encoding for Matrix Webhooks (#37190) (#37201) Backport #37190 by @bircni `url.PathEscape` unnecessarily encodes ! to %21, causing Matrix homeservers to reject the request with 401. Replace %21 back to ! after escaping. Fixes #36012 Signed-off-by: wxiaoguang Co-authored-by: Nicolas Co-authored-by: Claude Sonnet 4.6 Co-authored-by: wxiaoguang --- routers/web/repo/setting/webhook.go | 11 ++++++++++- routers/web/repo/setting/webhook_test.go | 15 +++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 routers/web/repo/setting/webhook_test.go diff --git a/routers/web/repo/setting/webhook.go b/routers/web/repo/setting/webhook.go index b0f3a5cfee..8c57a68b25 100644 --- a/routers/web/repo/setting/webhook.go +++ b/routers/web/repo/setting/webhook.go @@ -450,12 +450,21 @@ func MatrixHooksEditPost(ctx *context.Context) { editWebhook(ctx, matrixHookParams(ctx)) } +func matrixRoomIDEncode(roomID string) string { + // See https://spec.matrix.org/latest/appendices/#room-ids + // Some (unrelated) demo links: https://spec.matrix.org/latest/appendices/#matrixto-navigation + // API spec: https://spec.matrix.org/v1.18/client-server-api/#sending-events-to-a-room + // Some of their examples show links like: "PUT /rooms/!roomid:domain/state/m.example.event" + return strings.NewReplacer("%21", "!", "%3A", ":").Replace(url.PathEscape(roomID)) +} + func matrixHookParams(ctx *context.Context) webhookParams { form := web.GetForm(ctx).(*forms.NewMatrixHookForm) + // TODO: need to migrate to the latest (v3) API: https://spec.matrix.org/v1.18/client-server-api/ return webhookParams{ Type: webhook_module.MATRIX, - URL: fmt.Sprintf("%s/_matrix/client/r0/rooms/%s/send/m.room.message", form.HomeserverURL, url.PathEscape(form.RoomID)), + URL: fmt.Sprintf("%s/_matrix/client/r0/rooms/%s/send/m.room.message", form.HomeserverURL, matrixRoomIDEncode(form.RoomID)), ContentType: webhook.ContentTypeJSON, HTTPMethod: http.MethodPut, WebhookForm: form.WebhookForm, diff --git a/routers/web/repo/setting/webhook_test.go b/routers/web/repo/setting/webhook_test.go new file mode 100644 index 0000000000..ca4a21e075 --- /dev/null +++ b/routers/web/repo/setting/webhook_test.go @@ -0,0 +1,15 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package setting + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestWebhookMatrix(t *testing.T) { + assert.Equal(t, "!roomid:domain", matrixRoomIDEncode("!roomid:domain")) + assert.Equal(t, "!room%23id:domain", matrixRoomIDEncode("!room#id:domain")) // maybe it should never really happen in real world +} -- 2.52.0 From 68f5e40e46e769ad2b1b956b1cd579bf3669e254 Mon Sep 17 00:00:00 2001 From: Giteabot Date: Tue, 14 Apr 2026 05:42:11 +0800 Subject: [PATCH 14/48] fix(api): handle missing base branch in PR commits API (#37193) (#37203) Backport #37193 by @Mohit25022005 fix(api): handle missing base branch in PR commits API Closes #36366 Previously, the PR commits API returned a 500 Internal Server Error when the base branch was missing due to an unhandled git "bad revision" error. This change: - Checks for base branch existence before performing git operations - Returns 404 when the base branch does not exist - Prevents git errors from surfacing as 500 responses This improves API robustness and aligns behavior with expected error handling. Tested locally by: - Creating a pull request - Deleting the base branch - Verifying that the API returns 404 instead of 500 Co-authored-by: Mohit Swarnkar Co-authored-by: wxiaoguang Co-authored-by: Lunny Xiao --- routers/api/v1/repo/pull.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index ef86f413b7..ef8cc6cd93 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -22,6 +22,7 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/git/gitcmd" "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" @@ -1429,7 +1430,11 @@ func GetPullRequestCommits(ctx *context.APIContext) { } else { compareInfo, err = git_service.GetCompareInfo(ctx, pr.BaseRepo, pr.BaseRepo, baseGitRepo, git.RefNameFromBranch(pr.BaseBranch), git.RefName(pr.GetGitHeadRefName()), false, false) } - if err != nil { + + if gitcmd.StderrHasPrefix(err, "fatal: bad revision") { + ctx.APIError(http.StatusNotFound, "invalid base branch or revision") + return + } else if err != nil { ctx.APIErrorInternal(err) return } -- 2.52.0 From df0ad4e8c1e51549216dcb0614df395193f41935 Mon Sep 17 00:00:00 2001 From: Giteabot Date: Tue, 14 Apr 2026 23:59:31 +0800 Subject: [PATCH 15/48] Fix UI regression (#37218) (#37219) Backport #37218 by wxiaoguang Fix #37213 Also fix the misaligned tags, remove unused classes, etc. Co-authored-by: wxiaoguang --- tailwind.config.ts | 3 + templates/repo/actions/list.tmpl | 80 +++++++++---------- templates/repo/actions/workflow_dispatch.tmpl | 9 ++- web_src/css/modules/message.css | 9 --- 4 files changed, 49 insertions(+), 52 deletions(-) diff --git a/tailwind.config.ts b/tailwind.config.ts index 415837f892..199433d0cf 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -93,6 +93,9 @@ export default { return [`${i}`, `${i === 0 ? '0' : `${i}px`}`]; })), }, + extend: { + zIndex: {'1': '1'}, + }, }, plugins: [ plugin(({addUtilities}) => { diff --git a/templates/repo/actions/list.tmpl b/templates/repo/actions/list.tmpl index f31ef1a73a..7baac43dcf 100644 --- a/templates/repo/actions/list.tmpl +++ b/templates/repo/actions/list.tmpl @@ -25,49 +25,49 @@
-
- {{ctx.Locale.TrN .Page.Paginater.Total "actions.runs.workflow_run_count_1" "actions.runs.workflow_run_count_n" .Page.Paginater.Total}} -