From a053126bd9f99ac09f3875f44dd8ab2c1aed8a64 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Thu, 4 Jun 2026 12:50:01 -0500 Subject: [PATCH 1/4] chore: consolidate changelog to minor version numbers only Merge patch-level entries into their minor version, drop .00 suffixes, and add today's Update Server and license UI fixes to moko.06. Co-Authored-By: Claude Opus 4.6 (1M context) --- CHANGELOG.md | 531 +++++---------------------------------------------- 1 file changed, 51 insertions(+), 480 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11b78462e4..faf3ddd835 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,9 @@ # Changelog -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). -## [v1.26.1-moko.06.02.00] - 2026-06-02 +All notable changes to MokoGitea are documented here. Versions follow the format +`v{upstream}-moko.{major}.{minor}` (e.g. `v1.26.1-moko.06.02`). + +## [v1.26.1-moko.06] - 2026-06-04 * FEATURES * feat(licenses): full commercial license management system @@ -11,40 +11,38 @@ been added to each release, please refer to the [blog](https://blog.gitea.com). * Search keys by customer, domain, key number, email, or payment ref * Download gating (none/prerelease/all modes) * Domain lock grace period (DomainLockHours) + * Domain restriction on packages and keys (comma-separated allowed domains) * RepoScope enforcement — packages scoped to specific repos * Configurable license key prefix per organization + * Master key always visible with Regenerate button + * License package creation at repo level via modal * Manual release-to-stream mapping with UI selector - * Joomla changelog XML endpoint (/changelog.xml) - * SHA256 checksums from sidecar files in Joomla updates.xml - * Joomla-standard tag values (dev/alpha/beta/rc/stable) * Double confirmation modals for permanent deletion * Combolist channel picker (replaces checkboxes) * Extension metadata in repo settings (per-repo override) * API: package CRUD, key revoke, key renew, settings GET/PUT * API: purchase webhook with PaymentRef idempotency * API: public validation endpoint (no auth) - * Migration v340-v342: all new columns synced - * feat(updates): 7 platform update feeds - * Joomla XML with downloadkey, SHA256, changelog URL + * Migration v340-v344: all new columns synced + * feat(updates): Update Server system (renamed from "Licensing") + * Joomla XML with SHA256, changelog URL, version from asset filename * Dolibarr JSON with channel filtering * WordPress PUC-compatible JSON (plugin-update-checker) * Composer packages.json * PrestaShop module update XML * Drupal update status XML * WHMCS module update JSON - * feat(updates): feed always public — downloads gated separately - * feat(updates): stream-name tags supported alongside version tags - * feat(updates): version extraction via regex from release titles - * feat(updates): infourl defaults to release listing / support URL - * feat(updates): downloadkey prefix matches Akeeba pattern (dlid=) + * Feed always public — downloads gated separately + * Stream-name tags supported alongside version tags + * Omit `` for package extension types + * No `` when require_key is off * feat(orgs): enterprise sub-org hierarchy with parent-child relationships * feat(repos): three-level visibility — Public (200), Private (403), Hidden (404) - * feat(settings): separate licensing settings page (/settings/licensing) - * feat(settings): advanced settings on dedicated page (/settings/advanced) - * feat(settings): section headers with dividers and icons - * feat(ui): icons on all settings navbars (repo, org, user, admin) + * feat(settings): Update Server settings page with enable toggle in Advanced Settings + * feat(settings): advanced settings on dedicated page with dividing headers + * feat(settings): icons on all settings navbars (repo, org, user, admin) * feat(ui): styled 403 Access Denied page with inline login form - * feat(ui): open-in-new-tab button on feed URLs + * feat(issues): custom fields foundation — model, migration, settings UI * SECURITY * fix(security): ownership guards on all API handlers (cross-org prevention) * fix(security): RepoScope JSON parsing (substring matching bug) @@ -55,48 +53,45 @@ been added to each release, please refer to the [blog](https://blog.gitea.com). * fix(security): licensed private repos allow release viewing for signed-in users * fix(security): anonymous download access respects download_gating setting * FIXES - * fix(licenses): expanded delete permissions to org owners + site admins * fix(licenses): explicit xorm column names for UpdateStreamConfig fields * fix(licenses): feed always public when licensing enabled + * fix(settings): prevent double-highlight on Advanced Settings nav item + * fix(settings): redirect back to /settings/advanced after save + * fix(build): remove stale custom field API routes and dead code + * fix(build): replace invalid UTF-8 character in API comment * fix(build): permanent fixes for AI migration, feed/file.go, unused imports + * fix(updateserver): version extracted from asset filename (not release title) + * fix(updateserver): omit `` for package types per Joomla spec -## [v1.26.1-moko.05.15.00] - 2026-05-31 +## [v1.26.1-moko.05] - 2026-05-31 * BREAKING CHANGES * Deprecated Issue.Ref branch selector UI (#307) * Removed branch/tag selector from issue sidebar and new issue form - * Removed ref badge from issue lists - * Removed POST /ref web route and UpdateIssueRef handler * DB column and commit-close logic preserved for backward compatibility - * API create/edit still accept `ref` field (no-op) for backward compat * FEATURES - * feat(ui): add generic combo-multiselect component (#361) + * feat(ui): generic combo-multiselect component (#361) * Reusable dropdown with search, checkable items, and selected-items display - * Template: `shared/combolist.tmpl` — accepts Items, Name, Title, SelectedValues - * Decoupled from issue sidebar — works in any form context + * Template: `shared/combolist.tmpl` * feat(updates): extension metadata settings for update feed generation * feat(licenses): platform enforcement, key deletion, expired key cleanup - * feat(licenses): store keys in plaintext, show full key with copy button + * feat(actions): rebrand actions bot user to mokogitea-actions (#233, #234) + * Backward-compatible: recognizes github-actions[bot], gitea-actions[bot] + * feat(actions): actions bot user in branch protection whitelist (#233, #234) + * WhitelistActionsUser, MergeWhitelistActionsUser, ForcePushAllowlistActionsUser * TECH DEBT - * chore: full namespace migration from git.mokoconsulting.tech to code.mokoconsulting.tech (#336, #337, #344) - * Go module path, all imports, template URLs, workflow configs (2,276 files) + * chore: full namespace migration to code.mokoconsulting.tech (#336, #337, #344) * fix(blame): set HasSourceRenderedToggle for renderable files (#344) - * fix(settings): translate team permission strings via data-locale attributes (#344) + * fix(settings): translate team permission strings via data-locale (#344) * fix(dropzone): use relative path for non-image attachment markdown links (#344) * fix(templates): add required validation to issue dropdown fields (#350) - * refactor(ts): remove redundant `handled` field from MarkdownHandleIndentionResult (#350) - * refactor(go): rename HasOrgOrUserVisible to IsOwnerVisibleToDoer (#350) * refactor(go): replace ValuesRepository with maps.Values (Go 1.21+) (#357) - * refactor(go): remove CanEnableEditor wrapper, use CanContentChange directly (#357) - * fix(ts): parseIssueHref now uses URL pathname and trims appSubUrl (#360) - * fix(actions): enforce MaxJobNumPerRun (256) limit when creating jobs (#360) + * refactor(go): remove CanEnableEditor wrapper (#357) + * fix(ts): parseIssueHref uses URL pathname and trims appSubUrl (#360) + * fix(actions): enforce MaxJobNumPerRun (256) limit (#360) * fix(css): use calc(infinity * 1px) for --border-radius-full (#361) - * fix(css): remove legacy .center class from 2015, replace with tw-text-center (#361) - * chore: remove stale TODO from OAuth2 regenerate secret (already implemented) (#332) - * chore: remove stale pull request test stub TODOs (#328) - * chore: remove stale GetProjectsMode TODO - * chore: remove stale mustNotBeArchived/mustEnableEditor FIXME from API - * fix(routes): remove dead legacy /cherry-pick/{sha} route (replaced by /_cherrypick/) + * fix(css): remove legacy .center class, replace with tw-text-center (#361) + * fix(routes): remove dead legacy /cherry-pick/{sha} route * fix(feed): use full ref name instead of ShortName for file feed revision * BUGFIXES * fix(build): use slices.Collect for maps.Values (Go 1.23+ compat) @@ -104,25 +99,10 @@ been added to each release, please refer to the [blog](https://blog.gitea.com). * fix(licenses): only show licenses tab when licensing is enabled * fix(licenses): show feed URLs based on repo update platform setting * fix(updates): correct dlid prefix and align XML with Joomla standard - -## [v1.26.1-moko.05.06.00] - 2026-05-30 - -* FEATURES - * feat(actions): rebrand actions bot user to mokogitea-actions (#233, #234) - * Name: gitea-actions → mokogitea-actions, FullName: MokoGitea Actions - * Email: mokogitea-actions[bot]@mokoconsulting.tech - * Backward-compatible: recognizes github-actions[bot], gitea-actions[bot], mokogitea-actions[bot] - * feat(actions): add actions bot user to branch protection whitelist (#233, #234) - * New toggles: WhitelistActionsUser, MergeWhitelistActionsUser, ForcePushAllowlistActionsUser - * Allows CI/CD workflows to push/merge/force-push to protected branches when enabled - * DB migration v334 adds the three boolean columns - * Exposed in API (create/edit branch protection) and web UI settings * INFRASTRUCTURE * fix(ci): auto-deploy to production on merge to main (#235) - * Deploy workflow now triggers on push to main, not just manual dispatch - * Version derived from git describe for auto-deploys -## [v1.26.1-moko.04.00.00] - 2026-05-24 +## [v1.26.1-moko.04] - 2026-05-24 * SECURITY * Backport 12 upstream v1.26.2 security fixes: @@ -131,47 +111,38 @@ been added to each release, please refer to the [blog](https://blog.gitea.com). * OAuth PKCE hardening and refresh token replay protection (#142) * Wiki git write and LFS token access enforcement (#143) * Public-only token filtering in API queries (#144) - * Reading permission fix (#145) * Artifact signature payload hardening (#146) * AWS credentials encryption (#161) * Mermaid v11.15.0 security update (#162) * Composer package permission check (#164) * BUGFIXES * fix(actions): nil pointer dereference in concurrency during PR creation (#136) - * fix(ui): actions runs list broken row layout — CSS class mismatch (#138) - * fix: scheduled action panic with null event payload (upstream #37459) - * fix: treat email addresses case-insensitively (upstream #37600) + * fix(ui): actions runs list broken row layout (#138) + * fix: scheduled action panic with null event payload + * fix: treat email addresses case-insensitively * fix: .mod lexer panic — removed invalid AMPL mapping - * fix: remove unused setting import in action.go - * fix: restore Permission field access in context middleware * FEATURES - * Joomla-style updates.xml with channel selection (stable/dev/security/rc) - * Update checker reads from updates.xml with configurable CHANNEL setting - * Admin dashboard shows update banner with channel name and docker pull command - * Upstream bug sync workflow — daily automated issue creation from release/v1.26 + * Joomla-style updates.xml with channel selection + * Update checker with configurable CHANNEL setting + * Admin dashboard update banner with docker pull command + * Upstream bug sync workflow — daily automated issue creation * PR RC release workflow — auto-build RC on PR to main * INFRASTRUCTURE * New 3-part versioning: v{upstream}-moko.{major}.{minor}.{patch} - * Branding updates: error pages, home page, settings link to MokoGitea + * Branding updates: error pages, home page, settings link * Deploy workflow updated for new version format * PROCESS * Created `type: bug` and `upstream` labels for automated issue tracking - * Deduplicated 19 duplicate feature request issues * Closed 24 upstream bug/security issues after backporting -## [MokoGitea Unreleased] +## [v1.26.1-moko.03] - 2026-05-15 * FEATURES - * feat(api): Bulk issue operations — add/remove/replace labels, close/reopen, set milestone, and set assignees across multiple issues in a single request (#21) - * `POST /api/v1/repos/{owner}/{repo}/issues/bulk/labels` - * `POST /api/v1/repos/{owner}/{repo}/issues/bulk/state` - * `POST /api/v1/repos/{owner}/{repo}/issues/bulk/milestone` - * `POST /api/v1/repos/{owner}/{repo}/issues/bulk/assignees` - * Partial-failure support: returns per-issue success/failure map + * feat(api): Bulk issue operations — add/remove/replace labels, close/reopen, set milestone, assignees (#21) * INFRASTRUCTURE - * Grafana: Standardized kiosk header across all 14 playlist dashboards — each now shows dashboard name, kiosk link, terminal/exit/switch instructions + * Grafana: Standardized kiosk header across all 14 playlist dashboards * PROCESS - * Reopened 9 closed issues lacking documented testing proof (#3, #5, #38, #41, #70, #74, #75, #76, #78) + * Reopened 9 closed issues lacking documented testing proof * Created `pending: testing` label for features awaiting verification * Established policy: issues must not be closed without documented testing proof @@ -196,403 +167,3 @@ been added to each release, please refer to the [blog](https://blog.gitea.com). * Fix org team assignee/reviewer lookups for team member permissions (#37365) #37391 * Fix repo init README EOL (#37388) #37399 * Fix: dump with default zip type produces uncompressed zip (#37401)#37402 - -## [1.26.0](https://github.com/go-gitea/gitea/releases/tag/v1.26.0) - 2026-04-17 - -* 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) - * Feature non-zipped actions artifacts (action v7 / nodejs / npm v6.2.0) (#36786) -* 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 - * Implement logout redirection for reverse proxy auth setups (#36085) (#37171) - * 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) - * 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 - * Frontend iframe renderer framework: 3D models, OpenAPI (#37233) (#37273) - * Fix CODEOWNERS absolute path matching. (#37244) (#37264) - * Swift registry metadata: preserve more JSON fields and accept empty metadata (#37254) (#37261) - * Fix user ssh key exporting and tests (#37256) (#37258) - * Fix team member avatar size and add tooltip (#37253) - * Fix commit title rendering in action run and blame (#37243) (#37251) - * Fix corrupted JSON caused by goccy library (#37214) (#37220) - * Add test for "fetch redirect", add CSS value validation for external render (#37207) (#37216) - * Fix incorrect concurrency check (#37205) (#37215) - * Fix handle missing base branch in PR commits API (#37193) (#37203) - * Fix encoding for Matrix Webhooks (#37190) (#37201) - * Fix handle fork-only commits in compare API (#37185) (#37199) - * Indicate form field readonly via background, fix RunUser config (#37175, #37180) (#37178) - * Report structurally invalid workflows to users (#37116) (#37164) - * 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 - * Clean up and improve non-gitea js error filter (#37148) (#37155) - * Always show owner/repo name in compare page dropdowns (#37172) (#37200) - * Remove dead CSS rules (#37173) (#37177) - * 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 - * Upgrade go-git to v5.18.0 (#37269) - * Replace rollup-plugin-license with rolldown-license-plugin (#37130) (#37158) - * Bump min go version to 1.26.2 (#37139) (#37143) - * 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) -- 2.52.0 From 10e76cf03347363b02d90b07b362b122a7753a62 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Thu, 4 Jun 2026 13:33:35 -0500 Subject: [PATCH 2/4] chore: remove build/ directory from tracking Build artifacts are created by CI, not tracked in source. Authored-by: Moko Consulting Co-Authored-By: Claude Opus 4.6 (1M context) --- .gitignore | 2 + build/generate-bindata.go | 27 --- build/generate-emoji.go | 219 ---------------------- build/generate-gitignores.go | 126 ------------- build/generate-go-licenses.go | 239 ------------------------ build/generate-openapi.go | 102 ----------- build/openapi3gen/convert.go | 281 ----------------------------- build/openapi3gen/convert_test.go | 170 ----------------- build/openapi3gen/enumscan.go | 188 ------------------- build/openapi3gen/enumscan_test.go | 239 ------------------------ build/test-env-check.sh | 24 --- build/test-env-prepare.sh | 11 -- build/update-locales.sh | 22 --- 13 files changed, 2 insertions(+), 1648 deletions(-) delete mode 100644 build/generate-bindata.go delete mode 100644 build/generate-emoji.go delete mode 100644 build/generate-gitignores.go delete mode 100644 build/generate-go-licenses.go delete mode 100644 build/generate-openapi.go delete mode 100644 build/openapi3gen/convert.go delete mode 100644 build/openapi3gen/convert_test.go delete mode 100644 build/openapi3gen/enumscan.go delete mode 100644 build/openapi3gen/enumscan_test.go delete mode 100755 build/test-env-check.sh delete mode 100755 build/test-env-prepare.sh delete mode 100755 build/update-locales.sh diff --git a/.gitignore b/.gitignore index 76a7578646..d997c141b9 100644 --- a/.gitignore +++ b/.gitignore @@ -120,3 +120,5 @@ prime/ # A Makefile for custom make targets Makefile.local +build/ +dist/ diff --git a/build/generate-bindata.go b/build/generate-bindata.go deleted file mode 100644 index 9ae9c1b15e..0000000000 --- a/build/generate-bindata.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2020 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build ignore - -package main - -import ( - "fmt" - "os" - - "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/assetfs" -) - -func main() { - if len(os.Args) != 3 { - fmt.Println("usage: ./generate-bindata {local-directory} {bindata-filename}") - os.Exit(1) - } - - dir, filename := os.Args[1], os.Args[2] - fmt.Printf("generating bindata for %s to %s\n", dir, filename) - if err := assetfs.GenerateEmbedBindata(dir, filename); err != nil { - fmt.Printf("failed: %s\n", err.Error()) - os.Exit(1) - } -} diff --git a/build/generate-emoji.go b/build/generate-emoji.go deleted file mode 100644 index f276c9e6f8..0000000000 --- a/build/generate-emoji.go +++ /dev/null @@ -1,219 +0,0 @@ -// Copyright 2020 The Gitea Authors. All rights reserved. -// Copyright 2015 Kenneth Shaw -// SPDX-License-Identifier: MIT - -//go:build ignore - -package main - -import ( - "flag" - "fmt" - "go/format" - "io" - "log" - "net/http" - "os" - "regexp" - "sort" - "strconv" - "strings" - "unicode/utf8" - - "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/json" -) - -const ( - gemojiURL = "https://raw.githubusercontent.com/rhysd/gemoji/537ff2d7e0496e9964824f7f73ec7ece88c9765a/db/emoji.json" - maxUnicodeVersion = 16 -) - -var flagOut = flag.String("o", "modules/emoji/emoji_data.go", "out") - -// Gemoji is a set of emoji data. -type Gemoji []Emoji - -// Emoji represents a single emoji and associated data. -type Emoji struct { - Emoji string `json:"emoji"` - Description string `json:"description,omitempty"` - Aliases []string `json:"aliases"` - UnicodeVersion string `json:"unicode_version,omitempty"` - SkinTones bool `json:"skin_tones,omitempty"` -} - -// Don't include some fields in JSON -func (e Emoji) MarshalJSON() ([]byte, error) { - type emoji Emoji - x := emoji(e) - x.UnicodeVersion = "" - x.Description = "" - x.SkinTones = false - return json.Marshal(x) -} - -func main() { - flag.Parse() - - // generate data - buf, err := generate() - if err != nil { - log.Fatalf("generate err: %v", err) - } - - // write - err = os.WriteFile(*flagOut, buf, 0o644) - if err != nil { - log.Fatalf("WriteFile err: %v", err) - } -} - -var replacer = strings.NewReplacer( - "main.Gemoji", "Gemoji", - "main.Emoji", "\n", - "}}", "},\n}", - ", Description:", ", ", - ", Aliases:", ", ", - ", UnicodeVersion:", ", ", - ", SkinTones:", ", ", -) - -var emojiRE = regexp.MustCompile(`\{Emoji:"([^"]*)"`) - -func generate() ([]byte, error) { - // load gemoji data - res, err := http.Get(gemojiURL) - if err != nil { - return nil, err - } - defer res.Body.Close() - - // read all - body, err := io.ReadAll(res.Body) - if err != nil { - return nil, err - } - - // unmarshal - var data Gemoji - err = json.Unmarshal(body, &data) - if err != nil { - return nil, err - } - - skinTones := make(map[string]string) - - skinTones["\U0001f3fb"] = "Light Skin Tone" - skinTones["\U0001f3fc"] = "Medium-Light Skin Tone" - skinTones["\U0001f3fd"] = "Medium Skin Tone" - skinTones["\U0001f3fe"] = "Medium-Dark Skin Tone" - skinTones["\U0001f3ff"] = "Dark Skin Tone" - - var tmp Gemoji - - // filter out emoji that require greater than max unicode version - for i := range data { - val, _ := strconv.ParseFloat(data[i].UnicodeVersion, 64) - if int(val) <= maxUnicodeVersion { - tmp = append(tmp, data[i]) - } - } - data = tmp - - sort.Slice(data, func(i, j int) bool { - return data[i].Aliases[0] < data[j].Aliases[0] - }) - - aliasMap := make(map[string]int, len(data)) - - for i, e := range data { - if e.Emoji == "" || len(e.Aliases) == 0 { - continue - } - for _, a := range e.Aliases { - if a == "" { - continue - } - aliasMap[a] = i - } - } - - // gitea customizations - i, ok := aliasMap["tada"] - if ok { - data[i].Aliases = append(data[i].Aliases, "hooray") - } - i, ok = aliasMap["laughing"] - if ok { - data[i].Aliases = append(data[i].Aliases, "laugh") - } - - // write a JSON file to use with tribute (write before adding skin tones since we can't support them there yet) - file, _ := json.MarshalIndent(data, "", " ") - _ = os.WriteFile("assets/emoji.json", append(file, '\n'), 0o644) - - // Add skin tones to emoji that support it - var ( - s []string - newEmoji string - newDescription string - newData Emoji - ) - - for i := range data { - if data[i].SkinTones { - for k, v := range skinTones { - s = strings.Split(data[i].Emoji, "") - - if utf8.RuneCountInString(data[i].Emoji) == 1 { - s = append(s, k) - } else { - // insert into slice after first element because all emoji that support skin tones - // have that modifier placed at this spot - s = append(s, "") - copy(s[2:], s[1:]) - s[1] = k - } - - newEmoji = strings.Join(s, "") - newDescription = data[i].Description + ": " + v - newAlias := data[i].Aliases[0] + "_" + strings.ReplaceAll(v, " ", "_") - - newData = Emoji{newEmoji, newDescription, []string{newAlias}, "12.0", false} - data = append(data, newData) - } - } - } - - sort.Slice(data, func(i, j int) bool { - return data[i].Aliases[0] < data[j].Aliases[0] - }) - - // add header - str := replacer.Replace(fmt.Sprintf(hdr, gemojiURL, data)) - - // change the format of the unicode string - str = emojiRE.ReplaceAllStringFunc(str, func(s string) string { - var err error - s, err = strconv.Unquote(s[len("{Emoji:"):]) - if err != nil { - panic(err) - } - return "{" + strconv.QuoteToASCII(s) - }) - - // format - return format.Source([]byte(str)) -} - -const hdr = ` -// Copyright 2020 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - - -package emoji - -// Code generated by build/generate-emoji.go. DO NOT EDIT. -// Sourced from %s -var GemojiData = %#v -` diff --git a/build/generate-gitignores.go b/build/generate-gitignores.go deleted file mode 100644 index 7c29ed69fe..0000000000 --- a/build/generate-gitignores.go +++ /dev/null @@ -1,126 +0,0 @@ -//go:build ignore - -package main - -import ( - "archive/tar" - "compress/gzip" - "flag" - "fmt" - "io" - "log" - "net/http" - "os" - "path" - "path/filepath" - "strings" - - "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/util" -) - -func main() { - var ( - prefix = "gitea-gitignore" - url = "https://api.github.com/repos/github/gitignore/tarball" - githubApiToken = "" - githubUsername = "" - destination = "" - ) - - flag.StringVar(&destination, "dest", "options/gitignore/", "destination for the gitignores") - flag.StringVar(&githubUsername, "username", "", "github username") - flag.StringVar(&githubApiToken, "token", "", "github api token") - flag.Parse() - - file, err := os.CreateTemp(os.TempDir(), prefix) - if err != nil { - log.Fatalf("Failed to create temp file. %s", err) - } - - defer util.Remove(file.Name()) - - req, err := http.NewRequest("GET", url, nil) - if err != nil { - log.Fatalf("Failed to download archive. %s", err) - } - - if len(githubApiToken) > 0 && len(githubUsername) > 0 { - req.SetBasicAuth(githubUsername, githubApiToken) - } - - resp, err := http.DefaultClient.Do(req) - if err != nil { - log.Fatalf("Failed to download archive. %s", err) - } - defer resp.Body.Close() - - if _, err := io.Copy(file, resp.Body); err != nil { - log.Fatalf("Failed to copy archive to file. %s", err) - } - - if _, err := file.Seek(0, 0); err != nil { - log.Fatalf("Failed to reset seek on archive. %s", err) - } - - gz, err := gzip.NewReader(file) - if err != nil { - log.Fatalf("Failed to gunzip the archive. %s", err) - } - - tr := tar.NewReader(gz) - - filesToCopy := make(map[string]string, 0) - - for { - hdr, err := tr.Next() - - if err == io.EOF { - break - } - - if err != nil { - log.Fatalf("Failed to iterate archive. %s", err) - } - - if filepath.Ext(hdr.Name) != ".gitignore" { - continue - } - - if hdr.Typeflag == tar.TypeSymlink { - fmt.Printf("Found symlink %s -> %s\n", hdr.Name, hdr.Linkname) - filesToCopy[strings.TrimSuffix(filepath.Base(hdr.Name), ".gitignore")] = strings.TrimSuffix(filepath.Base(hdr.Linkname), ".gitignore") - continue - } - - out, err := os.Create(path.Join(destination, strings.TrimSuffix(filepath.Base(hdr.Name), ".gitignore"))) - if err != nil { - log.Fatalf("Failed to create new file. %s", err) - } - - defer out.Close() - - if _, err := io.Copy(out, tr); err != nil { - log.Fatalf("Failed to write new file. %s", err) - } else { - fmt.Printf("Written %s\n", out.Name()) - } - } - - for dst, src := range filesToCopy { - // Read all content of src to data - src = path.Join(destination, src) - data, err := os.ReadFile(src) - if err != nil { - log.Fatalf("Failed to read src file. %s", err) - } - // Write data to dst - dst = path.Join(destination, dst) - err = os.WriteFile(dst, data, 0o644) - if err != nil { - log.Fatalf("Failed to write new file. %s", err) - } - fmt.Printf("Written (copy of %s) %s\n", src, dst) - } - - fmt.Println("Done") -} diff --git a/build/generate-go-licenses.go b/build/generate-go-licenses.go deleted file mode 100644 index eee01cf371..0000000000 --- a/build/generate-go-licenses.go +++ /dev/null @@ -1,239 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build ignore - -package main - -import ( - "encoding/json" - "fmt" - "os" - "os/exec" - "path/filepath" - "regexp" - "slices" - "sort" - "strings" -) - -// 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. -// When a directory has both primary and variant files (e.g. LICENSE and -// LICENSE.docs), only the primary files are kept. -var primaryLicenseRe = regexp.MustCompile(`^(?i)(LICEN[SC]E|COPYING)$`) - -// ignoredNames are LicenseEntry.Name values to exclude from the output. -var ignoredNames = map[string]bool{ - "code.gitea.io/gitea": true, - "code.mokoconsulting.tech/MokoConsulting/MokoGitea/options/license": true, -} - -var excludedExt = map[string]bool{ - ".gitignore": true, - ".go": true, - ".mod": true, - ".sum": true, - ".toml": true, - ".yaml": true, - ".yml": true, -} - -type ModuleInfo struct { - Path string - Dir string - PkgDirs []string // directories of packages imported from this module -} - -type LicenseEntry struct { - Name string `json:"name"` - Path string `json:"path"` - LicenseText string `json:"licenseText"` -} - -// getModules returns all dependency modules with their local directory paths -// and the package directories used from each module. -func getModules(goCmd string) []ModuleInfo { - cmd := exec.Command(goCmd, "list", "-deps", "-f", - "{{if .Module}}{{.Module.Path}}\t{{.Module.Dir}}\t{{.Dir}}{{end}}", "./...") - cmd.Stderr = os.Stderr - // Use GOOS=linux with CGO to ensure we capture all platform-specific - // dependencies, matching the CI environment. - cmd.Env = append(os.Environ(), "GOOS=linux", "GOARCH=amd64", "CGO_ENABLED=1") - output, err := cmd.Output() - if err != nil { - fmt.Fprintf(os.Stderr, "failed to run 'go list -deps': %v\n", err) - os.Exit(1) - } - - var modules []ModuleInfo - seen := make(map[string]int) // module path -> index in modules - for _, line := range strings.Split(string(output), "\n") { - line = strings.TrimSpace(line) - if line == "" { - continue - } - parts := strings.Split(line, "\t") - if len(parts) != 3 { - continue - } - modPath, modDir, pkgDir := parts[0], parts[1], parts[2] - if idx, ok := seen[modPath]; ok { - modules[idx].PkgDirs = append(modules[idx].PkgDirs, pkgDir) - } else { - seen[modPath] = len(modules) - modules = append(modules, ModuleInfo{ - Path: modPath, - Dir: modDir, - PkgDirs: []string{pkgDir}, - }) - } - } - return modules -} - -// findLicenseFiles scans a module's root directory and its used package -// directories for license files. It also walks up from each package directory -// to the module root, scanning intermediate directories. Subdirectory licenses -// are only included if their text differs from the root license(s). -func findLicenseFiles(mod ModuleInfo) []LicenseEntry { - var entries []LicenseEntry - seenTexts := make(map[string]bool) - - // First, collect root-level license files. - entries = append(entries, scanDirForLicenses(mod.Dir, mod.Path, "")...) - for _, e := range entries { - seenTexts[e.LicenseText] = true - } - - // Then check each package directory and all intermediate parent directories - // up to the module root for license files with unique text. - seenDirs := map[string]bool{mod.Dir: true} - for _, pkgDir := range mod.PkgDirs { - for dir := pkgDir; dir != mod.Dir && strings.HasPrefix(dir, mod.Dir); dir = filepath.Dir(dir) { - if seenDirs[dir] { - continue - } - seenDirs[dir] = true - for _, e := range scanDirForLicenses(dir, mod.Path, mod.Dir) { - if !seenTexts[e.LicenseText] { - seenTexts[e.LicenseText] = true - entries = append(entries, e) - } - } - } - } - return entries -} - -// scanDirForLicenses reads a single directory for license files and returns entries. -// If moduleRoot is non-empty, paths are made relative to it. -func scanDirForLicenses(dir, modulePath, moduleRoot string) []LicenseEntry { - dirEntries, err := os.ReadDir(dir) - if err != nil { - return nil - } - - var entries []LicenseEntry - for _, entry := range dirEntries { - if entry.IsDir() { - continue - } - name := entry.Name() - if !licenseRe.MatchString(name) { - continue - } - if excludedExt[strings.ToLower(filepath.Ext(name))] { - continue - } - - content, err := os.ReadFile(filepath.Join(dir, name)) - if err != nil { - continue - } - - entryName := modulePath - entryPath := modulePath + "/" + name - if moduleRoot != "" { - rel, _ := filepath.Rel(moduleRoot, dir) - if rel != "." { - relSlash := filepath.ToSlash(rel) - entryName = modulePath + "/" + relSlash - entryPath = modulePath + "/" + relSlash + "/" + name - } - } - - entries = append(entries, LicenseEntry{ - Name: entryName, - Path: entryPath, - LicenseText: string(content), - }) - } - - // When multiple license files exist, prefer primary files (e.g. LICENSE) - // over variants with suffixes (e.g. LICENSE.docs, LICENSE-2.0.txt). - // If no primary file exists, keep only the first variant. - if len(entries) > 1 { - var primary []LicenseEntry - for _, e := range entries { - fileName := e.Path[strings.LastIndex(e.Path, "/")+1:] - if primaryLicenseRe.MatchString(fileName) { - primary = append(primary, e) - } - } - if len(primary) > 0 { - return primary - } - return entries[:1] - } - - return entries -} - -func main() { - if len(os.Args) != 2 { - fmt.Println("usage: go run generate-go-licenses.go ") - os.Exit(1) - } - - out := os.Args[1] - - goCmd := "go" - if env := os.Getenv("GO"); env != "" { - goCmd = env - } - - modules := getModules(goCmd) - - var entries []LicenseEntry - for _, mod := range modules { - entries = append(entries, findLicenseFiles(mod)...) - } - - entries = slices.DeleteFunc(entries, func(e LicenseEntry) bool { - return ignoredNames[e.Name] - }) - - sort.Slice(entries, func(i, j int) bool { - return entries[i].Path < entries[j].Path - }) - - jsonBytes, err := json.MarshalIndent(entries, "", " ") - if err != nil { - panic(err) - } - - // Ensure file has a final newline - if jsonBytes[len(jsonBytes)-1] != '\n' { - jsonBytes = append(jsonBytes, '\n') - } - - err = os.WriteFile(out, jsonBytes, 0o644) - if err != nil { - panic(err) - } -} diff --git a/build/generate-openapi.go b/build/generate-openapi.go deleted file mode 100644 index d0eab54269..0000000000 --- a/build/generate-openapi.go +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright 2026 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -// generate-openapi converts Gitea's Swagger 2.0 spec into an OpenAPI 3.0 spec. -// -// Gitea generates a Swagger 2.0 spec from code annotations (make generate-swagger). -// This tool converts it to OAS3 so that SDK generators and tools that require -// OAS3 (e.g. progenitor for Rust) can consume it directly. The conversion also -// deduplicates inline enum definitions into named schema components, producing -// cleaner SDK output with proper enum types instead of anonymous strings. -// -// Run: go run build/generate-openapi.go -// Output: templates/swagger/v1_openapi3_json.tmpl - -//go:build ignore - -package main - -import ( - "encoding/json" - "fmt" - "log" - "os" - "regexp" - "sort" - "strings" - - "code.mokoconsulting.tech/MokoConsulting/MokoGitea/build/openapi3gen" - - "github.com/getkin/kin-openapi/openapi3" -) - -const ( - swaggerSpecPath = "templates/swagger/v1_json.tmpl" - openapi3OutPath = "templates/swagger/v1_openapi3_json.tmpl" - - appSubUrlVar = "{{.SwaggerAppSubUrl}}" - appVerVar = "{{.SwaggerAppVer}}" - appNameVar = "{{.SwaggerAppName}}" - - appSubUrlPlaceholder = "GITEA_APP_SUB_URL_PLACEHOLDER" - appVerPlaceholder = "0.0.0-gitea-placeholder" - appNamePlaceholder = "GiteaAppNamePlaceholder" -) - -var ( - appSubUrlRe = regexp.MustCompile(regexp.QuoteMeta(appSubUrlVar)) - appVerRe = regexp.MustCompile(regexp.QuoteMeta(appVerVar)) - appNameRe = regexp.MustCompile(regexp.QuoteMeta(appNameVar)) - - enumScanDirs = []string{ - "modules/structs", - "modules/commitstatus", - } -) - -func main() { - astEnumMap, err := openapi3gen.ScanSwaggerEnumTypes(enumScanDirs) - if err != nil { - log.Fatalf("scanning swagger:enum annotations: %v", err) - } - names := make([]string, 0, len(astEnumMap)) - for _, n := range astEnumMap { - names = append(names, n) - } - sort.Strings(names) - fmt.Fprintf(os.Stderr, "discovered %d swagger:enum types: %s\n", len(names), strings.Join(names, ", ")) - - data, err := os.ReadFile(swaggerSpecPath) - if err != nil { - log.Fatalf("reading swagger spec: %v", err) - } - - cleaned := appSubUrlRe.ReplaceAll(data, []byte(appSubUrlPlaceholder)) - cleaned = appVerRe.ReplaceAll(cleaned, []byte(appVerPlaceholder)) - cleaned = appNameRe.ReplaceAll(cleaned, []byte(appNamePlaceholder)) - - oas3, err := openapi3gen.Convert(cleaned, astEnumMap) - if err != nil { - log.Fatalf("converting to openapi 3.0: %v", err) - } - - oas3.Servers = openapi3.Servers{ - {URL: appSubUrlPlaceholder + "/api/v1"}, - } - - out, err := json.MarshalIndent(oas3, "", " ") - if err != nil { - log.Fatalf("marshaling openapi 3.0: %v", err) - } - - result := strings.ReplaceAll(string(out), appSubUrlPlaceholder, appSubUrlVar) - result = strings.ReplaceAll(result, appVerPlaceholder, appVerVar) - result = strings.ReplaceAll(result, appNamePlaceholder, appNameVar) - result = strings.TrimSpace(result) - - if err := os.WriteFile(openapi3OutPath, []byte(result), 0o644); err != nil { - log.Fatalf("writing openapi 3.0 spec: %v", err) - } - - fmt.Printf("Generated %s\n", openapi3OutPath) -} diff --git a/build/openapi3gen/convert.go b/build/openapi3gen/convert.go deleted file mode 100644 index 312f7e444e..0000000000 --- a/build/openapi3gen/convert.go +++ /dev/null @@ -1,281 +0,0 @@ -// Copyright 2026 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package openapi3gen - -import ( - "fmt" - "regexp" - "strings" - - "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/json" - - "github.com/getkin/kin-openapi/openapi2" - "github.com/getkin/kin-openapi/openapi2conv" - "github.com/getkin/kin-openapi/openapi3" -) - -// rxDeprecated matches "deprecated" as a word at the start of a description -// or preceded by whitespace/punctuation that indicates a leading marker (e.g. -// "Deprecated: true", "deprecated (use X instead)"). Rejects negated phrases -// like "not deprecated" or "previously deprecated, now supported". -var rxDeprecated = regexp.MustCompile(`(?i)(?:^|[\n.;])\s*deprecated\b`) - -// Convert parses a Swagger 2.0 spec and returns an OAS3 spec, applying -// Gitea-specific post-processing: file-schema fixups, URI formats, -// deprecated flags, and shared-enum extraction. -// -// astEnumMap is a value-set-key → Go-type-name map (built by -// ScanSwaggerEnumTypes). If a shared enum in the spec has no entry in the -// map, Convert returns an error — no fallback naming. -func Convert(swaggerJSON []byte, astEnumMap map[string]string) (*openapi3.T, error) { - var swagger2 openapi2.T - if err := json.Unmarshal(swaggerJSON, &swagger2); err != nil { - return nil, fmt.Errorf("parsing swagger 2.0: %w", err) - } - - oas3, err := openapi2conv.ToV3(&swagger2) - if err != nil { - return nil, fmt.Errorf("converting to openapi 3.0: %w", err) - } - - fixFileSchemas(oas3) - addURIFormats(oas3) - addDeprecatedFlags(oas3) - if err := extractSharedEnums(oas3, astEnumMap); err != nil { - return nil, err - } - return oas3, nil -} - -func fixFileSchemas(doc *openapi3.T) { - for _, pathItem := range doc.Paths.Map() { - for _, op := range []*openapi3.Operation{ - pathItem.Get, pathItem.Post, pathItem.Put, pathItem.Patch, - pathItem.Delete, pathItem.Head, pathItem.Options, pathItem.Trace, - } { - if op == nil { - continue - } - for _, resp := range op.Responses.Map() { - if resp.Value == nil { - continue - } - for _, mediaType := range resp.Value.Content { - fixSchema(mediaType.Schema) - } - } - if op.RequestBody != nil && op.RequestBody.Value != nil { - for _, mediaType := range op.RequestBody.Value.Content { - fixSchema(mediaType.Schema) - } - } - } - } -} - -// fixSchema rewrites any "type: file" schemas to the OAS3 equivalent -// (type: string, format: binary), recursing into Properties, Items, and -// AllOf/OneOf/AnyOf/Not branches. $ref nodes are skipped so shared schemas -// are rewritten exactly once when visited through their declaration. -func fixSchema(ref *openapi3.SchemaRef) { - if ref == nil || ref.Value == nil || ref.Ref != "" { - return - } - s := ref.Value - if s.Type.Is("file") { - s.Type = &openapi3.Types{"string"} - s.Format = "binary" - } - for _, p := range s.Properties { - fixSchema(p) - } - fixSchema(s.Items) - for _, sub := range s.AllOf { - fixSchema(sub) - } - for _, sub := range s.OneOf { - fixSchema(sub) - } - for _, sub := range s.AnyOf { - fixSchema(sub) - } - fixSchema(s.Not) -} - -// addURIFormats sets format: uri on string properties whose names indicate -// they hold URLs. This information is lost in Swagger 2.0 but is valuable -// for code generators. -func addURIFormats(doc *openapi3.T) { - if doc.Components == nil { - return - } - for _, schemaRef := range doc.Components.Schemas { - if schemaRef.Value == nil { - continue - } - for propName, propRef := range schemaRef.Value.Properties { - if propRef == nil || propRef.Value == nil || propRef.Ref != "" { - continue - } - prop := propRef.Value - if !prop.Type.Is("string") || prop.Format != "" { - continue - } - if isURLProperty(propName) { - prop.Format = "uri" - } - } - } -} - -func isURLProperty(name string) bool { - if strings.HasSuffix(name, "_url") { - return true - } - switch name { - case "url", "html_url", "clone_url": - return true - } - return false -} - -// addDeprecatedFlags sets deprecated: true on schema properties whose -// description starts with a "deprecated" marker (e.g. "Deprecated: true" -// or "deprecated (use X instead)"). Does not match negated phrases. -func addDeprecatedFlags(doc *openapi3.T) { - if doc.Components == nil { - return - } - for _, schemaRef := range doc.Components.Schemas { - if schemaRef.Value == nil { - continue - } - for _, propRef := range schemaRef.Value.Properties { - if propRef == nil || propRef.Value == nil || propRef.Ref != "" { - continue - } - if rxDeprecated.MatchString(propRef.Value.Description) { - propRef.Value.Deprecated = true - } - } - } -} - -type enumUsage struct { - schemaName string - propName string - propRef *openapi3.SchemaRef - inItems bool -} - -// extractSharedEnums finds identical enum arrays used by multiple schema -// properties, creates a standalone named schema for each, and replaces -// the inline enums with $ref pointers. -// -// If the derived enum name collides with an existing component schema, or -// no // swagger:enum annotation matches the value set, generation aborts -// with an actionable error — there are no silent fallbacks. -func extractSharedEnums(doc *openapi3.T, astEnumMap map[string]string) error { - if doc.Components == nil { - return nil - } - - enumGroups := map[string][]enumUsage{} - - for schemaName, schemaRef := range doc.Components.Schemas { - if schemaRef.Value == nil { - continue - } - for propName, propRef := range schemaRef.Value.Properties { - if propRef == nil || propRef.Value == nil || propRef.Ref != "" { - continue - } - if len(propRef.Value.Enum) > 1 && propRef.Value.Type.Is("string") { - key := EnumKey(propRef.Value.Enum) - enumGroups[key] = append(enumGroups[key], enumUsage{schemaName, propName, propRef, false}) - } - if propRef.Value.Type.Is("array") && propRef.Value.Items != nil && - propRef.Value.Items.Value != nil && propRef.Value.Items.Ref == "" && - len(propRef.Value.Items.Value.Enum) > 1 && propRef.Value.Items.Value.Type.Is("string") { - key := EnumKey(propRef.Value.Items.Value.Enum) - enumGroups[key] = append(enumGroups[key], enumUsage{schemaName, propName, propRef, true}) - } - } - } - - for key, usages := range enumGroups { - if len(usages) < 2 { - continue - } - - enumName, err := deriveEnumName(key, usages, astEnumMap) - if err != nil { - return err - } - if _, exists := doc.Components.Schemas[enumName]; exists { - return fmt.Errorf("enum name collision: %s already exists as a component schema", enumName) - } - - var enumValues []any - if usages[0].inItems { - enumValues = usages[0].propRef.Value.Items.Value.Enum - } else { - enumValues = usages[0].propRef.Value.Enum - } - - doc.Components.Schemas[enumName] = &openapi3.SchemaRef{ - Value: &openapi3.Schema{ - Type: &openapi3.Types{"string"}, - Enum: enumValues, - }, - } - - ref := "#/components/schemas/" + enumName - - for _, usage := range usages { - if usage.inItems { - usage.propRef.Value.Items = &openapi3.SchemaRef{Ref: ref} - } else { - old := usage.propRef.Value - if old.Description == "" && !old.Deprecated && old.Format == "" { - usage.propRef.Ref = ref - usage.propRef.Value = nil - } else { - usage.propRef.Value = &openapi3.Schema{ - AllOf: openapi3.SchemaRefs{ - {Ref: ref}, - }, - Description: old.Description, - Deprecated: old.Deprecated, - Format: old.Format, - } - } - } - } - } - return nil -} - -// deriveEnumName looks up a shared enum's Go type name from astEnumMap by -// value-set key. If no annotation matches, returns an error identifying the -// offending properties and the fix. -func deriveEnumName(key string, usages []enumUsage, astEnumMap map[string]string) (string, error) { - if name, ok := astEnumMap[key]; ok { - return name, nil - } - - props := map[string]bool{} - for _, u := range usages { - props[fmt.Sprintf("%s.%s", u.schemaName, u.propName)] = true - } - propList := make([]string, 0, len(props)) - for p := range props { - propList = append(propList, p) - } - return "", fmt.Errorf( - "no swagger:enum annotation matches value-set %q used by %d properties: %v; "+ - "fix by adding a named string type with // swagger:enum to modules/structs or modules/commitstatus", - key, len(usages), propList, - ) -} diff --git a/build/openapi3gen/convert_test.go b/build/openapi3gen/convert_test.go deleted file mode 100644 index a9a715e6c2..0000000000 --- a/build/openapi3gen/convert_test.go +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright 2026 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package openapi3gen - -import ( - "strings" - "testing" - - "github.com/getkin/kin-openapi/openapi3" -) - -func TestDeriveEnumName_hit(t *testing.T) { - key := EnumKey([]any{"red", "green", "blue"}) - astMap := map[string]string{key: "Color"} - usages := []enumUsage{{schemaName: "Paint", propName: "color"}} - got, err := deriveEnumName(key, usages, astMap) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if got != "Color" { - t.Fatalf("got %q, want %q", got, "Color") - } -} - -func TestDeriveEnumName_miss(t *testing.T) { - key := EnumKey([]any{"x", "y"}) - usages := []enumUsage{{schemaName: "Thing", propName: "kind"}} - _, err := deriveEnumName(key, usages, map[string]string{}) - if err == nil { - t.Fatal("expected miss error, got nil") - } - msg := err.Error() - if !strings.Contains(msg, "Thing.kind") { - t.Fatalf("error %q should list the missing usage", msg) - } - if !strings.Contains(msg, "swagger:enum") { - t.Fatalf("error %q should hint at the fix", msg) - } -} - -func TestExtractSharedEnums_usesASTMap(t *testing.T) { - doc := &openapi3.T{ - Components: &openapi3.Components{ - Schemas: openapi3.Schemas{ - "A": {Value: &openapi3.Schema{ - Type: &openapi3.Types{"object"}, - Properties: openapi3.Schemas{ - "color": {Value: &openapi3.Schema{ - Type: &openapi3.Types{"string"}, - Enum: []any{"red", "green", "blue"}, - }}, - }, - }}, - "B": {Value: &openapi3.Schema{ - Type: &openapi3.Types{"object"}, - Properties: openapi3.Schemas{ - "color": {Value: &openapi3.Schema{ - Type: &openapi3.Types{"string"}, - Enum: []any{"red", "green", "blue"}, - }}, - }, - }}, - }, - }, - } - astMap := map[string]string{EnumKey([]any{"red", "green", "blue"}): "Color"} - if err := extractSharedEnums(doc, astMap); err != nil { - t.Fatalf("extractSharedEnums: %v", err) - } - if _, ok := doc.Components.Schemas["Color"]; !ok { - t.Fatalf("expected Color schema to be extracted") - } -} - -func TestFixFileSchemas_recursesIntoNested(t *testing.T) { - fileType := func() *openapi3.SchemaRef { - return &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"file"}}} - } - doc := &openapi3.T{ - Paths: openapi3.NewPaths(), - } - doc.Paths.Set("/upload", &openapi3.PathItem{ - Post: &openapi3.Operation{ - RequestBody: &openapi3.RequestBodyRef{ - Value: &openapi3.RequestBody{ - Content: openapi3.Content{ - "multipart/form-data": { - Schema: &openapi3.SchemaRef{Value: &openapi3.Schema{ - Type: &openapi3.Types{"object"}, - Properties: openapi3.Schemas{ - "attachment": fileType(), - "items": {Value: &openapi3.Schema{ - Type: &openapi3.Types{"array"}, - Items: fileType(), - }}, - "alt": {Value: &openapi3.Schema{ - AllOf: openapi3.SchemaRefs{fileType()}, - }}, - "one": {Value: &openapi3.Schema{ - OneOf: openapi3.SchemaRefs{fileType()}, - }}, - "any": {Value: &openapi3.Schema{ - AnyOf: openapi3.SchemaRefs{fileType()}, - }}, - "not": {Value: &openapi3.Schema{ - Not: fileType(), - }}, - }, - }}, - }, - }, - }, - }, - Responses: openapi3.NewResponses(), - }, - }) - - fixFileSchemas(doc) - - props := doc.Paths.Value("/upload").Post.RequestBody.Value.Content["multipart/form-data"].Schema.Value.Properties - if !props["attachment"].Value.Type.Is("string") || props["attachment"].Value.Format != "binary" { - t.Errorf("nested property not fixed: %+v", props["attachment"].Value) - } - if !props["items"].Value.Items.Value.Type.Is("string") || props["items"].Value.Items.Value.Format != "binary" { - t.Errorf("array items not fixed: %+v", props["items"].Value.Items.Value) - } - if !props["alt"].Value.AllOf[0].Value.Type.Is("string") || props["alt"].Value.AllOf[0].Value.Format != "binary" { - t.Errorf("allOf branch not fixed: %+v", props["alt"].Value.AllOf[0].Value) - } - if !props["one"].Value.OneOf[0].Value.Type.Is("string") || props["one"].Value.OneOf[0].Value.Format != "binary" { - t.Errorf("oneOf branch not fixed: %+v", props["one"].Value.OneOf[0].Value) - } - if !props["any"].Value.AnyOf[0].Value.Type.Is("string") || props["any"].Value.AnyOf[0].Value.Format != "binary" { - t.Errorf("anyOf branch not fixed: %+v", props["any"].Value.AnyOf[0].Value) - } - if !props["not"].Value.Not.Value.Type.Is("string") || props["not"].Value.Not.Value.Format != "binary" { - t.Errorf("not branch not fixed: %+v", props["not"].Value.Not.Value) - } -} - -func TestExtractSharedEnums_missReturnsError(t *testing.T) { - doc := &openapi3.T{ - Components: &openapi3.Components{ - Schemas: openapi3.Schemas{ - "A": {Value: &openapi3.Schema{ - Type: &openapi3.Types{"object"}, - Properties: openapi3.Schemas{ - "color": {Value: &openapi3.Schema{ - Type: &openapi3.Types{"string"}, - Enum: []any{"red", "green"}, - }}, - }, - }}, - "B": {Value: &openapi3.Schema{ - Type: &openapi3.Types{"object"}, - Properties: openapi3.Schemas{ - "color": {Value: &openapi3.Schema{ - Type: &openapi3.Types{"string"}, - Enum: []any{"red", "green"}, - }}, - }, - }}, - }, - }, - } - if err := extractSharedEnums(doc, map[string]string{}); err == nil { - t.Fatal("expected miss error") - } -} diff --git a/build/openapi3gen/enumscan.go b/build/openapi3gen/enumscan.go deleted file mode 100644 index dd11620549..0000000000 --- a/build/openapi3gen/enumscan.go +++ /dev/null @@ -1,188 +0,0 @@ -// Copyright 2026 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -// Package openapi3gen converts Gitea's Swagger 2.0 spec to an OpenAPI 3.0 -// spec. It discovers Go enum type names by scanning swagger:enum annotations -// in the source tree, then names extracted shared-enum schemas accordingly. -package openapi3gen - -import ( - "fmt" - "go/ast" - "go/parser" - "go/token" - "os" - "path/filepath" - "regexp" - "sort" - "strconv" - "strings" -) - -// EnumKey returns a canonical key for a set of enum values: values are -// stringified, sorted, and joined with "|". Used to match enum value sets -// across spec properties and scanned Go type declarations. -func EnumKey(values []any) string { - strs := make([]string, len(values)) - for i, v := range values { - strs[i] = fmt.Sprintf("%v", v) - } - sort.Strings(strs) - return strings.Join(strs, "|") -} - -var rxSwaggerEnum = regexp.MustCompile(`swagger:enum\s+(\w+)`) - -// ScanSwaggerEnumTypes walks .go files under each dir and returns a map from -// a canonical value-set key (see EnumKey) to the Go type name declared with -// // swagger:enum TypeName. -// -// Returns an error on parse failure, on an annotation for a type whose -// constants can't be extracted, or on value-set collisions between two -// different enum types. -func ScanSwaggerEnumTypes(dirs []string) (map[string]string, error) { - fset := token.NewFileSet() - parsed := []*ast.File{} - - for _, dir := range dirs { - entries, err := os.ReadDir(dir) - if err != nil { - return nil, fmt.Errorf("reading %s: %w", dir, err) - } - for _, entry := range entries { - if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".go") { - continue - } - if strings.HasSuffix(entry.Name(), "_test.go") { - continue - } - path := filepath.Join(dir, entry.Name()) - file, err := parser.ParseFile(fset, path, nil, parser.ParseComments) - if err != nil { - return nil, fmt.Errorf("%s: %w", path, err) - } - parsed = append(parsed, file) - } - } - - enumTypes := map[string]string{} // typeName → "" (presence marker) - enumValues := map[string][]any{} // typeName → values - - // Pass 1: collect every // swagger:enum TypeName declaration. - for _, file := range parsed { - for _, decl := range file.Decls { - gd, ok := decl.(*ast.GenDecl) - if !ok || gd.Tok != token.TYPE { - continue - } - if err := collectEnumType(gd, enumTypes); err != nil { - return nil, fmt.Errorf("%s: %w", fset.Position(gd.Pos()).Filename, err) - } - } - } - - // Pass 2: collect const values; now every annotated type is visible. - for _, file := range parsed { - for _, decl := range file.Decls { - gd, ok := decl.(*ast.GenDecl) - if !ok || gd.Tok != token.CONST { - continue - } - collectEnumValues(gd, enumTypes, enumValues) - } - } - - result := map[string]string{} - for typeName := range enumTypes { - values, ok := enumValues[typeName] - if !ok || len(values) == 0 { - return nil, fmt.Errorf("swagger:enum %s has no const block with typed string values", typeName) - } - key := EnumKey(values) - if existing, ok := result[key]; ok && existing != typeName { - return nil, fmt.Errorf("swagger:enum value-set collision: %s and %s both use %q", existing, typeName, key) - } - result[key] = typeName - } - return result, nil -} - -// collectEnumType scans a `type` GenDecl for // swagger:enum annotations, -// handling both the lone form (`// swagger:enum Foo\n type Foo string`) -// where the comment group is attached to the GenDecl, and the grouped form: -// -// type ( -// // swagger:enum Foo -// Foo string -// ) -// -// where the comment group is attached to each TypeSpec. Caveat: Go's parser -// only attaches a CommentGroup when it is immediately adjacent to the decl. -// A blank line (not a `//` continuation line) between the comment and the -// declaration drops the Doc, so annotations MUST sit directly above their -// type. All current annotated files obey this — the rule is noted here so -// a future edit that inserts a blank line fails fast rather than silently. -func collectEnumType(gd *ast.GenDecl, enumTypes map[string]string) error { - if err := registerEnumAnnotation(gd.Doc, gd.Specs, enumTypes); err != nil { - return err - } - for _, spec := range gd.Specs { - ts, ok := spec.(*ast.TypeSpec) - if !ok || ts.Doc == nil { - continue - } - if err := registerEnumAnnotation(ts.Doc, []ast.Spec{ts}, enumTypes); err != nil { - return err - } - } - return nil -} - -func registerEnumAnnotation(doc *ast.CommentGroup, specs []ast.Spec, enumTypes map[string]string) error { - if doc == nil { - return nil - } - matches := rxSwaggerEnum.FindStringSubmatch(doc.Text()) - if len(matches) < 2 { - return nil - } - annotated := matches[1] - for _, spec := range specs { - ts, ok := spec.(*ast.TypeSpec) - if !ok { - continue - } - if ts.Name.Name == annotated { - enumTypes[annotated] = "" - return nil - } - } - return fmt.Errorf("swagger:enum %s: no type declaration with that name in the same decl group; check for a typo", annotated) -} - -func collectEnumValues(gd *ast.GenDecl, enumTypes map[string]string, enumValues map[string][]any) { - for _, spec := range gd.Specs { - vs, ok := spec.(*ast.ValueSpec) - if !ok || vs.Type == nil { - continue - } - ident, ok := vs.Type.(*ast.Ident) - if !ok { - continue - } - if _, isEnum := enumTypes[ident.Name]; !isEnum { - continue - } - for _, val := range vs.Values { - lit, ok := val.(*ast.BasicLit) - if !ok || lit.Kind != token.STRING { - continue - } - unquoted, err := strconv.Unquote(lit.Value) - if err != nil { - continue - } - enumValues[ident.Name] = append(enumValues[ident.Name], unquoted) - } - } -} diff --git a/build/openapi3gen/enumscan_test.go b/build/openapi3gen/enumscan_test.go deleted file mode 100644 index 2e5fe99db0..0000000000 --- a/build/openapi3gen/enumscan_test.go +++ /dev/null @@ -1,239 +0,0 @@ -// Copyright 2026 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package openapi3gen - -import ( - "os" - "path/filepath" - "strings" - "testing" -) - -func TestEnumKey_sortsAndJoins(t *testing.T) { - key := EnumKey([]any{"b", "a", "c"}) - if key != "a|b|c" { - t.Fatalf("EnumKey = %q, want %q", key, "a|b|c") - } -} - -func TestEnumKey_handlesNonStringValues(t *testing.T) { - key := EnumKey([]any{2, 1, 3}) - if key != "1|2|3" { - t.Fatalf("EnumKey = %q, want %q", key, "1|2|3") - } -} - -func TestScanSwaggerEnumTypes_basic(t *testing.T) { - dir := t.TempDir() - src := `package fixture - -// Color is a primary color. -// swagger:enum Color -type Color string - -const ( - ColorRed Color = "red" - ColorGreen Color = "green" - ColorBlue Color = "blue" -) -` - if err := os.WriteFile(filepath.Join(dir, "color.go"), []byte(src), 0o644); err != nil { - t.Fatal(err) - } - - got, err := ScanSwaggerEnumTypes([]string{dir}) - if err != nil { - t.Fatalf("ScanSwaggerEnumTypes: %v", err) - } - wantKey := EnumKey([]any{"red", "green", "blue"}) - if got[wantKey] != "Color" { - t.Fatalf("map[%q] = %q, want %q", wantKey, got[wantKey], "Color") - } -} - -func TestScanSwaggerEnumTypes_orphanAnnotation(t *testing.T) { - dir := t.TempDir() - src := `package fixture - -// swagger:enum Sttype -type StateType string - -const ( - StateOpen StateType = "open" -) -` - if err := os.WriteFile(filepath.Join(dir, "typo.go"), []byte(src), 0o644); err != nil { - t.Fatal(err) - } - - _, err := ScanSwaggerEnumTypes([]string{dir}) - if err == nil { - t.Fatal("expected error for annotation referencing a non-matching type name") - } - if !strings.Contains(err.Error(), "Sttype") { - t.Fatalf("error %q should mention the typo'd name Sttype", err.Error()) - } -} - -func TestScanSwaggerEnumTypes_collision(t *testing.T) { - dir := t.TempDir() - src := `package fixture - -// swagger:enum Alpha -type Alpha string -const ( - AlphaX Alpha = "x" - AlphaY Alpha = "y" -) - -// swagger:enum Beta -type Beta string -const ( - BetaX Beta = "x" - BetaY Beta = "y" -) -` - if err := os.WriteFile(filepath.Join(dir, "dup.go"), []byte(src), 0o644); err != nil { - t.Fatal(err) - } - - _, err := ScanSwaggerEnumTypes([]string{dir}) - if err == nil { - t.Fatal("expected collision error, got nil") - } - msg := err.Error() - if !strings.Contains(msg, "Alpha") || !strings.Contains(msg, "Beta") { - t.Fatalf("error %q should mention both Alpha and Beta", msg) - } -} - -func TestScanSwaggerEnumTypes_parseFailure(t *testing.T) { - dir := t.TempDir() - if err := os.WriteFile(filepath.Join(dir, "bad.go"), []byte("package fixture\nfunc Foo() {"), 0o644); err != nil { - t.Fatal(err) - } - - _, err := ScanSwaggerEnumTypes([]string{dir}) - if err == nil { - t.Fatal("expected parse error, got nil") - } -} - -func TestScanSwaggerEnumTypes_annotationWithoutConsts(t *testing.T) { - dir := t.TempDir() - src := `package fixture - -// swagger:enum Lonely -type Lonely string -` - if err := os.WriteFile(filepath.Join(dir, "lonely.go"), []byte(src), 0o644); err != nil { - t.Fatal(err) - } - - _, err := ScanSwaggerEnumTypes([]string{dir}) - if err == nil { - t.Fatal("expected error for annotation without consts") - } - if !strings.Contains(err.Error(), "Lonely") { - t.Fatalf("error %q should mention Lonely", err.Error()) - } -} - -func TestScanSwaggerEnumTypes_constsAndTypeInDifferentFiles(t *testing.T) { - dir := t.TempDir() - // Name ordering: `a_consts.go` < `b_type.go`, so readdir returns consts first. - // Old single-pass scanner would miss the values; two-pass must not. - constsSrc := `package fixture - -const ( - HueA Hue = "a" - HueB Hue = "b" -) -` - typeSrc := `package fixture - -// swagger:enum Hue -type Hue string -` - if err := os.WriteFile(filepath.Join(dir, "a_consts.go"), []byte(constsSrc), 0o644); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(dir, "b_type.go"), []byte(typeSrc), 0o644); err != nil { - t.Fatal(err) - } - - got, err := ScanSwaggerEnumTypes([]string{dir}) - if err != nil { - t.Fatalf("ScanSwaggerEnumTypes: %v", err) - } - wantKey := EnumKey([]any{"a", "b"}) - if got[wantKey] != "Hue" { - t.Fatalf("map[%q] = %q, want %q", wantKey, got[wantKey], "Hue") - } -} - -func TestScanSwaggerEnumTypes_constsBeforeType(t *testing.T) { - dir := t.TempDir() - src := `package fixture - -const ( - ShadeDark Shade = "dark" - ShadeLight Shade = "light" -) - -// swagger:enum Shade -type Shade string -` - if err := os.WriteFile(filepath.Join(dir, "shade.go"), []byte(src), 0o644); err != nil { - t.Fatal(err) - } - - got, err := ScanSwaggerEnumTypes([]string{dir}) - if err != nil { - t.Fatalf("ScanSwaggerEnumTypes: %v", err) - } - wantKey := EnumKey([]any{"dark", "light"}) - if got[wantKey] != "Shade" { - t.Fatalf("map[%q] = %q, want %q", wantKey, got[wantKey], "Shade") - } -} - -func TestScanSwaggerEnumTypes_groupedTypeDecl(t *testing.T) { - dir := t.TempDir() - src := `package fixture - -type ( - // swagger:enum Color - Color string - // swagger:enum Shade - Shade string -) - -const ( - ColorRed Color = "red" - ColorBlue Color = "blue" -) - -const ( - ShadeDark Shade = "dark" - ShadeLight Shade = "light" -) -` - if err := os.WriteFile(filepath.Join(dir, "grouped.go"), []byte(src), 0o644); err != nil { - t.Fatal(err) - } - - got, err := ScanSwaggerEnumTypes([]string{dir}) - if err != nil { - t.Fatalf("ScanSwaggerEnumTypes: %v", err) - } - colorKey := EnumKey([]any{"red", "blue"}) - shadeKey := EnumKey([]any{"dark", "light"}) - if got[colorKey] != "Color" { - t.Fatalf("Color: map[%q] = %q, want %q", colorKey, got[colorKey], "Color") - } - if got[shadeKey] != "Shade" { - t.Fatalf("Shade: map[%q] = %q, want %q", shadeKey, got[shadeKey], "Shade") - } -} diff --git a/build/test-env-check.sh b/build/test-env-check.sh deleted file mode 100755 index 38e5a28823..0000000000 --- a/build/test-env-check.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/sh - -set -e - -if [ ! -f ./build/test-env-check.sh ]; then - echo "${0} can only be executed in gitea source root directory" - exit 1 -fi - - -echo "check uid ..." - -# the uid of gitea defined in "https://gitea.com/gitea/test-env" is 1000 -gitea_uid=$(id -u gitea) -if [ "$gitea_uid" != "1000" ]; then - echo "The uid of linux user 'gitea' is expected to be 1000, but it is $gitea_uid" - exit 1 -fi - -cur_uid=$(id -u) -if [ "$cur_uid" != "0" -a "$cur_uid" != "$gitea_uid" ]; then - echo "The uid of current linux user is expected to be 0 or $gitea_uid, but it is $cur_uid" - exit 1 -fi diff --git a/build/test-env-prepare.sh b/build/test-env-prepare.sh deleted file mode 100755 index 0c5bc25f11..0000000000 --- a/build/test-env-prepare.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -set -e - -if [ ! -f ./build/test-env-prepare.sh ]; then - echo "${0} can only be executed in gitea source root directory" - exit 1 -fi - -echo "change the owner of files to gitea ..." -chown -R gitea:gitea . diff --git a/build/update-locales.sh b/build/update-locales.sh deleted file mode 100755 index 5316746f30..0000000000 --- a/build/update-locales.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh - -# this script runs in alpine image which only has `sh` shell -if [ ! -f ./options/locale/locale_en-US.json ]; then - echo "please run this script in the root directory of the project" - exit 1 -fi - -mv ./options/locale/locale_en-US.json ./options/ - -# Remove translation under 25% of en_us -baselines=$(cat "./options/locale_en-US.json" | wc -l) -baselines=$((baselines / 4)) -for filename in ./options/locale/*.json; do - lines=$(cat "$filename" | wc -l) - if [ "$lines" -lt "$baselines" ]; then - echo "Removing $filename: $lines/$baselines" - rm "$filename" - fi -done - -mv ./options/locale_en-US.json ./options/locale/ -- 2.52.0 From 4ec0db86588b86ff1cdc0617dccbd8a80d8864d3 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Thu, 4 Jun 2026 13:48:22 -0500 Subject: [PATCH 3/4] feat(issues): show custom fields in issue sidebar with inline editing - Load custom field definitions and values in ViewIssue handler - New sidebar template displays dropdown fields with onchange submit - POST handler at /issues/{id}/custom-fields/{field_id} saves values - Dropdown options parsed from JSON and passed to template - Non-dropdown fields display as read-only text - Section appears between Labels and Milestone in sidebar Co-Authored-By: Claude Opus 4.6 (1M context) --- options/locale/locale_en-US.json | 1 + routers/web/repo/issue_custom_field.go | 33 +++++++++++++++++++ routers/web/repo/issue_view.go | 20 +++++++++++ routers/web/web.go | 1 + .../repo/issue/sidebar/custom_fields.tmpl | 33 +++++++++++++++++++ .../repo/issue/view_content/sidebar.tmpl | 2 ++ 6 files changed, 90 insertions(+) create mode 100644 routers/web/repo/issue_custom_field.go create mode 100644 templates/repo/issue/sidebar/custom_fields.tmpl diff --git a/options/locale/locale_en-US.json b/options/locale/locale_en-US.json index 1c332a8337..c3f2e899c2 100644 --- a/options/locale/locale_en-US.json +++ b/options/locale/locale_en-US.json @@ -1412,6 +1412,7 @@ "repo.issues.new.open_projects": "Open Projects", "repo.issues.new.closed_projects": "Closed Projects", "repo.issues.new.no_items": "No items", + "repo.issues.custom_fields": "Custom Fields", "repo.issues.new.milestone": "Milestone", "repo.issues.new.no_milestone": "No Milestone", "repo.issues.new.clear_milestone": "Clear milestone", diff --git a/routers/web/repo/issue_custom_field.go b/routers/web/repo/issue_custom_field.go new file mode 100644 index 0000000000..d9345f9c65 --- /dev/null +++ b/routers/web/repo/issue_custom_field.go @@ -0,0 +1,33 @@ +// Copyright 2026 Moko Consulting +// SPDX-License-Identifier: GPL-3.0-or-later + +package repo + +import ( + "fmt" + "net/http" + + issues_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/issues" + "code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context" +) + +// UpdateIssueCustomField handles POST to set a custom field value on an issue. +func UpdateIssueCustomField(ctx *context.Context) { + issueID := ctx.PathParamInt64("id") + fieldID := ctx.PathParamInt64("field_id") + value := ctx.FormString("value") + + // Look up issue to get the index for redirect. + issue, err := issues_model.GetIssueByID(ctx, issueID) + if err != nil { + ctx.ServerError("GetIssueByID", err) + return + } + + if err := issues_model.SetCustomFieldValue(ctx, issueID, fieldID, value); err != nil { + ctx.ServerError("SetCustomFieldValue", err) + return + } + + ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issue.Index), http.StatusSeeOther) +} diff --git a/routers/web/repo/issue_view.go b/routers/web/repo/issue_view.go index 7ab1be62f0..dfa2a8b34e 100644 --- a/routers/web/repo/issue_view.go +++ b/routers/web/repo/issue_view.go @@ -4,6 +4,7 @@ package repo import ( + "encoding/json" "fmt" "math/big" "net/http" @@ -337,6 +338,25 @@ func ViewIssue(ctx *context.Context) { ctx.Data["IsProjectsEnabled"] = ctx.Repo.Permission.CanRead(unit.TypeProjects) ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled + + // Load custom fields for the issue sidebar. + customFieldDefs, _ := issues_model.GetCustomFieldsByRepo(ctx, ctx.Repo.Repository.ID) + ctx.Data["CustomFieldDefs"] = customFieldDefs + customFieldValues := make(map[int64]string) + fieldOptions := make(map[int64][]string) + if len(customFieldDefs) > 0 { + customFieldValues, _ = issues_model.GetCustomFieldValuesMap(ctx, issue.ID) + for _, f := range customFieldDefs { + if f.Options != "" { + var opts []string + if err := json.Unmarshal([]byte(f.Options), &opts); err == nil { + fieldOptions[f.ID] = opts + } + } + } + } + ctx.Data["CustomFieldValues"] = customFieldValues + ctx.Data["CustomFieldOptions"] = fieldOptions upload.AddUploadContext(ctx, "comment") if err := issue.LoadAttributes(ctx); err != nil { diff --git a/routers/web/web.go b/routers/web/web.go index 4a8ecc18ae..745d4d34e0 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1397,6 +1397,7 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) { m.Post("/projects/column", reqRepoIssuesOrPullsWriter, reqRepoProjectsWriter, repo.UpdateIssueProjectColumn) m.Post("/assignee", reqRepoIssuesOrPullsWriter, repo.UpdateIssueAssignee) m.Post("/status", reqRepoIssuesOrPullsWriter, repo.UpdateIssueStatus) + m.Post("/{id}/custom-fields/{field_id}", reqRepoIssuesOrPullsWriter, repo.UpdateIssueCustomField) m.Post("/delete", reqRepoAdmin, repo.BatchDeleteIssues) m.Delete("/unpin/{index}", reqRepoAdmin, repo.IssueUnpin) m.Post("/move_pin", reqRepoAdmin, repo.IssuePinMove) diff --git a/templates/repo/issue/sidebar/custom_fields.tmpl b/templates/repo/issue/sidebar/custom_fields.tmpl new file mode 100644 index 0000000000..99757434c4 --- /dev/null +++ b/templates/repo/issue/sidebar/custom_fields.tmpl @@ -0,0 +1,33 @@ +{{if .CustomFieldDefs}} +
+
+ {{$issueID := .Issue.ID}} + {{$repoLink := .RepoLink}} + {{$canModify := .HasIssuesOrPullsWritePermission}} + {{$values := .CustomFieldValues}} + {{$fieldOptions := .CustomFieldOptions}} + {{range .CustomFieldDefs}} + {{$currentVal := index $values .ID}} +
+ {{.Name}} + {{if and $canModify (eq .Options "")}} + {{/* Non-dropdown: just display the value */}} + {{if $currentVal}}{{$currentVal}}{{else}}{{end}} + {{else if $canModify}} +
+ {{$.CsrfTokenHtml}} + +
+ {{else}} + {{if $currentVal}}{{$currentVal}}{{else}}{{end}} + {{end}} +
+ {{end}} +
+{{end}} diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index 6427d10968..9f064ce6ee 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -7,6 +7,8 @@ {{template "repo/issue/sidebar/label_list" $.IssuePageMetaData}} + {{template "repo/issue/sidebar/custom_fields" $}} + {{template "repo/issue/sidebar/milestone_list" $.IssuePageMetaData}} {{if .IsProjectsEnabled}} {{template "repo/issue/sidebar/project_list" $.IssuePageMetaData}} -- 2.52.0 From 5a80b8da33e6b1944b968fd86acaf8fd2b3a8b27 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Thu, 4 Jun 2026 14:29:26 -0500 Subject: [PATCH 4/4] docs(updateserver): correct joomlaTagName comment with Joomla source reference Joomla's Update.php maps tags via STABILITY_ + strtoupper(tag). Valid values: dev, alpha, beta, rc, stable. Full names like "development" silently fall back to STABILITY_STABLE. Co-Authored-By: Claude Opus 4.6 (1M context) --- services/updateserver/joomla.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/services/updateserver/joomla.go b/services/updateserver/joomla.go index a8bb8055fa..039b8f30c8 100644 --- a/services/updateserver/joomla.go +++ b/services/updateserver/joomla.go @@ -120,7 +120,10 @@ func isStreamName(s string, streams []licenses.StreamDef) bool { } // joomlaTagName maps internal stream names to Joomla-standard tag values. -// Joomla recognizes: dev, alpha, beta, rc, stable. +// Joomla's Update.php maps tags via STABILITY_ + strtoupper(tag) constants. +// Valid values: dev (0), alpha (1), beta (2), rc (3), stable (4). +// Using full names like "development" or "release-candidate" would silently +// fall back to STABILITY_STABLE, breaking pre-release channel filtering. func joomlaTagName(channel string) string { switch channel { case ChannelDevelopment: -- 2.52.0