mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-03 08:02:36 +09:00 
			
		
		
		
	Compare commits
	
		
			120 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					de7026528b | ||
| 
						 | 
					ee3f5e8fac | ||
| 
						 | 
					b2707bcd18 | ||
| 
						 | 
					0512b02b01 | ||
| 
						 | 
					99545ae2fd | ||
| 
						 | 
					7697df9f93 | ||
| 
						 | 
					d17f8ffcc1 | ||
| 
						 | 
					5e9cc919cf | ||
| 
						 | 
					cc6ec56738 | ||
| 
						 | 
					76bd60fc1d | ||
| 
						 | 
					744f7c8200 | ||
| 
						 | 
					da33b708af | ||
| 
						 | 
					8fa3925874 | ||
| 
						 | 
					7794ff0874 | ||
| 
						 | 
					7c17d0a73e | ||
| 
						 | 
					a014d071e4 | ||
| 
						 | 
					312565e3c2 | ||
| 
						 | 
					58daaf66e8 | ||
| 
						 | 
					f076ada601 | ||
| 
						 | 
					92436b8b2a | ||
| 
						 | 
					2df7d0835a | ||
| 
						 | 
					200cb6140d | ||
| 
						 | 
					2746c6f1aa | ||
| 
						 | 
					23971a77a0 | ||
| 
						 | 
					ebac324ff2 | ||
| 
						 | 
					7df1204795 | ||
| 
						 | 
					159544a950 | ||
| 
						 | 
					9780da583d | ||
| 
						 | 
					a8eaf43f97 | ||
| 
						 | 
					b6fd8741ee | ||
| 
						 | 
					2674d27fb8 | ||
| 
						 | 
					6f3837284d | ||
| 
						 | 
					c30f4f4be5 | ||
| 
						 | 
					4578288ea3 | ||
| 
						 | 
					826fffb59e | ||
| 
						 | 
					2196ba5e42 | ||
| 
						 | 
					12347f07ae | ||
| 
						 | 
					a3c5358d35 | ||
| 
						 | 
					987d014468 | ||
| 
						 | 
					e08eed9040 | ||
| 
						 | 
					4ffa49aa04 | ||
| 
						 | 
					72837530bf | ||
| 
						 | 
					eef635523a | ||
| 
						 | 
					8f45a11919 | ||
| 
						 | 
					e72d001708 | ||
| 
						 | 
					8d9ea68f19 | ||
| 
						 | 
					bf664c2e85 | ||
| 
						 | 
					52d298890b | ||
| 
						 | 
					c09e43acf5 | ||
| 
						 | 
					3b4af01633 | ||
| 
						 | 
					b4e2d5e8ee | ||
| 
						 | 
					2984a7c121 | ||
| 
						 | 
					80cc87b3d8 | ||
| 
						 | 
					10b6047498 | ||
| 
						 | 
					2c47b06869 | ||
| 
						 | 
					31f2a325dc | ||
| 
						 | 
					fcbbc24cc4 | ||
| 
						 | 
					1454e1b6eb | ||
| 
						 | 
					d70348836b | ||
| 
						 | 
					940a930d13 | ||
| 
						 | 
					45d21a0d5c | ||
| 
						 | 
					15ad001aef | ||
| 
						 | 
					ed1828ca92 | ||
| 
						 | 
					3cfff5af0d | ||
| 
						 | 
					6f6c66a07d | ||
| 
						 | 
					d65af69c2b | ||
| 
						 | 
					12c24c2189 | ||
| 
						 | 
					a330f42f01 | ||
| 
						 | 
					531f36ea4a | ||
| 
						 | 
					b4f0eed969 | ||
| 
						 | 
					63b3a33bf2 | ||
| 
						 | 
					9899989ece | ||
| 
						 | 
					0fad40dd8c | ||
| 
						 | 
					e637008fe3 | ||
| 
						 | 
					fd281518ae | ||
| 
						 | 
					e10d222434 | ||
| 
						 | 
					7a35f90b29 | ||
| 
						 | 
					d371aa3031 | ||
| 
						 | 
					81768675d4 | ||
| 
						 | 
					39cc72562b | ||
| 
						 | 
					bc83fb26ef | ||
| 
						 | 
					68736ec292 | ||
| 
						 | 
					3df11c07a8 | ||
| 
						 | 
					96fff862dc | ||
| 
						 | 
					968c04c7da | ||
| 
						 | 
					27de60381d | ||
| 
						 | 
					d2d763318c | ||
| 
						 | 
					610b2fb88d | ||
| 
						 | 
					0858a36016 | ||
| 
						 | 
					fef364e7d6 | ||
| 
						 | 
					ce6a60a38b | ||
| 
						 | 
					74159a8855 | ||
| 
						 | 
					ce6464123f | ||
| 
						 | 
					7f0050cf39 | ||
| 
						 | 
					c102e344f3 | ||
| 
						 | 
					f27128bf94 | ||
| 
						 | 
					f35ab5cd52 | ||
| 
						 | 
					0137bc4e5c | ||
| 
						 | 
					eed0968c37 | ||
| 
						 | 
					a0b65ed17f | ||
| 
						 | 
					ad1b76540e | ||
| 
						 | 
					6636b37a9c | ||
| 
						 | 
					af5e5e8f00 | ||
| 
						 | 
					0e0ebf68d7 | ||
| 
						 | 
					90bd08ceef | ||
| 
						 | 
					0c581106d2 | ||
| 
						 | 
					e18e31d557 | ||
| 
						 | 
					e1026feddc | ||
| 
						 | 
					d670820722 | ||
| 
						 | 
					a8f98fd3be | ||
| 
						 | 
					c442c682ef | ||
| 
						 | 
					57868c2315 | ||
| 
						 | 
					b1c21880c1 | ||
| 
						 | 
					1e71ad89ce | ||
| 
						 | 
					c20642fa99 | ||
| 
						 | 
					a4291fd553 | ||
| 
						 | 
					fa5a064559 | ||
| 
						 | 
					cb42232080 | ||
| 
						 | 
					c8ffe777cf | ||
| 
						 | 
					e98dd6ee5b | 
							
								
								
									
										481
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										481
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -4,6 +4,487 @@ This changelog goes through the changes that have been made in each release
 | 
			
		||||
without substantial changes to our git log; to see the highlights of what has
 | 
			
		||||
been added to each release, please refer to the [blog](https://blog.gitea.com).
 | 
			
		||||
 | 
			
		||||
## [1.23.4](https://github.com/go-gitea/gitea/releases/tag/v1.23.4) - 2025-02-16
 | 
			
		||||
 | 
			
		||||
* SECURITY
 | 
			
		||||
  * Enhance routers for the Actions variable operations (#33547) (#33553)
 | 
			
		||||
  * Enhance routers for the Actions runner operations (#33549) (#33555)
 | 
			
		||||
  * Fix project issues list and counting (#33594) #33619
 | 
			
		||||
* PERFORMANCES
 | 
			
		||||
  * Performance optimization for pull request files loading comments attachments (#33585) (#33592)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Add a transaction to `pickTask` (#33543) (#33563)
 | 
			
		||||
  * Fix mirror bug (#33597) (#33607)
 | 
			
		||||
  * Use default Git timeout when checking repo health (#33593) (#33598)
 | 
			
		||||
  * Fix PR's target branch dropdown (#33589) (#33591)
 | 
			
		||||
  * Fix various problems (artifact order, api empty slice, assignee check, fuzzy prompt, mirror proxy, adopt git) (#33569) (#33577)
 | 
			
		||||
  * Rework suggestion backend (#33538) (#33546)
 | 
			
		||||
  * Fix context usage (#33554) (#33557)
 | 
			
		||||
  * Only show the latest version in the Arch index (#33262) (#33580)
 | 
			
		||||
  * Skip deletion error for action artifacts (#33476) (#33568)
 | 
			
		||||
  * Make actions URL in commit status webhooks absolute (#33620) #33632
 | 
			
		||||
  * Add missing locale (#33641) #33642
 | 
			
		||||
 | 
			
		||||
## [1.23.3](https://github.com/go-gitea/gitea/releases/tag/v1.23.3) - 2025-02-06
 | 
			
		||||
 | 
			
		||||
* Security
 | 
			
		||||
  * Build Gitea with Golang v1.23.6 to fix security bugs
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Fix a bug caused by status webhook template #33512
 | 
			
		||||
 | 
			
		||||
## [1.23.2](https://github.com/go-gitea/gitea/releases/tag/1.23.2) - 2025-02-04
 | 
			
		||||
 | 
			
		||||
* BREAKING
 | 
			
		||||
  * Add tests for webhook and fix some webhook bugs (#33396) (#33442)
 | 
			
		||||
    * Package webhook’s Organization was incorrectly used as the User struct. This PR fixes the issue.
 | 
			
		||||
    * This changelog is just a hint. The change is not really breaking because most fields are the same, most users are not affected.
 | 
			
		||||
* ENHANCEMENTS
 | 
			
		||||
  * Clone button enhancements (#33362) (#33404)
 | 
			
		||||
  * Repo homepage styling tweaks (#33289) (#33381)
 | 
			
		||||
  * Add a confirm dialog for "sync fork" (#33270) (#33273)
 | 
			
		||||
  * Make tracked time representation display as hours (#33315) (#33334)
 | 
			
		||||
  * Improve sync fork behavior (#33319) (#33332)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Fix code button alignment (#33345) (#33351)
 | 
			
		||||
  * Correct bot label `vertical-align` (#33477) (#33480)
 | 
			
		||||
  * Fix SSH LFS memory usage (#33455) (#33460)
 | 
			
		||||
  * Fix issue sidebar dropdown keyboard support (#33447) (#33450)
 | 
			
		||||
  * Fix user avatar (#33439)
 | 
			
		||||
  * Fix `GetCommitBranchStart` bug (#33298) (#33421)
 | 
			
		||||
  * Add pubdate for repository rss and add some tests (#33411) (#33416)
 | 
			
		||||
  * Add missed auto merge feed message on dashboard (#33309) (#33405)
 | 
			
		||||
  * Fix issue suggestion bug (#33389) (#33391)
 | 
			
		||||
  * Make issue suggestion work for all editors (#33340) (#33342)
 | 
			
		||||
  * Fix issue count (#33338) (#33341)
 | 
			
		||||
  * Fix Account linking page (#33325) (#33327)
 | 
			
		||||
  * Fix closed dependency title (#33285) (#33287)
 | 
			
		||||
  * Fix sidebar milestone link (#33269) (#33272)
 | 
			
		||||
  * Fix missing license when sync mirror (#33255) (#33258)
 | 
			
		||||
  * Fix upload file form (#33230) (#33233)
 | 
			
		||||
  * Fix mirror bug (#33224) (#33225)
 | 
			
		||||
  * Fix system admin cannot fork or get private fork with API (#33401) (#33417)
 | 
			
		||||
  * Fix push message behavior (#33215) (#33317)
 | 
			
		||||
  * Trivial fixes (#33304) (#33312)
 | 
			
		||||
  * Fix "stop time tracking button" on navbar (#33084) (#33300)
 | 
			
		||||
  * Fix tag route and empty repo (#33253)
 | 
			
		||||
  * Fix cache test triggered by non memory cache (#33220) (#33221)
 | 
			
		||||
  * Revert empty lfs ref name (#33454) (#33457)
 | 
			
		||||
  * Fix flex width (#33414) (#33418)
 | 
			
		||||
  * Fix commit status events (#33320) #33493
 | 
			
		||||
  * Fix unnecessary comment when moving issue on the same project column (#33496) #33499
 | 
			
		||||
  * Add timetzdata build tag to binary releases (#33463) #33503
 | 
			
		||||
* MISC
 | 
			
		||||
  * Use ProtonMail/go-crypto to replace keybase/go-crypto (#33402) (#33410)
 | 
			
		||||
  * Update katex to latest version (#33361)
 | 
			
		||||
  * Update go tool dependencies (#32916) (#33355)
 | 
			
		||||
 | 
			
		||||
## [1.23.1](https://github.com/go-gitea/gitea/releases/tag/v1.23.1) - 2025-01-09
 | 
			
		||||
 | 
			
		||||
* ENHANCEMENTS
 | 
			
		||||
  * Move repo size to sidebar (#33155) (#33182)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Use updated path to s6-svscan after alpine upgrade (#33185) (#33188)
 | 
			
		||||
  * Fix fuzz test (#33156) (#33158)
 | 
			
		||||
  * Fix raw file API ref handling (#33172) (#33189)
 | 
			
		||||
  * Fix ACME panic (#33178) (#33186)
 | 
			
		||||
  * Fix branch dropdown not display ref name (#33159) (#33183)
 | 
			
		||||
  * Fix assignee list overlapping in Issue sidebar (#33176) (#33181)
 | 
			
		||||
  * Fix sync fork for consistency (#33147) #33192
 | 
			
		||||
  * Fix editor markdown not incrementing in a numbered list (#33187) #33193
 | 
			
		||||
 | 
			
		||||
## [1.23.0](https://github.com/go-gitea/gitea/releases/tag/v1.23.0) - 2025-01-08
 | 
			
		||||
 | 
			
		||||
* BREAKING
 | 
			
		||||
  * Rename config option `[camo].Allways` to `[camo].Always` (#32097)
 | 
			
		||||
  * Remove SHA1 for support for ssh rsa signing (#31857)
 | 
			
		||||
  * Use UTC as default timezone when schedule Actions cron tasks (#31742)
 | 
			
		||||
  * Delete Actions logs older than 1 year by default (#31735)
 | 
			
		||||
  * Make OIDC introspection authentication strictly require Client ID and secret (#31632)
 | 
			
		||||
 | 
			
		||||
* SECURITY
 | 
			
		||||
  * Include file extension checks in attachment API (#32151)
 | 
			
		||||
  * Include all security fixes which have been backported to v1.22
 | 
			
		||||
 | 
			
		||||
* FEATURES
 | 
			
		||||
  * Allow to fork repository into the same owner (#32819)
 | 
			
		||||
  * Support "merge upstream branch" (Sync fork) (#32741)
 | 
			
		||||
  * Add Arch package registry (#32692)
 | 
			
		||||
  * Allow to disable the password-based login (sign-in) form (#32687)
 | 
			
		||||
  * Allow cropping an avatar before setting it (#32565)
 | 
			
		||||
  * Support quote selected comments to reply (#32431)
 | 
			
		||||
  * Add reviewers selection to new pull request (#32403)
 | 
			
		||||
  * Suggestions for issues (#32327)
 | 
			
		||||
  * Add priority to protected branch (#32286)
 | 
			
		||||
  * Included tag search capabilities (#32045)
 | 
			
		||||
  * Add option to filter board cards by labels and assignees (#31999)
 | 
			
		||||
  * Add automatic light/dark option for the colorblind theme (#31997)
 | 
			
		||||
  * Support migration from AWS CodeCommit (#31981)
 | 
			
		||||
  * Introduce globallock as distributed locks (#31908 & #31813)
 | 
			
		||||
  * Support compression for Actions logs & enable by default (#31761 & #32013)
 | 
			
		||||
  * Add pure SSH LFS support (#31516)
 | 
			
		||||
  * Add Passkey login support (#31504)
 | 
			
		||||
  * Actions support workflow dispatch event (#28163)
 | 
			
		||||
  * Support repo license (#24872)
 | 
			
		||||
  * Issue time estimate, meaningful time tracking (#23113)
 | 
			
		||||
  * GitHub like repo home page (#32213 & #32847)
 | 
			
		||||
  * Rearrange Clone Panel (#31142)
 | 
			
		||||
  * Enhancing Gitea OAuth2 Provider with Granular Scopes for Resource Access (#32573)
 | 
			
		||||
  * Use env GITEA_RUNNER_REGISTRATION_TOKEN as global runner token (#32946) #32964
 | 
			
		||||
  * Update i18n.go - Language Picker (#32933) #32935
 | 
			
		||||
 | 
			
		||||
* PERFORMANCE
 | 
			
		||||
  * Perf: add extra index to notification table (#32395)
 | 
			
		||||
  * Introduce OrgList and add LoadTeams, optimaze Load teams for orgs (#32543)
 | 
			
		||||
  * Improve performance of diffs (#32393)
 | 
			
		||||
  * Make LFS http_client parallel within a batch. (#32369)
 | 
			
		||||
  * Add new index for action to resolve the performance problem (#32333)
 | 
			
		||||
  * Improve get feed with pagination (#31821)
 | 
			
		||||
  * Performance improvements for pull request list API (#30490)
 | 
			
		||||
  * Use batch database operations instead of one by one to optimze api pulls (#32680)
 | 
			
		||||
  * Use gitrepo.GetTreePathLatestCommit to get file lastest commit instead from latest commit cache (#32987) #33046
 | 
			
		||||
 | 
			
		||||
* ENHANCEMENTS
 | 
			
		||||
  * Code
 | 
			
		||||
    * Remove unnecessary border in repo home page sidebar (#32767)
 | 
			
		||||
    * Add 'Copy path' button to file view (#32584)
 | 
			
		||||
    * Improve diff file tree (#32658)
 | 
			
		||||
    * Add new [lfs_client].BATCH_SIZE and [server].LFS_MAX_BATCH_SIZE config settings. (#32307)
 | 
			
		||||
    * Updated tokenizer to better matching when search for code snippets (#32261)
 | 
			
		||||
    * Change the code search to sort results by relevance (#32134)
 | 
			
		||||
    * Support migrating GitHub/GitLab PR draft status (#32242)
 | 
			
		||||
    * Move lock icon position and add additional tooltips to branch list page (#31839)
 | 
			
		||||
    * Add tag name in the commits list (#31082)
 | 
			
		||||
    * Add `MAX_ROWS` option for CSV rendering (#30268)
 | 
			
		||||
    * Allow code search by filename (#32210)
 | 
			
		||||
    * Make git push options accept short name (#32245)
 | 
			
		||||
    * Repo file list enhancements (#32835)
 | 
			
		||||
 | 
			
		||||
  * Markdown & Editor
 | 
			
		||||
    * Refactor markdown math render, add dollor-backquote syntax support (#32831)
 | 
			
		||||
    * Make Monaco theme follow browser, fully type codeeditor.ts (#32756)
 | 
			
		||||
    * Refactor markdown editor and use it for milestone description editor (#32688)
 | 
			
		||||
    * Add some handy markdown editor features (#32400)
 | 
			
		||||
    * Improve markdown textarea for indentation and lists (#31406)
 | 
			
		||||
 | 
			
		||||
  * Issue
 | 
			
		||||
    * Add label/author/assignee filters to the user/org home issue list (#32779)
 | 
			
		||||
    * Refactor issue filter (labels, poster, assignee) (#32771)
 | 
			
		||||
    * Style unification for the issue_management area (#32605)
 | 
			
		||||
    * Add "View all branches/tags" entry to Branch Selector (#32653)
 | 
			
		||||
    * Improve textarea paste (#31948)
 | 
			
		||||
    * Add avif image file support (#32508)
 | 
			
		||||
    * Prevent from submitting issue/comment on uploading (#32263)
 | 
			
		||||
    * Issue Templates: add option to have dropdown printed list (#31577)
 | 
			
		||||
    * Allow searching issues by ID (#31479)
 | 
			
		||||
    * Add `is_archived` option for issue indexer (#32735)
 | 
			
		||||
    * Improve attachment upload methods (#30513)
 | 
			
		||||
    * Support issue template assignees (#31083)
 | 
			
		||||
    * Prevent simultaneous editing of comments and issues (#31053)
 | 
			
		||||
    * Add issue comment when moving issues from one column to another of the project (#29311)
 | 
			
		||||
 | 
			
		||||
  * Pull Request
 | 
			
		||||
    * Display head branch more comfortable on pull request view (#32000)
 | 
			
		||||
    * Simplify review UI (#31062)
 | 
			
		||||
    * Allow force push to protected branches (#28086)
 | 
			
		||||
    * Add line-through for deleted branch on pull request view page (#32500)
 | 
			
		||||
    * Support requested_reviewers data in comment webhook events (#26178)
 | 
			
		||||
    * Allow maintainers to view and edit files of private repos when "Allow maintainers to edit" is enabled (#32215)
 | 
			
		||||
    * Allow including `Reviewed-on`/`Reviewed-by` lines for custom merge messages (#31211)
 | 
			
		||||
 | 
			
		||||
  * Actions
 | 
			
		||||
    * Render job title as commit message (#32748)
 | 
			
		||||
    * Refactor RepoActionView.vue, add `::group::` support (#32713)
 | 
			
		||||
    * Make RepoActionView.vue support `##[group]` (#32770)
 | 
			
		||||
    * Support `pull_request_target` event for commit status (#31703)
 | 
			
		||||
    * Detect whether action view branch was deleted (#32764)
 | 
			
		||||
    * Allow users with write permission to run actions (#32644)
 | 
			
		||||
    * Show latest run when visit /run/latest (#31808)
 | 
			
		||||
 | 
			
		||||
  * Packages
 | 
			
		||||
    * Improve rubygems package registry (#31357)
 | 
			
		||||
    * Add support for npm bundleDependencies (#30751)
 | 
			
		||||
    * Add signature support for the RPM module (#27069)
 | 
			
		||||
    * Extract and display readme and comments for Composer packages (#30927)
 | 
			
		||||
 | 
			
		||||
  * Project
 | 
			
		||||
    * Add title to project view page (#32747)
 | 
			
		||||
    * Set the columns height to hug all its contents (#31726)
 | 
			
		||||
    * Rename project `board` -> `column` to make the UI less confusing (#30170)
 | 
			
		||||
 | 
			
		||||
  * User & Organazition
 | 
			
		||||
    * Use better name for userinfo structure (#32544)
 | 
			
		||||
    * Use user.FullName in Oauth2 id_token response (#32542)
 | 
			
		||||
    * Limit org member view of restricted users (#32211)
 | 
			
		||||
    * Allow disabling authentication related user features (#31535)
 | 
			
		||||
    * Add option to change mail from user display name (#31528)
 | 
			
		||||
    * Use FullName in Emails to address the recipient if possible (#31527)
 | 
			
		||||
 | 
			
		||||
  * Administration
 | 
			
		||||
    * Add support for a credentials chain for minio access (#31051)
 | 
			
		||||
    * Move admin routers from /admin to /-/admin (#32189)
 | 
			
		||||
    * Add cache test for admins (#31265)
 | 
			
		||||
    * Add option for mailer to override mail headers (#27860)
 | 
			
		||||
    * Azure blob storage support (#30995)
 | 
			
		||||
    * Supports forced use of S3 virtual-hosted style (#30969)
 | 
			
		||||
    * Move repository visibility to danger zone in the settings area (#31126)
 | 
			
		||||
 | 
			
		||||
  * Others
 | 
			
		||||
    * Remove urls from translations (#31950)
 | 
			
		||||
    * Simplify 404/500 page (#31409)
 | 
			
		||||
    * Optimize installation-page experience (#32558)
 | 
			
		||||
    * Refactor login page (#31530)
 | 
			
		||||
    * Add new event commit status creation and webhook implementation (#27151)
 | 
			
		||||
    * Repo Activity: count new issues that were closed (#31776)
 | 
			
		||||
    * Set manual `tabindex`es on login page (#31689)
 | 
			
		||||
    * Add `YEAR`, `MONTH`, `MONTH_ENGLISH`, `DAY` variables for template repos (#31584)
 | 
			
		||||
    * Add typescript guideline and typescript-specific eslint plugins and fix issues (#31521)
 | 
			
		||||
    * Make toast support preventDuplicates (#31501)
 | 
			
		||||
    * Fix tautological conditions (#30735)
 | 
			
		||||
    * Issue change title notifications (#33050) #33065
 | 
			
		||||
 | 
			
		||||
* API
 | 
			
		||||
  * Implement update branch API (#32433)
 | 
			
		||||
  * Fix missing outputs for jobs with matrix (#32823)
 | 
			
		||||
  * Make API "compare" accept commit IDs (#32801)
 | 
			
		||||
  * Add github compatible tarball download API endpoints (#32572)
 | 
			
		||||
  * Harden runner updateTask and updateLog api (#32462)
 | 
			
		||||
  * Add `DISABLE_ORGANIZATIONS_PAGE` and `DISABLE_CODE_PAGE` settings for explore pages and fix an issue related to user search (#32288)
 | 
			
		||||
  * Make admins adhere to branch protection rules (#32248)
 | 
			
		||||
  * Calculate `PublicOnly` for org membership only once (#32234)
 | 
			
		||||
  * Allow filtering PRs by poster in the ListPullRequests API (#32209)
 | 
			
		||||
  * Return 404 instead of error when commit not exist (#31977)
 | 
			
		||||
  * Save initial signup information for users to aid in spam prevention (#31852)
 | 
			
		||||
  * Fix upload maven pacakge parallelly (#31851)
 | 
			
		||||
  * Fix null requested_reviewer from API (#31773)
 | 
			
		||||
  * Add permission description for API to add repo collaborator (#31744)
 | 
			
		||||
  * Add return type to GetRawFileOrLFS and GetRawFile (#31680)
 | 
			
		||||
  * Add skip secondary authorization option for public oauth2 clients (#31454)
 | 
			
		||||
  * Add tag protection via rest api #17862 (#31295)
 | 
			
		||||
  * Document possible action types for the user activity feed API (#31196)
 | 
			
		||||
  * Add topics for repository API (#31127)
 | 
			
		||||
  * Add support for searching users by email (#30908)
 | 
			
		||||
  * Add API endpoints for getting action jobs status (#26673)
 | 
			
		||||
 | 
			
		||||
* REFACTOR
 | 
			
		||||
  * Update JS and PY dependencies (#31940)
 | 
			
		||||
  * Enable `no-jquery/no-parse-html-literal` and fix violation (#31684)
 | 
			
		||||
  * Refactor image diff (#31444)
 | 
			
		||||
  * Refactor CSRF token (#32216)
 | 
			
		||||
  * Fix some typescript issues (#32586)
 | 
			
		||||
  * Refactor names (#31405)
 | 
			
		||||
  * Use per package global lock for container uploads instead of memory lock (#31860)
 | 
			
		||||
  * Move team related functions to service layer (#32537)
 | 
			
		||||
  * Move GetFeeds to service layer (#32526)
 | 
			
		||||
  * Resolve lint for unused parameter and unnecessary type arguments (#30750)
 | 
			
		||||
  * Reimplement GetUserOrgsList to make it simple and clear (#32486)
 | 
			
		||||
  * Move some functions from issue.go to standalone files (#32468)
 | 
			
		||||
  * Refactor sidebar assignee&milestone&project selectors (#32465)
 | 
			
		||||
  * Refactor sidebar label selector (#32460)
 | 
			
		||||
  * Fix a number of typescript issues (#32459)
 | 
			
		||||
  * Refactor language menu and dom utils (#32450)
 | 
			
		||||
  * Refactor issue page info (#32445)
 | 
			
		||||
  * Split issue sidebar into small templates (#32444)
 | 
			
		||||
  * Refactor template ctx and render utils (#32422)
 | 
			
		||||
  * Refactor repo legacy (#32404)
 | 
			
		||||
  * Refactor markup package (#32399)
 | 
			
		||||
  * Refactor markup render system (#32533 & #32589 & #32612)
 | 
			
		||||
  * Refactor the DB migration system slightly (#32344)
 | 
			
		||||
  * Remove jQuery import from some files (#32512)
 | 
			
		||||
  * Strict pagination check (#32548)
 | 
			
		||||
  * Split mail sender sub package from mailer service package (#32618)
 | 
			
		||||
  * Remove outdated code about fixture generation (#32708)
 | 
			
		||||
  * Refactor RepoBranchTagSelector (#32681)
 | 
			
		||||
  * Refactor issue list (#32755)
 | 
			
		||||
  * Refactor LabelEdit (#32752)
 | 
			
		||||
  * Split issue/pull view router function as multiple smaller functions (#32749)
 | 
			
		||||
  * Refactor some LDAP code (#32849)
 | 
			
		||||
  * Unify repo search order by logic (#30876)
 | 
			
		||||
  * Remove duplicate empty repo check in delete branch API (#32569)
 | 
			
		||||
  * Replace deprecated `math/rand` functions (#30733)
 | 
			
		||||
  * Remove fomantic dimmer module (#30723)
 | 
			
		||||
  * Add types to fetch,toast,bootstrap,svg (#31627)
 | 
			
		||||
  * Refactor webhook (#31587)
 | 
			
		||||
  * Move AddCollabrator and CreateRepositoryByExample to service layer (#32419)
 | 
			
		||||
  * Refactor RepoRefByType (#32413)
 | 
			
		||||
  * Refactor: remove redundant err declarations (#32381)
 | 
			
		||||
  * Refactor markup code (#31399)
 | 
			
		||||
  * Refactor render system (orgmode) (#32671)
 | 
			
		||||
  * Refactor render system (#32492)
 | 
			
		||||
  * Refactor markdown render (#32736 & #32728)
 | 
			
		||||
  * Refactor repo unit "disabled" check (#31389)
 | 
			
		||||
  * Refactor route path normalization (#31381)
 | 
			
		||||
  * Refactor to use UnsafeStringToBytes (#31358)
 | 
			
		||||
  * Migrate vue components to setup (#32329)
 | 
			
		||||
  * Refactor globallock (#31933)
 | 
			
		||||
  * Use correct function name (#31887)
 | 
			
		||||
  * Use a common message template instead of a special one (#31878)
 | 
			
		||||
  * Fix a number of Typescript issues (#31877)
 | 
			
		||||
  * Refactor dropzone (#31482)
 | 
			
		||||
  * Move custom `tw-` helpers to tailwind plugin (#31184)
 | 
			
		||||
  * Replace `gt-word-break` with `tw-break-anywhere` (#31183)
 | 
			
		||||
  * Drop `IDOrderDesc` for listing Actions task and always order by `id DESC` (#31150)
 | 
			
		||||
  * Split common-global.js into separate files (#31438)
 | 
			
		||||
  * Improve detecting empty files (#31332)
 | 
			
		||||
  * Use `querySelector` over alternative DOM methods (#31280)
 | 
			
		||||
  * Remove jQuery `.text()` (#30506)
 | 
			
		||||
  * Use repo as of renderctx's member rather than a repoPath on metas (#29222)
 | 
			
		||||
  * Refactor some frontend problems (#32646)
 | 
			
		||||
  * Refactor DateUtils and merge TimeSince (#32409)
 | 
			
		||||
  * Replace DateTime with proper functions (#32402)
 | 
			
		||||
  * Replace DateTime with DateUtils (#32383)
 | 
			
		||||
  * Convert frontend code to typescript (#31559)
 | 
			
		||||
  * Refactor maven package registry (#33049) #33057
 | 
			
		||||
  * Refactor testfixtures #33028
 | 
			
		||||
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Fix issues with inconsistent spacing in areas (#32607)
 | 
			
		||||
  * Fix incomplete Actions status aggregations (#32859)
 | 
			
		||||
  * In some lfs server implementations, they require the ref attribute. (#32838)
 | 
			
		||||
  * Update the list of watchers and stargazers when clicking watch/unwatch or star/unstar (#32570)
 | 
			
		||||
  * Fix `recentupdate` sorting bugs (#32505)
 | 
			
		||||
  * Fix incorrect "Target branch does not exist" in PR title (#32222)
 | 
			
		||||
  * Handle "close" actionable references for manual merges (#31879)
 | 
			
		||||
  * render plain text file if the LFS object doesn't exist (#31812)
 | 
			
		||||
  * Fix Null Pointer error for CommitStatusesHideActionsURL (#31731)
 | 
			
		||||
  * Fix loadRepository error when access user dashboard (#31719)
 | 
			
		||||
  * Hide the "Details" link of commit status when the user cannot access actions (#30156)
 | 
			
		||||
  * Fix duplicate dropdown dividers (#32760)
 | 
			
		||||
  * Fix SSPI button visibility when SSPI is the only enabled method (#32841)
 | 
			
		||||
  * Fix overflow on org header (#32837)
 | 
			
		||||
  * Exclude protected branches from recently pushed (#31748)
 | 
			
		||||
  * Fix large image overflow in comment page (#31740)
 | 
			
		||||
  * Fix milestone deadline and date related problems (#32339)
 | 
			
		||||
  * Fix markdown preview $$ support (#31514)
 | 
			
		||||
  * Fix a compilation error in the Gitpod environment (#32559)
 | 
			
		||||
  * Fix PR diff review form submit (#32596)
 | 
			
		||||
  * Fix a number of typescript issues (#32308)
 | 
			
		||||
  * Fix some function names in comment (#32300)
 | 
			
		||||
  * Fix absolute-date (#32375)
 | 
			
		||||
  * Clarify Actions resources ownership (#31724)
 | 
			
		||||
  * Try to fix ACME directory problem (#33072) #33077
 | 
			
		||||
  * Inherit submodules from template repository content (#16237) #33068
 | 
			
		||||
  * Use project's redirect url instead of composing url (#33058) #33064
 | 
			
		||||
  * Fix toggle commit body button ui when latest commit message is long (#32997) #33034
 | 
			
		||||
  * Fix package error handling and npm meta and empty repo guide #33112
 | 
			
		||||
  * Fix empty git repo handling logic and fix mobile view (#33101) #33102
 | 
			
		||||
  * Fix line-number and scroll bugs (#33094) #33095
 | 
			
		||||
  * Fix bleve fuzziness search (#33078) #33087
 | 
			
		||||
  * Fix broken forms #33082
 | 
			
		||||
  * Fix empty repo updated time (#33120) #33124
 | 
			
		||||
  * Add missing transaction when set merge #33113
 | 
			
		||||
  * Fix issue comment number (#30556) #33055
 | 
			
		||||
  * Fix duplicate co-author in squashed merge commit messages (#33020) #33054
 | 
			
		||||
  * Fix Agit pull request permission check (#32999) #33005
 | 
			
		||||
  * Fix scoped label ui when contains emoji (#33007) #33014
 | 
			
		||||
  * Fix bug on activities (#33008) #33016
 | 
			
		||||
  * Fix review code comment avatar alignment (#33031) #33032
 | 
			
		||||
  * Fix templating in pull request comparison (#33025) #33038
 | 
			
		||||
  * Fix bug automerge cannot be chosed when there is only 1 merge style (#33040) #33043
 | 
			
		||||
  * Fix settings not being loaded at CLI (#26402) #33048
 | 
			
		||||
  * Support for email addresses containing uppercase characters when activating user account (#32998) #33001
 | 
			
		||||
  * Support org labels when adding labels by label names (#32988) #32996
 | 
			
		||||
  * Do not render truncated links in markdown (#32980) #32983
 | 
			
		||||
  * Demilestone should not include milestone (#32923) #32979
 | 
			
		||||
  * Fix Azure blob object Seek (#32974) #32975
 | 
			
		||||
  * Fix maven pom inheritance (#32943) #32976
 | 
			
		||||
  * Fix textarea newline handle (#32966) #32977
 | 
			
		||||
  * Fix outdated tmpl code (#32953) #32961
 | 
			
		||||
  * Fix commit range paging (#32944) #32962
 | 
			
		||||
  * Fix repo avatar conflict (#32958) #32960
 | 
			
		||||
  * Fix trailing comma not matched in the case of alphanumeric issue (#32945)
 | 
			
		||||
  * Relax the version checking for Arch packages (#32908) #32913
 | 
			
		||||
  * Add more load functions to make sure the reference object loaded (#32901) #32912
 | 
			
		||||
  * Filter reviews of one pull request in memory instead of database to reduce slow response because of lacking database index (#33106) #33128
 | 
			
		||||
  * Fix git remote error check, fix dependencies, fix js error (#33129) #33133
 | 
			
		||||
 | 
			
		||||
* MISC
 | 
			
		||||
  * Optimize branch protection rule loading (#32280)
 | 
			
		||||
  * Bump to go 1.23 (#31855)
 | 
			
		||||
  * Remove unused call to $.HeadRepo in view_title template (#32317)
 | 
			
		||||
  * Do not display `attestation-manifest` and use short sha256 instead of full sha256 (#32851)
 | 
			
		||||
  * Upgrade htmx to 2.0.4 (#32834)
 | 
			
		||||
  * Improve JSX/TSX support in code editor (#32833)
 | 
			
		||||
  * Add User-Agent for gitea's self-implemented lfs client. (#32832)
 | 
			
		||||
  * Use errors.New to replace fmt.Errorf with no parameters (#32800)
 | 
			
		||||
  * Add "n commits" link to contributors in contributors graph page (#32799)
 | 
			
		||||
  * Update dependencies, tweak eslint (#32719)
 | 
			
		||||
  * Remove all "floated" CSS styles (#32691)
 | 
			
		||||
  * Show tag name on branch/tag selector if repo shown from tag ref (#32689)
 | 
			
		||||
  * Use new mail package instead of an unmintained one (#32682)
 | 
			
		||||
  * Optimize the styling of icon buttons within file-header-right (#32675)
 | 
			
		||||
  * Validate OAuth Redirect URIs (#32643)
 | 
			
		||||
  * Support optional/configurable IAMEndpoint for Minio Client (#32581) (#32581)
 | 
			
		||||
  * Make search box in issue sidebar dropdown list always show when scrolling (#32576)
 | 
			
		||||
  * Bump CI,Flake and Snap to Node 22 (#32487)
 | 
			
		||||
  * Update `github.com/meilisearch/meilisearch-go` (#32484)
 | 
			
		||||
  * Add `DEFAULT_MIRROR_REPO_UNITS` and `DEFAULT_TEMPLATE_REPO_UNITS` options (#32416)
 | 
			
		||||
  * Update go dependencies (#32389)
 | 
			
		||||
  * Update JS and PY dependencies (#32388)
 | 
			
		||||
  * Upgrade rollup to 4.24.0 (#32312)
 | 
			
		||||
  * Upgrade vue to 3.5.12 (#32311)
 | 
			
		||||
  * Improve the maintainblity of the reserved username list (#32229)
 | 
			
		||||
  * Upgrade htmx to 2.0.3 (#32192)
 | 
			
		||||
  * Count typescript files as frontend for labeling (#32088)
 | 
			
		||||
  * Only use Host header from reverse proxy (#32060)
 | 
			
		||||
  * Failed authentications are logged to level Warning (#32016)
 | 
			
		||||
  * Enhance USER_DISABLED_FEATURES to allow disabling change username or full name (#31959)
 | 
			
		||||
  * Distinguish official vs non-official reviews, add tool tips, and upgr… (#31924)
 | 
			
		||||
  * Update mermaid to v11 (#31913)
 | 
			
		||||
  * Bump relative-time-element to v4.4.3 (#31910)
 | 
			
		||||
  * Upgrade `htmx` to `2.0.2` (#31847)
 | 
			
		||||
  * Add warning message in merge instructions when `AutodetectManualMerge` was not enabled (#31805)
 | 
			
		||||
  * Add types to various low-level functions (#31781)
 | 
			
		||||
  * Update JS dependencies (#31766)
 | 
			
		||||
  * Remove unused code from models/repos/release.go (#31756)
 | 
			
		||||
  * Support delete user email in admin panel (#31690)
 | 
			
		||||
  * Add `username` to OIDC introspection response (#31688)
 | 
			
		||||
  * Use GetDisplayName() instead of DisplayName() to generate rss feeds (#31687)
 | 
			
		||||
  * Code editor theme enhancements (#31629)
 | 
			
		||||
  * Update JS dependencies (#31616)
 | 
			
		||||
  * Add types for js globals (#31586)
 | 
			
		||||
  * Add back esbuild-loader for .js files (#31585)
 | 
			
		||||
  * Don't show hidden labels when filling out an issue template (#31576)
 | 
			
		||||
  * Allow synchronizing user status from OAuth2 login providers (#31572)
 | 
			
		||||
  * Display app name in the registration email title (#31562)
 | 
			
		||||
  * Use stable version of fabric (#31526)
 | 
			
		||||
  * Support legacy _links LFS batch responses (#31513)
 | 
			
		||||
  * Fix JS error with disabled attachment and easymde (#31511)
 | 
			
		||||
  * Always use HTML attributes for avatar size (#31509)
 | 
			
		||||
  * Use nolyfill to remove some polyfills (#31468)
 | 
			
		||||
  * Disable issue/PR comment button given empty input (#31463)
 | 
			
		||||
  * Add simple JS init performance trace (#31459)
 | 
			
		||||
  * Bump htmx to 2.0.0 (#31413)
 | 
			
		||||
  * Update JS dependencies, remove `eslint-plugin-jquery` (#31402)
 | 
			
		||||
  * Split org Propfile README to a new tab `overview` (#31373)
 | 
			
		||||
  * Update nix flake and add gofumpt (#31320)
 | 
			
		||||
  * Code optimization (#31315)
 | 
			
		||||
  * Enable poetry non-package mode (#31282)
 | 
			
		||||
  * Optimize profile layout to enhance visual experience (#31278)
 | 
			
		||||
  * Update `golang.org/x/net` (#31260)
 | 
			
		||||
  * Bump `@github/relative-time-element` to v4.4.1 (#31232)
 | 
			
		||||
  * Remove unnecessary inline style for tab-size (#31224)
 | 
			
		||||
  * Update golangci-lint to v1.59.0 (#31221)
 | 
			
		||||
  * Update chroma to v2.14.0 (#31177)
 | 
			
		||||
  * Update JS dependencies (#31120)
 | 
			
		||||
  * Improve the handling of `jobs.<job_id>.if` (#31070)
 | 
			
		||||
  * Clean up revive linter config, tweak golangci output (#30980)
 | 
			
		||||
  * Use CSS `inset` shorthand (#30939)
 | 
			
		||||
  * Forbid deprecated `break-word` in CSS (#30934)
 | 
			
		||||
  * Remove obsolete monaco workaround (#30893)
 | 
			
		||||
  * Update JS dependencies, add new eslint rules (#30840)
 | 
			
		||||
  * Fix body margin shifting with modals, fix error on project column edit (#30831)
 | 
			
		||||
  * Remove disk-clean workflow (#30741)
 | 
			
		||||
  * Bump `github.com/google/go-github` to v61 (#30738)
 | 
			
		||||
  * Add built js files to eslint ignore (#30737)
 | 
			
		||||
  * Use `ProtonMail/go-crypto` for `opengpg` in tests (#30736)
 | 
			
		||||
  * Upgrade xorm to v1.3.9 and improve some migrations Sync (#29899)
 | 
			
		||||
  * Added default sorting milestones by name (#27084)
 | 
			
		||||
  * Enable `unparam` linter (#31277)
 | 
			
		||||
  * Use Alpine 3.21 for the docker images (#32924) #32951
 | 
			
		||||
  * Bump x/net (#32896) #32899
 | 
			
		||||
  * Use -s -w ldflags for release artifacts (#33041) #33042
 | 
			
		||||
  * Remove aws go sdk package dependency (#33029) #33047
 | 
			
		||||
 | 
			
		||||
## [1.22.4](https://github.com/go-gitea/gitea/releases/tag/v1.22.4) - 2024-11-14
 | 
			
		||||
 | 
			
		||||
* SECURITY
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
# Build stage
 | 
			
		||||
FROM docker.io/library/golang:1.23-alpine3.20 AS build-env
 | 
			
		||||
FROM docker.io/library/golang:1.23-alpine3.21 AS build-env
 | 
			
		||||
 | 
			
		||||
ARG GOPROXY
 | 
			
		||||
ENV GOPROXY=${GOPROXY:-direct}
 | 
			
		||||
@@ -41,7 +41,7 @@ RUN chmod 755 /tmp/local/usr/bin/entrypoint \
 | 
			
		||||
              /go/src/code.gitea.io/gitea/environment-to-ini
 | 
			
		||||
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
 | 
			
		||||
 | 
			
		||||
FROM docker.io/library/alpine:3.20
 | 
			
		||||
FROM docker.io/library/alpine:3.21
 | 
			
		||||
LABEL maintainer="maintainers@gitea.io"
 | 
			
		||||
 | 
			
		||||
EXPOSE 22 3000
 | 
			
		||||
@@ -78,7 +78,7 @@ ENV GITEA_CUSTOM=/data/gitea
 | 
			
		||||
VOLUME ["/data"]
 | 
			
		||||
 | 
			
		||||
ENTRYPOINT ["/usr/bin/entrypoint"]
 | 
			
		||||
CMD ["/bin/s6-svscan", "/etc/s6"]
 | 
			
		||||
CMD ["/usr/bin/s6-svscan", "/etc/s6"]
 | 
			
		||||
 | 
			
		||||
COPY --from=build-env /tmp/local /
 | 
			
		||||
COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
# Build stage
 | 
			
		||||
FROM docker.io/library/golang:1.23-alpine3.20 AS build-env
 | 
			
		||||
FROM docker.io/library/golang:1.23-alpine3.21 AS build-env
 | 
			
		||||
 | 
			
		||||
ARG GOPROXY
 | 
			
		||||
ENV GOPROXY=${GOPROXY:-direct}
 | 
			
		||||
@@ -39,7 +39,7 @@ RUN chmod 755 /tmp/local/usr/local/bin/docker-entrypoint.sh \
 | 
			
		||||
              /go/src/code.gitea.io/gitea/environment-to-ini
 | 
			
		||||
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
 | 
			
		||||
 | 
			
		||||
FROM docker.io/library/alpine:3.20
 | 
			
		||||
FROM docker.io/library/alpine:3.21
 | 
			
		||||
LABEL maintainer="maintainers@gitea.io"
 | 
			
		||||
 | 
			
		||||
EXPOSE 2222 3000
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										18
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								Makefile
									
									
									
									
									
								
							@@ -26,17 +26,17 @@ COMMA := ,
 | 
			
		||||
XGO_VERSION := go-1.23.x
 | 
			
		||||
 | 
			
		||||
AIR_PACKAGE ?= github.com/air-verse/air@v1
 | 
			
		||||
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/cmd/editorconfig-checker@2.7.0
 | 
			
		||||
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.0.3
 | 
			
		||||
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.7.0
 | 
			
		||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.2
 | 
			
		||||
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11
 | 
			
		||||
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.5.1
 | 
			
		||||
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.12
 | 
			
		||||
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.6.0
 | 
			
		||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.31.0
 | 
			
		||||
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
 | 
			
		||||
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1
 | 
			
		||||
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1
 | 
			
		||||
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1
 | 
			
		||||
GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.15.3
 | 
			
		||||
GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.17.0
 | 
			
		||||
 | 
			
		||||
DOCKER_IMAGE ?= gitea/gitea
 | 
			
		||||
DOCKER_TAG ?= latest
 | 
			
		||||
@@ -806,22 +806,22 @@ $(DIST_DIRS):
 | 
			
		||||
 | 
			
		||||
.PHONY: release-windows
 | 
			
		||||
release-windows: | $(DIST_DIRS)
 | 
			
		||||
	CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) .
 | 
			
		||||
	CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'osusergo $(TAGS)' -ldflags '-s -w -linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) .
 | 
			
		||||
ifeq (,$(findstring gogit,$(TAGS)))
 | 
			
		||||
	CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'osusergo gogit $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION)-gogit .
 | 
			
		||||
	CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'osusergo gogit $(TAGS)' -ldflags '-s -w -linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION)-gogit .
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
.PHONY: release-linux
 | 
			
		||||
release-linux: | $(DIST_DIRS)
 | 
			
		||||
	CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets '$(LINUX_ARCHS)' -out gitea-$(VERSION) .
 | 
			
		||||
	CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-s -w -linkmode external -extldflags "-static" $(LDFLAGS)' -targets '$(LINUX_ARCHS)' -out gitea-$(VERSION) .
 | 
			
		||||
 | 
			
		||||
.PHONY: release-darwin
 | 
			
		||||
release-darwin: | $(DIST_DIRS)
 | 
			
		||||
	CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'darwin-10.12/amd64,darwin-10.12/arm64' -out gitea-$(VERSION) .
 | 
			
		||||
	CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-s -w $(LDFLAGS)' -targets 'darwin-10.12/amd64,darwin-10.12/arm64' -out gitea-$(VERSION) .
 | 
			
		||||
 | 
			
		||||
.PHONY: release-freebsd
 | 
			
		||||
release-freebsd: | $(DIST_DIRS)
 | 
			
		||||
	CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'freebsd/amd64' -out gitea-$(VERSION) .
 | 
			
		||||
	CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-s -w $(LDFLAGS)' -targets 'freebsd/amd64' -out gitea-$(VERSION) .
 | 
			
		||||
 | 
			
		||||
.PHONY: release-copy
 | 
			
		||||
release-copy: | $(DIST_DIRS)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										77
									
								
								assets/go-licenses.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										77
									
								
								assets/go-licenses.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -69,6 +69,10 @@ var microcmdUserCreate = &cli.Command{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func runCreateUser(c *cli.Context) error {
 | 
			
		||||
	// this command highly depends on the many setting options (create org, visibility, etc.), so it must have a full setting load first
 | 
			
		||||
	// duplicate setting loading should be safe at the moment, but it should be refactored & improved in the future.
 | 
			
		||||
	setting.LoadSettings()
 | 
			
		||||
 | 
			
		||||
	if err := argsSet(c, "email"); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,7 @@ import (
 | 
			
		||||
var CmdMigrate = &cli.Command{
 | 
			
		||||
	Name:        "migrate",
 | 
			
		||||
	Usage:       "Migrate the database",
 | 
			
		||||
	Description: "This is a command for migrating the database, so that you can run gitea admin create-user before starting the server.",
 | 
			
		||||
	Description: `This is a command for migrating the database, so that you can run "gitea admin create user" before starting the server.`,
 | 
			
		||||
	Action:      runMigrate,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								cmd/web.go
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								cmd/web.go
									
									
									
									
									
								
							@@ -12,6 +12,7 @@ import (
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	_ "net/http/pprof" // Used for debugging if enabled and a web server is running
 | 
			
		||||
 | 
			
		||||
@@ -115,6 +116,16 @@ func showWebStartupMessage(msg string) {
 | 
			
		||||
	log.Info("* CustomPath: %s", setting.CustomPath)
 | 
			
		||||
	log.Info("* ConfigFile: %s", setting.CustomConf)
 | 
			
		||||
	log.Info("%s", msg) // show startup message
 | 
			
		||||
 | 
			
		||||
	if setting.CORSConfig.Enabled {
 | 
			
		||||
		log.Info("CORS Service Enabled")
 | 
			
		||||
	}
 | 
			
		||||
	if setting.DefaultUILocation != time.Local {
 | 
			
		||||
		log.Info("Default UI Location is %v", setting.DefaultUILocation.String())
 | 
			
		||||
	}
 | 
			
		||||
	if setting.MailService != nil {
 | 
			
		||||
		log.Info("Mail Service Enabled: RegisterEmailConfirm=%v, Service.EnableNotifyMail=%v", setting.Service.RegisterEmailConfirm, setting.Service.EnableNotifyMail)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func serveInstall(ctx *cli.Context) error {
 | 
			
		||||
 
 | 
			
		||||
@@ -54,8 +54,10 @@ func runACME(listenAddr string, m http.Handler) error {
 | 
			
		||||
		altTLSALPNPort = p
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// FIXME: this path is not right, it uses "AppWorkPath" incorrectly, and writes the data into "AppWorkPath/https"
 | 
			
		||||
	// Ideally it should migrate to AppDataPath write to "AppDataPath/https"
 | 
			
		||||
	certmagic.Default.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory}
 | 
			
		||||
	magic := certmagic.NewDefault()
 | 
			
		||||
	magic.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory}
 | 
			
		||||
	// Try to use private CA root if provided, otherwise defaults to system's trust
 | 
			
		||||
	var certPool *x509.CertPool
 | 
			
		||||
	if setting.AcmeCARoot != "" {
 | 
			
		||||
 
 | 
			
		||||
@@ -1482,6 +1482,10 @@ LEVEL = Info
 | 
			
		||||
;REPO_INDEXER_EXCLUDE =
 | 
			
		||||
;;
 | 
			
		||||
;MAX_FILE_SIZE = 1048576
 | 
			
		||||
;;
 | 
			
		||||
;; Bleve engine has performance problems with fuzzy search, so we limit the fuzziness to 0 by default to disable it.
 | 
			
		||||
;; If you'd like to enable it, you can set it to a value between 0 and 2.
 | 
			
		||||
;TYPE_BLEVE_MAX_FUZZINESS = 0
 | 
			
		||||
 | 
			
		||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 | 
			
		||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 | 
			
		||||
 
 | 
			
		||||
@@ -37,5 +37,5 @@ done
 | 
			
		||||
if [ $# -gt 0 ]; then
 | 
			
		||||
    exec "$@"
 | 
			
		||||
else
 | 
			
		||||
    exec /bin/s6-svscan /etc/s6
 | 
			
		||||
    exec /usr/bin/s6-svscan /etc/s6
 | 
			
		||||
fi
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										38
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										38
									
								
								go.mod
									
									
									
									
									
								
							@@ -24,11 +24,10 @@ require (
 | 
			
		||||
	github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0
 | 
			
		||||
	github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1
 | 
			
		||||
	github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
 | 
			
		||||
	github.com/ProtonMail/go-crypto v1.0.0
 | 
			
		||||
	github.com/ProtonMail/go-crypto v1.1.4
 | 
			
		||||
	github.com/PuerkitoBio/goquery v1.10.0
 | 
			
		||||
	github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.3
 | 
			
		||||
	github.com/alecthomas/chroma/v2 v2.14.0
 | 
			
		||||
	github.com/aws/aws-sdk-go v1.55.5
 | 
			
		||||
	github.com/alecthomas/chroma/v2 v2.15.0
 | 
			
		||||
	github.com/aws/aws-sdk-go-v2/credentials v1.17.42
 | 
			
		||||
	github.com/aws/aws-sdk-go-v2/service/codecommit v1.27.3
 | 
			
		||||
	github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
 | 
			
		||||
@@ -55,13 +54,12 @@ require (
 | 
			
		||||
	github.com/go-chi/cors v1.2.1
 | 
			
		||||
	github.com/go-co-op/gocron v1.37.0
 | 
			
		||||
	github.com/go-enry/go-enry/v2 v2.9.1
 | 
			
		||||
	github.com/go-git/go-billy/v5 v5.6.0
 | 
			
		||||
	github.com/go-git/go-git/v5 v5.12.0
 | 
			
		||||
	github.com/go-git/go-billy/v5 v5.6.1
 | 
			
		||||
	github.com/go-git/go-git/v5 v5.13.1
 | 
			
		||||
	github.com/go-ldap/ldap/v3 v3.4.8
 | 
			
		||||
	github.com/go-redsync/redsync/v4 v4.13.0
 | 
			
		||||
	github.com/go-sql-driver/mysql v1.8.1
 | 
			
		||||
	github.com/go-swagger/go-swagger v0.31.0
 | 
			
		||||
	github.com/go-testfixtures/testfixtures/v3 v3.11.0
 | 
			
		||||
	github.com/go-webauthn/webauthn v0.11.2
 | 
			
		||||
	github.com/gobwas/glob v0.2.3
 | 
			
		||||
	github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
 | 
			
		||||
@@ -81,7 +79,6 @@ require (
 | 
			
		||||
	github.com/jhillyerd/enmime v1.3.0
 | 
			
		||||
	github.com/json-iterator/go v1.1.12
 | 
			
		||||
	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
 | 
			
		||||
	github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4
 | 
			
		||||
	github.com/klauspost/compress v1.17.11
 | 
			
		||||
	github.com/klauspost/cpuid/v2 v2.2.8
 | 
			
		||||
	github.com/lib/pq v1.10.9
 | 
			
		||||
@@ -109,7 +106,7 @@ require (
 | 
			
		||||
	github.com/sassoftware/go-rpmutils v0.4.0
 | 
			
		||||
	github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3
 | 
			
		||||
	github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92
 | 
			
		||||
	github.com/stretchr/testify v1.9.0
 | 
			
		||||
	github.com/stretchr/testify v1.10.0
 | 
			
		||||
	github.com/syndtr/goleveldb v1.0.0
 | 
			
		||||
	github.com/tstranex/u2f v1.0.0
 | 
			
		||||
	github.com/ulikunitz/xz v0.5.12
 | 
			
		||||
@@ -121,14 +118,14 @@ require (
 | 
			
		||||
	github.com/yuin/goldmark v1.7.8
 | 
			
		||||
	github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
 | 
			
		||||
	github.com/yuin/goldmark-meta v1.1.0
 | 
			
		||||
	golang.org/x/crypto v0.31.0
 | 
			
		||||
	golang.org/x/crypto v0.32.0
 | 
			
		||||
	golang.org/x/image v0.21.0
 | 
			
		||||
	golang.org/x/net v0.30.0
 | 
			
		||||
	golang.org/x/net v0.34.0
 | 
			
		||||
	golang.org/x/oauth2 v0.23.0
 | 
			
		||||
	golang.org/x/sync v0.10.0
 | 
			
		||||
	golang.org/x/sys v0.28.0
 | 
			
		||||
	golang.org/x/sys v0.29.0
 | 
			
		||||
	golang.org/x/text v0.21.0
 | 
			
		||||
	golang.org/x/tools v0.26.0
 | 
			
		||||
	golang.org/x/tools v0.29.0
 | 
			
		||||
	google.golang.org/grpc v1.67.1
 | 
			
		||||
	google.golang.org/protobuf v1.35.1
 | 
			
		||||
	gopkg.in/ini.v1 v1.67.0
 | 
			
		||||
@@ -145,8 +142,6 @@ require (
 | 
			
		||||
	filippo.io/edwards25519 v1.1.0 // indirect
 | 
			
		||||
	git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect
 | 
			
		||||
	github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
 | 
			
		||||
	github.com/ClickHouse/ch-go v0.63.1 // indirect
 | 
			
		||||
	github.com/ClickHouse/clickhouse-go/v2 v2.24.0 // indirect
 | 
			
		||||
	github.com/DataDog/zstd v1.5.6 // indirect
 | 
			
		||||
	github.com/Masterminds/goutils v1.1.1 // indirect
 | 
			
		||||
	github.com/Masterminds/semver/v3 v3.3.0 // indirect
 | 
			
		||||
@@ -191,7 +186,7 @@ require (
 | 
			
		||||
	github.com/couchbase/gomemcached v0.3.2 // indirect
 | 
			
		||||
	github.com/couchbase/goutils v0.1.2 // indirect
 | 
			
		||||
	github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
 | 
			
		||||
	github.com/cyphar/filepath-securejoin v0.3.4 // indirect
 | 
			
		||||
	github.com/cyphar/filepath-securejoin v0.3.6 // indirect
 | 
			
		||||
	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
 | 
			
		||||
	github.com/davidmz/go-pageant v1.0.2 // indirect
 | 
			
		||||
	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
 | 
			
		||||
@@ -204,8 +199,6 @@ require (
 | 
			
		||||
	github.com/go-ap/errors v0.0.0-20240910140019-1e9d33cc1568 // indirect
 | 
			
		||||
	github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect
 | 
			
		||||
	github.com/go-enry/go-oniguruma v1.2.1 // indirect
 | 
			
		||||
	github.com/go-faster/city v1.0.1 // indirect
 | 
			
		||||
	github.com/go-faster/errors v0.7.1 // indirect
 | 
			
		||||
	github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e // indirect
 | 
			
		||||
	github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
 | 
			
		||||
	github.com/go-ini/ini v1.67.0 // indirect
 | 
			
		||||
@@ -226,7 +219,7 @@ require (
 | 
			
		||||
	github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
 | 
			
		||||
	github.com/golang-sql/sqlexp v0.1.0 // indirect
 | 
			
		||||
	github.com/golang/geo v0.0.0-20230421003525-6adc56603217 // indirect
 | 
			
		||||
	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
 | 
			
		||||
	github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
 | 
			
		||||
	github.com/golang/protobuf v1.5.4 // indirect
 | 
			
		||||
	github.com/golang/snappy v0.0.4 // indirect
 | 
			
		||||
	github.com/google/btree v1.1.3 // indirect
 | 
			
		||||
@@ -261,6 +254,7 @@ require (
 | 
			
		||||
	github.com/mitchellh/copystructure v1.2.0 // indirect
 | 
			
		||||
	github.com/mitchellh/mapstructure v1.5.0 // indirect
 | 
			
		||||
	github.com/mitchellh/reflectwalk v1.0.2 // indirect
 | 
			
		||||
	github.com/mmcloughlin/avo v0.6.0 // indirect
 | 
			
		||||
	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 | 
			
		||||
	github.com/modern-go/reflect2 v1.0.2 // indirect
 | 
			
		||||
	github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect
 | 
			
		||||
@@ -270,10 +264,9 @@ require (
 | 
			
		||||
	github.com/oklog/ulid v1.3.1 // indirect
 | 
			
		||||
	github.com/olekukonko/tablewriter v0.0.5 // indirect
 | 
			
		||||
	github.com/onsi/ginkgo v1.16.5 // indirect
 | 
			
		||||
	github.com/paulmach/orb v0.11.1 // indirect
 | 
			
		||||
	github.com/pelletier/go-toml/v2 v2.2.3 // indirect
 | 
			
		||||
	github.com/pierrec/lz4/v4 v4.1.21 // indirect
 | 
			
		||||
	github.com/pjbgf/sha1cd v0.3.0 // indirect
 | 
			
		||||
	github.com/pjbgf/sha1cd v0.3.1 // indirect
 | 
			
		||||
	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
 | 
			
		||||
	github.com/prometheus/client_model v0.6.1 // indirect
 | 
			
		||||
	github.com/prometheus/common v0.60.1 // indirect
 | 
			
		||||
@@ -285,7 +278,6 @@ require (
 | 
			
		||||
	github.com/russross/blackfriday/v2 v2.1.0 // indirect
 | 
			
		||||
	github.com/sagikazarmark/locafero v0.6.0 // indirect
 | 
			
		||||
	github.com/sagikazarmark/slog-shim v0.1.0 // indirect
 | 
			
		||||
	github.com/segmentio/asm v1.2.0 // indirect
 | 
			
		||||
	github.com/shopspring/decimal v1.4.0 // indirect
 | 
			
		||||
	github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect
 | 
			
		||||
	github.com/sirupsen/logrus v1.9.3 // indirect
 | 
			
		||||
@@ -310,13 +302,11 @@ require (
 | 
			
		||||
	github.com/zeebo/blake3 v0.2.4 // indirect
 | 
			
		||||
	go.etcd.io/bbolt v1.3.11 // indirect
 | 
			
		||||
	go.mongodb.org/mongo-driver v1.17.1 // indirect
 | 
			
		||||
	go.opentelemetry.io/otel v1.31.0 // indirect
 | 
			
		||||
	go.opentelemetry.io/otel/trace v1.31.0 // indirect
 | 
			
		||||
	go.uber.org/atomic v1.11.0 // indirect
 | 
			
		||||
	go.uber.org/multierr v1.11.0 // indirect
 | 
			
		||||
	go.uber.org/zap v1.27.0 // indirect
 | 
			
		||||
	golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect
 | 
			
		||||
	golang.org/x/mod v0.21.0 // indirect
 | 
			
		||||
	golang.org/x/mod v0.22.0 // indirect
 | 
			
		||||
	golang.org/x/time v0.7.0 // indirect
 | 
			
		||||
	google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect
 | 
			
		||||
	gopkg.in/warnings.v0 v0.1.2 // indirect
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										142
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										142
									
								
								go.sum
									
									
									
									
									
								
							@@ -59,10 +59,6 @@ github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzS
 | 
			
		||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
 | 
			
		||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
 | 
			
		||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 | 
			
		||||
github.com/ClickHouse/ch-go v0.63.1 h1:s2JyZvWLTCSAGdtjMBBmAgQQHMco6pawLJMOXi0FODM=
 | 
			
		||||
github.com/ClickHouse/ch-go v0.63.1/go.mod h1:I1kJJCL3WJcBMGe1m+HVK0+nREaG+JOYYBWjrDrF3R0=
 | 
			
		||||
github.com/ClickHouse/clickhouse-go/v2 v2.24.0 h1:L/n/pVVpk95KtkHOiKuSnO7cu2ckeW4gICbbOh5qs74=
 | 
			
		||||
github.com/ClickHouse/clickhouse-go/v2 v2.24.0/go.mod h1:iDTViXk2Fgvf1jn2dbJd1ys+fBkdD1UMRnXlwmhijhQ=
 | 
			
		||||
github.com/DataDog/zstd v1.5.6 h1:LbEglqepa/ipmmQJUDnSsfvA8e8IStVcGaFWDuxvGOY=
 | 
			
		||||
github.com/DataDog/zstd v1.5.6/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
 | 
			
		||||
github.com/Julusian/godocdown v0.0.0-20170816220326-6d19f8ff2df8/go.mod h1:INZr5t32rG59/5xeltqoCJoNY7e5x/3xoY9WSWVWg74=
 | 
			
		||||
@@ -75,8 +71,8 @@ github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSC
 | 
			
		||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
 | 
			
		||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
 | 
			
		||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
 | 
			
		||||
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
 | 
			
		||||
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
 | 
			
		||||
github.com/ProtonMail/go-crypto v1.1.4 h1:G5U5asvD5N/6/36oIw3k2bOfBn5XVcZrb7PBjzzKKoE=
 | 
			
		||||
github.com/ProtonMail/go-crypto v1.1.4/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
 | 
			
		||||
github.com/PuerkitoBio/goquery v1.10.0 h1:6fiXdLuUvYs2OJSvNRqlNPoBm6YABE226xrbavY5Wv4=
 | 
			
		||||
github.com/PuerkitoBio/goquery v1.10.0/go.mod h1:TjZZl68Q3eGHNBA8CWaxAN7rOU1EbDz3CWuolcO5Yu4=
 | 
			
		||||
github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
 | 
			
		||||
@@ -85,11 +81,11 @@ github.com/RoaringBitmap/roaring v1.9.4 h1:yhEIoH4YezLYT04s1nHehNO64EKFTop/wBhxv
 | 
			
		||||
github.com/RoaringBitmap/roaring v1.9.4/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90=
 | 
			
		||||
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.3 h1:BP0HiyNT3AQEYi+if3wkRcIdQFHtsw6xX3Kx0glckgA=
 | 
			
		||||
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.3/go.mod h1:hMNtySovKkn2gdDuLqnqveP+mfhUSaBdoBcr2I7Zt0E=
 | 
			
		||||
github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE=
 | 
			
		||||
github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
 | 
			
		||||
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
 | 
			
		||||
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
 | 
			
		||||
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
 | 
			
		||||
github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E=
 | 
			
		||||
github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I=
 | 
			
		||||
github.com/alecthomas/chroma/v2 v2.15.0 h1:LxXTQHFoYrstG2nnV9y2X5O94sOBzf0CIUpSTbpxvMc=
 | 
			
		||||
github.com/alecthomas/chroma/v2 v2.15.0/go.mod h1:gUhVLrPDXPtp/f+L1jo9xepo9gL4eLwRuGAunSZMkio=
 | 
			
		||||
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
 | 
			
		||||
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
 | 
			
		||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
 | 
			
		||||
@@ -109,8 +105,6 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd
 | 
			
		||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
 | 
			
		||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
 | 
			
		||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
 | 
			
		||||
github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU=
 | 
			
		||||
github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
 | 
			
		||||
github.com/aws/aws-sdk-go-v2 v1.32.3 h1:T0dRlFBKcdaUPGNtkBSwHZxrtis8CQU17UpNBZYd0wk=
 | 
			
		||||
github.com/aws/aws-sdk-go-v2 v1.32.3/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo=
 | 
			
		||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.42 h1:sBP0RPjBU4neGpIYyx8mkU2QqLPl5u9cmdTWVzIpHkM=
 | 
			
		||||
@@ -194,7 +188,6 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
 | 
			
		||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
 | 
			
		||||
github.com/buildkite/terminal-to-html/v3 v3.16.3 h1:IGuJjboHjuMLWOGsKZKNxbbn41emOLiHzXPmQZk31fk=
 | 
			
		||||
github.com/buildkite/terminal-to-html/v3 v3.16.3/go.mod h1:r/J7cC9c3EzBzP3/wDz0RJLPwv5PUAMp+KF2w+ntMc0=
 | 
			
		||||
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
 | 
			
		||||
github.com/caddyserver/certmagic v0.21.4 h1:e7VobB8rffHv8ZZpSiZtEwnLDHUwLVYLWzWSa1FfKI0=
 | 
			
		||||
github.com/caddyserver/certmagic v0.21.4/go.mod h1:swUXjQ1T9ZtMv95qj7/InJvWLXURU85r+CfG0T+ZbDE=
 | 
			
		||||
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
 | 
			
		||||
@@ -211,7 +204,6 @@ github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moA
 | 
			
		||||
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
 | 
			
		||||
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
 | 
			
		||||
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
 | 
			
		||||
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
 | 
			
		||||
github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys=
 | 
			
		||||
github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
 | 
			
		||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 | 
			
		||||
@@ -229,16 +221,14 @@ github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwc
 | 
			
		||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
 | 
			
		||||
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 | 
			
		||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 | 
			
		||||
github.com/cyphar/filepath-securejoin v0.3.4 h1:VBWugsJh2ZxJmLFSM06/0qzQyiQX2Qs0ViKrUAcqdZ8=
 | 
			
		||||
github.com/cyphar/filepath-securejoin v0.3.4/go.mod h1:8s/MCNJREmFK0H02MF6Ihv1nakJe4L/w3WZLHNkvlYM=
 | 
			
		||||
github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM=
 | 
			
		||||
github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0=
 | 
			
		||||
github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE=
 | 
			
		||||
github.com/denisenkom/go-mssqldb v0.12.3 h1:pBSGx9Tq67pBOTLmxNuirNTeB8Vjmf886Kx+8Y+8shw=
 | 
			
		||||
github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDrorD1Vrm1KEz5hxDo=
 | 
			
		||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
 | 
			
		||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
 | 
			
		||||
github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21 h1:PdsjTl0Cg+ZJgOx/CFV5NNgO1ThTreqdgKYiDCMHJwA=
 | 
			
		||||
@@ -262,8 +252,8 @@ github.com/dvyukov/go-fuzz v0.0.0-20210429054444-fca39067bc72/go.mod h1:11Gm+ccJ
 | 
			
		||||
github.com/editorconfig/editorconfig-core-go/v2 v2.6.2 h1:dKG8sc7n321deIVRcQtwlMNoBEra7j0qQ8RwxO8RN0w=
 | 
			
		||||
github.com/editorconfig/editorconfig-core-go/v2 v2.6.2/go.mod h1:7dvD3GCm7eBw53xZ/lsiq72LqobdMg3ITbMBxnmJmqY=
 | 
			
		||||
github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
 | 
			
		||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
 | 
			
		||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
 | 
			
		||||
github.com/elazarl/goproxy v1.2.3 h1:xwIyKHbaP5yfT6O9KIeYJR5549MXRQkoQMRXGztz8YQ=
 | 
			
		||||
github.com/elazarl/goproxy v1.2.3/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64=
 | 
			
		||||
github.com/emersion/go-imap v1.2.1 h1:+s9ZjMEjOB8NzZMVTM3cCenz2JrQIGGo5j1df19WjTA=
 | 
			
		||||
github.com/emersion/go-imap v1.2.1/go.mod h1:Qlx1FSx2FTxjnjWpIlVNEuX+ylerZQNFE5NsmKFSejY=
 | 
			
		||||
github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4=
 | 
			
		||||
@@ -317,20 +307,16 @@ github.com/go-enry/go-enry/v2 v2.9.1 h1:G9iDteJ/Mc0F4Di5NeQknf83R2OkRbwY9cAYmcqV
 | 
			
		||||
github.com/go-enry/go-enry/v2 v2.9.1/go.mod h1:9yrj4ES1YrbNb1Wb7/PWYr2bpaCXUGRt0uafN0ISyG8=
 | 
			
		||||
github.com/go-enry/go-oniguruma v1.2.1 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo=
 | 
			
		||||
github.com/go-enry/go-oniguruma v1.2.1/go.mod h1:bWDhYP+S6xZQgiRL7wlTScFYBe023B6ilRZbCAD5Hf4=
 | 
			
		||||
github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw=
 | 
			
		||||
github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw=
 | 
			
		||||
github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg=
 | 
			
		||||
github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo=
 | 
			
		||||
github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e h1:oRq/fiirun5HqlEWMLIcDmLpIELlG4iGbd0s8iqgPi8=
 | 
			
		||||
github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
 | 
			
		||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
 | 
			
		||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
 | 
			
		||||
github.com/go-git/go-billy/v5 v5.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8=
 | 
			
		||||
github.com/go-git/go-billy/v5 v5.6.0/go.mod h1:sFDq7xD3fn3E0GOwUSZqHo9lrkmx8xJhA0ZrfvjBRGM=
 | 
			
		||||
github.com/go-git/go-billy/v5 v5.6.1 h1:u+dcrgaguSSkbjzHwelEjc0Yj300NUevrrPphk/SoRA=
 | 
			
		||||
github.com/go-git/go-billy/v5 v5.6.1/go.mod h1:0AsLr1z2+Uksi4NlElmMblP5rPcDZNRCD8ujZCRR2BE=
 | 
			
		||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
 | 
			
		||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
 | 
			
		||||
github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys=
 | 
			
		||||
github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY=
 | 
			
		||||
github.com/go-git/go-git/v5 v5.13.1 h1:DAQ9APonnlvSWpvolXWIuV6Q6zXy2wHbN4cVlNR5Q+M=
 | 
			
		||||
github.com/go-git/go-git/v5 v5.13.1/go.mod h1:qryJB4cSBoq3FRoBRf5A77joojuBcmPJ0qu3XXXVixc=
 | 
			
		||||
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
 | 
			
		||||
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
 | 
			
		||||
github.com/go-ldap/ldap/v3 v3.4.8 h1:loKJyspcRezt2Q3ZRMq2p/0v8iOurlmeXDPw6fikSvQ=
 | 
			
		||||
@@ -372,8 +358,6 @@ github.com/go-swagger/go-swagger v0.31.0/go.mod h1:WSigRRWEig8zV6t6Sm8Y+EmUjlzA/
 | 
			
		||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
 | 
			
		||||
github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
 | 
			
		||||
github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
 | 
			
		||||
github.com/go-testfixtures/testfixtures/v3 v3.11.0 h1:XxQr8AnPORcZkyNd7go5UNLPD3dULN8ixYISlzrlfEQ=
 | 
			
		||||
github.com/go-testfixtures/testfixtures/v3 v3.11.0/go.mod h1:THmudHF1Ixq++J2/UodcJpxUphfyEd77m83TvDtryqE=
 | 
			
		||||
github.com/go-webauthn/webauthn v0.11.2 h1:Fgx0/wlmkClTKlnOsdOQ+K5HcHDsDcYIvtYmfhEOSUc=
 | 
			
		||||
github.com/go-webauthn/webauthn v0.11.2/go.mod h1:aOtudaF94pM71g3jRwTYYwQTG1KyTILTcZqN1srkmD0=
 | 
			
		||||
github.com/go-webauthn/x v0.1.15 h1:eG1OhggBJTkDE8gUeOlGRbRe8E/PSVG26YG4AyFbwkU=
 | 
			
		||||
@@ -385,7 +369,6 @@ github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6Wezm
 | 
			
		||||
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
 | 
			
		||||
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
 | 
			
		||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
 | 
			
		||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
 | 
			
		||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7wCLuiqMaUh5SJkkzI2gDs+FgLs=
 | 
			
		||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
 | 
			
		||||
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 h1:UjoPNDAQ5JPCjlxoJd6K8ALZqSDDhk2ymieAZOVaDg0=
 | 
			
		||||
@@ -400,8 +383,8 @@ github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei
 | 
			
		||||
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
 | 
			
		||||
github.com/golang/geo v0.0.0-20230421003525-6adc56603217 h1:HKlyj6in2JV6wVkmQ4XmG/EIm+SCYlPZ+V4GWit7Z+I=
 | 
			
		||||
github.com/golang/geo v0.0.0-20230421003525-6adc56603217/go.mod h1:8wI0hitZ3a1IxZfeH3/5I97CI8i5cLGsYe7xNhQGs9U=
 | 
			
		||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
 | 
			
		||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 | 
			
		||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
 | 
			
		||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
 | 
			
		||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 | 
			
		||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 | 
			
		||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
 | 
			
		||||
@@ -410,7 +393,6 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
 | 
			
		||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
 | 
			
		||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
 | 
			
		||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 | 
			
		||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
 | 
			
		||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
 | 
			
		||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
 | 
			
		||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 | 
			
		||||
@@ -497,22 +479,6 @@ github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI
 | 
			
		||||
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
 | 
			
		||||
github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
 | 
			
		||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 | 
			
		||||
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
 | 
			
		||||
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
 | 
			
		||||
github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w=
 | 
			
		||||
github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM=
 | 
			
		||||
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
 | 
			
		||||
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
 | 
			
		||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
 | 
			
		||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
 | 
			
		||||
github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag=
 | 
			
		||||
github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
 | 
			
		||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
 | 
			
		||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
 | 
			
		||||
github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw=
 | 
			
		||||
github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
 | 
			
		||||
github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA=
 | 
			
		||||
github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw=
 | 
			
		||||
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 h1:iCHtR9CQyktQ5+f3dMVZfwD2KWJUgm7M0gdL9NGr8KA=
 | 
			
		||||
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
 | 
			
		||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
 | 
			
		||||
@@ -533,10 +499,6 @@ github.com/jessevdk/go-flags v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bB
 | 
			
		||||
github.com/jessevdk/go-flags v1.6.1/go.mod h1:Mk8T1hIAWpOiJiHa9rJASDK2UGWji0EuPGBnNLMooyc=
 | 
			
		||||
github.com/jhillyerd/enmime v1.3.0 h1:LV5kzfLidiOr8qRGIpYYmUZCnhrPbcFAnAFUnWn99rw=
 | 
			
		||||
github.com/jhillyerd/enmime v1.3.0/go.mod h1:6c6jg5HdRRV2FtvVL69LjiX1M8oE0xDX9VEhV3oy4gs=
 | 
			
		||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
 | 
			
		||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
 | 
			
		||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
 | 
			
		||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
 | 
			
		||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
 | 
			
		||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
 | 
			
		||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
 | 
			
		||||
@@ -548,13 +510,8 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNU
 | 
			
		||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
 | 
			
		||||
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
 | 
			
		||||
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
 | 
			
		||||
github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4 h1:cTxwSmnaqLoo+4tLukHoB9iqHOu3LmLhRmgUxZo6Vp4=
 | 
			
		||||
github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M=
 | 
			
		||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 | 
			
		||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 | 
			
		||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
 | 
			
		||||
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
 | 
			
		||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
 | 
			
		||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
 | 
			
		||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
 | 
			
		||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
 | 
			
		||||
@@ -624,12 +581,13 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua
 | 
			
		||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 | 
			
		||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
 | 
			
		||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
 | 
			
		||||
github.com/mmcloughlin/avo v0.6.0 h1:QH6FU8SKoTLaVs80GA8TJuLNkUYl4VokHKlPhVDg4YY=
 | 
			
		||||
github.com/mmcloughlin/avo v0.6.0/go.mod h1:8CoAGaCSYXtCPR+8y18Y9aB/kxb8JSS6FRI7mSkvD+8=
 | 
			
		||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 | 
			
		||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
 | 
			
		||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 | 
			
		||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
 | 
			
		||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
 | 
			
		||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
 | 
			
		||||
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 h1:j2kD3MT1z4PXCiUllUJF9mWUESr9TWKS7iEKsQ/IipM=
 | 
			
		||||
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM=
 | 
			
		||||
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
 | 
			
		||||
@@ -670,9 +628,6 @@ github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3I
 | 
			
		||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
 | 
			
		||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
 | 
			
		||||
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
 | 
			
		||||
github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU=
 | 
			
		||||
github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU=
 | 
			
		||||
github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
 | 
			
		||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 | 
			
		||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
 | 
			
		||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
 | 
			
		||||
@@ -680,8 +635,8 @@ github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG
 | 
			
		||||
github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
 | 
			
		||||
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
 | 
			
		||||
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
 | 
			
		||||
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
 | 
			
		||||
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
 | 
			
		||||
github.com/pjbgf/sha1cd v0.3.1 h1:Dh2GYdpJnO84lIw0LJwTFXjcNbasP/bklicSznyAaPI=
 | 
			
		||||
github.com/pjbgf/sha1cd v0.3.1/go.mod h1:Y8t7jSB/dEI/lQE04A1HVKteqjj9bX5O4+Cex0TCu8s=
 | 
			
		||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
 | 
			
		||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
 | 
			
		||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
 | 
			
		||||
@@ -735,8 +690,6 @@ github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6Ng
 | 
			
		||||
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY=
 | 
			
		||||
github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg=
 | 
			
		||||
github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI=
 | 
			
		||||
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
 | 
			
		||||
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
 | 
			
		||||
github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs=
 | 
			
		||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
 | 
			
		||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
 | 
			
		||||
@@ -783,21 +736,19 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
 | 
			
		||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 | 
			
		||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 | 
			
		||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 | 
			
		||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
			
		||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
			
		||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
			
		||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 | 
			
		||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 | 
			
		||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 | 
			
		||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
 | 
			
		||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 | 
			
		||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
 | 
			
		||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 | 
			
		||||
github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203 h1:QVqDTf3h2WHt08YuiTGPZLls0Wq99X9bWd0Q5ZSBesM=
 | 
			
		||||
github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKNihBbwlX8dLpwxCl3+HnXKV/R0e+sRLd9C8=
 | 
			
		||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
 | 
			
		||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
 | 
			
		||||
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
 | 
			
		||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
 | 
			
		||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
 | 
			
		||||
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
 | 
			
		||||
github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ=
 | 
			
		||||
github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM=
 | 
			
		||||
@@ -823,9 +774,6 @@ github.com/xanzy/go-gitlab v0.112.0 h1:6Z0cqEooCvBMfBIHw+CgO4AKGRV8na/9781xOb0+D
 | 
			
		||||
github.com/xanzy/go-gitlab v0.112.0/go.mod h1:wKNKh3GkYDMOsGmnfuX+ITCmDuSDWFO0G+C4AygL9RY=
 | 
			
		||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
 | 
			
		||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
 | 
			
		||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
 | 
			
		||||
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
 | 
			
		||||
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
 | 
			
		||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
 | 
			
		||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
 | 
			
		||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
 | 
			
		||||
@@ -842,9 +790,7 @@ github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZ
 | 
			
		||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
 | 
			
		||||
github.com/yohcop/openid-go v1.0.1 h1:DPRd3iPO5F6O5zX2e62XpVAbPT6wV51cuucH0z9g3js=
 | 
			
		||||
github.com/yohcop/openid-go v1.0.1/go.mod h1:b/AvD03P0KHj4yuihb+VtLD6bYYgsy0zqBzPCRjkCNs=
 | 
			
		||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
 | 
			
		||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 | 
			
		||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 | 
			
		||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 | 
			
		||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 | 
			
		||||
github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 | 
			
		||||
@@ -863,13 +809,8 @@ github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l
 | 
			
		||||
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
 | 
			
		||||
go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0=
 | 
			
		||||
go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I=
 | 
			
		||||
go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
 | 
			
		||||
go.mongodb.org/mongo-driver v1.17.1 h1:Wic5cJIwJgSpBhe3lx3+/RybR5PiYRMpVFgO7cOHyIM=
 | 
			
		||||
go.mongodb.org/mongo-driver v1.17.1/go.mod h1:wwWm/+BuOddhcq3n68LKRmgk2wXzmF6s0SFOa0GINL4=
 | 
			
		||||
go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=
 | 
			
		||||
go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE=
 | 
			
		||||
go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys=
 | 
			
		||||
go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
 | 
			
		||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 | 
			
		||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
 | 
			
		||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
 | 
			
		||||
@@ -886,16 +827,14 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
 | 
			
		||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 | 
			
		||||
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
 | 
			
		||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
 | 
			
		||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
 | 
			
		||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
 | 
			
		||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
 | 
			
		||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
 | 
			
		||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
 | 
			
		||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
 | 
			
		||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
 | 
			
		||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
 | 
			
		||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
 | 
			
		||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
 | 
			
		||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
 | 
			
		||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
 | 
			
		||||
golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s=
 | 
			
		||||
@@ -909,8 +848,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 | 
			
		||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 | 
			
		||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 | 
			
		||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 | 
			
		||||
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
 | 
			
		||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
 | 
			
		||||
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
 | 
			
		||||
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
 | 
			
		||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 | 
			
		||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
			
		||||
@@ -922,18 +861,16 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
 | 
			
		||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 | 
			
		||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 | 
			
		||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 | 
			
		||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
 | 
			
		||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 | 
			
		||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 | 
			
		||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
 | 
			
		||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
 | 
			
		||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 | 
			
		||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
 | 
			
		||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
 | 
			
		||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
 | 
			
		||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
 | 
			
		||||
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
 | 
			
		||||
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
 | 
			
		||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
 | 
			
		||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
 | 
			
		||||
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
 | 
			
		||||
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
 | 
			
		||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
@@ -941,7 +878,6 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
 | 
			
		||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
 | 
			
		||||
@@ -974,8 +910,6 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
 | 
			
		||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
@@ -985,14 +919,12 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 | 
			
		||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 | 
			
		||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 | 
			
		||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 | 
			
		||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
 | 
			
		||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 | 
			
		||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
 | 
			
		||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 | 
			
		||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
 | 
			
		||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
			
		||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 | 
			
		||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
 | 
			
		||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 | 
			
		||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
 | 
			
		||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
 | 
			
		||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
 | 
			
		||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
 | 
			
		||||
@@ -1000,15 +932,13 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
 | 
			
		||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
 | 
			
		||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
 | 
			
		||||
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
 | 
			
		||||
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
 | 
			
		||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
 | 
			
		||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
 | 
			
		||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
 | 
			
		||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
			
		||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
			
		||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
			
		||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 | 
			
		||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 | 
			
		||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 | 
			
		||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 | 
			
		||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 | 
			
		||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 | 
			
		||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 | 
			
		||||
@@ -1022,16 +952,14 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
 | 
			
		||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
			
		||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 | 
			
		||||
golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
 | 
			
		||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 | 
			
		||||
golang.org/x/tools v0.0.0-20200928182047-19e03678916f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
 | 
			
		||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 | 
			
		||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 | 
			
		||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 | 
			
		||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 | 
			
		||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
 | 
			
		||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
 | 
			
		||||
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
 | 
			
		||||
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
 | 
			
		||||
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
 | 
			
		||||
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
@@ -1046,8 +974,6 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
 | 
			
		||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
 | 
			
		||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
 | 
			
		||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 | 
			
		||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 | 
			
		||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 | 
			
		||||
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
 | 
			
		||||
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
 | 
			
		||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										16
									
								
								main_timezones.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								main_timezones.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
// Copyright 2025 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
//go:build windows
 | 
			
		||||
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
// Golang has the ability to load OS's timezone data from most UNIX systems (https://github.com/golang/go/blob/master/src/time/zoneinfo_unix.go)
 | 
			
		||||
// Even if the timezone data is missing, users could install the related packages to get it.
 | 
			
		||||
// But on Windows, although `zoneinfo_windows.go` tries to load the timezone data from Windows registry,
 | 
			
		||||
// some users still suffer from the issue that the timezone data is missing: https://github.com/go-gitea/gitea/issues/33235
 | 
			
		||||
// So we import the tzdata package to make sure the timezone data is included in the binary.
 | 
			
		||||
//
 | 
			
		||||
// For non-Windows package builders, they could still use the "TAGS=timetzdata" to include the tzdata package in the binary.
 | 
			
		||||
// If we decided to add the tzdata for other platforms, modify the "go:build" directive above.
 | 
			
		||||
import _ "time/tzdata"
 | 
			
		||||
@@ -114,6 +114,12 @@ type FindArtifactsOptions struct {
 | 
			
		||||
	Status       int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (opts FindArtifactsOptions) ToOrders() string {
 | 
			
		||||
	return "id"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var _ db.FindOptionsOrder = (*FindArtifactsOptions)(nil)
 | 
			
		||||
 | 
			
		||||
func (opts FindArtifactsOptions) ToConds() builder.Cond {
 | 
			
		||||
	cond := builder.NewCond()
 | 
			
		||||
	if opts.RepoID > 0 {
 | 
			
		||||
@@ -132,7 +138,7 @@ func (opts FindArtifactsOptions) ToConds() builder.Cond {
 | 
			
		||||
	return cond
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ActionArtifactMeta is the meta data of an artifact
 | 
			
		||||
// ActionArtifactMeta is the meta-data of an artifact
 | 
			
		||||
type ActionArtifactMeta struct {
 | 
			
		||||
	ArtifactName string
 | 
			
		||||
	FileSize     int64
 | 
			
		||||
 
 | 
			
		||||
@@ -69,7 +69,7 @@ func TestAggregateJobStatus(t *testing.T) {
 | 
			
		||||
		{[]Status{StatusFailure, StatusBlocked}, StatusFailure},
 | 
			
		||||
 | 
			
		||||
		// skipped with other status
 | 
			
		||||
		// TODO: need to clarify whether a PR with "skipped" job status is considered as "mergeable" or not.
 | 
			
		||||
		// "all skipped" is also considered as "mergeable" by "services/actions.toCommitStatus", the same as GitHub
 | 
			
		||||
		{[]Status{StatusSkipped}, StatusSkipped},
 | 
			
		||||
		{[]Status{StatusSkipped, StatusSuccess}, StatusSuccess},
 | 
			
		||||
		{[]Status{StatusSkipped, StatusFailure}, StatusFailure},
 | 
			
		||||
 
 | 
			
		||||
@@ -167,6 +167,7 @@ func init() {
 | 
			
		||||
 | 
			
		||||
type FindRunnerOptions struct {
 | 
			
		||||
	db.ListOptions
 | 
			
		||||
	IDs           []int64
 | 
			
		||||
	RepoID        int64
 | 
			
		||||
	OwnerID       int64 // it will be ignored if RepoID is set
 | 
			
		||||
	Sort          string
 | 
			
		||||
@@ -178,6 +179,14 @@ type FindRunnerOptions struct {
 | 
			
		||||
func (opts FindRunnerOptions) ToConds() builder.Cond {
 | 
			
		||||
	cond := builder.NewCond()
 | 
			
		||||
 | 
			
		||||
	if len(opts.IDs) > 0 {
 | 
			
		||||
		if len(opts.IDs) == 1 {
 | 
			
		||||
			cond = cond.And(builder.Eq{"id": opts.IDs[0]})
 | 
			
		||||
		} else {
 | 
			
		||||
			cond = cond.And(builder.In("id", opts.IDs))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if opts.RepoID > 0 {
 | 
			
		||||
		c := builder.NewCond().And(builder.Eq{"repo_id": opts.RepoID})
 | 
			
		||||
		if opts.WithAvailable {
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
	"code.gitea.io/gitea/modules/base"
 | 
			
		||||
	"code.gitea.io/gitea/modules/timeutil"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
)
 | 
			
		||||
@@ -51,7 +52,7 @@ func GetRunnerToken(ctx context.Context, token string) (*ActionRunnerToken, erro
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	} else if !has {
 | 
			
		||||
		return nil, fmt.Errorf("runner token %q: %w", token, util.ErrNotExist)
 | 
			
		||||
		return nil, fmt.Errorf(`runner token "%s...": %w`, base.TruncateString(token, 3), util.ErrNotExist)
 | 
			
		||||
	}
 | 
			
		||||
	return &runnerToken, nil
 | 
			
		||||
}
 | 
			
		||||
@@ -68,19 +69,15 @@ func UpdateRunnerToken(ctx context.Context, r *ActionRunnerToken, cols ...string
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewRunnerToken creates a new active runner token and invalidate all old tokens
 | 
			
		||||
// NewRunnerTokenWithValue creates a new active runner token and invalidate all old tokens
 | 
			
		||||
// ownerID will be ignored and treated as 0 if repoID is non-zero.
 | 
			
		||||
func NewRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerToken, error) {
 | 
			
		||||
func NewRunnerTokenWithValue(ctx context.Context, ownerID, repoID int64, token string) (*ActionRunnerToken, error) {
 | 
			
		||||
	if ownerID != 0 && repoID != 0 {
 | 
			
		||||
		// It's trying to create a runner token that belongs to a repository, but OwnerID has been set accidentally.
 | 
			
		||||
		// Remove OwnerID to avoid confusion; it's not worth returning an error here.
 | 
			
		||||
		ownerID = 0
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	token, err := util.CryptoRandomString(40)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	runnerToken := &ActionRunnerToken{
 | 
			
		||||
		OwnerID:  ownerID,
 | 
			
		||||
		RepoID:   repoID,
 | 
			
		||||
@@ -95,11 +92,19 @@ func NewRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerTo
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		_, err = db.GetEngine(ctx).Insert(runnerToken)
 | 
			
		||||
		_, err := db.GetEngine(ctx).Insert(runnerToken)
 | 
			
		||||
		return err
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerToken, error) {
 | 
			
		||||
	token, err := util.CryptoRandomString(40)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return NewRunnerTokenWithValue(ctx, ownerID, repoID, token)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetLatestRunnerToken returns the latest runner token
 | 
			
		||||
func GetLatestRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerToken, error) {
 | 
			
		||||
	if ownerID != 0 && repoID != 0 {
 | 
			
		||||
 
 | 
			
		||||
@@ -58,6 +58,7 @@ func InsertVariable(ctx context.Context, ownerID, repoID int64, name, data strin
 | 
			
		||||
 | 
			
		||||
type FindVariablesOpts struct {
 | 
			
		||||
	db.ListOptions
 | 
			
		||||
	IDs     []int64
 | 
			
		||||
	RepoID  int64
 | 
			
		||||
	OwnerID int64 // it will be ignored if RepoID is set
 | 
			
		||||
	Name    string
 | 
			
		||||
@@ -65,6 +66,15 @@ type FindVariablesOpts struct {
 | 
			
		||||
 | 
			
		||||
func (opts FindVariablesOpts) ToConds() builder.Cond {
 | 
			
		||||
	cond := builder.NewCond()
 | 
			
		||||
 | 
			
		||||
	if len(opts.IDs) > 0 {
 | 
			
		||||
		if len(opts.IDs) == 1 {
 | 
			
		||||
			cond = cond.And(builder.Eq{"id": opts.IDs[0]})
 | 
			
		||||
		} else {
 | 
			
		||||
			cond = cond.And(builder.In("id", opts.IDs))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Since we now support instance-level variables,
 | 
			
		||||
	// there is no need to check for null values for `owner_id` and `repo_id`
 | 
			
		||||
	cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
 | 
			
		||||
@@ -85,12 +95,12 @@ func FindVariables(ctx context.Context, opts FindVariablesOpts) ([]*ActionVariab
 | 
			
		||||
	return db.Find[ActionVariable](ctx, opts)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func UpdateVariable(ctx context.Context, variable *ActionVariable) (bool, error) {
 | 
			
		||||
	count, err := db.GetEngine(ctx).ID(variable.ID).Cols("name", "data").
 | 
			
		||||
		Update(&ActionVariable{
 | 
			
		||||
			Name: variable.Name,
 | 
			
		||||
			Data: variable.Data,
 | 
			
		||||
		})
 | 
			
		||||
func UpdateVariableCols(ctx context.Context, variable *ActionVariable, cols ...string) (bool, error) {
 | 
			
		||||
	variable.Name = strings.ToUpper(variable.Name)
 | 
			
		||||
	count, err := db.GetEngine(ctx).
 | 
			
		||||
		ID(variable.ID).
 | 
			
		||||
		Cols(cols...).
 | 
			
		||||
		Update(variable)
 | 
			
		||||
	return count != 0, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -72,9 +72,9 @@ func (at ActionType) String() string {
 | 
			
		||||
	case ActionRenameRepo:
 | 
			
		||||
		return "rename_repo"
 | 
			
		||||
	case ActionStarRepo:
 | 
			
		||||
		return "star_repo"
 | 
			
		||||
		return "star_repo" // will not displayed in feeds.tmpl
 | 
			
		||||
	case ActionWatchRepo:
 | 
			
		||||
		return "watch_repo"
 | 
			
		||||
		return "watch_repo" // will not displayed in feeds.tmpl
 | 
			
		||||
	case ActionCommitRepo:
 | 
			
		||||
		return "commit_repo"
 | 
			
		||||
	case ActionCreateIssue:
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,7 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/modules/git"
 | 
			
		||||
	"code.gitea.io/gitea/modules/gitrepo"
 | 
			
		||||
 | 
			
		||||
	"xorm.io/builder"
 | 
			
		||||
	"xorm.io/xorm"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -337,8 +338,10 @@ func newlyCreatedIssues(ctx context.Context, repoID int64, fromTime time.Time) *
 | 
			
		||||
func activeIssues(ctx context.Context, repoID int64, fromTime time.Time) *xorm.Session {
 | 
			
		||||
	sess := db.GetEngine(ctx).Where("issue.repo_id = ?", repoID).
 | 
			
		||||
		And("issue.is_pull = ?", false).
 | 
			
		||||
		And("issue.created_unix >= ?", fromTime.Unix()).
 | 
			
		||||
		Or("issue.closed_unix >= ?", fromTime.Unix())
 | 
			
		||||
		And(builder.Or(
 | 
			
		||||
			builder.Gte{"issue.created_unix": fromTime.Unix()},
 | 
			
		||||
			builder.Gte{"issue.closed_unix": fromTime.Unix()},
 | 
			
		||||
		))
 | 
			
		||||
 | 
			
		||||
	return sess
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -13,8 +13,8 @@ import (
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
	"code.gitea.io/gitea/modules/timeutil"
 | 
			
		||||
 | 
			
		||||
	"github.com/keybase/go-crypto/openpgp"
 | 
			
		||||
	"github.com/keybase/go-crypto/openpgp/packet"
 | 
			
		||||
	"github.com/ProtonMail/go-crypto/openpgp"
 | 
			
		||||
	"github.com/ProtonMail/go-crypto/openpgp/packet"
 | 
			
		||||
	"xorm.io/builder"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -141,7 +141,11 @@ func parseGPGKey(ctx context.Context, ownerID int64, e *openpgp.Entity, verified
 | 
			
		||||
	// Parse Subkeys
 | 
			
		||||
	subkeys := make([]*GPGKey, len(e.Subkeys))
 | 
			
		||||
	for i, k := range e.Subkeys {
 | 
			
		||||
		subs, err := parseSubGPGKey(ownerID, pubkey.KeyIdString(), k.PublicKey, expiry)
 | 
			
		||||
		subkeyExpiry := expiry
 | 
			
		||||
		if k.Sig.KeyLifetimeSecs != nil {
 | 
			
		||||
			subkeyExpiry = k.PublicKey.CreationTime.Add(time.Duration(*k.Sig.KeyLifetimeSecs) * time.Second)
 | 
			
		||||
		}
 | 
			
		||||
		subs, err := parseSubGPGKey(ownerID, pubkey.KeyIdString(), k.PublicKey, subkeyExpiry)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, ErrGPGKeyParsing{ParseError: err}
 | 
			
		||||
		}
 | 
			
		||||
@@ -156,7 +160,7 @@ func parseGPGKey(ctx context.Context, ownerID int64, e *openpgp.Entity, verified
 | 
			
		||||
 | 
			
		||||
	emails := make([]*user_model.EmailAddress, 0, len(e.Identities))
 | 
			
		||||
	for _, ident := range e.Identities {
 | 
			
		||||
		if ident.Revocation != nil {
 | 
			
		||||
		if ident.Revoked(time.Now()) {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		email := strings.ToLower(strings.TrimSpace(ident.UserId.Email))
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
 | 
			
		||||
	"github.com/keybase/go-crypto/openpgp"
 | 
			
		||||
	"github.com/ProtonMail/go-crypto/openpgp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
//   __________________  ________   ____  __.
 | 
			
		||||
@@ -83,12 +83,12 @@ func AddGPGKey(ctx context.Context, ownerID int64, content, token, signature str
 | 
			
		||||
	verified := false
 | 
			
		||||
	// Handle provided signature
 | 
			
		||||
	if signature != "" {
 | 
			
		||||
		signer, err := openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token), strings.NewReader(signature))
 | 
			
		||||
		signer, err := openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token), strings.NewReader(signature), nil)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			signer, err = openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token+"\n"), strings.NewReader(signature))
 | 
			
		||||
			signer, err = openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token+"\n"), strings.NewReader(signature), nil)
 | 
			
		||||
		}
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			signer, err = openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token+"\r\n"), strings.NewReader(signature))
 | 
			
		||||
			signer, err = openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token+"\r\n"), strings.NewReader(signature), nil)
 | 
			
		||||
		}
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Error("Unable to validate token signature. Error: %v", err)
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
 | 
			
		||||
	"github.com/keybase/go-crypto/openpgp/packet"
 | 
			
		||||
	"github.com/ProtonMail/go-crypto/openpgp/packet"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
//   __________________  ________   ____  __.
 | 
			
		||||
 
 | 
			
		||||
@@ -13,9 +13,9 @@ import (
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/keybase/go-crypto/openpgp"
 | 
			
		||||
	"github.com/keybase/go-crypto/openpgp/armor"
 | 
			
		||||
	"github.com/keybase/go-crypto/openpgp/packet"
 | 
			
		||||
	"github.com/ProtonMail/go-crypto/openpgp"
 | 
			
		||||
	"github.com/ProtonMail/go-crypto/openpgp/armor"
 | 
			
		||||
	"github.com/ProtonMail/go-crypto/openpgp/packet"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
//   __________________  ________   ____  __.
 | 
			
		||||
@@ -80,7 +80,7 @@ func base64DecPubKey(content string) (*packet.PublicKey, error) {
 | 
			
		||||
	return pkey, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getExpiryTime extract the expire time of primary key based on sig
 | 
			
		||||
// getExpiryTime extract the expiry time of primary key based on sig
 | 
			
		||||
func getExpiryTime(e *openpgp.Entity) time.Time {
 | 
			
		||||
	expiry := time.Time{}
 | 
			
		||||
	// Extract self-sign for expire date based on : https://github.com/golang/crypto/blob/master/openpgp/keys.go#L165
 | 
			
		||||
@@ -88,12 +88,12 @@ func getExpiryTime(e *openpgp.Entity) time.Time {
 | 
			
		||||
	for _, ident := range e.Identities {
 | 
			
		||||
		if selfSig == nil {
 | 
			
		||||
			selfSig = ident.SelfSignature
 | 
			
		||||
		} else if ident.SelfSignature.IsPrimaryId != nil && *ident.SelfSignature.IsPrimaryId {
 | 
			
		||||
		} else if ident.SelfSignature != nil && ident.SelfSignature.IsPrimaryId != nil && *ident.SelfSignature.IsPrimaryId {
 | 
			
		||||
			selfSig = ident.SelfSignature
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if selfSig.KeyLifetimeSecs != nil {
 | 
			
		||||
	if selfSig != nil && selfSig.KeyLifetimeSecs != nil {
 | 
			
		||||
		expiry = e.PrimaryKey.CreationTime.Add(time.Duration(*selfSig.KeyLifetimeSecs) * time.Second)
 | 
			
		||||
	}
 | 
			
		||||
	return expiry
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,8 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/modules/timeutil"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
 | 
			
		||||
	"github.com/keybase/go-crypto/openpgp/packet"
 | 
			
		||||
	"github.com/ProtonMail/go-crypto/openpgp"
 | 
			
		||||
	"github.com/ProtonMail/go-crypto/openpgp/packet"
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -403,3 +404,25 @@ func TestTryGetKeyIDFromSignature(t *testing.T) {
 | 
			
		||||
		IssuerFingerprint: []uint8{0xb, 0x23, 0x24, 0xc7, 0xe6, 0xfe, 0x4f, 0x3a, 0x6, 0x26, 0xc1, 0x21, 0x3, 0x8d, 0x1a, 0x3e, 0xad, 0xdb, 0xea, 0x9c},
 | 
			
		||||
	}))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseGPGKey(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
			
		||||
	assert.NoError(t, db.Insert(db.DefaultContext, &user_model.EmailAddress{UID: 1, Email: "email1@example.com", IsActivated: true}))
 | 
			
		||||
 | 
			
		||||
	// create a key for test email
 | 
			
		||||
	e, err := openpgp.NewEntity("name", "comment", "email1@example.com", nil)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	k, err := parseGPGKey(db.DefaultContext, 1, e, true)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.NotEmpty(t, k.KeyID)
 | 
			
		||||
	assert.NotEmpty(t, k.Emails) // the key is valid, matches the email
 | 
			
		||||
 | 
			
		||||
	// then revoke the key
 | 
			
		||||
	for _, id := range e.Identities {
 | 
			
		||||
		id.Revocations = append(id.Revocations, &packet.Signature{RevocationReason: util.ToPointer(packet.KeyCompromised)})
 | 
			
		||||
	}
 | 
			
		||||
	k, err = parseGPGKey(db.DefaultContext, 1, e, true)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.NotEmpty(t, k.KeyID)
 | 
			
		||||
	assert.Empty(t, k.Emails) // the key is revoked, matches no email
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -64,7 +64,7 @@
 | 
			
		||||
  name: job2
 | 
			
		||||
  attempt: 1
 | 
			
		||||
  job_id: job2
 | 
			
		||||
  needs: [job1]
 | 
			
		||||
  needs: '["job1"]'
 | 
			
		||||
  task_id: 51
 | 
			
		||||
  status: 5
 | 
			
		||||
  started: 1683636528
 | 
			
		||||
 
 | 
			
		||||
@@ -96,3 +96,14 @@
 | 
			
		||||
  num_issues: 0
 | 
			
		||||
  num_closed_issues: 0
 | 
			
		||||
  archived_unix: 0
 | 
			
		||||
 | 
			
		||||
-
 | 
			
		||||
  id: 10
 | 
			
		||||
  repo_id: 3
 | 
			
		||||
  org_id: 0
 | 
			
		||||
  name: repo3label1
 | 
			
		||||
  color: '#112233'
 | 
			
		||||
  exclusive: false
 | 
			
		||||
  num_issues: 0
 | 
			
		||||
  num_closed_issues: 0
 | 
			
		||||
  archived_unix: 0
 | 
			
		||||
 
 | 
			
		||||
@@ -2,23 +2,23 @@
 | 
			
		||||
  id: 1
 | 
			
		||||
  repo_id: 4
 | 
			
		||||
  name_pattern: /v.+/
 | 
			
		||||
  allowlist_user_i_ds: []
 | 
			
		||||
  allowlist_team_i_ds: []
 | 
			
		||||
  allowlist_user_i_ds: "[]"
 | 
			
		||||
  allowlist_team_i_ds: "[]"
 | 
			
		||||
  created_unix: 1715596037
 | 
			
		||||
  updated_unix: 1715596037
 | 
			
		||||
-
 | 
			
		||||
  id: 2
 | 
			
		||||
  repo_id: 1
 | 
			
		||||
  name_pattern: v-*
 | 
			
		||||
  allowlist_user_i_ds: []
 | 
			
		||||
  allowlist_team_i_ds: []
 | 
			
		||||
  allowlist_user_i_ds: "[]"
 | 
			
		||||
  allowlist_team_i_ds: "[]"
 | 
			
		||||
  created_unix: 1715596037
 | 
			
		||||
  updated_unix: 1715596037
 | 
			
		||||
-
 | 
			
		||||
  id: 3
 | 
			
		||||
  repo_id: 1
 | 
			
		||||
  name_pattern: v-1.1
 | 
			
		||||
  allowlist_user_i_ds: [2]
 | 
			
		||||
  allowlist_team_i_ds: []
 | 
			
		||||
  allowlist_user_i_ds: "[2]"
 | 
			
		||||
  allowlist_team_i_ds: "[]"
 | 
			
		||||
  created_unix: 1715596037
 | 
			
		||||
  updated_unix: 1715596037
 | 
			
		||||
 
 | 
			
		||||
@@ -167,6 +167,9 @@ func GetBranch(ctx context.Context, repoID int64, branchName string) (*Branch, e
 | 
			
		||||
			BranchName: branchName,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// FIXME: this design is not right: it doesn't check `branch.IsDeleted`, it doesn't make sense to make callers to check IsDeleted again and again.
 | 
			
		||||
	// It causes inconsistency with `GetBranches` and `git.GetBranch`, and will lead to strange bugs
 | 
			
		||||
	// In the future, there should be 2 functions: `GetBranchExisting` and `GetBranchWithDeleted`
 | 
			
		||||
	return &branch, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -440,6 +443,8 @@ type FindRecentlyPushedNewBranchesOptions struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type RecentlyPushedNewBranch struct {
 | 
			
		||||
	BranchRepo        *repo_model.Repository
 | 
			
		||||
	BranchName        string
 | 
			
		||||
	BranchDisplayName string
 | 
			
		||||
	BranchLink        string
 | 
			
		||||
	BranchCompareURL  string
 | 
			
		||||
@@ -540,7 +545,9 @@ func FindRecentlyPushedNewBranches(ctx context.Context, doer *user_model.User, o
 | 
			
		||||
				branchDisplayName = fmt.Sprintf("%s:%s", branch.Repo.FullName(), branchDisplayName)
 | 
			
		||||
			}
 | 
			
		||||
			newBranches = append(newBranches, &RecentlyPushedNewBranch{
 | 
			
		||||
				BranchRepo:        branch.Repo,
 | 
			
		||||
				BranchDisplayName: branchDisplayName,
 | 
			
		||||
				BranchName:        branch.Name,
 | 
			
		||||
				BranchLink:        fmt.Sprintf("%s/src/branch/%s", branch.Repo.Link(), util.PathEscapeSegments(branch.Name)),
 | 
			
		||||
				BranchCompareURL:  branch.Repo.ComposeBranchCompareURL(opts.BaseRepo, branch.Name),
 | 
			
		||||
				CommitTime:        branch.CommitTime,
 | 
			
		||||
 
 | 
			
		||||
@@ -197,6 +197,20 @@ func (t CommentType) HasMailReplySupport() bool {
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t CommentType) CountedAsConversation() bool {
 | 
			
		||||
	for _, ct := range ConversationCountedCommentType() {
 | 
			
		||||
		if t == ct {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ConversationCountedCommentType returns the comment types that are counted as a conversation
 | 
			
		||||
func ConversationCountedCommentType() []CommentType {
 | 
			
		||||
	return []CommentType{CommentTypeComment, CommentTypeReview}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RoleInRepo presents the user's participation in the repo
 | 
			
		||||
type RoleInRepo string
 | 
			
		||||
 | 
			
		||||
@@ -893,7 +907,7 @@ func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment
 | 
			
		||||
		}
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case CommentTypeComment:
 | 
			
		||||
		if _, err = db.Exec(ctx, "UPDATE `issue` SET num_comments=num_comments+1 WHERE id=?", opts.Issue.ID); err != nil {
 | 
			
		||||
		if err := UpdateIssueNumComments(ctx, opts.Issue.ID); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		fallthrough
 | 
			
		||||
@@ -1182,8 +1196,8 @@ func DeleteComment(ctx context.Context, comment *Comment) error {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if comment.Type == CommentTypeComment {
 | 
			
		||||
		if _, err := e.ID(comment.IssueID).Decr("num_comments").Update(new(Issue)); err != nil {
 | 
			
		||||
	if comment.Type.CountedAsConversation() {
 | 
			
		||||
		if err := UpdateIssueNumComments(ctx, comment.IssueID); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -1300,6 +1314,21 @@ func (c *Comment) HasOriginalAuthor() bool {
 | 
			
		||||
	return c.OriginalAuthor != "" && c.OriginalAuthorID != 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func UpdateIssueNumCommentsBuilder(issueID int64) *builder.Builder {
 | 
			
		||||
	subQuery := builder.Select("COUNT(*)").From("`comment`").Where(
 | 
			
		||||
		builder.Eq{"issue_id": issueID}.And(
 | 
			
		||||
			builder.In("`type`", ConversationCountedCommentType()),
 | 
			
		||||
		))
 | 
			
		||||
 | 
			
		||||
	return builder.Update(builder.Eq{"num_comments": subQuery}).
 | 
			
		||||
		From("`issue`").Where(builder.Eq{"id": issueID})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func UpdateIssueNumComments(ctx context.Context, issueID int64) error {
 | 
			
		||||
	_, err := db.GetEngine(ctx).Exec(UpdateIssueNumCommentsBuilder(issueID))
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// InsertIssueComments inserts many comments of issues.
 | 
			
		||||
func InsertIssueComments(ctx context.Context, comments []*Comment) error {
 | 
			
		||||
	if len(comments) == 0 {
 | 
			
		||||
@@ -1332,8 +1361,7 @@ func InsertIssueComments(ctx context.Context, comments []*Comment) error {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, issueID := range issueIDs {
 | 
			
		||||
		if _, err := db.Exec(ctx, "UPDATE issue set num_comments = (SELECT count(*) FROM comment WHERE issue_id = ? AND `type`=?) WHERE id = ?",
 | 
			
		||||
			issueID, CommentTypeComment, issueID); err != nil {
 | 
			
		||||
		if err := UpdateIssueNumComments(ctx, issueID); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -86,8 +86,10 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu
 | 
			
		||||
			ids = append(ids, comment.ReviewID)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if err := e.In("id", ids).Find(&reviews); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	if len(ids) > 0 {
 | 
			
		||||
		if err := e.In("id", ids).Find(&reviews); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	n := 0
 | 
			
		||||
 
 | 
			
		||||
@@ -97,3 +97,12 @@ func TestMigrate_InsertIssueComments(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	unittest.CheckConsistencyFor(t, &issues_model.Issue{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_UpdateIssueNumComments(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
			
		||||
	issue2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
 | 
			
		||||
 | 
			
		||||
	assert.NoError(t, issues_model.UpdateIssueNumComments(db.DefaultContext, issue2.ID))
 | 
			
		||||
	issue2 = unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
 | 
			
		||||
	assert.EqualValues(t, 1, issue2.NumComments)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,7 @@ import (
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
	"code.gitea.io/gitea/modules/container"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/optional"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	api "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
	"code.gitea.io/gitea/modules/timeutil"
 | 
			
		||||
@@ -531,6 +532,45 @@ func GetIssueByIndex(ctx context.Context, repoID, index int64) (*Issue, error) {
 | 
			
		||||
	return issue, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isPullToCond(isPull optional.Option[bool]) builder.Cond {
 | 
			
		||||
	if isPull.Has() {
 | 
			
		||||
		return builder.Eq{"is_pull": isPull.Value()}
 | 
			
		||||
	}
 | 
			
		||||
	return builder.NewCond()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func FindLatestUpdatedIssues(ctx context.Context, repoID int64, isPull optional.Option[bool], pageSize int) (IssueList, error) {
 | 
			
		||||
	issues := make([]*Issue, 0, pageSize)
 | 
			
		||||
	err := db.GetEngine(ctx).Where("repo_id = ?", repoID).
 | 
			
		||||
		And(isPullToCond(isPull)).
 | 
			
		||||
		OrderBy("updated_unix DESC").
 | 
			
		||||
		Limit(pageSize).
 | 
			
		||||
		Find(&issues)
 | 
			
		||||
	return issues, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func FindIssuesSuggestionByKeyword(ctx context.Context, repoID int64, keyword string, isPull optional.Option[bool], excludedID int64, pageSize int) (IssueList, error) {
 | 
			
		||||
	cond := builder.NewCond()
 | 
			
		||||
	if excludedID > 0 {
 | 
			
		||||
		cond = cond.And(builder.Neq{"`id`": excludedID})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// It seems that GitHub searches both title and content (maybe sorting by the search engine's ranking system?)
 | 
			
		||||
	// The first PR (https://github.com/go-gitea/gitea/pull/32327) uses "search indexer" to search "name(title) +  content"
 | 
			
		||||
	// But it seems that searching "content" (especially LIKE by DB engine) generates worse (unusable) results.
 | 
			
		||||
	// So now (https://github.com/go-gitea/gitea/pull/33538) it only searches "name(title)", leave the improvements to the future.
 | 
			
		||||
	cond = cond.And(db.BuildCaseInsensitiveLike("`name`", keyword))
 | 
			
		||||
 | 
			
		||||
	issues := make([]*Issue, 0, pageSize)
 | 
			
		||||
	err := db.GetEngine(ctx).Where("repo_id = ?", repoID).
 | 
			
		||||
		And(isPullToCond(isPull)).
 | 
			
		||||
		And(cond).
 | 
			
		||||
		OrderBy("updated_unix DESC, `index` DESC").
 | 
			
		||||
		Limit(pageSize).
 | 
			
		||||
		Find(&issues)
 | 
			
		||||
	return issues, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetIssueWithAttrsByIndex returns issue by index in a repository.
 | 
			
		||||
func GetIssueWithAttrsByIndex(ctx context.Context, repoID, index int64) (*Issue, error) {
 | 
			
		||||
	issue, err := GetIssueByIndex(ctx, repoID, index)
 | 
			
		||||
 
 | 
			
		||||
@@ -38,13 +38,30 @@ func (issue *Issue) projectID(ctx context.Context) int64 {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ProjectColumnID return project column id if issue was assigned to one
 | 
			
		||||
func (issue *Issue) ProjectColumnID(ctx context.Context) int64 {
 | 
			
		||||
func (issue *Issue) ProjectColumnID(ctx context.Context) (int64, error) {
 | 
			
		||||
	var ip project_model.ProjectIssue
 | 
			
		||||
	has, err := db.GetEngine(ctx).Where("issue_id=?", issue.ID).Get(&ip)
 | 
			
		||||
	if err != nil || !has {
 | 
			
		||||
		return 0
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0, err
 | 
			
		||||
	} else if !has {
 | 
			
		||||
		return 0, nil
 | 
			
		||||
	}
 | 
			
		||||
	return ip.ProjectColumnID
 | 
			
		||||
	return ip.ProjectColumnID, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func LoadProjectIssueColumnMap(ctx context.Context, projectID, defaultColumnID int64) (map[int64]int64, error) {
 | 
			
		||||
	issues := make([]project_model.ProjectIssue, 0)
 | 
			
		||||
	if err := db.GetEngine(ctx).Where("project_id=?", projectID).Find(&issues); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	result := make(map[int64]int64, len(issues))
 | 
			
		||||
	for _, issue := range issues {
 | 
			
		||||
		if issue.ProjectColumnID == 0 {
 | 
			
		||||
			issue.ProjectColumnID = defaultColumnID
 | 
			
		||||
		}
 | 
			
		||||
		result[issue.IssueID] = issue.ProjectColumnID
 | 
			
		||||
	}
 | 
			
		||||
	return result, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LoadIssuesFromColumn load issues assigned to this column
 | 
			
		||||
@@ -59,11 +76,11 @@ func LoadIssuesFromColumn(ctx context.Context, b *project_model.Column, opts *Is
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if b.Default {
 | 
			
		||||
		issues, err := Issues(ctx, &IssuesOptions{
 | 
			
		||||
			ProjectColumnID: db.NoConditionID,
 | 
			
		||||
			ProjectID:       b.ProjectID,
 | 
			
		||||
			SortType:        "project-column-sorting",
 | 
			
		||||
		})
 | 
			
		||||
		issues, err := Issues(ctx, opts.Copy(func(o *IssuesOptions) {
 | 
			
		||||
			o.ProjectColumnID = db.NoConditionID
 | 
			
		||||
			o.ProjectID = b.ProjectID
 | 
			
		||||
			o.SortType = "project-column-sorting"
 | 
			
		||||
		}))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
@@ -77,19 +94,6 @@ func LoadIssuesFromColumn(ctx context.Context, b *project_model.Column, opts *Is
 | 
			
		||||
	return issueList, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LoadIssuesFromColumnList load issues assigned to the columns
 | 
			
		||||
func LoadIssuesFromColumnList(ctx context.Context, bs project_model.ColumnList, opts *IssuesOptions) (map[int64]IssueList, error) {
 | 
			
		||||
	issuesMap := make(map[int64]IssueList, len(bs))
 | 
			
		||||
	for i := range bs {
 | 
			
		||||
		il, err := LoadIssuesFromColumn(ctx, bs[i], opts)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		issuesMap[bs[i].ID] = il
 | 
			
		||||
	}
 | 
			
		||||
	return issuesMap, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IssueAssignOrRemoveProject changes the project associated with an issue
 | 
			
		||||
// If newProjectID is 0, the issue is removed from the project
 | 
			
		||||
func IssueAssignOrRemoveProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID, newColumnID int64) error {
 | 
			
		||||
@@ -110,7 +114,7 @@ func IssueAssignOrRemoveProject(ctx context.Context, issue *Issue, doer *user_mo
 | 
			
		||||
				return util.NewPermissionDeniedErrorf("issue %d can't be accessed by project %d", issue.ID, newProject.ID)
 | 
			
		||||
			}
 | 
			
		||||
			if newColumnID == 0 {
 | 
			
		||||
				newDefaultColumn, err := newProject.GetDefaultColumn(ctx)
 | 
			
		||||
				newDefaultColumn, err := newProject.MustDefaultColumn(ctx)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
 
 | 
			
		||||
@@ -49,9 +49,9 @@ type IssuesOptions struct { //nolint
 | 
			
		||||
	// prioritize issues from this repo
 | 
			
		||||
	PriorityRepoID int64
 | 
			
		||||
	IsArchived     optional.Option[bool]
 | 
			
		||||
	Org            *organization.Organization // issues permission scope
 | 
			
		||||
	Team           *organization.Team         // issues permission scope
 | 
			
		||||
	User           *user_model.User           // issues permission scope
 | 
			
		||||
	Owner          *user_model.User   // issues permission scope, it could be an organization or a user
 | 
			
		||||
	Team           *organization.Team // issues permission scope
 | 
			
		||||
	Doer           *user_model.User   // issues permission scope
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Copy returns a copy of the options.
 | 
			
		||||
@@ -273,8 +273,12 @@ func applyConditions(sess *xorm.Session, opts *IssuesOptions) {
 | 
			
		||||
 | 
			
		||||
	applyLabelsCondition(sess, opts)
 | 
			
		||||
 | 
			
		||||
	if opts.User != nil {
 | 
			
		||||
		sess.And(issuePullAccessibleRepoCond("issue.repo_id", opts.User.ID, opts.Org, opts.Team, opts.IsPull.Value()))
 | 
			
		||||
	if opts.Owner != nil {
 | 
			
		||||
		sess.And(repo_model.UserOwnedRepoCond(opts.Owner.ID))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if opts.Doer != nil && !opts.Doer.IsAdmin {
 | 
			
		||||
		sess.And(issuePullAccessibleRepoCond("issue.repo_id", opts.Doer.ID, opts.Owner, opts.Team, opts.IsPull.Value()))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -321,20 +325,20 @@ func teamUnitsRepoCond(id string, userID, orgID, teamID int64, units ...unit.Typ
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// issuePullAccessibleRepoCond userID must not be zero, this condition require join repository table
 | 
			
		||||
func issuePullAccessibleRepoCond(repoIDstr string, userID int64, org *organization.Organization, team *organization.Team, isPull bool) builder.Cond {
 | 
			
		||||
func issuePullAccessibleRepoCond(repoIDstr string, userID int64, owner *user_model.User, team *organization.Team, isPull bool) builder.Cond {
 | 
			
		||||
	cond := builder.NewCond()
 | 
			
		||||
	unitType := unit.TypeIssues
 | 
			
		||||
	if isPull {
 | 
			
		||||
		unitType = unit.TypePullRequests
 | 
			
		||||
	}
 | 
			
		||||
	if org != nil {
 | 
			
		||||
	if owner != nil && owner.IsOrganization() {
 | 
			
		||||
		if team != nil {
 | 
			
		||||
			cond = cond.And(teamUnitsRepoCond(repoIDstr, userID, org.ID, team.ID, unitType)) // special team member repos
 | 
			
		||||
			cond = cond.And(teamUnitsRepoCond(repoIDstr, userID, owner.ID, team.ID, unitType)) // special team member repos
 | 
			
		||||
		} else {
 | 
			
		||||
			cond = cond.And(
 | 
			
		||||
				builder.Or(
 | 
			
		||||
					repo_model.UserOrgUnitRepoCond(repoIDstr, userID, org.ID, unitType), // team member repos
 | 
			
		||||
					repo_model.UserOrgPublicUnitRepoCond(userID, org.ID),                // user org public non-member repos, TODO: check repo has issues
 | 
			
		||||
					repo_model.UserOrgUnitRepoCond(repoIDstr, userID, owner.ID, unitType), // team member repos
 | 
			
		||||
					repo_model.UserOrgPublicUnitRepoCond(userID, owner.ID),                // user org public non-member repos, TODO: check repo has issues
 | 
			
		||||
				),
 | 
			
		||||
			)
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -349,6 +349,17 @@ func GetLabelIDsInRepoByNames(ctx context.Context, repoID int64, labelNames []st
 | 
			
		||||
		Find(&labelIDs)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetLabelIDsInOrgByNames returns a list of labelIDs by names in a given org.
 | 
			
		||||
func GetLabelIDsInOrgByNames(ctx context.Context, orgID int64, labelNames []string) ([]int64, error) {
 | 
			
		||||
	labelIDs := make([]int64, 0, len(labelNames))
 | 
			
		||||
	return labelIDs, db.GetEngine(ctx).Table("label").
 | 
			
		||||
		Where("org_id = ?", orgID).
 | 
			
		||||
		In("name", labelNames).
 | 
			
		||||
		Asc("name").
 | 
			
		||||
		Cols("id").
 | 
			
		||||
		Find(&labelIDs)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// BuildLabelNamesIssueIDsCondition returns a builder where get issue ids match label names
 | 
			
		||||
func BuildLabelNamesIssueIDsCondition(labelNames []string) *builder.Builder {
 | 
			
		||||
	return builder.Select("issue_label.issue_id").
 | 
			
		||||
 
 | 
			
		||||
@@ -301,7 +301,7 @@ func (pr *PullRequest) LoadRequestedReviewers(ctx context.Context) error {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	reviews, err := GetReviewsByIssueID(ctx, pr.Issue.ID)
 | 
			
		||||
	reviews, _, err := GetReviewsByIssueID(ctx, pr.Issue.ID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
@@ -320,7 +320,7 @@ func (pr *PullRequest) LoadRequestedReviewers(ctx context.Context) error {
 | 
			
		||||
 | 
			
		||||
// LoadRequestedReviewersTeams loads the requested reviewers teams.
 | 
			
		||||
func (pr *PullRequest) LoadRequestedReviewersTeams(ctx context.Context) error {
 | 
			
		||||
	reviews, err := GetReviewsByIssueID(ctx, pr.Issue.ID)
 | 
			
		||||
	reviews, _, err := GetReviewsByIssueID(ctx, pr.Issue.ID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -639,6 +639,10 @@ func InsertReviews(ctx context.Context, reviews []*Review) error {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := UpdateIssueNumComments(ctx, review.IssueID); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return committer.Commit()
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,8 @@ package issues
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"slices"
 | 
			
		||||
	"sort"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	organization_model "code.gitea.io/gitea/models/organization"
 | 
			
		||||
@@ -153,43 +155,60 @@ func CountReviews(ctx context.Context, opts FindReviewOptions) (int64, error) {
 | 
			
		||||
	return db.GetEngine(ctx).Where(opts.toCond()).Count(&Review{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetReviewersFromOriginalAuthorsByIssueID gets the latest review of each original authors for a pull request
 | 
			
		||||
func GetReviewersFromOriginalAuthorsByIssueID(ctx context.Context, issueID int64) (ReviewList, error) {
 | 
			
		||||
	reviews := make([]*Review, 0, 10)
 | 
			
		||||
 | 
			
		||||
	// Get latest review of each reviewer, sorted in order they were made
 | 
			
		||||
	if err := db.GetEngine(ctx).SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_team_id = 0 AND type in (?, ?, ?) AND original_author_id <> 0 GROUP BY issue_id, original_author_id) ORDER BY review.updated_unix ASC",
 | 
			
		||||
		issueID, ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest).
 | 
			
		||||
		Find(&reviews); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return reviews, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetReviewsByIssueID gets the latest review of each reviewer for a pull request
 | 
			
		||||
func GetReviewsByIssueID(ctx context.Context, issueID int64) (ReviewList, error) {
 | 
			
		||||
// The first returned parameter is the latest review of each individual reviewer or team
 | 
			
		||||
// The second returned parameter is the latest review of each original author which is migrated from other systems
 | 
			
		||||
// The reviews are sorted by updated time
 | 
			
		||||
func GetReviewsByIssueID(ctx context.Context, issueID int64) (latestReviews, migratedOriginalReviews ReviewList, err error) {
 | 
			
		||||
	reviews := make([]*Review, 0, 10)
 | 
			
		||||
 | 
			
		||||
	sess := db.GetEngine(ctx)
 | 
			
		||||
 | 
			
		||||
	// Get latest review of each reviewer, sorted in order they were made
 | 
			
		||||
	if err := sess.SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_team_id = 0 AND type in (?, ?, ?) AND dismissed = ? AND original_author_id = 0 GROUP BY issue_id, reviewer_id) ORDER BY review.updated_unix ASC",
 | 
			
		||||
		issueID, ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest, false).
 | 
			
		||||
		Find(&reviews); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	// Get all reviews for the issue id
 | 
			
		||||
	if err := db.GetEngine(ctx).Where("issue_id=?", issueID).OrderBy("updated_unix ASC").Find(&reviews); err != nil {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// filter them in memory to get the latest review of each reviewer
 | 
			
		||||
	// Since the reviews should not be too many for one issue, less than 100 commonly, it's acceptable to do this in memory
 | 
			
		||||
	// And since there are too less indexes in review table, it will be very slow to filter in the database
 | 
			
		||||
	reviewersMap := make(map[int64][]*Review)         // key is reviewer id
 | 
			
		||||
	originalReviewersMap := make(map[int64][]*Review) // key is original author id
 | 
			
		||||
	reviewTeamsMap := make(map[int64][]*Review)       // key is reviewer team id
 | 
			
		||||
	countedReivewTypes := []ReviewType{ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest}
 | 
			
		||||
	for _, review := range reviews {
 | 
			
		||||
		if review.ReviewerTeamID == 0 && slices.Contains(countedReivewTypes, review.Type) && !review.Dismissed {
 | 
			
		||||
			if review.OriginalAuthorID != 0 {
 | 
			
		||||
				originalReviewersMap[review.OriginalAuthorID] = append(originalReviewersMap[review.OriginalAuthorID], review)
 | 
			
		||||
			} else {
 | 
			
		||||
				reviewersMap[review.ReviewerID] = append(reviewersMap[review.ReviewerID], review)
 | 
			
		||||
			}
 | 
			
		||||
		} else if review.ReviewerTeamID != 0 && review.OriginalAuthorID == 0 {
 | 
			
		||||
			reviewTeamsMap[review.ReviewerTeamID] = append(reviewTeamsMap[review.ReviewerTeamID], review)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	individualReviews := make([]*Review, 0, 10)
 | 
			
		||||
	for _, reviews := range reviewersMap {
 | 
			
		||||
		individualReviews = append(individualReviews, reviews[len(reviews)-1])
 | 
			
		||||
	}
 | 
			
		||||
	sort.Slice(individualReviews, func(i, j int) bool {
 | 
			
		||||
		return individualReviews[i].UpdatedUnix < individualReviews[j].UpdatedUnix
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	originalReviews := make([]*Review, 0, 10)
 | 
			
		||||
	for _, reviews := range originalReviewersMap {
 | 
			
		||||
		originalReviews = append(originalReviews, reviews[len(reviews)-1])
 | 
			
		||||
	}
 | 
			
		||||
	sort.Slice(originalReviews, func(i, j int) bool {
 | 
			
		||||
		return originalReviews[i].UpdatedUnix < originalReviews[j].UpdatedUnix
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	teamReviewRequests := make([]*Review, 0, 5)
 | 
			
		||||
	if err := sess.SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_team_id <> 0 AND original_author_id = 0 GROUP BY issue_id, reviewer_team_id) ORDER BY review.updated_unix ASC",
 | 
			
		||||
		issueID).
 | 
			
		||||
		Find(&teamReviewRequests); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	for _, reviews := range reviewTeamsMap {
 | 
			
		||||
		teamReviewRequests = append(teamReviewRequests, reviews[len(reviews)-1])
 | 
			
		||||
	}
 | 
			
		||||
	sort.Slice(teamReviewRequests, func(i, j int) bool {
 | 
			
		||||
		return teamReviewRequests[i].UpdatedUnix < teamReviewRequests[j].UpdatedUnix
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	if len(teamReviewRequests) > 0 {
 | 
			
		||||
		reviews = append(reviews, teamReviewRequests...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return reviews, nil
 | 
			
		||||
	return append(individualReviews, teamReviewRequests...), originalReviews, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -162,8 +162,9 @@ func TestGetReviewersByIssueID(t *testing.T) {
 | 
			
		||||
		},
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	allReviews, err := issues_model.GetReviewsByIssueID(db.DefaultContext, issue.ID)
 | 
			
		||||
	allReviews, migratedReviews, err := issues_model.GetReviewsByIssueID(db.DefaultContext, issue.ID)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Empty(t, migratedReviews)
 | 
			
		||||
	for _, review := range allReviews {
 | 
			
		||||
		assert.NoError(t, review.LoadReviewer(db.DefaultContext))
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -48,7 +48,7 @@ func (s Stopwatch) Seconds() int64 {
 | 
			
		||||
 | 
			
		||||
// Duration returns a human-readable duration string based on local server time
 | 
			
		||||
func (s Stopwatch) Duration() string {
 | 
			
		||||
	return util.SecToTime(s.Seconds())
 | 
			
		||||
	return util.SecToHours(s.Seconds())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getStopwatch(ctx context.Context, userID, issueID int64) (sw *Stopwatch, exists bool, err error) {
 | 
			
		||||
@@ -201,7 +201,7 @@ func FinishIssueStopwatch(ctx context.Context, user *user_model.User, issue *Iss
 | 
			
		||||
		Doer:    user,
 | 
			
		||||
		Issue:   issue,
 | 
			
		||||
		Repo:    issue.Repo,
 | 
			
		||||
		Content: util.SecToTime(timediff),
 | 
			
		||||
		Content: util.SecToHours(timediff),
 | 
			
		||||
		Type:    CommentTypeStopTracking,
 | 
			
		||||
		TimeID:  tt.ID,
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
 
 | 
			
		||||
@@ -248,6 +248,18 @@ func GetPackageByID(ctx context.Context, packageID int64) (*Package, error) {
 | 
			
		||||
	return p, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdatePackageNameByID updates the package's name, it is only for internal usage, for example: rename some legacy packages
 | 
			
		||||
func UpdatePackageNameByID(ctx context.Context, ownerID int64, packageType Type, packageID int64, name string) error {
 | 
			
		||||
	var cond builder.Cond = builder.Eq{
 | 
			
		||||
		"package.id":          packageID,
 | 
			
		||||
		"package.owner_id":    ownerID,
 | 
			
		||||
		"package.type":        packageType,
 | 
			
		||||
		"package.is_internal": false,
 | 
			
		||||
	}
 | 
			
		||||
	_, err := db.GetEngine(ctx).Where(cond).Update(&Package{Name: name, LowerName: strings.ToLower(name)})
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetPackageByName gets a package by name
 | 
			
		||||
func GetPackageByName(ctx context.Context, ownerID int64, packageType Type, name string) (*Package, error) {
 | 
			
		||||
	var cond builder.Cond = builder.Eq{
 | 
			
		||||
 
 | 
			
		||||
@@ -48,6 +48,8 @@ type Column struct {
 | 
			
		||||
	ProjectID int64 `xorm:"INDEX NOT NULL"`
 | 
			
		||||
	CreatorID int64 `xorm:"NOT NULL"`
 | 
			
		||||
 | 
			
		||||
	NumIssues int64 `xorm:"-"`
 | 
			
		||||
 | 
			
		||||
	CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
 | 
			
		||||
	UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
 | 
			
		||||
}
 | 
			
		||||
@@ -57,20 +59,6 @@ func (Column) TableName() string {
 | 
			
		||||
	return "project_board" // TODO: the legacy table name should be project_column
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NumIssues return counter of all issues assigned to the column
 | 
			
		||||
func (c *Column) NumIssues(ctx context.Context) int {
 | 
			
		||||
	total, err := db.GetEngine(ctx).Table("project_issue").
 | 
			
		||||
		Where("project_id=?", c.ProjectID).
 | 
			
		||||
		And("project_board_id=?", c.ID).
 | 
			
		||||
		GroupBy("issue_id").
 | 
			
		||||
		Cols("issue_id").
 | 
			
		||||
		Count()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
	return int(total)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Column) GetIssues(ctx context.Context) ([]*ProjectIssue, error) {
 | 
			
		||||
	issues := make([]*ProjectIssue, 0, 5)
 | 
			
		||||
	if err := db.GetEngine(ctx).Where("project_id=?", c.ProjectID).
 | 
			
		||||
@@ -192,7 +180,7 @@ func deleteColumnByID(ctx context.Context, columnID int64) error {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defaultColumn, err := project.GetDefaultColumn(ctx)
 | 
			
		||||
	defaultColumn, err := project.MustDefaultColumn(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
@@ -257,8 +245,8 @@ func (p *Project) GetColumns(ctx context.Context) (ColumnList, error) {
 | 
			
		||||
	return columns, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetDefaultColumn return default column and ensure only one exists
 | 
			
		||||
func (p *Project) GetDefaultColumn(ctx context.Context) (*Column, error) {
 | 
			
		||||
// getDefaultColumn return default column and ensure only one exists
 | 
			
		||||
func (p *Project) getDefaultColumn(ctx context.Context) (*Column, error) {
 | 
			
		||||
	var column Column
 | 
			
		||||
	has, err := db.GetEngine(ctx).
 | 
			
		||||
		Where("project_id=? AND `default` = ?", p.ID, true).
 | 
			
		||||
@@ -270,6 +258,33 @@ func (p *Project) GetDefaultColumn(ctx context.Context) (*Column, error) {
 | 
			
		||||
	if has {
 | 
			
		||||
		return &column, nil
 | 
			
		||||
	}
 | 
			
		||||
	return nil, ErrProjectColumnNotExist{ColumnID: 0}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MustDefaultColumn returns the default column for a project.
 | 
			
		||||
// If one exists, it is returned
 | 
			
		||||
// If none exists, the first column will be elevated to the default column of this project
 | 
			
		||||
func (p *Project) MustDefaultColumn(ctx context.Context) (*Column, error) {
 | 
			
		||||
	c, err := p.getDefaultColumn(ctx)
 | 
			
		||||
	if err != nil && !IsErrProjectColumnNotExist(err) {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if c != nil {
 | 
			
		||||
		return c, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var column Column
 | 
			
		||||
	has, err := db.GetEngine(ctx).Where("project_id=?", p.ID).OrderBy("sorting, id").Get(&column)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if has {
 | 
			
		||||
		column.Default = true
 | 
			
		||||
		if _, err := db.GetEngine(ctx).ID(column.ID).Cols("`default`").Update(&column); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		return &column, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// create a default column if none is found
 | 
			
		||||
	column = Column{
 | 
			
		||||
 
 | 
			
		||||
@@ -20,19 +20,19 @@ func TestGetDefaultColumn(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	// check if default column was added
 | 
			
		||||
	column, err := projectWithoutDefault.GetDefaultColumn(db.DefaultContext)
 | 
			
		||||
	column, err := projectWithoutDefault.MustDefaultColumn(db.DefaultContext)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, int64(5), column.ProjectID)
 | 
			
		||||
	assert.Equal(t, "Uncategorized", column.Title)
 | 
			
		||||
	assert.Equal(t, "Done", column.Title)
 | 
			
		||||
 | 
			
		||||
	projectWithMultipleDefaults, err := GetProjectByID(db.DefaultContext, 6)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	// check if multiple defaults were removed
 | 
			
		||||
	column, err = projectWithMultipleDefaults.GetDefaultColumn(db.DefaultContext)
 | 
			
		||||
	column, err = projectWithMultipleDefaults.MustDefaultColumn(db.DefaultContext)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, int64(6), column.ProjectID)
 | 
			
		||||
	assert.Equal(t, int64(9), column.ID)
 | 
			
		||||
	assert.Equal(t, int64(9), column.ID) // there are 2 default columns in the test data, use the latest one
 | 
			
		||||
 | 
			
		||||
	// set 8 as default column
 | 
			
		||||
	assert.NoError(t, SetDefaultColumn(db.DefaultContext, column.ProjectID, 8))
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,6 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -34,48 +33,6 @@ func deleteProjectIssuesByProjectID(ctx context.Context, projectID int64) error
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NumIssues return counter of all issues assigned to a project
 | 
			
		||||
func (p *Project) NumIssues(ctx context.Context) int {
 | 
			
		||||
	c, err := db.GetEngine(ctx).Table("project_issue").
 | 
			
		||||
		Where("project_id=?", p.ID).
 | 
			
		||||
		GroupBy("issue_id").
 | 
			
		||||
		Cols("issue_id").
 | 
			
		||||
		Count()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error("NumIssues: %v", err)
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
	return int(c)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NumClosedIssues return counter of closed issues assigned to a project
 | 
			
		||||
func (p *Project) NumClosedIssues(ctx context.Context) int {
 | 
			
		||||
	c, err := db.GetEngine(ctx).Table("project_issue").
 | 
			
		||||
		Join("INNER", "issue", "project_issue.issue_id=issue.id").
 | 
			
		||||
		Where("project_issue.project_id=? AND issue.is_closed=?", p.ID, true).
 | 
			
		||||
		Cols("issue_id").
 | 
			
		||||
		Count()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error("NumClosedIssues: %v", err)
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
	return int(c)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NumOpenIssues return counter of open issues assigned to a project
 | 
			
		||||
func (p *Project) NumOpenIssues(ctx context.Context) int {
 | 
			
		||||
	c, err := db.GetEngine(ctx).Table("project_issue").
 | 
			
		||||
		Join("INNER", "issue", "project_issue.issue_id=issue.id").
 | 
			
		||||
		Where("project_issue.project_id=? AND issue.is_closed=?", p.ID, false).
 | 
			
		||||
		Cols("issue_id").
 | 
			
		||||
		Count()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error("NumOpenIssues: %v", err)
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
	return int(c)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Column) moveIssuesToAnotherColumn(ctx context.Context, newColumn *Column) error {
 | 
			
		||||
	if c.ProjectID != newColumn.ProjectID {
 | 
			
		||||
		return fmt.Errorf("columns have to be in the same project")
 | 
			
		||||
 
 | 
			
		||||
@@ -97,6 +97,9 @@ type Project struct {
 | 
			
		||||
	Type         Type
 | 
			
		||||
 | 
			
		||||
	RenderedContent template.HTML `xorm:"-"`
 | 
			
		||||
	NumOpenIssues   int64         `xorm:"-"`
 | 
			
		||||
	NumClosedIssues int64         `xorm:"-"`
 | 
			
		||||
	NumIssues       int64         `xorm:"-"`
 | 
			
		||||
 | 
			
		||||
	CreatedUnix    timeutil.TimeStamp `xorm:"INDEX created"`
 | 
			
		||||
	UpdatedUnix    timeutil.TimeStamp `xorm:"INDEX updated"`
 | 
			
		||||
@@ -126,6 +129,14 @@ func (p *Project) LoadRepo(ctx context.Context) (err error) {
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ProjectLinkForOrg(org *user_model.User, projectID int64) string { //nolint
 | 
			
		||||
	return fmt.Sprintf("%s/-/projects/%d", org.HomeLink(), projectID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ProjectLinkForRepo(repo *repo_model.Repository, projectID int64) string { //nolint
 | 
			
		||||
	return fmt.Sprintf("%s/projects/%d", repo.Link(), projectID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Link returns the project's relative URL.
 | 
			
		||||
func (p *Project) Link(ctx context.Context) string {
 | 
			
		||||
	if p.OwnerID > 0 {
 | 
			
		||||
@@ -134,7 +145,7 @@ func (p *Project) Link(ctx context.Context) string {
 | 
			
		||||
			log.Error("LoadOwner: %v", err)
 | 
			
		||||
			return ""
 | 
			
		||||
		}
 | 
			
		||||
		return fmt.Sprintf("%s/-/projects/%d", p.Owner.HomeLink(), p.ID)
 | 
			
		||||
		return ProjectLinkForOrg(p.Owner, p.ID)
 | 
			
		||||
	}
 | 
			
		||||
	if p.RepoID > 0 {
 | 
			
		||||
		err := p.LoadRepo(ctx)
 | 
			
		||||
@@ -142,7 +153,7 @@ func (p *Project) Link(ctx context.Context) string {
 | 
			
		||||
			log.Error("LoadRepo: %v", err)
 | 
			
		||||
			return ""
 | 
			
		||||
		}
 | 
			
		||||
		return fmt.Sprintf("%s/projects/%d", p.Repo.Link(), p.ID)
 | 
			
		||||
		return ProjectLinkForRepo(p.Repo, p.ID)
 | 
			
		||||
	}
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,8 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/models/unit"
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
 | 
			
		||||
	"xorm.io/builder"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Init initialize model
 | 
			
		||||
@@ -27,7 +29,7 @@ func Init(ctx context.Context) error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type repoChecker struct {
 | 
			
		||||
	querySQL   func(ctx context.Context) ([]map[string][]byte, error)
 | 
			
		||||
	querySQL   func(ctx context.Context) ([]int64, error)
 | 
			
		||||
	correctSQL func(ctx context.Context, id int64) error
 | 
			
		||||
	desc       string
 | 
			
		||||
}
 | 
			
		||||
@@ -38,8 +40,7 @@ func repoStatsCheck(ctx context.Context, checker *repoChecker) {
 | 
			
		||||
		log.Error("Select %s: %v", checker.desc, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	for _, result := range results {
 | 
			
		||||
		id, _ := strconv.ParseInt(string(result["id"]), 10, 64)
 | 
			
		||||
	for _, id := range results {
 | 
			
		||||
		select {
 | 
			
		||||
		case <-ctx.Done():
 | 
			
		||||
			log.Warn("CheckRepoStats: Cancelled before checking %s for with id=%d", checker.desc, id)
 | 
			
		||||
@@ -54,21 +55,23 @@ func repoStatsCheck(ctx context.Context, checker *repoChecker) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func StatsCorrectSQL(ctx context.Context, sql string, id int64) error {
 | 
			
		||||
	_, err := db.GetEngine(ctx).Exec(sql, id, id)
 | 
			
		||||
func StatsCorrectSQL(ctx context.Context, sql any, ids ...any) error {
 | 
			
		||||
	args := []any{sql}
 | 
			
		||||
	args = append(args, ids...)
 | 
			
		||||
	_, err := db.GetEngine(ctx).Exec(args...)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func repoStatsCorrectNumWatches(ctx context.Context, id int64) error {
 | 
			
		||||
	return StatsCorrectSQL(ctx, "UPDATE `repository` SET num_watches=(SELECT COUNT(*) FROM `watch` WHERE repo_id=? AND mode<>2) WHERE id=?", id)
 | 
			
		||||
	return StatsCorrectSQL(ctx, "UPDATE `repository` SET num_watches=(SELECT COUNT(*) FROM `watch` WHERE repo_id=? AND mode<>2) WHERE id=?", id, id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func repoStatsCorrectNumStars(ctx context.Context, id int64) error {
 | 
			
		||||
	return StatsCorrectSQL(ctx, "UPDATE `repository` SET num_stars=(SELECT COUNT(*) FROM `star` WHERE repo_id=?) WHERE id=?", id)
 | 
			
		||||
	return StatsCorrectSQL(ctx, "UPDATE `repository` SET num_stars=(SELECT COUNT(*) FROM `star` WHERE repo_id=?) WHERE id=?", id, id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func labelStatsCorrectNumIssues(ctx context.Context, id int64) error {
 | 
			
		||||
	return StatsCorrectSQL(ctx, "UPDATE `label` SET num_issues=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=?) WHERE id=?", id)
 | 
			
		||||
	return StatsCorrectSQL(ctx, "UPDATE `label` SET num_issues=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=?) WHERE id=?", id, id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func labelStatsCorrectNumIssuesRepo(ctx context.Context, id int64) error {
 | 
			
		||||
@@ -105,11 +108,11 @@ func milestoneStatsCorrectNumIssuesRepo(ctx context.Context, id int64) error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func userStatsCorrectNumRepos(ctx context.Context, id int64) error {
 | 
			
		||||
	return StatsCorrectSQL(ctx, "UPDATE `user` SET num_repos=(SELECT COUNT(*) FROM `repository` WHERE owner_id=?) WHERE id=?", id)
 | 
			
		||||
	return StatsCorrectSQL(ctx, "UPDATE `user` SET num_repos=(SELECT COUNT(*) FROM `repository` WHERE owner_id=?) WHERE id=?", id, id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func repoStatsCorrectIssueNumComments(ctx context.Context, id int64) error {
 | 
			
		||||
	return StatsCorrectSQL(ctx, "UPDATE `issue` SET num_comments=(SELECT COUNT(*) FROM `comment` WHERE issue_id=? AND type=0) WHERE id=?", id)
 | 
			
		||||
	return StatsCorrectSQL(ctx, issues_model.UpdateIssueNumCommentsBuilder(id))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func repoStatsCorrectNumIssues(ctx context.Context, id int64) error {
 | 
			
		||||
@@ -128,9 +131,12 @@ func repoStatsCorrectNumClosedPulls(ctx context.Context, id int64) error {
 | 
			
		||||
	return repo_model.UpdateRepoIssueNumbers(ctx, id, true, true)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func statsQuery(args ...any) func(context.Context) ([]map[string][]byte, error) {
 | 
			
		||||
	return func(ctx context.Context) ([]map[string][]byte, error) {
 | 
			
		||||
		return db.GetEngine(ctx).Query(args...)
 | 
			
		||||
// statsQuery returns a function that queries the database for a list of IDs
 | 
			
		||||
// sql could be a string or a *builder.Builder
 | 
			
		||||
func statsQuery(sql any, args ...any) func(context.Context) ([]int64, error) {
 | 
			
		||||
	return func(ctx context.Context) ([]int64, error) {
 | 
			
		||||
		var ids []int64
 | 
			
		||||
		return ids, db.GetEngine(ctx).SQL(sql, args...).Find(&ids)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -201,7 +207,16 @@ func CheckRepoStats(ctx context.Context) error {
 | 
			
		||||
		},
 | 
			
		||||
		// Issue.NumComments
 | 
			
		||||
		{
 | 
			
		||||
			statsQuery("SELECT `issue`.id FROM `issue` WHERE `issue`.num_comments!=(SELECT COUNT(*) FROM `comment` WHERE issue_id=`issue`.id AND type=0)"),
 | 
			
		||||
			statsQuery(builder.Select("`issue`.id").From("`issue`").Where(
 | 
			
		||||
				builder.Neq{
 | 
			
		||||
					"`issue`.num_comments": builder.Select("COUNT(*)").From("`comment`").Where(
 | 
			
		||||
						builder.Expr("issue_id = `issue`.id").And(
 | 
			
		||||
							builder.In("type", issues_model.ConversationCountedCommentType()),
 | 
			
		||||
						),
 | 
			
		||||
					),
 | 
			
		||||
				},
 | 
			
		||||
			),
 | 
			
		||||
			),
 | 
			
		||||
			repoStatsCorrectIssueNumComments,
 | 
			
		||||
			"issue count 'num_comments'",
 | 
			
		||||
		},
 | 
			
		||||
 
 | 
			
		||||
@@ -54,6 +54,7 @@ func UpdateRepoLicenses(ctx context.Context, repo *Repository, commitID string,
 | 
			
		||||
		for _, o := range oldLicenses {
 | 
			
		||||
			// Update already existing license
 | 
			
		||||
			if o.License == license {
 | 
			
		||||
				o.CommitID = commitID
 | 
			
		||||
				if _, err := db.GetEngine(ctx).ID(o.ID).Cols("`commit_id`").Update(o); err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
 
 | 
			
		||||
@@ -276,6 +276,8 @@ func (repo *Repository) IsBroken() bool {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MarkAsBrokenEmpty marks the repo as broken and empty
 | 
			
		||||
// FIXME: the status "broken" and "is_empty" were abused,
 | 
			
		||||
// The code always set them together, no way to distinguish whether a repo is really "empty" or "broken"
 | 
			
		||||
func (repo *Repository) MarkAsBrokenEmpty() {
 | 
			
		||||
	repo.Status = RepositoryBroken
 | 
			
		||||
	repo.IsEmpty = true
 | 
			
		||||
 
 | 
			
		||||
@@ -46,6 +46,12 @@ func UpdateRepositoryCols(ctx context.Context, repo *Repository, cols ...string)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateRepositoryColsNoAutoTime updates repository's columns and but applies time change automatically
 | 
			
		||||
func UpdateRepositoryColsNoAutoTime(ctx context.Context, repo *Repository, cols ...string) error {
 | 
			
		||||
	_, err := db.GetEngine(ctx).ID(repo.ID).Cols(cols...).NoAutoTime().Update(repo)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ErrReachLimitOfRepo represents a "ReachLimitOfRepo" kind of error.
 | 
			
		||||
type ErrReachLimitOfRepo struct {
 | 
			
		||||
	Limit int
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@ import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	issues_model "code.gitea.io/gitea/models/issues"
 | 
			
		||||
	"code.gitea.io/gitea/models/unittest"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
@@ -22,3 +23,16 @@ func TestDoctorUserStarNum(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	assert.NoError(t, DoctorUserStarNum(db.DefaultContext))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_repoStatsCorrectIssueNumComments(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
			
		||||
 | 
			
		||||
	issue2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
 | 
			
		||||
	assert.NotNil(t, issue2)
 | 
			
		||||
	assert.EqualValues(t, 0, issue2.NumComments) // the fixture data is wrong, but we don't fix it here
 | 
			
		||||
 | 
			
		||||
	assert.NoError(t, repoStatsCorrectIssueNumComments(db.DefaultContext, 2))
 | 
			
		||||
	// reload the issue
 | 
			
		||||
	issue2 = unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
 | 
			
		||||
	assert.EqualValues(t, 1, issue2.NumComments)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,19 +6,21 @@ package unittest
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	"code.gitea.io/gitea/modules/auth/password/hash"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
 | 
			
		||||
	"github.com/go-testfixtures/testfixtures/v3"
 | 
			
		||||
	"xorm.io/xorm"
 | 
			
		||||
	"xorm.io/xorm/schemas"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var fixturesLoader *testfixtures.Loader
 | 
			
		||||
type FixturesLoader interface {
 | 
			
		||||
	Load() error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var fixturesLoader FixturesLoader
 | 
			
		||||
 | 
			
		||||
// GetXORMEngine gets the XORM engine
 | 
			
		||||
func GetXORMEngine(engine ...*xorm.Engine) (x *xorm.Engine) {
 | 
			
		||||
@@ -31,38 +33,7 @@ func GetXORMEngine(engine ...*xorm.Engine) (x *xorm.Engine) {
 | 
			
		||||
// InitFixtures initialize test fixtures for a test database
 | 
			
		||||
func InitFixtures(opts FixturesOptions, engine ...*xorm.Engine) (err error) {
 | 
			
		||||
	e := GetXORMEngine(engine...)
 | 
			
		||||
	var fixtureOptionFiles func(*testfixtures.Loader) error
 | 
			
		||||
	if opts.Dir != "" {
 | 
			
		||||
		fixtureOptionFiles = testfixtures.Directory(opts.Dir)
 | 
			
		||||
	} else {
 | 
			
		||||
		fixtureOptionFiles = testfixtures.Files(opts.Files...)
 | 
			
		||||
	}
 | 
			
		||||
	dialect := "unknown"
 | 
			
		||||
	switch e.Dialect().URI().DBType {
 | 
			
		||||
	case schemas.POSTGRES:
 | 
			
		||||
		dialect = "postgres"
 | 
			
		||||
	case schemas.MYSQL:
 | 
			
		||||
		dialect = "mysql"
 | 
			
		||||
	case schemas.MSSQL:
 | 
			
		||||
		dialect = "mssql"
 | 
			
		||||
	case schemas.SQLITE:
 | 
			
		||||
		dialect = "sqlite3"
 | 
			
		||||
	default:
 | 
			
		||||
		fmt.Println("Unsupported RDBMS for integration tests")
 | 
			
		||||
		os.Exit(1)
 | 
			
		||||
	}
 | 
			
		||||
	loaderOptions := []func(loader *testfixtures.Loader) error{
 | 
			
		||||
		testfixtures.Database(e.DB().DB),
 | 
			
		||||
		testfixtures.Dialect(dialect),
 | 
			
		||||
		testfixtures.DangerousSkipTestDatabaseCheck(),
 | 
			
		||||
		fixtureOptionFiles,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if e.Dialect().URI().DBType == schemas.POSTGRES {
 | 
			
		||||
		loaderOptions = append(loaderOptions, testfixtures.SkipResetSequences())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fixturesLoader, err = testfixtures.New(loaderOptions...)
 | 
			
		||||
	fixturesLoader, err = NewFixturesLoader(e, opts)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										201
									
								
								models/unittest/fixtures_loader.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								models/unittest/fixtures_loader.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,201 @@
 | 
			
		||||
// Copyright 2024 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package unittest
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"database/sql"
 | 
			
		||||
	"encoding/hex"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"slices"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"gopkg.in/yaml.v3"
 | 
			
		||||
	"xorm.io/xorm"
 | 
			
		||||
	"xorm.io/xorm/schemas"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type fixtureItem struct {
 | 
			
		||||
	tableName       string
 | 
			
		||||
	tableNameQuoted string
 | 
			
		||||
	sqlInserts      []string
 | 
			
		||||
	sqlInsertArgs   [][]any
 | 
			
		||||
 | 
			
		||||
	mssqlHasIdentityColumn bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type fixturesLoaderInternal struct {
 | 
			
		||||
	db               *sql.DB
 | 
			
		||||
	dbType           schemas.DBType
 | 
			
		||||
	files            []string
 | 
			
		||||
	fixtures         map[string]*fixtureItem
 | 
			
		||||
	quoteObject      func(string) string
 | 
			
		||||
	paramPlaceholder func(idx int) string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *fixturesLoaderInternal) mssqlTableHasIdentityColumn(db *sql.DB, tableName string) (bool, error) {
 | 
			
		||||
	row := db.QueryRow(`SELECT COUNT(*) FROM sys.identity_columns WHERE OBJECT_ID = OBJECT_ID(?)`, tableName)
 | 
			
		||||
	var count int
 | 
			
		||||
	if err := row.Scan(&count); err != nil {
 | 
			
		||||
		return false, err
 | 
			
		||||
	}
 | 
			
		||||
	return count > 0, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *fixturesLoaderInternal) preprocessFixtureRow(row []map[string]any) (err error) {
 | 
			
		||||
	for _, m := range row {
 | 
			
		||||
		for k, v := range m {
 | 
			
		||||
			if s, ok := v.(string); ok {
 | 
			
		||||
				if strings.HasPrefix(s, "0x") {
 | 
			
		||||
					if m[k], err = hex.DecodeString(s[2:]); err != nil {
 | 
			
		||||
						return err
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *fixturesLoaderInternal) prepareFixtureItem(file string) (_ *fixtureItem, err error) {
 | 
			
		||||
	fixture := &fixtureItem{}
 | 
			
		||||
	fixture.tableName, _, _ = strings.Cut(filepath.Base(file), ".")
 | 
			
		||||
	fixture.tableNameQuoted = f.quoteObject(fixture.tableName)
 | 
			
		||||
 | 
			
		||||
	if f.dbType == schemas.MSSQL {
 | 
			
		||||
		fixture.mssqlHasIdentityColumn, err = f.mssqlTableHasIdentityColumn(f.db, fixture.tableName)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	data, err := os.ReadFile(file)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to read file %q: %w", file, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var rows []map[string]any
 | 
			
		||||
	if err = yaml.Unmarshal(data, &rows); err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to unmarshal yaml data from %q: %w", file, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err = f.preprocessFixtureRow(rows); err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to preprocess fixture rows from %q: %w", file, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var sqlBuf []byte
 | 
			
		||||
	var sqlArguments []any
 | 
			
		||||
	for _, row := range rows {
 | 
			
		||||
		sqlBuf = append(sqlBuf, fmt.Sprintf("INSERT INTO %s (", fixture.tableNameQuoted)...)
 | 
			
		||||
		for k, v := range row {
 | 
			
		||||
			sqlBuf = append(sqlBuf, f.quoteObject(k)...)
 | 
			
		||||
			sqlBuf = append(sqlBuf, ","...)
 | 
			
		||||
			sqlArguments = append(sqlArguments, v)
 | 
			
		||||
		}
 | 
			
		||||
		sqlBuf = sqlBuf[:len(sqlBuf)-1]
 | 
			
		||||
		sqlBuf = append(sqlBuf, ") VALUES ("...)
 | 
			
		||||
		paramIdx := 1
 | 
			
		||||
		for range row {
 | 
			
		||||
			sqlBuf = append(sqlBuf, f.paramPlaceholder(paramIdx)...)
 | 
			
		||||
			sqlBuf = append(sqlBuf, ',')
 | 
			
		||||
			paramIdx++
 | 
			
		||||
		}
 | 
			
		||||
		sqlBuf[len(sqlBuf)-1] = ')'
 | 
			
		||||
		fixture.sqlInserts = append(fixture.sqlInserts, string(sqlBuf))
 | 
			
		||||
		fixture.sqlInsertArgs = append(fixture.sqlInsertArgs, slices.Clone(sqlArguments))
 | 
			
		||||
		sqlBuf = sqlBuf[:0]
 | 
			
		||||
		sqlArguments = sqlArguments[:0]
 | 
			
		||||
	}
 | 
			
		||||
	return fixture, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *fixturesLoaderInternal) loadFixtures(tx *sql.Tx, file string) (err error) {
 | 
			
		||||
	fixture := f.fixtures[file]
 | 
			
		||||
	if fixture == nil {
 | 
			
		||||
		if fixture, err = f.prepareFixtureItem(file); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		f.fixtures[file] = fixture
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err = tx.Exec(fmt.Sprintf("DELETE FROM %s", fixture.tableNameQuoted)) // sqlite3 doesn't support truncate
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if fixture.mssqlHasIdentityColumn {
 | 
			
		||||
		_, err = tx.Exec(fmt.Sprintf("SET IDENTITY_INSERT %s ON", fixture.tableNameQuoted))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		defer func() { _, err = tx.Exec(fmt.Sprintf("SET IDENTITY_INSERT %s OFF", fixture.tableNameQuoted)) }()
 | 
			
		||||
	}
 | 
			
		||||
	for i := range fixture.sqlInserts {
 | 
			
		||||
		_, err = tx.Exec(fixture.sqlInserts[i], fixture.sqlInsertArgs[i]...)
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *fixturesLoaderInternal) Load() error {
 | 
			
		||||
	tx, err := f.db.Begin()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer func() { _ = tx.Rollback() }()
 | 
			
		||||
 | 
			
		||||
	for _, file := range f.files {
 | 
			
		||||
		if err := f.loadFixtures(tx, file); err != nil {
 | 
			
		||||
			return fmt.Errorf("failed to load fixtures from %s: %w", file, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return tx.Commit()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func FixturesFileFullPaths(dir string, files []string) ([]string, error) {
 | 
			
		||||
	if files != nil && len(files) == 0 {
 | 
			
		||||
		return nil, nil // load nothing
 | 
			
		||||
	}
 | 
			
		||||
	files = slices.Clone(files)
 | 
			
		||||
	if len(files) == 0 {
 | 
			
		||||
		entries, err := os.ReadDir(dir)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		for _, e := range entries {
 | 
			
		||||
			files = append(files, e.Name())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for i, file := range files {
 | 
			
		||||
		if !filepath.IsAbs(file) {
 | 
			
		||||
			files[i] = filepath.Join(dir, file)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return files, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewFixturesLoader(x *xorm.Engine, opts FixturesOptions) (FixturesLoader, error) {
 | 
			
		||||
	files, err := FixturesFileFullPaths(opts.Dir, opts.Files)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to get fixtures files: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
	f := &fixturesLoaderInternal{db: x.DB().DB, dbType: x.Dialect().URI().DBType, files: files, fixtures: map[string]*fixtureItem{}}
 | 
			
		||||
	switch f.dbType {
 | 
			
		||||
	case schemas.SQLITE:
 | 
			
		||||
		f.quoteObject = func(s string) string { return fmt.Sprintf(`"%s"`, s) }
 | 
			
		||||
		f.paramPlaceholder = func(idx int) string { return "?" }
 | 
			
		||||
	case schemas.POSTGRES:
 | 
			
		||||
		f.quoteObject = func(s string) string { return fmt.Sprintf(`"%s"`, s) }
 | 
			
		||||
		f.paramPlaceholder = func(idx int) string { return fmt.Sprintf(`$%d`, idx) }
 | 
			
		||||
	case schemas.MYSQL:
 | 
			
		||||
		f.quoteObject = func(s string) string { return fmt.Sprintf("`%s`", s) }
 | 
			
		||||
		f.paramPlaceholder = func(idx int) string { return "?" }
 | 
			
		||||
	case schemas.MSSQL:
 | 
			
		||||
		f.quoteObject = func(s string) string { return fmt.Sprintf("[%s]", s) }
 | 
			
		||||
		f.paramPlaceholder = func(idx int) string { return "?" }
 | 
			
		||||
	}
 | 
			
		||||
	return f, nil
 | 
			
		||||
}
 | 
			
		||||
@@ -38,27 +38,30 @@ func GenerateRandomAvatar(ctx context.Context, u *User) error {
 | 
			
		||||
 | 
			
		||||
	u.Avatar = avatars.HashEmail(seed)
 | 
			
		||||
 | 
			
		||||
	// Don't share the images so that we can delete them easily
 | 
			
		||||
	if err := storage.SaveFrom(storage.Avatars, u.CustomAvatarRelativePath(), func(w io.Writer) error {
 | 
			
		||||
		if err := png.Encode(w, img); err != nil {
 | 
			
		||||
			log.Error("Encode: %v", err)
 | 
			
		||||
	_, err = storage.Avatars.Stat(u.CustomAvatarRelativePath())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		// If unable to Stat the avatar file (usually it means non-existing), then try to save a new one
 | 
			
		||||
		// Don't share the images so that we can delete them easily
 | 
			
		||||
		if err := storage.SaveFrom(storage.Avatars, u.CustomAvatarRelativePath(), func(w io.Writer) error {
 | 
			
		||||
			if err := png.Encode(w, img); err != nil {
 | 
			
		||||
				log.Error("Encode: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
			return nil
 | 
			
		||||
		}); err != nil {
 | 
			
		||||
			return fmt.Errorf("failed to save avatar %s: %w", u.CustomAvatarRelativePath(), err)
 | 
			
		||||
		}
 | 
			
		||||
		return err
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to create dir %s: %w", u.CustomAvatarRelativePath(), err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := db.GetEngine(ctx).ID(u.ID).Cols("avatar").Update(u); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log.Info("New random avatar created: %d", u.ID)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AvatarLinkWithSize returns a link to the user's avatar with size. size <= 0 means default size
 | 
			
		||||
func (u *User) AvatarLinkWithSize(ctx context.Context, size int) string {
 | 
			
		||||
	if u.IsGhost() {
 | 
			
		||||
	if u.IsGhost() || u.IsGiteaActions() {
 | 
			
		||||
		return avatars.DefaultAvatarLink()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,13 +4,19 @@
 | 
			
		||||
package user
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"io"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	"code.gitea.io/gitea/models/unittest"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/modules/storage"
 | 
			
		||||
	"code.gitea.io/gitea/modules/test"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"github.com/stretchr/testify/require"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestUserAvatarLink(t *testing.T) {
 | 
			
		||||
@@ -26,3 +32,37 @@ func TestUserAvatarLink(t *testing.T) {
 | 
			
		||||
	link = u.AvatarLink(db.DefaultContext)
 | 
			
		||||
	assert.Equal(t, "https://localhost/sub-path/avatars/avatar.png", link)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestUserAvatarGenerate(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
			
		||||
	var err error
 | 
			
		||||
	tmpDir := t.TempDir()
 | 
			
		||||
	storage.Avatars, err = storage.NewLocalStorage(context.Background(), &setting.Storage{Path: tmpDir})
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	u := unittest.AssertExistsAndLoadBean(t, &User{ID: 2})
 | 
			
		||||
 | 
			
		||||
	// there was no avatar, generate a new one
 | 
			
		||||
	assert.Empty(t, u.Avatar)
 | 
			
		||||
	err = GenerateRandomAvatar(db.DefaultContext, u)
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	assert.NotEmpty(t, u.Avatar)
 | 
			
		||||
 | 
			
		||||
	// make sure the generated one exists
 | 
			
		||||
	oldAvatarPath := u.CustomAvatarRelativePath()
 | 
			
		||||
	_, err = storage.Avatars.Stat(u.CustomAvatarRelativePath())
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	// and try to change its content
 | 
			
		||||
	_, err = storage.Avatars.Save(u.CustomAvatarRelativePath(), strings.NewReader("abcd"), 4)
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	// try to generate again
 | 
			
		||||
	err = GenerateRandomAvatar(db.DefaultContext, u)
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, oldAvatarPath, u.CustomAvatarRelativePath())
 | 
			
		||||
	f, err := storage.Avatars.Open(u.CustomAvatarRelativePath())
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	defer f.Close()
 | 
			
		||||
	content, _ := io.ReadAll(f)
 | 
			
		||||
	assert.Equal(t, "abcd", string(content))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -357,8 +357,8 @@ func VerifyActiveEmailCode(ctx context.Context, code, email string) *EmailAddres
 | 
			
		||||
	if user := GetVerifyUser(ctx, code); user != nil {
 | 
			
		||||
		// time limit code
 | 
			
		||||
		prefix := code[:base.TimeLimitCodeLength]
 | 
			
		||||
		data := fmt.Sprintf("%d%s%s%s%s", user.ID, email, user.LowerName, user.Passwd, user.Rands)
 | 
			
		||||
 | 
			
		||||
		opts := &TimeLimitCodeOptions{Purpose: TimeLimitCodeActivateEmail, NewEmail: email}
 | 
			
		||||
		data := makeTimeLimitCodeHashData(opts, user)
 | 
			
		||||
		if base.VerifyTimeLimitCode(time.Now(), data, setting.Service.ActiveCodeLives, prefix) {
 | 
			
		||||
			emailAddress := &EmailAddress{UID: user.ID, Email: email}
 | 
			
		||||
			if has, _ := db.GetEngine(ctx).Get(emailAddress); has {
 | 
			
		||||
@@ -486,10 +486,10 @@ func ActivateUserEmail(ctx context.Context, userID int64, email string, activate
 | 
			
		||||
 | 
			
		||||
	// Activate/deactivate a user's primary email address and account
 | 
			
		||||
	if addr.IsPrimary {
 | 
			
		||||
		user, exist, err := db.Get[User](ctx, builder.Eq{"id": userID, "email": email})
 | 
			
		||||
		user, exist, err := db.Get[User](ctx, builder.Eq{"id": userID})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		} else if !exist {
 | 
			
		||||
		} else if !exist || !strings.EqualFold(user.Email, email) {
 | 
			
		||||
			return fmt.Errorf("no user with ID: %d and Email: %s", userID, email)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -181,7 +181,8 @@ func (u *User) BeforeUpdate() {
 | 
			
		||||
		u.MaxRepoCreation = -1
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Organization does not need email
 | 
			
		||||
	// FIXME: this email doesn't need to be in lowercase, because the emails are mainly managed by the email table with lower_email field
 | 
			
		||||
	// This trick could be removed in new releases to display the user inputed email as-is.
 | 
			
		||||
	u.Email = strings.ToLower(u.Email)
 | 
			
		||||
	if !u.IsOrganization() {
 | 
			
		||||
		if len(u.AvatarEmail) == 0 {
 | 
			
		||||
@@ -310,17 +311,6 @@ func (u *User) OrganisationLink() string {
 | 
			
		||||
	return setting.AppSubURL + "/org/" + url.PathEscape(u.Name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GenerateEmailActivateCode generates an activate code based on user information and given e-mail.
 | 
			
		||||
func (u *User) GenerateEmailActivateCode(email string) string {
 | 
			
		||||
	code := base.CreateTimeLimitCode(
 | 
			
		||||
		fmt.Sprintf("%d%s%s%s%s", u.ID, email, u.LowerName, u.Passwd, u.Rands),
 | 
			
		||||
		setting.Service.ActiveCodeLives, time.Now(), nil)
 | 
			
		||||
 | 
			
		||||
	// Add tail hex username
 | 
			
		||||
	code += hex.EncodeToString([]byte(u.LowerName))
 | 
			
		||||
	return code
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetUserFollowers returns range of user's followers.
 | 
			
		||||
func GetUserFollowers(ctx context.Context, u, viewer *User, listOptions db.ListOptions) ([]*User, int64, error) {
 | 
			
		||||
	sess := db.GetEngine(ctx).
 | 
			
		||||
@@ -848,12 +838,38 @@ func GetVerifyUser(ctx context.Context, code string) (user *User) {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// VerifyUserActiveCode verifies active code when active account
 | 
			
		||||
func VerifyUserActiveCode(ctx context.Context, code string) (user *User) {
 | 
			
		||||
type TimeLimitCodePurpose string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	TimeLimitCodeActivateAccount TimeLimitCodePurpose = "activate_account"
 | 
			
		||||
	TimeLimitCodeActivateEmail   TimeLimitCodePurpose = "activate_email"
 | 
			
		||||
	TimeLimitCodeResetPassword   TimeLimitCodePurpose = "reset_password"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type TimeLimitCodeOptions struct {
 | 
			
		||||
	Purpose  TimeLimitCodePurpose
 | 
			
		||||
	NewEmail string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func makeTimeLimitCodeHashData(opts *TimeLimitCodeOptions, u *User) string {
 | 
			
		||||
	return fmt.Sprintf("%s|%d|%s|%s|%s|%s", opts.Purpose, u.ID, strings.ToLower(util.IfZero(opts.NewEmail, u.Email)), u.LowerName, u.Passwd, u.Rands)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GenerateUserTimeLimitCode generates a time-limit code based on user information and given e-mail.
 | 
			
		||||
// TODO: need to use cache or db to store it to make sure a code can only be consumed once
 | 
			
		||||
func GenerateUserTimeLimitCode(opts *TimeLimitCodeOptions, u *User) string {
 | 
			
		||||
	data := makeTimeLimitCodeHashData(opts, u)
 | 
			
		||||
	code := base.CreateTimeLimitCode(data, setting.Service.ActiveCodeLives, time.Now(), nil)
 | 
			
		||||
	code += hex.EncodeToString([]byte(u.LowerName)) // Add tail hex username
 | 
			
		||||
	return code
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// VerifyUserTimeLimitCode verifies the time-limit code
 | 
			
		||||
func VerifyUserTimeLimitCode(ctx context.Context, opts *TimeLimitCodeOptions, code string) (user *User) {
 | 
			
		||||
	if user = GetVerifyUser(ctx, code); user != nil {
 | 
			
		||||
		// time limit code
 | 
			
		||||
		prefix := code[:base.TimeLimitCodeLength]
 | 
			
		||||
		data := fmt.Sprintf("%d%s%s%s%s", user.ID, user.Email, user.LowerName, user.Passwd, user.Rands)
 | 
			
		||||
		data := makeTimeLimitCodeHashData(opts, user)
 | 
			
		||||
		if base.VerifyTimeLimitCode(time.Now(), data, setting.Service.ActiveCodeLives, prefix) {
 | 
			
		||||
			return user
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,10 @@ func NewGhostUser() *User {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func IsGhostUserName(name string) bool {
 | 
			
		||||
	return strings.EqualFold(name, GhostUserName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsGhost check if user is fake user for a deleted account
 | 
			
		||||
func (u *User) IsGhost() bool {
 | 
			
		||||
	if u == nil {
 | 
			
		||||
@@ -48,6 +52,10 @@ const (
 | 
			
		||||
	ActionsEmail    = "teabot@gitea.io"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func IsGiteaActionsUserName(name string) bool {
 | 
			
		||||
	return strings.EqualFold(name, ActionsUserName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewActionsUser creates and returns a fake user for running the actions.
 | 
			
		||||
func NewActionsUser() *User {
 | 
			
		||||
	return &User{
 | 
			
		||||
@@ -65,6 +73,16 @@ func NewActionsUser() *User {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (u *User) IsActions() bool {
 | 
			
		||||
func (u *User) IsGiteaActions() bool {
 | 
			
		||||
	return u != nil && u.ID == ActionsUserID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetSystemUserByName(name string) *User {
 | 
			
		||||
	if IsGhostUserName(name) {
 | 
			
		||||
		return NewGhostUser()
 | 
			
		||||
	}
 | 
			
		||||
	if IsGiteaActionsUserName(name) {
 | 
			
		||||
		return NewActionsUser()
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										32
									
								
								models/user/user_system_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								models/user/user_system_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
// Copyright 2025 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package user
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"github.com/stretchr/testify/require"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestSystemUser(t *testing.T) {
 | 
			
		||||
	u, err := GetPossibleUserByID(db.DefaultContext, -1)
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, "Ghost", u.Name)
 | 
			
		||||
	assert.Equal(t, "ghost", u.LowerName)
 | 
			
		||||
	assert.True(t, u.IsGhost())
 | 
			
		||||
	assert.True(t, IsGhostUserName("gHost"))
 | 
			
		||||
 | 
			
		||||
	u, err = GetPossibleUserByID(db.DefaultContext, -2)
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, "gitea-actions", u.Name)
 | 
			
		||||
	assert.Equal(t, "gitea-actions", u.LowerName)
 | 
			
		||||
	assert.True(t, u.IsGiteaActions())
 | 
			
		||||
	assert.True(t, IsGiteaActionsUserName("Gitea-actionS"))
 | 
			
		||||
 | 
			
		||||
	_, err = GetPossibleUserByID(db.DefaultContext, -3)
 | 
			
		||||
	require.Error(t, err)
 | 
			
		||||
}
 | 
			
		||||
@@ -299,6 +299,11 @@ func (w *Webhook) HasPackageEvent() bool {
 | 
			
		||||
		(w.ChooseEvents && w.HookEvents.Package)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w *Webhook) HasStatusEvent() bool {
 | 
			
		||||
	return w.SendEverything ||
 | 
			
		||||
		(w.ChooseEvents && w.HookEvents.Status)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// HasPullRequestReviewRequestEvent returns true if hook enabled pull request review request event.
 | 
			
		||||
func (w *Webhook) HasPullRequestReviewRequestEvent() bool {
 | 
			
		||||
	return w.SendEverything ||
 | 
			
		||||
@@ -337,6 +342,7 @@ func (w *Webhook) EventCheckers() []struct {
 | 
			
		||||
		{w.HasReleaseEvent, webhook_module.HookEventRelease},
 | 
			
		||||
		{w.HasPackageEvent, webhook_module.HookEventPackage},
 | 
			
		||||
		{w.HasPullRequestReviewRequestEvent, webhook_module.HookEventPullRequestReviewRequest},
 | 
			
		||||
		{w.HasStatusEvent, webhook_module.HookEventStatus},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -74,7 +74,7 @@ func TestWebhook_EventsArray(t *testing.T) {
 | 
			
		||||
		"pull_request", "pull_request_assign", "pull_request_label", "pull_request_milestone",
 | 
			
		||||
		"pull_request_comment", "pull_request_review_approved", "pull_request_review_rejected",
 | 
			
		||||
		"pull_request_review_comment", "pull_request_sync", "wiki", "repository", "release",
 | 
			
		||||
		"package", "pull_request_review_request",
 | 
			
		||||
		"package", "pull_request_review_request", "status",
 | 
			
		||||
	},
 | 
			
		||||
		(&Webhook{
 | 
			
		||||
			HookEvent: &webhook_module.HookEvent{SendEverything: true},
 | 
			
		||||
 
 | 
			
		||||
@@ -15,5 +15,5 @@ func TestPamAuth(t *testing.T) {
 | 
			
		||||
	result, err := Auth("gitea", "user1", "false-pwd")
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
	assert.EqualError(t, err, "Authentication failure")
 | 
			
		||||
	assert.Len(t, result)
 | 
			
		||||
	assert.Empty(t, result)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										9
									
								
								modules/cache/cache.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								modules/cache/cache.go
									
									
									
									
										vendored
									
									
								
							@@ -37,10 +37,15 @@ func Init() error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	testCacheKey       = "DefaultCache.TestKey"
 | 
			
		||||
	SlowCacheThreshold = 100 * time.Microsecond
 | 
			
		||||
	testCacheKey = "DefaultCache.TestKey"
 | 
			
		||||
	// SlowCacheThreshold marks cache tests as slow
 | 
			
		||||
	// set to 30ms per discussion: https://github.com/go-gitea/gitea/issues/33190
 | 
			
		||||
	// TODO: Replace with metrics histogram
 | 
			
		||||
	SlowCacheThreshold = 30 * time.Millisecond
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Test performs delete, put and get operations on a predefined key
 | 
			
		||||
// returns
 | 
			
		||||
func Test() (time.Duration, error) {
 | 
			
		||||
	if defaultCache == nil {
 | 
			
		||||
		return 0, fmt.Errorf("default cache not initialized")
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								modules/cache/cache_test.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								modules/cache/cache_test.go
									
									
									
									
										vendored
									
									
								
							@@ -43,7 +43,8 @@ func TestTest(t *testing.T) {
 | 
			
		||||
	elapsed, err := Test()
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	// mem cache should take from 300ns up to 1ms on modern hardware ...
 | 
			
		||||
	assert.Less(t, elapsed, time.Millisecond)
 | 
			
		||||
	assert.Positive(t, elapsed)
 | 
			
		||||
	assert.Less(t, elapsed, SlowCacheThreshold)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetCache(t *testing.T) {
 | 
			
		||||
 
 | 
			
		||||
@@ -253,7 +253,7 @@ func BinToHex(objectFormat ObjectFormat, sha, out []byte) []byte {
 | 
			
		||||
	return out
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ParseTreeLine reads an entry from a tree in a cat-file --batch stream
 | 
			
		||||
// ParseCatFileTreeLine reads an entry from a tree in a cat-file --batch stream
 | 
			
		||||
// This carefully avoids allocations - except where fnameBuf is too small.
 | 
			
		||||
// It is recommended therefore to pass in an fnameBuf large enough to avoid almost all allocations
 | 
			
		||||
//
 | 
			
		||||
@@ -261,7 +261,7 @@ func BinToHex(objectFormat ObjectFormat, sha, out []byte) []byte {
 | 
			
		||||
// <mode-in-ascii-dropping-initial-zeros> SP <fname> NUL <binary HASH>
 | 
			
		||||
//
 | 
			
		||||
// We don't attempt to convert the raw HASH to save a lot of time
 | 
			
		||||
func ParseTreeLine(objectFormat ObjectFormat, rd *bufio.Reader, modeBuf, fnameBuf, shaBuf []byte) (mode, fname, sha []byte, n int, err error) {
 | 
			
		||||
func ParseCatFileTreeLine(objectFormat ObjectFormat, rd *bufio.Reader, modeBuf, fnameBuf, shaBuf []byte) (mode, fname, sha []byte, n int, err error) {
 | 
			
		||||
	var readBytes []byte
 | 
			
		||||
 | 
			
		||||
	// Read the Mode & fname
 | 
			
		||||
@@ -271,7 +271,7 @@ func ParseTreeLine(objectFormat ObjectFormat, rd *bufio.Reader, modeBuf, fnameBu
 | 
			
		||||
	}
 | 
			
		||||
	idx := bytes.IndexByte(readBytes, ' ')
 | 
			
		||||
	if idx < 0 {
 | 
			
		||||
		log.Debug("missing space in readBytes ParseTreeLine: %s", readBytes)
 | 
			
		||||
		log.Debug("missing space in readBytes ParseCatFileTreeLine: %s", readBytes)
 | 
			
		||||
		return mode, fname, sha, n, &ErrNotExist{}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -360,5 +360,5 @@ func Test_GetCommitBranchStart(t *testing.T) {
 | 
			
		||||
	startCommitID, err := repo.GetCommitBranchStart(os.Environ(), "branch1", commit.ID.String())
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.NotEmpty(t, startCommitID)
 | 
			
		||||
	assert.EqualValues(t, "9c9aef8dd84e02bc7ec12641deb4c930a7c30185", startCommitID)
 | 
			
		||||
	assert.EqualValues(t, "95bb4d39648ee7e325106df01a621c530863a653", startCommitID)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										78
									
								
								modules/git/parse.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								modules/git/parse.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,78 @@
 | 
			
		||||
// Copyright 2024 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package git
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/optional"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var sepSpace = []byte{' '}
 | 
			
		||||
 | 
			
		||||
type LsTreeEntry struct {
 | 
			
		||||
	ID        ObjectID
 | 
			
		||||
	EntryMode EntryMode
 | 
			
		||||
	Name      string
 | 
			
		||||
	Size      optional.Option[int64]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func parseLsTreeLine(line []byte) (*LsTreeEntry, error) {
 | 
			
		||||
	// expect line to be of the form:
 | 
			
		||||
	// <mode> <type> <sha> <space-padded-size>\t<filename>
 | 
			
		||||
	// <mode> <type> <sha>\t<filename>
 | 
			
		||||
 | 
			
		||||
	var err error
 | 
			
		||||
	posTab := bytes.IndexByte(line, '\t')
 | 
			
		||||
	if posTab == -1 {
 | 
			
		||||
		return nil, fmt.Errorf("invalid ls-tree output (no tab): %q", line)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	entry := new(LsTreeEntry)
 | 
			
		||||
 | 
			
		||||
	entryAttrs := line[:posTab]
 | 
			
		||||
	entryName := line[posTab+1:]
 | 
			
		||||
 | 
			
		||||
	entryMode, entryAttrs, _ := bytes.Cut(entryAttrs, sepSpace)
 | 
			
		||||
	_ /* entryType */, entryAttrs, _ = bytes.Cut(entryAttrs, sepSpace) // the type is not used, the mode is enough to determine the type
 | 
			
		||||
	entryObjectID, entryAttrs, _ := bytes.Cut(entryAttrs, sepSpace)
 | 
			
		||||
	if len(entryAttrs) > 0 {
 | 
			
		||||
		entrySize := entryAttrs // the last field is the space-padded-size
 | 
			
		||||
		size, _ := strconv.ParseInt(strings.TrimSpace(string(entrySize)), 10, 64)
 | 
			
		||||
		entry.Size = optional.Some(size)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch string(entryMode) {
 | 
			
		||||
	case "100644":
 | 
			
		||||
		entry.EntryMode = EntryModeBlob
 | 
			
		||||
	case "100755":
 | 
			
		||||
		entry.EntryMode = EntryModeExec
 | 
			
		||||
	case "120000":
 | 
			
		||||
		entry.EntryMode = EntryModeSymlink
 | 
			
		||||
	case "160000":
 | 
			
		||||
		entry.EntryMode = EntryModeCommit
 | 
			
		||||
	case "040000", "040755": // git uses 040000 for tree object, but some users may get 040755 for unknown reasons
 | 
			
		||||
		entry.EntryMode = EntryModeTree
 | 
			
		||||
	default:
 | 
			
		||||
		return nil, fmt.Errorf("unknown type: %v", string(entryMode))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	entry.ID, err = NewIDFromString(string(entryObjectID))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("invalid ls-tree output (invalid object id): %q, err: %w", line, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(entryName) > 0 && entryName[0] == '"' {
 | 
			
		||||
		entry.Name, err = strconv.Unquote(string(entryName))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, fmt.Errorf("invalid ls-tree output (invalid name): %q, err: %w", line, err)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		entry.Name = string(entryName)
 | 
			
		||||
	}
 | 
			
		||||
	return entry, nil
 | 
			
		||||
}
 | 
			
		||||
@@ -10,8 +10,6 @@ import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
)
 | 
			
		||||
@@ -21,71 +19,30 @@ func ParseTreeEntries(data []byte) ([]*TreeEntry, error) {
 | 
			
		||||
	return parseTreeEntries(data, nil)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var sepSpace = []byte{' '}
 | 
			
		||||
 | 
			
		||||
// parseTreeEntries FIXME this function's design is not right, it should make the caller read all data into memory
 | 
			
		||||
func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) {
 | 
			
		||||
	var err error
 | 
			
		||||
	entries := make([]*TreeEntry, 0, bytes.Count(data, []byte{'\n'})+1)
 | 
			
		||||
	for pos := 0; pos < len(data); {
 | 
			
		||||
		// expect line to be of the form:
 | 
			
		||||
		// <mode> <type> <sha> <space-padded-size>\t<filename>
 | 
			
		||||
		// <mode> <type> <sha>\t<filename>
 | 
			
		||||
		posEnd := bytes.IndexByte(data[pos:], '\n')
 | 
			
		||||
		if posEnd == -1 {
 | 
			
		||||
			posEnd = len(data)
 | 
			
		||||
		} else {
 | 
			
		||||
			posEnd += pos
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		line := data[pos:posEnd]
 | 
			
		||||
		posTab := bytes.IndexByte(line, '\t')
 | 
			
		||||
		if posTab == -1 {
 | 
			
		||||
			return nil, fmt.Errorf("invalid ls-tree output (no tab): %q", line)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		entry := new(TreeEntry)
 | 
			
		||||
		entry.ptree = ptree
 | 
			
		||||
 | 
			
		||||
		entryAttrs := line[:posTab]
 | 
			
		||||
		entryName := line[posTab+1:]
 | 
			
		||||
 | 
			
		||||
		entryMode, entryAttrs, _ := bytes.Cut(entryAttrs, sepSpace)
 | 
			
		||||
		_ /* entryType */, entryAttrs, _ = bytes.Cut(entryAttrs, sepSpace) // the type is not used, the mode is enough to determine the type
 | 
			
		||||
		entryObjectID, entryAttrs, _ := bytes.Cut(entryAttrs, sepSpace)
 | 
			
		||||
		if len(entryAttrs) > 0 {
 | 
			
		||||
			entrySize := entryAttrs // the last field is the space-padded-size
 | 
			
		||||
			entry.size, _ = strconv.ParseInt(strings.TrimSpace(string(entrySize)), 10, 64)
 | 
			
		||||
			entry.sized = true
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		switch string(entryMode) {
 | 
			
		||||
		case "100644":
 | 
			
		||||
			entry.entryMode = EntryModeBlob
 | 
			
		||||
		case "100755":
 | 
			
		||||
			entry.entryMode = EntryModeExec
 | 
			
		||||
		case "120000":
 | 
			
		||||
			entry.entryMode = EntryModeSymlink
 | 
			
		||||
		case "160000":
 | 
			
		||||
			entry.entryMode = EntryModeCommit
 | 
			
		||||
		case "040000", "040755": // git uses 040000 for tree object, but some users may get 040755 for unknown reasons
 | 
			
		||||
			entry.entryMode = EntryModeTree
 | 
			
		||||
		default:
 | 
			
		||||
			return nil, fmt.Errorf("unknown type: %v", string(entryMode))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		entry.ID, err = NewIDFromString(string(entryObjectID))
 | 
			
		||||
		lsTreeLine, err := parseLsTreeLine(line)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, fmt.Errorf("invalid ls-tree output (invalid object id): %q, err: %w", line, err)
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(entryName) > 0 && entryName[0] == '"' {
 | 
			
		||||
			entry.name, err = strconv.Unquote(string(entryName))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, fmt.Errorf("invalid ls-tree output (invalid name): %q, err: %w", line, err)
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			entry.name = string(entryName)
 | 
			
		||||
		entry := &TreeEntry{
 | 
			
		||||
			ptree:     ptree,
 | 
			
		||||
			ID:        lsTreeLine.ID,
 | 
			
		||||
			entryMode: lsTreeLine.EntryMode,
 | 
			
		||||
			name:      lsTreeLine.Name,
 | 
			
		||||
			size:      lsTreeLine.Size.Value(),
 | 
			
		||||
			sized:     lsTreeLine.Size.Has(),
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		pos = posEnd + 1
 | 
			
		||||
		entries = append(entries, entry)
 | 
			
		||||
	}
 | 
			
		||||
@@ -100,7 +57,7 @@ func catBatchParseTreeEntries(objectFormat ObjectFormat, ptree *Tree, rd *bufio.
 | 
			
		||||
 | 
			
		||||
loop:
 | 
			
		||||
	for sz > 0 {
 | 
			
		||||
		mode, fname, sha, count, err := ParseTreeLine(objectFormat, rd, modeBuf, fnameBuf, shaBuf)
 | 
			
		||||
		mode, fname, sha, count, err := ParseCatFileTreeLine(objectFormat, rd, modeBuf, fnameBuf, shaBuf)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			if err == io.EOF {
 | 
			
		||||
				break loop
 | 
			
		||||
 
 | 
			
		||||
@@ -114,7 +114,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
 | 
			
		||||
			case "tree":
 | 
			
		||||
				var n int64
 | 
			
		||||
				for n < size {
 | 
			
		||||
					mode, fname, binObjectID, count, err := git.ParseTreeLine(objectID.Type(), batchReader, modeBuf, fnameBuf, workingShaBuf)
 | 
			
		||||
					mode, fname, binObjectID, count, err := git.ParseCatFileTreeLine(objectID.Type(), batchReader, modeBuf, fnameBuf, workingShaBuf)
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						return nil, err
 | 
			
		||||
					}
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,8 @@ func TestRefName(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	// Test pull names
 | 
			
		||||
	assert.Equal(t, "1", RefName("refs/pull/1/head").PullName())
 | 
			
		||||
	assert.True(t, RefName("refs/pull/1/head").IsPull())
 | 
			
		||||
	assert.True(t, RefName("refs/pull/1/merge").IsPull())
 | 
			
		||||
	assert.Equal(t, "my/pull", RefName("refs/pull/my/pull/head").PullName())
 | 
			
		||||
 | 
			
		||||
	// Test for branch names
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ package git
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	giturl "code.gitea.io/gitea/modules/git/url"
 | 
			
		||||
)
 | 
			
		||||
@@ -37,3 +38,12 @@ func GetRemoteURL(ctx context.Context, repoPath, remoteName string) (*giturl.Git
 | 
			
		||||
	}
 | 
			
		||||
	return giturl.Parse(addr)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsRemoteNotExistError checks the prefix of the error message to see whether a remote does not exist.
 | 
			
		||||
func IsRemoteNotExistError(err error) bool {
 | 
			
		||||
	// see: https://github.com/go-gitea/gitea/issues/32889#issuecomment-2571848216
 | 
			
		||||
	// Should not add space in the end, sometimes git will add a `:`
 | 
			
		||||
	prefix1 := "exit status 128 - fatal: No such remote" // git < 2.30
 | 
			
		||||
	prefix2 := "exit status 2 - error: No such remote"   // git >= 2.30
 | 
			
		||||
	return strings.HasPrefix(err.Error(), prefix1) || strings.HasPrefix(err.Error(), prefix2)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -216,8 +216,6 @@ type CommitsByFileAndRangeOptions struct {
 | 
			
		||||
 | 
			
		||||
// CommitsByFileAndRange return the commits according revision file and the page
 | 
			
		||||
func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions) ([]*Commit, error) {
 | 
			
		||||
	skip := (opts.Page - 1) * setting.Git.CommitsRangeSize
 | 
			
		||||
 | 
			
		||||
	stdoutReader, stdoutWriter := io.Pipe()
 | 
			
		||||
	defer func() {
 | 
			
		||||
		_ = stdoutReader.Close()
 | 
			
		||||
@@ -226,8 +224,8 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions)
 | 
			
		||||
	go func() {
 | 
			
		||||
		stderr := strings.Builder{}
 | 
			
		||||
		gitCmd := NewCommand(repo.Ctx, "rev-list").
 | 
			
		||||
			AddOptionFormat("--max-count=%d", setting.Git.CommitsRangeSize*opts.Page).
 | 
			
		||||
			AddOptionFormat("--skip=%d", skip)
 | 
			
		||||
			AddOptionFormat("--max-count=%d", setting.Git.CommitsRangeSize).
 | 
			
		||||
			AddOptionFormat("--skip=%d", (opts.Page-1)*setting.Git.CommitsRangeSize)
 | 
			
		||||
		gitCmd.AddDynamicArguments(opts.Revision)
 | 
			
		||||
 | 
			
		||||
		if opts.Not != "" {
 | 
			
		||||
@@ -521,6 +519,7 @@ func (repo *Repository) AddLastCommitCache(cacheKey, fullName, sha string) error
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetCommitBranchStart returns the commit where the branch diverged
 | 
			
		||||
func (repo *Repository) GetCommitBranchStart(env []string, branch, endCommitID string) (string, error) {
 | 
			
		||||
	cmd := NewCommand(repo.Ctx, "log", prettyLogFormat)
 | 
			
		||||
	cmd.AddDynamicArguments(endCommitID)
 | 
			
		||||
@@ -535,7 +534,8 @@ func (repo *Repository) GetCommitBranchStart(env []string, branch, endCommitID s
 | 
			
		||||
 | 
			
		||||
	parts := bytes.Split(bytes.TrimSpace(stdout), []byte{'\n'})
 | 
			
		||||
 | 
			
		||||
	var startCommitID string
 | 
			
		||||
	// check the commits one by one until we find a commit contained by another branch
 | 
			
		||||
	// and we think this commit is the divergence point
 | 
			
		||||
	for _, commitID := range parts {
 | 
			
		||||
		branches, err := repo.getBranches(env, string(commitID), 2)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
@@ -543,11 +543,9 @@ func (repo *Repository) GetCommitBranchStart(env []string, branch, endCommitID s
 | 
			
		||||
		}
 | 
			
		||||
		for _, b := range branches {
 | 
			
		||||
			if b != branch {
 | 
			
		||||
				return startCommitID, nil
 | 
			
		||||
				return string(commitID), nil
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		startCommitID = string(commitID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return "", nil
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,11 @@ import (
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/modules/test"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"github.com/stretchr/testify/require"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestRepository_GetCommitBranches(t *testing.T) {
 | 
			
		||||
@@ -126,3 +130,21 @@ func TestGetRefCommitID(t *testing.T) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCommitsByFileAndRange(t *testing.T) {
 | 
			
		||||
	defer test.MockVariableValue(&setting.Git.CommitsRangeSize, 2)()
 | 
			
		||||
 | 
			
		||||
	bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
 | 
			
		||||
	bareRepo1, err := openRepositoryWithDefaultContext(bareRepo1Path)
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	defer bareRepo1.Close()
 | 
			
		||||
 | 
			
		||||
	// "foo" has 3 commits in "master" branch
 | 
			
		||||
	commits, err := bareRepo1.CommitsByFileAndRange(CommitsByFileAndRangeOptions{Revision: "master", File: "foo", Page: 1})
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	assert.Len(t, commits, 2)
 | 
			
		||||
 | 
			
		||||
	commits, err = bareRepo1.CommitsByFileAndRange(CommitsByFileAndRangeOptions{Revision: "master", File: "foo", Page: 2})
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	assert.Len(t, commits, 1)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										66
									
								
								modules/git/submodule.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								modules/git/submodule.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,66 @@
 | 
			
		||||
// Copyright 2024 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package git
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type TemplateSubmoduleCommit struct {
 | 
			
		||||
	Path   string
 | 
			
		||||
	Commit string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetTemplateSubmoduleCommits returns a list of submodules paths and their commits from a repository
 | 
			
		||||
// This function is only for generating new repos based on existing template, the template couldn't be too large.
 | 
			
		||||
func GetTemplateSubmoduleCommits(ctx context.Context, repoPath string) (submoduleCommits []TemplateSubmoduleCommit, _ error) {
 | 
			
		||||
	stdoutReader, stdoutWriter, err := os.Pipe()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	opts := &RunOpts{
 | 
			
		||||
		Dir:    repoPath,
 | 
			
		||||
		Stdout: stdoutWriter,
 | 
			
		||||
		PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error {
 | 
			
		||||
			_ = stdoutWriter.Close()
 | 
			
		||||
			defer stdoutReader.Close()
 | 
			
		||||
 | 
			
		||||
			scanner := bufio.NewScanner(stdoutReader)
 | 
			
		||||
			for scanner.Scan() {
 | 
			
		||||
				entry, err := parseLsTreeLine(scanner.Bytes())
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					cancel()
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
				if entry.EntryMode == EntryModeCommit {
 | 
			
		||||
					submoduleCommits = append(submoduleCommits, TemplateSubmoduleCommit{Path: entry.Name, Commit: entry.ID.String()})
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			return scanner.Err()
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	err = NewCommand(ctx, "ls-tree", "-r", "--", "HEAD").Run(opts)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("GetTemplateSubmoduleCommits: error running git ls-tree: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	return submoduleCommits, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AddTemplateSubmoduleIndexes Adds the given submodules to the git index.
 | 
			
		||||
// It is only for generating new repos based on existing template, requires the .gitmodules file to be already present in the work dir.
 | 
			
		||||
func AddTemplateSubmoduleIndexes(ctx context.Context, repoPath string, submodules []TemplateSubmoduleCommit) error {
 | 
			
		||||
	for _, submodule := range submodules {
 | 
			
		||||
		cmd := NewCommand(ctx, "update-index", "--add", "--cacheinfo", "160000").AddDynamicArguments(submodule.Commit, submodule.Path)
 | 
			
		||||
		if stdout, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath}); err != nil {
 | 
			
		||||
			log.Error("Unable to add %s as submodule to repo %s: stdout %s\nError: %v", submodule.Path, repoPath, stdout, err)
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										48
									
								
								modules/git/submodule_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								modules/git/submodule_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
			
		||||
// Copyright 2024 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package git
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"github.com/stretchr/testify/require"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestGetTemplateSubmoduleCommits(t *testing.T) {
 | 
			
		||||
	testRepoPath := filepath.Join(testReposDir, "repo4_submodules")
 | 
			
		||||
	submodules, err := GetTemplateSubmoduleCommits(DefaultContext, testRepoPath)
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	assert.Len(t, submodules, 2)
 | 
			
		||||
 | 
			
		||||
	assert.EqualValues(t, "<°)))><", submodules[0].Path)
 | 
			
		||||
	assert.EqualValues(t, "d2932de67963f23d43e1c7ecf20173e92ee6c43c", submodules[0].Commit)
 | 
			
		||||
 | 
			
		||||
	assert.EqualValues(t, "libtest", submodules[1].Path)
 | 
			
		||||
	assert.EqualValues(t, "1234567890123456789012345678901234567890", submodules[1].Commit)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAddTemplateSubmoduleIndexes(t *testing.T) {
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	tmpDir := t.TempDir()
 | 
			
		||||
	var err error
 | 
			
		||||
	_, _, err = NewCommand(ctx, "init").RunStdString(&RunOpts{Dir: tmpDir})
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	_ = os.Mkdir(filepath.Join(tmpDir, "new-dir"), 0o755)
 | 
			
		||||
	err = AddTemplateSubmoduleIndexes(ctx, tmpDir, []TemplateSubmoduleCommit{{Path: "new-dir", Commit: "1234567890123456789012345678901234567890"}})
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	_, _, err = NewCommand(ctx, "add", "--all").RunStdString(&RunOpts{Dir: tmpDir})
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	_, _, err = NewCommand(ctx, "-c", "user.name=a", "-c", "user.email=b", "commit", "-m=test").RunStdString(&RunOpts{Dir: tmpDir})
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	submodules, err := GetTemplateSubmoduleCommits(DefaultContext, tmpDir)
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	assert.Len(t, submodules, 1)
 | 
			
		||||
	assert.EqualValues(t, "new-dir", submodules[0].Path)
 | 
			
		||||
	assert.EqualValues(t, "1234567890123456789012345678901234567890", submodules[0].Commit)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								modules/git/tests/repos/repo4_submodules/HEAD
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								modules/git/tests/repos/repo4_submodules/HEAD
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
ref: refs/heads/master
 | 
			
		||||
							
								
								
									
										4
									
								
								modules/git/tests/repos/repo4_submodules/config
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								modules/git/tests/repos/repo4_submodules/config
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
[core]
 | 
			
		||||
	repositoryformatversion = 0
 | 
			
		||||
	filemode = true
 | 
			
		||||
	bare = true
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
x<01><>[
 | 
			
		||||
<EFBFBD>0E<><45>*<2A>_<EFBFBD><5F>$M<10>5tifBk Iŕ<49>7<>k~<7E><>9ܘ<39><DC98>ܠ<1B><><11>.j<><6A>	<09>O<><0C><>"z<>`<60>#I<>irF<72><46><EFBFBD><CDB9>$%<25><><18><>|4)<29><>?t<><74>=<3D><1D>:K<><4B><EFBFBD>#[$D<15><><1F><>^<5E><><EFBFBD><EFBFBD><EFBFBD>Ӓy<D392>HU/<2F>f?G
 | 
			
		||||
@@ -0,0 +1 @@
 | 
			
		||||
e1e59caba97193d48862d6809912043871f37437
 | 
			
		||||
@@ -17,7 +17,7 @@ func NewTree(repo *Repository, id ObjectID) *Tree {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SubTree get a sub tree by the sub dir path
 | 
			
		||||
// SubTree get a subtree by the sub dir path
 | 
			
		||||
func (t *Tree) SubTree(rpath string) (*Tree, error) {
 | 
			
		||||
	if len(rpath) == 0 {
 | 
			
		||||
		return t, nil
 | 
			
		||||
@@ -62,3 +62,14 @@ func (repo *Repository) LsTree(ref string, filenames ...string) ([]string, error
 | 
			
		||||
 | 
			
		||||
	return filelist, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetTreePathLatestCommit returns the latest commit of a tree path
 | 
			
		||||
func (repo *Repository) GetTreePathLatestCommit(refName, treePath string) (*Commit, error) {
 | 
			
		||||
	stdout, _, err := NewCommand(repo.Ctx, "rev-list", "-1").
 | 
			
		||||
		AddDynamicArguments(refName).AddDashesAndList(treePath).
 | 
			
		||||
		RunStdString(&RunOpts{Dir: repo.Path})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return repo.GetCommit(strings.TrimSpace(stdout))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,6 @@ func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) {
 | 
			
		||||
			ptree:     t,
 | 
			
		||||
			ID:        t.ID,
 | 
			
		||||
			name:      "",
 | 
			
		||||
			fullName:  "",
 | 
			
		||||
			entryMode: EntryModeTree,
 | 
			
		||||
		}, nil
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,23 +9,17 @@ import "code.gitea.io/gitea/modules/log"
 | 
			
		||||
 | 
			
		||||
// TreeEntry the leaf in the git tree
 | 
			
		||||
type TreeEntry struct {
 | 
			
		||||
	ID ObjectID
 | 
			
		||||
 | 
			
		||||
	ID    ObjectID
 | 
			
		||||
	ptree *Tree
 | 
			
		||||
 | 
			
		||||
	entryMode EntryMode
 | 
			
		||||
	name      string
 | 
			
		||||
 | 
			
		||||
	size     int64
 | 
			
		||||
	sized    bool
 | 
			
		||||
	fullName string
 | 
			
		||||
	size      int64
 | 
			
		||||
	sized     bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Name returns the name of the entry
 | 
			
		||||
func (te *TreeEntry) Name() string {
 | 
			
		||||
	if te.fullName != "" {
 | 
			
		||||
		return te.fullName
 | 
			
		||||
	}
 | 
			
		||||
	return te.name
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -25,3 +25,18 @@ func TestSubTree_Issue29101(t *testing.T) {
 | 
			
		||||
		assert.True(t, IsErrNotExist(err))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_GetTreePathLatestCommit(t *testing.T) {
 | 
			
		||||
	repo, err := openRepositoryWithDefaultContext(filepath.Join(testReposDir, "repo6_blame"))
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	defer repo.Close()
 | 
			
		||||
 | 
			
		||||
	commitID, err := repo.GetBranchCommitID("master")
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.EqualValues(t, "544d8f7a3b15927cddf2299b4b562d6ebd71b6a7", commitID)
 | 
			
		||||
 | 
			
		||||
	commit, err := repo.GetTreePathLatestCommit("master", "blame.txt")
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.NotNil(t, commit)
 | 
			
		||||
	assert.EqualValues(t, "45fb6cbc12f970b04eacd5cd4165edd11c8d7376", commit.ID.String())
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -99,10 +99,10 @@ func (r *Request) Param(key, value string) *Request {
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Body adds request raw body.
 | 
			
		||||
// it supports string and []byte.
 | 
			
		||||
// Body adds request raw body. It supports string, []byte and io.Reader as body.
 | 
			
		||||
func (r *Request) Body(data any) *Request {
 | 
			
		||||
	switch t := data.(type) {
 | 
			
		||||
	case nil: // do nothing
 | 
			
		||||
	case string:
 | 
			
		||||
		bf := bytes.NewBufferString(t)
 | 
			
		||||
		r.req.Body = io.NopCloser(bf)
 | 
			
		||||
@@ -111,6 +111,12 @@ func (r *Request) Body(data any) *Request {
 | 
			
		||||
		bf := bytes.NewBuffer(t)
 | 
			
		||||
		r.req.Body = io.NopCloser(bf)
 | 
			
		||||
		r.req.ContentLength = int64(len(t))
 | 
			
		||||
	case io.ReadCloser:
 | 
			
		||||
		r.req.Body = t
 | 
			
		||||
	case io.Reader:
 | 
			
		||||
		r.req.Body = io.NopCloser(t)
 | 
			
		||||
	default:
 | 
			
		||||
		panic(fmt.Sprintf("unsupported request body type %T", t))
 | 
			
		||||
	}
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
@@ -141,7 +147,7 @@ func (r *Request) getResponse() (*http.Response, error) {
 | 
			
		||||
		}
 | 
			
		||||
	} else if r.req.Method == "POST" && r.req.Body == nil && len(paramBody) > 0 {
 | 
			
		||||
		r.Header("Content-Type", "application/x-www-form-urlencoded")
 | 
			
		||||
		r.Body(paramBody)
 | 
			
		||||
		r.Body(paramBody) // string
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var err error
 | 
			
		||||
@@ -185,6 +191,7 @@ func (r *Request) getResponse() (*http.Response, error) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Response executes request client gets response manually.
 | 
			
		||||
// Caller MUST close the response body if no error occurs
 | 
			
		||||
func (r *Request) Response() (*http.Response, error) {
 | 
			
		||||
	return r.getResponse()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -123,13 +123,12 @@ func Init() {
 | 
			
		||||
			for _, indexerData := range items {
 | 
			
		||||
				log.Trace("IndexerData Process Repo: %d", indexerData.RepoID)
 | 
			
		||||
				if err := index(ctx, indexer, indexerData.RepoID); err != nil {
 | 
			
		||||
					unhandled = append(unhandled, indexerData)
 | 
			
		||||
					if !setting.IsInTesting {
 | 
			
		||||
						log.Error("Codes indexer handler: index error for repo %v: %v", indexerData.RepoID, err)
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			return unhandled
 | 
			
		||||
			return nil // do not re-queue the failed items, otherwise some broken repo will block the queue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		indexerQueue = queue.CreateUniqueQueue(ctx, "code_indexer", handler)
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,8 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/modules/indexer/code/bleve"
 | 
			
		||||
	"code.gitea.io/gitea/modules/indexer/code/elasticsearch"
 | 
			
		||||
	"code.gitea.io/gitea/modules/indexer/code/internal"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/modules/test"
 | 
			
		||||
 | 
			
		||||
	_ "code.gitea.io/gitea/models"
 | 
			
		||||
	_ "code.gitea.io/gitea/models/actions"
 | 
			
		||||
@@ -279,7 +281,7 @@ func testIndexer(name string, t *testing.T, indexer internal.Indexer) {
 | 
			
		||||
 | 
			
		||||
func TestBleveIndexAndSearch(t *testing.T) {
 | 
			
		||||
	unittest.PrepareTestEnv(t)
 | 
			
		||||
 | 
			
		||||
	defer test.MockVariableValue(&setting.Indexer.TypeBleveMaxFuzzniess, 2)()
 | 
			
		||||
	dir := t.TempDir()
 | 
			
		||||
 | 
			
		||||
	idx := bleve.NewIndexer(dir)
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@ import (
 | 
			
		||||
	"unicode"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
 | 
			
		||||
	"github.com/blevesearch/bleve/v2"
 | 
			
		||||
@@ -54,9 +55,9 @@ func openIndexer(path string, latestVersion int) (bleve.Index, int, error) {
 | 
			
		||||
	return index, 0, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// This method test the GuessFuzzinessByKeyword method. The fuzziness is based on the levenshtein distance and determines how many chars
 | 
			
		||||
// may be different on two string and they still be considered equivalent.
 | 
			
		||||
// Given a phrasse, its shortest word determines its fuzziness. If a phrase uses CJK (eg: `갃갃갃` `啊啊啊`), the fuzziness is zero.
 | 
			
		||||
// GuessFuzzinessByKeyword guesses fuzziness based on the levenshtein distance and determines how many chars
 | 
			
		||||
// may be different on two string, and they still be considered equivalent.
 | 
			
		||||
// Given a phrase, its shortest word determines its fuzziness. If a phrase uses CJK (eg: `갃갃갃` `啊啊啊`), the fuzziness is zero.
 | 
			
		||||
func GuessFuzzinessByKeyword(s string) int {
 | 
			
		||||
	tokenizer := unicode_tokenizer.NewUnicodeTokenizer()
 | 
			
		||||
	tokens := tokenizer.Tokenize([]byte(s))
 | 
			
		||||
@@ -85,5 +86,5 @@ func guessFuzzinessByKeyword(s string) int {
 | 
			
		||||
			return 0
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return min(maxFuzziness, len(s)/4)
 | 
			
		||||
	return min(min(setting.Indexer.TypeBleveMaxFuzzniess, maxFuzziness), len(s)/4)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,10 +7,15 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/modules/test"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestBleveGuessFuzzinessByKeyword(t *testing.T) {
 | 
			
		||||
	defer test.MockVariableValue(&setting.Indexer.TypeBleveMaxFuzzniess, 2)()
 | 
			
		||||
 | 
			
		||||
	scenarios := []struct {
 | 
			
		||||
		Input     string
 | 
			
		||||
		Fuzziness int // See util.go for the definition of fuzziness in this particular context
 | 
			
		||||
@@ -46,7 +51,7 @@ func TestBleveGuessFuzzinessByKeyword(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, scenario := range scenarios {
 | 
			
		||||
		t.Run(fmt.Sprintf("ensure fuzziness of '%s' is '%d'", scenario.Input, scenario.Fuzziness), func(t *testing.T) {
 | 
			
		||||
		t.Run(fmt.Sprintf("Fuziniess:%s=%d", scenario.Input, scenario.Fuzziness), func(t *testing.T) {
 | 
			
		||||
			assert.Equal(t, scenario.Fuzziness, GuessFuzzinessByKeyword(scenario.Input))
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -73,9 +73,9 @@ func ToDBOptions(ctx context.Context, options *internal.SearchOptions) (*issue_m
 | 
			
		||||
		UpdatedBeforeUnix:  options.UpdatedBeforeUnix.Value(),
 | 
			
		||||
		PriorityRepoID:     0,
 | 
			
		||||
		IsArchived:         options.IsArchived,
 | 
			
		||||
		Org:                nil,
 | 
			
		||||
		Owner:              nil,
 | 
			
		||||
		Team:               nil,
 | 
			
		||||
		User:               nil,
 | 
			
		||||
		Doer:               nil,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(options.MilestoneIDs) == 1 && options.MilestoneIDs[0] == 0 {
 | 
			
		||||
 
 | 
			
		||||
@@ -92,6 +92,11 @@ func getIssueIndexerData(ctx context.Context, issueID int64) (*internal.IndexerD
 | 
			
		||||
		projectID = issue.Project.ID
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	projectColumnID, err := issue.ProjectColumnID(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, false, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &internal.IndexerData{
 | 
			
		||||
		ID:                 issue.ID,
 | 
			
		||||
		RepoID:             issue.RepoID,
 | 
			
		||||
@@ -106,7 +111,7 @@ func getIssueIndexerData(ctx context.Context, issueID int64) (*internal.IndexerD
 | 
			
		||||
		NoLabel:            len(labels) == 0,
 | 
			
		||||
		MilestoneID:        issue.MilestoneID,
 | 
			
		||||
		ProjectID:          projectID,
 | 
			
		||||
		ProjectColumnID:    issue.ProjectColumnID(ctx),
 | 
			
		||||
		ProjectColumnID:    projectColumnID,
 | 
			
		||||
		PosterID:           issue.PosterID,
 | 
			
		||||
		AssigneeID:         issue.AssigneeID,
 | 
			
		||||
		MentionIDs:         mentionIDs,
 | 
			
		||||
 
 | 
			
		||||
@@ -72,10 +72,14 @@ func (c *HTTPClient) batch(ctx context.Context, operation string, objects []Poin
 | 
			
		||||
 | 
			
		||||
	url := fmt.Sprintf("%s/objects/batch", c.endpoint)
 | 
			
		||||
 | 
			
		||||
	// Original:  In some lfs server implementations, they require the ref attribute. #32838
 | 
			
		||||
	// `ref` is an "optional object describing the server ref that the objects belong to"
 | 
			
		||||
	// but some (incorrect) lfs servers require it, so maybe adding an empty ref here doesn't break the correct ones.
 | 
			
		||||
	// but some (incorrect) lfs servers like aliyun require it, so maybe adding an empty ref here doesn't break the correct ones.
 | 
			
		||||
	// https://github.com/git-lfs/git-lfs/blob/a32a02b44bf8a511aa14f047627c49e1a7fd5021/docs/api/batch.md?plain=1#L37
 | 
			
		||||
	request := &BatchRequest{operation, c.transferNames(), &Reference{}, objects}
 | 
			
		||||
	//
 | 
			
		||||
	// UPDATE: it can't use "empty ref" here because it breaks others like https://github.com/go-gitea/gitea/issues/33453
 | 
			
		||||
	request := &BatchRequest{operation, c.transferNames(), nil, objects}
 | 
			
		||||
 | 
			
		||||
	payload := new(bytes.Buffer)
 | 
			
		||||
	err := json.NewEncoder(payload).Encode(request)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,6 @@
 | 
			
		||||
package backend
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/base64"
 | 
			
		||||
	"fmt"
 | 
			
		||||
@@ -29,7 +28,7 @@ var Capabilities = []string{
 | 
			
		||||
	"locking",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var _ transfer.Backend = &GiteaBackend{}
 | 
			
		||||
var _ transfer.Backend = (*GiteaBackend)(nil)
 | 
			
		||||
 | 
			
		||||
// GiteaBackend is an adapter between git-lfs-transfer library and Gitea's internal LFS API
 | 
			
		||||
type GiteaBackend struct {
 | 
			
		||||
@@ -78,17 +77,17 @@ func (g *GiteaBackend) Batch(_ string, pointers []transfer.BatchItem, args trans
 | 
			
		||||
		headerAccept:            mimeGitLFS,
 | 
			
		||||
		headerContentType:       mimeGitLFS,
 | 
			
		||||
	}
 | 
			
		||||
	req := newInternalRequest(g.ctx, url, http.MethodPost, headers, bodyBytes)
 | 
			
		||||
	req := newInternalRequestLFS(g.ctx, url, http.MethodPost, headers, bodyBytes)
 | 
			
		||||
	resp, err := req.Response()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		g.logger.Log("http request error", err)
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	defer resp.Body.Close()
 | 
			
		||||
	if resp.StatusCode != http.StatusOK {
 | 
			
		||||
		g.logger.Log("http statuscode error", resp.StatusCode, statusCodeToErr(resp.StatusCode))
 | 
			
		||||
		return nil, statusCodeToErr(resp.StatusCode)
 | 
			
		||||
	}
 | 
			
		||||
	defer resp.Body.Close()
 | 
			
		||||
	respBytes, err := io.ReadAll(resp.Body)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		g.logger.Log("http read error", err)
 | 
			
		||||
@@ -158,8 +157,7 @@ func (g *GiteaBackend) Batch(_ string, pointers []transfer.BatchItem, args trans
 | 
			
		||||
	return pointers, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Download implements transfer.Backend. The returned reader must be closed by the
 | 
			
		||||
// caller.
 | 
			
		||||
// Download implements transfer.Backend. The returned reader must be closed by the caller.
 | 
			
		||||
func (g *GiteaBackend) Download(oid string, args transfer.Args) (io.ReadCloser, int64, error) {
 | 
			
		||||
	idMapStr, exists := args[argID]
 | 
			
		||||
	if !exists {
 | 
			
		||||
@@ -187,25 +185,25 @@ func (g *GiteaBackend) Download(oid string, args transfer.Args) (io.ReadCloser,
 | 
			
		||||
		headerGiteaInternalAuth: g.internalAuth,
 | 
			
		||||
		headerAccept:            mimeOctetStream,
 | 
			
		||||
	}
 | 
			
		||||
	req := newInternalRequest(g.ctx, url, http.MethodGet, headers, nil)
 | 
			
		||||
	req := newInternalRequestLFS(g.ctx, url, http.MethodGet, headers, nil)
 | 
			
		||||
	resp, err := req.Response()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, 0, err
 | 
			
		||||
		return nil, 0, fmt.Errorf("failed to get response: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
	// no need to close the body here by "defer resp.Body.Close()", see below
 | 
			
		||||
	if resp.StatusCode != http.StatusOK {
 | 
			
		||||
		return nil, 0, statusCodeToErr(resp.StatusCode)
 | 
			
		||||
	}
 | 
			
		||||
	defer resp.Body.Close()
 | 
			
		||||
	respBytes, err := io.ReadAll(resp.Body)
 | 
			
		||||
 | 
			
		||||
	respSize, err := strconv.ParseInt(resp.Header.Get("X-Gitea-LFS-Content-Length"), 10, 64)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, 0, err
 | 
			
		||||
		return nil, 0, fmt.Errorf("failed to parse content length: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
	respSize := int64(len(respBytes))
 | 
			
		||||
	respBuf := io.NopCloser(bytes.NewBuffer(respBytes))
 | 
			
		||||
	return respBuf, respSize, nil
 | 
			
		||||
	// transfer.Backend will check io.Closer interface and close this Body reader
 | 
			
		||||
	return resp.Body, respSize, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// StartUpload implements transfer.Backend.
 | 
			
		||||
// Upload implements transfer.Backend.
 | 
			
		||||
func (g *GiteaBackend) Upload(oid string, size int64, r io.Reader, args transfer.Args) error {
 | 
			
		||||
	idMapStr, exists := args[argID]
 | 
			
		||||
	if !exists {
 | 
			
		||||
@@ -234,15 +232,14 @@ func (g *GiteaBackend) Upload(oid string, size int64, r io.Reader, args transfer
 | 
			
		||||
		headerContentType:       mimeOctetStream,
 | 
			
		||||
		headerContentLength:     strconv.FormatInt(size, 10),
 | 
			
		||||
	}
 | 
			
		||||
	reqBytes, err := io.ReadAll(r)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	req := newInternalRequest(g.ctx, url, http.MethodPut, headers, reqBytes)
 | 
			
		||||
 | 
			
		||||
	req := newInternalRequestLFS(g.ctx, url, http.MethodPut, headers, nil)
 | 
			
		||||
	req.Body(r)
 | 
			
		||||
	resp, err := req.Response()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer resp.Body.Close()
 | 
			
		||||
	if resp.StatusCode != http.StatusOK {
 | 
			
		||||
		return statusCodeToErr(resp.StatusCode)
 | 
			
		||||
	}
 | 
			
		||||
@@ -284,11 +281,12 @@ func (g *GiteaBackend) Verify(oid string, size int64, args transfer.Args) (trans
 | 
			
		||||
		headerAccept:            mimeGitLFS,
 | 
			
		||||
		headerContentType:       mimeGitLFS,
 | 
			
		||||
	}
 | 
			
		||||
	req := newInternalRequest(g.ctx, url, http.MethodPost, headers, bodyBytes)
 | 
			
		||||
	req := newInternalRequestLFS(g.ctx, url, http.MethodPost, headers, bodyBytes)
 | 
			
		||||
	resp, err := req.Response()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return transfer.NewStatus(transfer.StatusInternalServerError), err
 | 
			
		||||
	}
 | 
			
		||||
	defer resp.Body.Close()
 | 
			
		||||
	if resp.StatusCode != http.StatusOK {
 | 
			
		||||
		return transfer.NewStatus(uint32(resp.StatusCode), http.StatusText(resp.StatusCode)), statusCodeToErr(resp.StatusCode)
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -50,7 +50,7 @@ func (g *giteaLockBackend) Create(path, refname string) (transfer.Lock, error) {
 | 
			
		||||
		headerAccept:            mimeGitLFS,
 | 
			
		||||
		headerContentType:       mimeGitLFS,
 | 
			
		||||
	}
 | 
			
		||||
	req := newInternalRequest(g.ctx, url, http.MethodPost, headers, bodyBytes)
 | 
			
		||||
	req := newInternalRequestLFS(g.ctx, url, http.MethodPost, headers, bodyBytes)
 | 
			
		||||
	resp, err := req.Response()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		g.logger.Log("http request error", err)
 | 
			
		||||
@@ -102,7 +102,7 @@ func (g *giteaLockBackend) Unlock(lock transfer.Lock) error {
 | 
			
		||||
		headerAccept:            mimeGitLFS,
 | 
			
		||||
		headerContentType:       mimeGitLFS,
 | 
			
		||||
	}
 | 
			
		||||
	req := newInternalRequest(g.ctx, url, http.MethodPost, headers, bodyBytes)
 | 
			
		||||
	req := newInternalRequestLFS(g.ctx, url, http.MethodPost, headers, bodyBytes)
 | 
			
		||||
	resp, err := req.Response()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		g.logger.Log("http request error", err)
 | 
			
		||||
@@ -185,7 +185,7 @@ func (g *giteaLockBackend) queryLocks(v url.Values) ([]transfer.Lock, string, er
 | 
			
		||||
		headerAccept:            mimeGitLFS,
 | 
			
		||||
		headerContentType:       mimeGitLFS,
 | 
			
		||||
	}
 | 
			
		||||
	req := newInternalRequest(g.ctx, url, http.MethodGet, headers, nil)
 | 
			
		||||
	req := newInternalRequestLFS(g.ctx, url, http.MethodGet, headers, nil)
 | 
			
		||||
	resp, err := req.Response()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		g.logger.Log("http request error", err)
 | 
			
		||||
 
 | 
			
		||||
@@ -5,15 +5,12 @@ package backend
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/httplib"
 | 
			
		||||
	"code.gitea.io/gitea/modules/proxyprotocol"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/modules/private"
 | 
			
		||||
 | 
			
		||||
	"github.com/charmbracelet/git-lfs-transfer/transfer"
 | 
			
		||||
)
 | 
			
		||||
@@ -89,53 +86,19 @@ func statusCodeToErr(code int) error {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newInternalRequest(ctx context.Context, url, method string, headers map[string]string, body []byte) *httplib.Request {
 | 
			
		||||
	req := httplib.NewRequest(url, method).
 | 
			
		||||
		SetContext(ctx).
 | 
			
		||||
		SetTimeout(10*time.Second, 60*time.Second).
 | 
			
		||||
		SetTLSClientConfig(&tls.Config{
 | 
			
		||||
			InsecureSkipVerify: true,
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
	if setting.Protocol == setting.HTTPUnix {
 | 
			
		||||
		req.SetTransport(&http.Transport{
 | 
			
		||||
			DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
 | 
			
		||||
				var d net.Dialer
 | 
			
		||||
				conn, err := d.DialContext(ctx, "unix", setting.HTTPAddr)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return conn, err
 | 
			
		||||
				}
 | 
			
		||||
				if setting.LocalUseProxyProtocol {
 | 
			
		||||
					if err = proxyprotocol.WriteLocalHeader(conn); err != nil {
 | 
			
		||||
						_ = conn.Close()
 | 
			
		||||
						return nil, err
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				return conn, err
 | 
			
		||||
			},
 | 
			
		||||
		})
 | 
			
		||||
	} else if setting.LocalUseProxyProtocol {
 | 
			
		||||
		req.SetTransport(&http.Transport{
 | 
			
		||||
			DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
 | 
			
		||||
				var d net.Dialer
 | 
			
		||||
				conn, err := d.DialContext(ctx, network, address)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return conn, err
 | 
			
		||||
				}
 | 
			
		||||
				if err = proxyprotocol.WriteLocalHeader(conn); err != nil {
 | 
			
		||||
					_ = conn.Close()
 | 
			
		||||
					return nil, err
 | 
			
		||||
				}
 | 
			
		||||
				return conn, err
 | 
			
		||||
			},
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
func newInternalRequestLFS(ctx context.Context, url, method string, headers map[string]string, body any) *httplib.Request {
 | 
			
		||||
	req := private.NewInternalRequest(ctx, url, method)
 | 
			
		||||
	for k, v := range headers {
 | 
			
		||||
		req.Header(k, v)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	req.Body(body)
 | 
			
		||||
 | 
			
		||||
	switch body := body.(type) {
 | 
			
		||||
	case nil: // do nothing
 | 
			
		||||
	case []byte:
 | 
			
		||||
		req.Body(body) // []byte
 | 
			
		||||
	case io.Reader:
 | 
			
		||||
		req.Body(body) // io.Reader or io.ReadCloser
 | 
			
		||||
	default:
 | 
			
		||||
		panic(fmt.Sprintf("unsupported request body type %T", body))
 | 
			
		||||
	}
 | 
			
		||||
	return req
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -13,10 +13,9 @@ import (
 | 
			
		||||
type Event struct {
 | 
			
		||||
	Time time.Time
 | 
			
		||||
 | 
			
		||||
	GoroutinePid string
 | 
			
		||||
	Caller       string
 | 
			
		||||
	Filename     string
 | 
			
		||||
	Line         int
 | 
			
		||||
	Caller   string
 | 
			
		||||
	Filename string
 | 
			
		||||
	Line     int
 | 
			
		||||
 | 
			
		||||
	Level Level
 | 
			
		||||
 | 
			
		||||
@@ -218,17 +217,16 @@ func EventFormatTextMessage(mode *WriterMode, event *Event, msgFormat string, ms
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if flags&Lgopid == Lgopid {
 | 
			
		||||
		if event.GoroutinePid != "" {
 | 
			
		||||
			buf = append(buf, '[')
 | 
			
		||||
			if mode.Colorize {
 | 
			
		||||
				buf = append(buf, ColorBytes(FgHiYellow)...)
 | 
			
		||||
			}
 | 
			
		||||
			buf = append(buf, event.GoroutinePid...)
 | 
			
		||||
			if mode.Colorize {
 | 
			
		||||
				buf = append(buf, resetBytes...)
 | 
			
		||||
			}
 | 
			
		||||
			buf = append(buf, ']', ' ')
 | 
			
		||||
		deprecatedGoroutinePid := "no-gopid" // use a dummy value to avoid breaking the log format
 | 
			
		||||
		buf = append(buf, '[')
 | 
			
		||||
		if mode.Colorize {
 | 
			
		||||
			buf = append(buf, ColorBytes(FgHiYellow)...)
 | 
			
		||||
		}
 | 
			
		||||
		buf = append(buf, deprecatedGoroutinePid...)
 | 
			
		||||
		if mode.Colorize {
 | 
			
		||||
			buf = append(buf, resetBytes...)
 | 
			
		||||
		}
 | 
			
		||||
		buf = append(buf, ']', ' ')
 | 
			
		||||
	}
 | 
			
		||||
	buf = append(buf, msg...)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -24,34 +24,32 @@ func TestItoa(t *testing.T) {
 | 
			
		||||
func TestEventFormatTextMessage(t *testing.T) {
 | 
			
		||||
	res := EventFormatTextMessage(&WriterMode{Prefix: "[PREFIX] ", Colorize: false, Flags: Flags{defined: true, flags: 0xffffffff}},
 | 
			
		||||
		&Event{
 | 
			
		||||
			Time:         time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC),
 | 
			
		||||
			Caller:       "caller",
 | 
			
		||||
			Filename:     "filename",
 | 
			
		||||
			Line:         123,
 | 
			
		||||
			GoroutinePid: "pid",
 | 
			
		||||
			Level:        ERROR,
 | 
			
		||||
			Stacktrace:   "stacktrace",
 | 
			
		||||
			Time:       time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC),
 | 
			
		||||
			Caller:     "caller",
 | 
			
		||||
			Filename:   "filename",
 | 
			
		||||
			Line:       123,
 | 
			
		||||
			Level:      ERROR,
 | 
			
		||||
			Stacktrace: "stacktrace",
 | 
			
		||||
		},
 | 
			
		||||
		"msg format: %v %v", "arg0", NewColoredValue("arg1", FgBlue),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	assert.Equal(t, `[PREFIX] 2020/01/02 03:04:05.000000 filename:123:caller [E] [pid] msg format: arg0 arg1
 | 
			
		||||
	assert.Equal(t, `[PREFIX] 2020/01/02 03:04:05.000000 filename:123:caller [E] [no-gopid] msg format: arg0 arg1
 | 
			
		||||
	stacktrace
 | 
			
		||||
 | 
			
		||||
`, string(res))
 | 
			
		||||
 | 
			
		||||
	res = EventFormatTextMessage(&WriterMode{Prefix: "[PREFIX] ", Colorize: true, Flags: Flags{defined: true, flags: 0xffffffff}},
 | 
			
		||||
		&Event{
 | 
			
		||||
			Time:         time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC),
 | 
			
		||||
			Caller:       "caller",
 | 
			
		||||
			Filename:     "filename",
 | 
			
		||||
			Line:         123,
 | 
			
		||||
			GoroutinePid: "pid",
 | 
			
		||||
			Level:        ERROR,
 | 
			
		||||
			Stacktrace:   "stacktrace",
 | 
			
		||||
			Time:       time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC),
 | 
			
		||||
			Caller:     "caller",
 | 
			
		||||
			Filename:   "filename",
 | 
			
		||||
			Line:       123,
 | 
			
		||||
			Level:      ERROR,
 | 
			
		||||
			Stacktrace: "stacktrace",
 | 
			
		||||
		},
 | 
			
		||||
		"msg format: %v %v", "arg0", NewColoredValue("arg1", FgBlue),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	assert.Equal(t, "[PREFIX] \x1b[36m2020/01/02 03:04:05.000000 \x1b[0m\x1b[32mfilename:123:\x1b[32mcaller\x1b[0m \x1b[1;31m[E]\x1b[0m [\x1b[93mpid\x1b[0m] msg format: arg0 \x1b[34marg1\x1b[0m\n\tstacktrace\n\n", string(res))
 | 
			
		||||
	assert.Equal(t, "[PREFIX] \x1b[36m2020/01/02 03:04:05.000000 \x1b[0m\x1b[32mfilename:123:\x1b[32mcaller\x1b[0m \x1b[1;31m[E]\x1b[0m [\x1b[93mno-gopid\x1b[0m] msg format: arg0 \x1b[34marg1\x1b[0m\n\tstacktrace\n\n", string(res))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -30,7 +30,7 @@ const (
 | 
			
		||||
	LUTC                              // if Ldate or Ltime is set, use UTC rather than the local time zone
 | 
			
		||||
	Llevelinitial                     // Initial character of the provided level in brackets, eg. [I] for info
 | 
			
		||||
	Llevel                            // Provided level in brackets [INFO]
 | 
			
		||||
	Lgopid                            // the Goroutine-PID of the context
 | 
			
		||||
	Lgopid                            // the Goroutine-PID of the context, deprecated and it is always a const value
 | 
			
		||||
 | 
			
		||||
	Lmedfile  = Lshortfile | Llongfile                                    // last 20 characters of the filename
 | 
			
		||||
	LstdFlags = Ldate | Ltime | Lmedfile | Lshortfuncname | Llevelinitial // default
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user