mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-03 08:02:36 +09:00 
			
		
		
		
	Compare commits
	
		
			104 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					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 | 
							
								
								
									
										460
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										460
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -4,6 +4,466 @@ 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
 | 
					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).
 | 
					been added to each release, please refer to the [blog](https://blog.gitea.com).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## [1.23.3](https://github.com/go-gitea/gitea/releases/tag/1.23.2) - 2025-02-05
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* 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
 | 
					## [1.22.4](https://github.com/go-gitea/gitea/releases/tag/v1.22.4) - 2024-11-14
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* SECURITY
 | 
					* SECURITY
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
# Build stage
 | 
					# 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
 | 
					ARG GOPROXY
 | 
				
			||||||
ENV GOPROXY=${GOPROXY:-direct}
 | 
					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
 | 
					              /go/src/code.gitea.io/gitea/environment-to-ini
 | 
				
			||||||
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
 | 
					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"
 | 
					LABEL maintainer="maintainers@gitea.io"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
EXPOSE 22 3000
 | 
					EXPOSE 22 3000
 | 
				
			||||||
@@ -78,7 +78,7 @@ ENV GITEA_CUSTOM=/data/gitea
 | 
				
			|||||||
VOLUME ["/data"]
 | 
					VOLUME ["/data"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ENTRYPOINT ["/usr/bin/entrypoint"]
 | 
					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 /tmp/local /
 | 
				
			||||||
COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
 | 
					COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
# Build stage
 | 
					# 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
 | 
					ARG GOPROXY
 | 
				
			||||||
ENV GOPROXY=${GOPROXY:-direct}
 | 
					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
 | 
					              /go/src/code.gitea.io/gitea/environment-to-ini
 | 
				
			||||||
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
 | 
					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"
 | 
					LABEL maintainer="maintainers@gitea.io"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
EXPOSE 2222 3000
 | 
					EXPOSE 2222 3000
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										18
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								Makefile
									
									
									
									
									
								
							@@ -26,17 +26,17 @@ COMMA := ,
 | 
				
			|||||||
XGO_VERSION := go-1.23.x
 | 
					XGO_VERSION := go-1.23.x
 | 
				
			||||||
 | 
					
 | 
				
			||||||
AIR_PACKAGE ?= github.com/air-verse/air@v1
 | 
					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
 | 
					GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.7.0
 | 
				
			||||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.2
 | 
					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
 | 
					GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.12
 | 
				
			||||||
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.5.1
 | 
					MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.6.0
 | 
				
			||||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.31.0
 | 
					SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.31.0
 | 
				
			||||||
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
 | 
					XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
 | 
				
			||||||
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1
 | 
					GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1
 | 
				
			||||||
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1
 | 
					GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1
 | 
				
			||||||
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@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_IMAGE ?= gitea/gitea
 | 
				
			||||||
DOCKER_TAG ?= latest
 | 
					DOCKER_TAG ?= latest
 | 
				
			||||||
@@ -806,22 +806,22 @@ $(DIST_DIRS):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
.PHONY: release-windows
 | 
					.PHONY: release-windows
 | 
				
			||||||
release-windows: | $(DIST_DIRS)
 | 
					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)))
 | 
					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
 | 
					endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.PHONY: release-linux
 | 
					.PHONY: release-linux
 | 
				
			||||||
release-linux: | $(DIST_DIRS)
 | 
					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
 | 
					.PHONY: release-darwin
 | 
				
			||||||
release-darwin: | $(DIST_DIRS)
 | 
					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
 | 
					.PHONY: release-freebsd
 | 
				
			||||||
release-freebsd: | $(DIST_DIRS)
 | 
					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
 | 
					.PHONY: release-copy
 | 
				
			||||||
release-copy: | $(DIST_DIRS)
 | 
					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 {
 | 
					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 {
 | 
						if err := argsSet(c, "email"); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,7 +18,7 @@ import (
 | 
				
			|||||||
var CmdMigrate = &cli.Command{
 | 
					var CmdMigrate = &cli.Command{
 | 
				
			||||||
	Name:        "migrate",
 | 
						Name:        "migrate",
 | 
				
			||||||
	Usage:       "Migrate the database",
 | 
						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,
 | 
						Action:      runMigrate,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										11
									
								
								cmd/web.go
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								cmd/web.go
									
									
									
									
									
								
							@@ -12,6 +12,7 @@ import (
 | 
				
			|||||||
	"path/filepath"
 | 
						"path/filepath"
 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_ "net/http/pprof" // Used for debugging if enabled and a web server is running
 | 
						_ "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("* CustomPath: %s", setting.CustomPath)
 | 
				
			||||||
	log.Info("* ConfigFile: %s", setting.CustomConf)
 | 
						log.Info("* ConfigFile: %s", setting.CustomConf)
 | 
				
			||||||
	log.Info("%s", msg) // show startup message
 | 
						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 {
 | 
					func serveInstall(ctx *cli.Context) error {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -54,8 +54,10 @@ func runACME(listenAddr string, m http.Handler) error {
 | 
				
			|||||||
		altTLSALPNPort = p
 | 
							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 := certmagic.NewDefault()
 | 
				
			||||||
	magic.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory}
 | 
					 | 
				
			||||||
	// Try to use private CA root if provided, otherwise defaults to system's trust
 | 
						// Try to use private CA root if provided, otherwise defaults to system's trust
 | 
				
			||||||
	var certPool *x509.CertPool
 | 
						var certPool *x509.CertPool
 | 
				
			||||||
	if setting.AcmeCARoot != "" {
 | 
						if setting.AcmeCARoot != "" {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1482,6 +1482,10 @@ LEVEL = Info
 | 
				
			|||||||
;REPO_INDEXER_EXCLUDE =
 | 
					;REPO_INDEXER_EXCLUDE =
 | 
				
			||||||
;;
 | 
					;;
 | 
				
			||||||
;MAX_FILE_SIZE = 1048576
 | 
					;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
 | 
					if [ $# -gt 0 ]; then
 | 
				
			||||||
    exec "$@"
 | 
					    exec "$@"
 | 
				
			||||||
else
 | 
					else
 | 
				
			||||||
    exec /bin/s6-svscan /etc/s6
 | 
					    exec /usr/bin/s6-svscan /etc/s6
 | 
				
			||||||
fi
 | 
					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/azcore v1.16.0
 | 
				
			||||||
	github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1
 | 
						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/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/PuerkitoBio/goquery v1.10.0
 | 
				
			||||||
	github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.3
 | 
						github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.3
 | 
				
			||||||
	github.com/alecthomas/chroma/v2 v2.14.0
 | 
						github.com/alecthomas/chroma/v2 v2.15.0
 | 
				
			||||||
	github.com/aws/aws-sdk-go v1.55.5
 | 
					 | 
				
			||||||
	github.com/aws/aws-sdk-go-v2/credentials v1.17.42
 | 
						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/aws/aws-sdk-go-v2/service/codecommit v1.27.3
 | 
				
			||||||
	github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
 | 
						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-chi/cors v1.2.1
 | 
				
			||||||
	github.com/go-co-op/gocron v1.37.0
 | 
						github.com/go-co-op/gocron v1.37.0
 | 
				
			||||||
	github.com/go-enry/go-enry/v2 v2.9.1
 | 
						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-billy/v5 v5.6.1
 | 
				
			||||||
	github.com/go-git/go-git/v5 v5.12.0
 | 
						github.com/go-git/go-git/v5 v5.13.1
 | 
				
			||||||
	github.com/go-ldap/ldap/v3 v3.4.8
 | 
						github.com/go-ldap/ldap/v3 v3.4.8
 | 
				
			||||||
	github.com/go-redsync/redsync/v4 v4.13.0
 | 
						github.com/go-redsync/redsync/v4 v4.13.0
 | 
				
			||||||
	github.com/go-sql-driver/mysql v1.8.1
 | 
						github.com/go-sql-driver/mysql v1.8.1
 | 
				
			||||||
	github.com/go-swagger/go-swagger v0.31.0
 | 
						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/go-webauthn/webauthn v0.11.2
 | 
				
			||||||
	github.com/gobwas/glob v0.2.3
 | 
						github.com/gobwas/glob v0.2.3
 | 
				
			||||||
	github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
 | 
						github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
 | 
				
			||||||
@@ -81,7 +79,6 @@ require (
 | 
				
			|||||||
	github.com/jhillyerd/enmime v1.3.0
 | 
						github.com/jhillyerd/enmime v1.3.0
 | 
				
			||||||
	github.com/json-iterator/go v1.1.12
 | 
						github.com/json-iterator/go v1.1.12
 | 
				
			||||||
	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
 | 
						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/compress v1.17.11
 | 
				
			||||||
	github.com/klauspost/cpuid/v2 v2.2.8
 | 
						github.com/klauspost/cpuid/v2 v2.2.8
 | 
				
			||||||
	github.com/lib/pq v1.10.9
 | 
						github.com/lib/pq v1.10.9
 | 
				
			||||||
@@ -109,7 +106,7 @@ require (
 | 
				
			|||||||
	github.com/sassoftware/go-rpmutils v0.4.0
 | 
						github.com/sassoftware/go-rpmutils v0.4.0
 | 
				
			||||||
	github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3
 | 
						github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3
 | 
				
			||||||
	github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92
 | 
						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/syndtr/goleveldb v1.0.0
 | 
				
			||||||
	github.com/tstranex/u2f v1.0.0
 | 
						github.com/tstranex/u2f v1.0.0
 | 
				
			||||||
	github.com/ulikunitz/xz v0.5.12
 | 
						github.com/ulikunitz/xz v0.5.12
 | 
				
			||||||
@@ -121,14 +118,14 @@ require (
 | 
				
			|||||||
	github.com/yuin/goldmark v1.7.8
 | 
						github.com/yuin/goldmark v1.7.8
 | 
				
			||||||
	github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
 | 
						github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
 | 
				
			||||||
	github.com/yuin/goldmark-meta v1.1.0
 | 
						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/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/oauth2 v0.23.0
 | 
				
			||||||
	golang.org/x/sync v0.10.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/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/grpc v1.67.1
 | 
				
			||||||
	google.golang.org/protobuf v1.35.1
 | 
						google.golang.org/protobuf v1.35.1
 | 
				
			||||||
	gopkg.in/ini.v1 v1.67.0
 | 
						gopkg.in/ini.v1 v1.67.0
 | 
				
			||||||
@@ -145,8 +142,6 @@ require (
 | 
				
			|||||||
	filippo.io/edwards25519 v1.1.0 // indirect
 | 
						filippo.io/edwards25519 v1.1.0 // indirect
 | 
				
			||||||
	git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // 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/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/DataDog/zstd v1.5.6 // indirect
 | 
				
			||||||
	github.com/Masterminds/goutils v1.1.1 // indirect
 | 
						github.com/Masterminds/goutils v1.1.1 // indirect
 | 
				
			||||||
	github.com/Masterminds/semver/v3 v3.3.0 // 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/gomemcached v0.3.2 // indirect
 | 
				
			||||||
	github.com/couchbase/goutils v0.1.2 // indirect
 | 
						github.com/couchbase/goutils v0.1.2 // indirect
 | 
				
			||||||
	github.com/cpuguy83/go-md2man/v2 v2.0.5 // 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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
 | 
				
			||||||
	github.com/davidmz/go-pageant v1.0.2 // indirect
 | 
						github.com/davidmz/go-pageant v1.0.2 // indirect
 | 
				
			||||||
	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // 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-ap/errors v0.0.0-20240910140019-1e9d33cc1568 // indirect
 | 
				
			||||||
	github.com/go-asn1-ber/asn1-ber v1.5.7 // 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-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-fed/httpsig v1.1.1-0.20201223112313-55836744818e // indirect
 | 
				
			||||||
	github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
 | 
						github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
 | 
				
			||||||
	github.com/go-ini/ini v1.67.0 // 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/civil v0.0.0-20220223132316-b832511892a9 // indirect
 | 
				
			||||||
	github.com/golang-sql/sqlexp v0.1.0 // indirect
 | 
						github.com/golang-sql/sqlexp v0.1.0 // indirect
 | 
				
			||||||
	github.com/golang/geo v0.0.0-20230421003525-6adc56603217 // 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/protobuf v1.5.4 // indirect
 | 
				
			||||||
	github.com/golang/snappy v0.0.4 // indirect
 | 
						github.com/golang/snappy v0.0.4 // indirect
 | 
				
			||||||
	github.com/google/btree v1.1.3 // 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/copystructure v1.2.0 // indirect
 | 
				
			||||||
	github.com/mitchellh/mapstructure v1.5.0 // indirect
 | 
						github.com/mitchellh/mapstructure v1.5.0 // indirect
 | 
				
			||||||
	github.com/mitchellh/reflectwalk v1.0.2 // 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/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 | 
				
			||||||
	github.com/modern-go/reflect2 v1.0.2 // indirect
 | 
						github.com/modern-go/reflect2 v1.0.2 // indirect
 | 
				
			||||||
	github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // 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/oklog/ulid v1.3.1 // indirect
 | 
				
			||||||
	github.com/olekukonko/tablewriter v0.0.5 // indirect
 | 
						github.com/olekukonko/tablewriter v0.0.5 // indirect
 | 
				
			||||||
	github.com/onsi/ginkgo v1.16.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/pelletier/go-toml/v2 v2.2.3 // indirect
 | 
				
			||||||
	github.com/pierrec/lz4/v4 v4.1.21 // 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/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
 | 
				
			||||||
	github.com/prometheus/client_model v0.6.1 // indirect
 | 
						github.com/prometheus/client_model v0.6.1 // indirect
 | 
				
			||||||
	github.com/prometheus/common v0.60.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/russross/blackfriday/v2 v2.1.0 // indirect
 | 
				
			||||||
	github.com/sagikazarmark/locafero v0.6.0 // indirect
 | 
						github.com/sagikazarmark/locafero v0.6.0 // indirect
 | 
				
			||||||
	github.com/sagikazarmark/slog-shim v0.1.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/shopspring/decimal v1.4.0 // indirect
 | 
				
			||||||
	github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect
 | 
						github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect
 | 
				
			||||||
	github.com/sirupsen/logrus v1.9.3 // indirect
 | 
						github.com/sirupsen/logrus v1.9.3 // indirect
 | 
				
			||||||
@@ -310,13 +302,11 @@ require (
 | 
				
			|||||||
	github.com/zeebo/blake3 v0.2.4 // indirect
 | 
						github.com/zeebo/blake3 v0.2.4 // indirect
 | 
				
			||||||
	go.etcd.io/bbolt v1.3.11 // indirect
 | 
						go.etcd.io/bbolt v1.3.11 // indirect
 | 
				
			||||||
	go.mongodb.org/mongo-driver v1.17.1 // 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/atomic v1.11.0 // indirect
 | 
				
			||||||
	go.uber.org/multierr v1.11.0 // indirect
 | 
						go.uber.org/multierr v1.11.0 // indirect
 | 
				
			||||||
	go.uber.org/zap v1.27.0 // indirect
 | 
						go.uber.org/zap v1.27.0 // indirect
 | 
				
			||||||
	golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // 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
 | 
						golang.org/x/time v0.7.0 // indirect
 | 
				
			||||||
	google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect
 | 
						google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect
 | 
				
			||||||
	gopkg.in/warnings.v0 v0.1.2 // 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 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
 | 
				
			||||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
 | 
					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/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 h1:LbEglqepa/ipmmQJUDnSsfvA8e8IStVcGaFWDuxvGOY=
 | 
				
			||||||
github.com/DataDog/zstd v1.5.6/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
 | 
					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=
 | 
					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.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
 | 
				
			||||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
 | 
					github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
 | 
				
			||||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
 | 
					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.1.4 h1:G5U5asvD5N/6/36oIw3k2bOfBn5XVcZrb7PBjzzKKoE=
 | 
				
			||||||
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
 | 
					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 h1:6fiXdLuUvYs2OJSvNRqlNPoBm6YABE226xrbavY5Wv4=
 | 
				
			||||||
github.com/PuerkitoBio/goquery v1.10.0/go.mod h1:TjZZl68Q3eGHNBA8CWaxAN7rOU1EbDz3CWuolcO5Yu4=
 | 
					github.com/PuerkitoBio/goquery v1.10.0/go.mod h1:TjZZl68Q3eGHNBA8CWaxAN7rOU1EbDz3CWuolcO5Yu4=
 | 
				
			||||||
github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
 | 
					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/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 h1:BP0HiyNT3AQEYi+if3wkRcIdQFHtsw6xX3Kx0glckgA=
 | 
				
			||||||
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.3/go.mod h1:hMNtySovKkn2gdDuLqnqveP+mfhUSaBdoBcr2I7Zt0E=
 | 
					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.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
 | 
				
			||||||
github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
 | 
					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.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
 | 
				
			||||||
github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E=
 | 
					github.com/alecthomas/chroma/v2 v2.15.0 h1:LxXTQHFoYrstG2nnV9y2X5O94sOBzf0CIUpSTbpxvMc=
 | 
				
			||||||
github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I=
 | 
					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.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 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
 | 
				
			||||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
 | 
					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/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 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
 | 
				
			||||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
 | 
					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 h1:T0dRlFBKcdaUPGNtkBSwHZxrtis8CQU17UpNBZYd0wk=
 | 
				
			||||||
github.com/aws/aws-sdk-go-v2 v1.32.3/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo=
 | 
					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=
 | 
					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/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 h1:IGuJjboHjuMLWOGsKZKNxbbn41emOLiHzXPmQZk31fk=
 | 
				
			||||||
github.com/buildkite/terminal-to-html/v3 v3.16.3/go.mod h1:r/J7cC9c3EzBzP3/wDz0RJLPwv5PUAMp+KF2w+ntMc0=
 | 
					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 h1:e7VobB8rffHv8ZZpSiZtEwnLDHUwLVYLWzWSa1FfKI0=
 | 
				
			||||||
github.com/caddyserver/certmagic v0.21.4/go.mod h1:swUXjQ1T9ZtMv95qj7/InJvWLXURU85r+CfG0T+ZbDE=
 | 
					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=
 | 
					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/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
 | 
				
			||||||
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
 | 
					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/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 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys=
 | 
				
			||||||
github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
 | 
					github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
 | 
				
			||||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 | 
					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 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
 | 
				
			||||||
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 | 
					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/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.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM=
 | 
				
			||||||
github.com/cyphar/filepath-securejoin v0.3.4/go.mod h1:8s/MCNJREmFK0H02MF6Ihv1nakJe4L/w3WZLHNkvlYM=
 | 
					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.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.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 h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
 | 
				
			||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
					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 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0=
 | 
				
			||||||
github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE=
 | 
					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 h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
 | 
				
			||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
 | 
					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=
 | 
					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 h1:dKG8sc7n321deIVRcQtwlMNoBEra7j0qQ8RwxO8RN0w=
 | 
				
			||||||
github.com/editorconfig/editorconfig-core-go/v2 v2.6.2/go.mod h1:7dvD3GCm7eBw53xZ/lsiq72LqobdMg3ITbMBxnmJmqY=
 | 
					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/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 v1.2.3 h1:xwIyKHbaP5yfT6O9KIeYJR5549MXRQkoQMRXGztz8YQ=
 | 
				
			||||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
 | 
					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 h1:+s9ZjMEjOB8NzZMVTM3cCenz2JrQIGGo5j1df19WjTA=
 | 
				
			||||||
github.com/emersion/go-imap v1.2.1/go.mod h1:Qlx1FSx2FTxjnjWpIlVNEuX+ylerZQNFE5NsmKFSejY=
 | 
					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=
 | 
					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-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 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo=
 | 
				
			||||||
github.com/go-enry/go-oniguruma v1.2.1/go.mod h1:bWDhYP+S6xZQgiRL7wlTScFYBe023B6ilRZbCAD5Hf4=
 | 
					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 h1:oRq/fiirun5HqlEWMLIcDmLpIELlG4iGbd0s8iqgPi8=
 | 
				
			||||||
github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
 | 
					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 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
 | 
				
			||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
 | 
					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.1 h1:u+dcrgaguSSkbjzHwelEjc0Yj300NUevrrPphk/SoRA=
 | 
				
			||||||
github.com/go-git/go-billy/v5 v5.6.0/go.mod h1:sFDq7xD3fn3E0GOwUSZqHo9lrkmx8xJhA0ZrfvjBRGM=
 | 
					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 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-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.13.1 h1:DAQ9APonnlvSWpvolXWIuV6Q6zXy2wHbN4cVlNR5Q+M=
 | 
				
			||||||
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/go.mod h1:qryJB4cSBoq3FRoBRf5A77joojuBcmPJ0qu3XXXVixc=
 | 
				
			||||||
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
 | 
					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-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
 | 
				
			||||||
github.com/go-ldap/ldap/v3 v3.4.8 h1:loKJyspcRezt2Q3ZRMq2p/0v8iOurlmeXDPw6fikSvQ=
 | 
					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-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 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
 | 
				
			||||||
github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
 | 
					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 h1:Fgx0/wlmkClTKlnOsdOQ+K5HcHDsDcYIvtYmfhEOSUc=
 | 
				
			||||||
github.com/go-webauthn/webauthn v0.11.2/go.mod h1:aOtudaF94pM71g3jRwTYYwQTG1KyTILTcZqN1srkmD0=
 | 
					github.com/go-webauthn/webauthn v0.11.2/go.mod h1:aOtudaF94pM71g3jRwTYYwQTG1KyTILTcZqN1srkmD0=
 | 
				
			||||||
github.com/go-webauthn/x v0.1.15 h1:eG1OhggBJTkDE8gUeOlGRbRe8E/PSVG26YG4AyFbwkU=
 | 
					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/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 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
 | 
				
			||||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
 | 
					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 h1:3BSP1Tbs2djlpprl7wCLuiqMaUh5SJkkzI2gDs+FgLs=
 | 
				
			||||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
 | 
					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=
 | 
					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-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 h1:HKlyj6in2JV6wVkmQ4XmG/EIm+SCYlPZ+V4GWit7Z+I=
 | 
				
			||||||
github.com/golang/geo v0.0.0-20230421003525-6adc56603217/go.mod h1:8wI0hitZ3a1IxZfeH3/5I97CI8i5cLGsYe7xNhQGs9U=
 | 
					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-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
 | 
				
			||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 | 
					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.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.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 | 
				
			||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
 | 
					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-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.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
 | 
				
			||||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 | 
					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 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
 | 
				
			||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
 | 
					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=
 | 
					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/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/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
 | 
				
			||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 | 
					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 h1:iCHtR9CQyktQ5+f3dMVZfwD2KWJUgm7M0gdL9NGr8KA=
 | 
				
			||||||
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
 | 
					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=
 | 
					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/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 h1:LV5kzfLidiOr8qRGIpYYmUZCnhrPbcFAnAFUnWn99rw=
 | 
				
			||||||
github.com/jhillyerd/enmime v1.3.0/go.mod h1:6c6jg5HdRRV2FtvVL69LjiX1M8oE0xDX9VEhV3oy4gs=
 | 
					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 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
 | 
				
			||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
 | 
					github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
 | 
				
			||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
 | 
					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/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 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
 | 
				
			||||||
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
 | 
					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.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
 | 
				
			||||||
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
 | 
					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 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
 | 
				
			||||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
 | 
					github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
 | 
				
			||||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
 | 
					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/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 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
 | 
				
			||||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
 | 
					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-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 h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
 | 
				
			||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 | 
					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 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
 | 
				
			||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
 | 
					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 h1:j2kD3MT1z4PXCiUllUJF9mWUESr9TWKS7iEKsQ/IipM=
 | 
				
			||||||
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM=
 | 
					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=
 | 
					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 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
 | 
				
			||||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
 | 
					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/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 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 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
 | 
				
			||||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
 | 
					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.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 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
 | 
				
			||||||
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
 | 
					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.1 h1:Dh2GYdpJnO84lIw0LJwTFXjcNbasP/bklicSznyAaPI=
 | 
				
			||||||
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
 | 
					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 h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
 | 
				
			||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
 | 
					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=
 | 
					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/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 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg=
 | 
				
			||||||
github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI=
 | 
					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/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.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
 | 
				
			||||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
 | 
					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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 | 
				
			||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 | 
					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.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.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.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.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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 | 
				
			||||||
github.com/stretchr/testify v1.8.2/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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
 | 
				
			||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 | 
					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 h1:QVqDTf3h2WHt08YuiTGPZLls0Wq99X9bWd0Q5ZSBesM=
 | 
				
			||||||
github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKNihBbwlX8dLpwxCl3+HnXKV/R0e+sRLd9C8=
 | 
					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 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
 | 
				
			||||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
 | 
					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 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
 | 
				
			||||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
 | 
					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/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 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ=
 | 
				
			||||||
github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM=
 | 
					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/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 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
 | 
				
			||||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
 | 
					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-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 h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
 | 
				
			||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
 | 
					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/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 h1:DPRd3iPO5F6O5zX2e62XpVAbPT6wV51cuucH0z9g3js=
 | 
				
			||||||
github.com/yohcop/openid-go v1.0.1/go.mod h1:b/AvD03P0KHj4yuihb+VtLD6bYYgsy0zqBzPCRjkCNs=
 | 
					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.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.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.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 | 
				
			||||||
github.com/yuin/goldmark v1.4.15/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.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
 | 
				
			||||||
go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0=
 | 
					go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0=
 | 
				
			||||||
go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I=
 | 
					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 h1:Wic5cJIwJgSpBhe3lx3+/RybR5PiYRMpVFgO7cOHyIM=
 | 
				
			||||||
go.mongodb.org/mongo-driver v1.17.1/go.mod h1:wwWm/+BuOddhcq3n68LKRmgk2wXzmF6s0SFOa0GINL4=
 | 
					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.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 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
 | 
				
			||||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
 | 
					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-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-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.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.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.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.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.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
 | 
				
			||||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
 | 
					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.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
 | 
				
			||||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
 | 
					golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
 | 
				
			||||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
 | 
					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 h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
 | 
				
			||||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
 | 
					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=
 | 
					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.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.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.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 | 
				
			||||||
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
 | 
					golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
 | 
				
			||||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
 | 
					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-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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 | 
				
			||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
					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-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-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.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.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.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.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
 | 
				
			||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 | 
					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.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
 | 
				
			||||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
 | 
					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.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
 | 
				
			||||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
 | 
					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.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
 | 
				
			||||||
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
 | 
					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 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
 | 
				
			||||||
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
 | 
					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=
 | 
					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-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-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-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.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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
				
			||||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
 | 
					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-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-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.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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
golang.org/x/sys v0.6.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=
 | 
					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.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.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.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 | 
				
			||||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
 | 
					golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
 | 
				
			||||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 | 
					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/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-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.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.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.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
 | 
				
			||||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
 | 
					golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
 | 
				
			||||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
 | 
					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.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.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
 | 
				
			||||||
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
 | 
					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.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
 | 
				
			||||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
 | 
					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.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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
				
			||||||
golang.org/x/text v0.3.6/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.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.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.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.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 | 
				
			||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 | 
					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-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-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-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-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-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.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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 | 
				
			||||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
 | 
					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.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.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
 | 
				
			||||||
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
 | 
					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-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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
				
			||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/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.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.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
 | 
				
			||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 | 
					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 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
 | 
				
			||||||
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
 | 
					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=
 | 
					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"
 | 
				
			||||||
@@ -69,7 +69,7 @@ func TestAggregateJobStatus(t *testing.T) {
 | 
				
			|||||||
		{[]Status{StatusFailure, StatusBlocked}, StatusFailure},
 | 
							{[]Status{StatusFailure, StatusBlocked}, StatusFailure},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// skipped with other status
 | 
							// 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}, StatusSkipped},
 | 
				
			||||||
		{[]Status{StatusSkipped, StatusSuccess}, StatusSuccess},
 | 
							{[]Status{StatusSkipped, StatusSuccess}, StatusSuccess},
 | 
				
			||||||
		{[]Status{StatusSkipped, StatusFailure}, StatusFailure},
 | 
							{[]Status{StatusSkipped, StatusFailure}, StatusFailure},
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,7 @@ import (
 | 
				
			|||||||
	"code.gitea.io/gitea/models/db"
 | 
						"code.gitea.io/gitea/models/db"
 | 
				
			||||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
						repo_model "code.gitea.io/gitea/models/repo"
 | 
				
			||||||
	user_model "code.gitea.io/gitea/models/user"
 | 
						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/timeutil"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/util"
 | 
						"code.gitea.io/gitea/modules/util"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@@ -51,7 +52,7 @@ func GetRunnerToken(ctx context.Context, token string) (*ActionRunnerToken, erro
 | 
				
			|||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	} else if !has {
 | 
						} 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
 | 
						return &runnerToken, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -68,19 +69,15 @@ func UpdateRunnerToken(ctx context.Context, r *ActionRunnerToken, cols ...string
 | 
				
			|||||||
	return err
 | 
						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.
 | 
					// 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 {
 | 
						if ownerID != 0 && repoID != 0 {
 | 
				
			||||||
		// It's trying to create a runner token that belongs to a repository, but OwnerID has been set accidentally.
 | 
							// 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.
 | 
							// Remove OwnerID to avoid confusion; it's not worth returning an error here.
 | 
				
			||||||
		ownerID = 0
 | 
							ownerID = 0
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	token, err := util.CryptoRandomString(40)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	runnerToken := &ActionRunnerToken{
 | 
						runnerToken := &ActionRunnerToken{
 | 
				
			||||||
		OwnerID:  ownerID,
 | 
							OwnerID:  ownerID,
 | 
				
			||||||
		RepoID:   repoID,
 | 
							RepoID:   repoID,
 | 
				
			||||||
@@ -95,11 +92,19 @@ func NewRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerTo
 | 
				
			|||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		_, err = db.GetEngine(ctx).Insert(runnerToken)
 | 
							_, err := db.GetEngine(ctx).Insert(runnerToken)
 | 
				
			||||||
		return err
 | 
							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
 | 
					// GetLatestRunnerToken returns the latest runner token
 | 
				
			||||||
func GetLatestRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerToken, error) {
 | 
					func GetLatestRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerToken, error) {
 | 
				
			||||||
	if ownerID != 0 && repoID != 0 {
 | 
						if ownerID != 0 && repoID != 0 {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -72,9 +72,9 @@ func (at ActionType) String() string {
 | 
				
			|||||||
	case ActionRenameRepo:
 | 
						case ActionRenameRepo:
 | 
				
			||||||
		return "rename_repo"
 | 
							return "rename_repo"
 | 
				
			||||||
	case ActionStarRepo:
 | 
						case ActionStarRepo:
 | 
				
			||||||
		return "star_repo"
 | 
							return "star_repo" // will not displayed in feeds.tmpl
 | 
				
			||||||
	case ActionWatchRepo:
 | 
						case ActionWatchRepo:
 | 
				
			||||||
		return "watch_repo"
 | 
							return "watch_repo" // will not displayed in feeds.tmpl
 | 
				
			||||||
	case ActionCommitRepo:
 | 
						case ActionCommitRepo:
 | 
				
			||||||
		return "commit_repo"
 | 
							return "commit_repo"
 | 
				
			||||||
	case ActionCreateIssue:
 | 
						case ActionCreateIssue:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,6 +16,7 @@ import (
 | 
				
			|||||||
	"code.gitea.io/gitea/modules/git"
 | 
						"code.gitea.io/gitea/modules/git"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/gitrepo"
 | 
						"code.gitea.io/gitea/modules/gitrepo"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"xorm.io/builder"
 | 
				
			||||||
	"xorm.io/xorm"
 | 
						"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 {
 | 
					func activeIssues(ctx context.Context, repoID int64, fromTime time.Time) *xorm.Session {
 | 
				
			||||||
	sess := db.GetEngine(ctx).Where("issue.repo_id = ?", repoID).
 | 
						sess := db.GetEngine(ctx).Where("issue.repo_id = ?", repoID).
 | 
				
			||||||
		And("issue.is_pull = ?", false).
 | 
							And("issue.is_pull = ?", false).
 | 
				
			||||||
		And("issue.created_unix >= ?", fromTime.Unix()).
 | 
							And(builder.Or(
 | 
				
			||||||
		Or("issue.closed_unix >= ?", fromTime.Unix())
 | 
								builder.Gte{"issue.created_unix": fromTime.Unix()},
 | 
				
			||||||
 | 
								builder.Gte{"issue.closed_unix": fromTime.Unix()},
 | 
				
			||||||
 | 
							))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return sess
 | 
						return sess
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,8 +13,8 @@ import (
 | 
				
			|||||||
	user_model "code.gitea.io/gitea/models/user"
 | 
						user_model "code.gitea.io/gitea/models/user"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/timeutil"
 | 
						"code.gitea.io/gitea/modules/timeutil"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/keybase/go-crypto/openpgp"
 | 
						"github.com/ProtonMail/go-crypto/openpgp"
 | 
				
			||||||
	"github.com/keybase/go-crypto/openpgp/packet"
 | 
						"github.com/ProtonMail/go-crypto/openpgp/packet"
 | 
				
			||||||
	"xorm.io/builder"
 | 
						"xorm.io/builder"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -141,7 +141,11 @@ func parseGPGKey(ctx context.Context, ownerID int64, e *openpgp.Entity, verified
 | 
				
			|||||||
	// Parse Subkeys
 | 
						// Parse Subkeys
 | 
				
			||||||
	subkeys := make([]*GPGKey, len(e.Subkeys))
 | 
						subkeys := make([]*GPGKey, len(e.Subkeys))
 | 
				
			||||||
	for i, k := range 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 {
 | 
							if err != nil {
 | 
				
			||||||
			return nil, ErrGPGKeyParsing{ParseError: err}
 | 
								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))
 | 
						emails := make([]*user_model.EmailAddress, 0, len(e.Identities))
 | 
				
			||||||
	for _, ident := range e.Identities {
 | 
						for _, ident := range e.Identities {
 | 
				
			||||||
		if ident.Revocation != nil {
 | 
							if ident.Revoked(time.Now()) {
 | 
				
			||||||
			continue
 | 
								continue
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		email := strings.ToLower(strings.TrimSpace(ident.UserId.Email))
 | 
							email := strings.ToLower(strings.TrimSpace(ident.UserId.Email))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,7 +10,7 @@ import (
 | 
				
			|||||||
	"code.gitea.io/gitea/models/db"
 | 
						"code.gitea.io/gitea/models/db"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/log"
 | 
						"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
 | 
						verified := false
 | 
				
			||||||
	// Handle provided signature
 | 
						// Handle provided signature
 | 
				
			||||||
	if 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 {
 | 
							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 {
 | 
							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 {
 | 
							if err != nil {
 | 
				
			||||||
			log.Error("Unable to validate token signature. Error: %v", err)
 | 
								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/log"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/setting"
 | 
						"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"
 | 
						"strings"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/keybase/go-crypto/openpgp"
 | 
						"github.com/ProtonMail/go-crypto/openpgp"
 | 
				
			||||||
	"github.com/keybase/go-crypto/openpgp/armor"
 | 
						"github.com/ProtonMail/go-crypto/openpgp/armor"
 | 
				
			||||||
	"github.com/keybase/go-crypto/openpgp/packet"
 | 
						"github.com/ProtonMail/go-crypto/openpgp/packet"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//   __________________  ________   ____  __.
 | 
					//   __________________  ________   ____  __.
 | 
				
			||||||
@@ -80,7 +80,7 @@ func base64DecPubKey(content string) (*packet.PublicKey, error) {
 | 
				
			|||||||
	return pkey, nil
 | 
						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 {
 | 
					func getExpiryTime(e *openpgp.Entity) time.Time {
 | 
				
			||||||
	expiry := time.Time{}
 | 
						expiry := time.Time{}
 | 
				
			||||||
	// Extract self-sign for expire date based on : https://github.com/golang/crypto/blob/master/openpgp/keys.go#L165
 | 
						// 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 {
 | 
						for _, ident := range e.Identities {
 | 
				
			||||||
		if selfSig == nil {
 | 
							if selfSig == nil {
 | 
				
			||||||
			selfSig = ident.SelfSignature
 | 
								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
 | 
								selfSig = ident.SelfSignature
 | 
				
			||||||
			break
 | 
								break
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if selfSig.KeyLifetimeSecs != nil {
 | 
						if selfSig != nil && selfSig.KeyLifetimeSecs != nil {
 | 
				
			||||||
		expiry = e.PrimaryKey.CreationTime.Add(time.Duration(*selfSig.KeyLifetimeSecs) * time.Second)
 | 
							expiry = e.PrimaryKey.CreationTime.Add(time.Duration(*selfSig.KeyLifetimeSecs) * time.Second)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return expiry
 | 
						return expiry
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,7 +13,8 @@ import (
 | 
				
			|||||||
	"code.gitea.io/gitea/modules/timeutil"
 | 
						"code.gitea.io/gitea/modules/timeutil"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/util"
 | 
						"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"
 | 
						"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},
 | 
							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
 | 
					  name: job2
 | 
				
			||||||
  attempt: 1
 | 
					  attempt: 1
 | 
				
			||||||
  job_id: job2
 | 
					  job_id: job2
 | 
				
			||||||
  needs: [job1]
 | 
					  needs: '["job1"]'
 | 
				
			||||||
  task_id: 51
 | 
					  task_id: 51
 | 
				
			||||||
  status: 5
 | 
					  status: 5
 | 
				
			||||||
  started: 1683636528
 | 
					  started: 1683636528
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -96,3 +96,14 @@
 | 
				
			|||||||
  num_issues: 0
 | 
					  num_issues: 0
 | 
				
			||||||
  num_closed_issues: 0
 | 
					  num_closed_issues: 0
 | 
				
			||||||
  archived_unix: 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
 | 
					  id: 1
 | 
				
			||||||
  repo_id: 4
 | 
					  repo_id: 4
 | 
				
			||||||
  name_pattern: /v.+/
 | 
					  name_pattern: /v.+/
 | 
				
			||||||
  allowlist_user_i_ds: []
 | 
					  allowlist_user_i_ds: "[]"
 | 
				
			||||||
  allowlist_team_i_ds: []
 | 
					  allowlist_team_i_ds: "[]"
 | 
				
			||||||
  created_unix: 1715596037
 | 
					  created_unix: 1715596037
 | 
				
			||||||
  updated_unix: 1715596037
 | 
					  updated_unix: 1715596037
 | 
				
			||||||
-
 | 
					-
 | 
				
			||||||
  id: 2
 | 
					  id: 2
 | 
				
			||||||
  repo_id: 1
 | 
					  repo_id: 1
 | 
				
			||||||
  name_pattern: v-*
 | 
					  name_pattern: v-*
 | 
				
			||||||
  allowlist_user_i_ds: []
 | 
					  allowlist_user_i_ds: "[]"
 | 
				
			||||||
  allowlist_team_i_ds: []
 | 
					  allowlist_team_i_ds: "[]"
 | 
				
			||||||
  created_unix: 1715596037
 | 
					  created_unix: 1715596037
 | 
				
			||||||
  updated_unix: 1715596037
 | 
					  updated_unix: 1715596037
 | 
				
			||||||
-
 | 
					-
 | 
				
			||||||
  id: 3
 | 
					  id: 3
 | 
				
			||||||
  repo_id: 1
 | 
					  repo_id: 1
 | 
				
			||||||
  name_pattern: v-1.1
 | 
					  name_pattern: v-1.1
 | 
				
			||||||
  allowlist_user_i_ds: [2]
 | 
					  allowlist_user_i_ds: "[2]"
 | 
				
			||||||
  allowlist_team_i_ds: []
 | 
					  allowlist_team_i_ds: "[]"
 | 
				
			||||||
  created_unix: 1715596037
 | 
					  created_unix: 1715596037
 | 
				
			||||||
  updated_unix: 1715596037
 | 
					  updated_unix: 1715596037
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -167,6 +167,9 @@ func GetBranch(ctx context.Context, repoID int64, branchName string) (*Branch, e
 | 
				
			|||||||
			BranchName: branchName,
 | 
								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
 | 
						return &branch, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -440,6 +443,8 @@ type FindRecentlyPushedNewBranchesOptions struct {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type RecentlyPushedNewBranch struct {
 | 
					type RecentlyPushedNewBranch struct {
 | 
				
			||||||
 | 
						BranchRepo        *repo_model.Repository
 | 
				
			||||||
 | 
						BranchName        string
 | 
				
			||||||
	BranchDisplayName string
 | 
						BranchDisplayName string
 | 
				
			||||||
	BranchLink        string
 | 
						BranchLink        string
 | 
				
			||||||
	BranchCompareURL  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)
 | 
									branchDisplayName = fmt.Sprintf("%s:%s", branch.Repo.FullName(), branchDisplayName)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			newBranches = append(newBranches, &RecentlyPushedNewBranch{
 | 
								newBranches = append(newBranches, &RecentlyPushedNewBranch{
 | 
				
			||||||
 | 
									BranchRepo:        branch.Repo,
 | 
				
			||||||
				BranchDisplayName: branchDisplayName,
 | 
									BranchDisplayName: branchDisplayName,
 | 
				
			||||||
 | 
									BranchName:        branch.Name,
 | 
				
			||||||
				BranchLink:        fmt.Sprintf("%s/src/branch/%s", branch.Repo.Link(), util.PathEscapeSegments(branch.Name)),
 | 
									BranchLink:        fmt.Sprintf("%s/src/branch/%s", branch.Repo.Link(), util.PathEscapeSegments(branch.Name)),
 | 
				
			||||||
				BranchCompareURL:  branch.Repo.ComposeBranchCompareURL(opts.BaseRepo, branch.Name),
 | 
									BranchCompareURL:  branch.Repo.ComposeBranchCompareURL(opts.BaseRepo, branch.Name),
 | 
				
			||||||
				CommitTime:        branch.CommitTime,
 | 
									CommitTime:        branch.CommitTime,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -197,6 +197,20 @@ func (t CommentType) HasMailReplySupport() bool {
 | 
				
			|||||||
	return false
 | 
						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
 | 
					// RoleInRepo presents the user's participation in the repo
 | 
				
			||||||
type RoleInRepo string
 | 
					type RoleInRepo string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -893,7 +907,7 @@ func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
		fallthrough
 | 
							fallthrough
 | 
				
			||||||
	case CommentTypeComment:
 | 
						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
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		fallthrough
 | 
							fallthrough
 | 
				
			||||||
@@ -1182,8 +1196,8 @@ func DeleteComment(ctx context.Context, comment *Comment) error {
 | 
				
			|||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if comment.Type == CommentTypeComment {
 | 
						if comment.Type.CountedAsConversation() {
 | 
				
			||||||
		if _, err := e.ID(comment.IssueID).Decr("num_comments").Update(new(Issue)); err != nil {
 | 
							if err := UpdateIssueNumComments(ctx, comment.IssueID); err != nil {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -1300,6 +1314,21 @@ func (c *Comment) HasOriginalAuthor() bool {
 | 
				
			|||||||
	return c.OriginalAuthor != "" && c.OriginalAuthorID != 0
 | 
						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.
 | 
					// InsertIssueComments inserts many comments of issues.
 | 
				
			||||||
func InsertIssueComments(ctx context.Context, comments []*Comment) error {
 | 
					func InsertIssueComments(ctx context.Context, comments []*Comment) error {
 | 
				
			||||||
	if len(comments) == 0 {
 | 
						if len(comments) == 0 {
 | 
				
			||||||
@@ -1332,8 +1361,7 @@ func InsertIssueComments(ctx context.Context, comments []*Comment) error {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, issueID := range issueIDs {
 | 
						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 = ?",
 | 
							if err := UpdateIssueNumComments(ctx, issueID); err != nil {
 | 
				
			||||||
			issueID, CommentTypeComment, issueID); err != nil {
 | 
					 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -97,3 +97,12 @@ func TestMigrate_InsertIssueComments(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	unittest.CheckConsistencyFor(t, &issues_model.Issue{})
 | 
						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)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -38,13 +38,15 @@ func (issue *Issue) projectID(ctx context.Context) int64 {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ProjectColumnID return project column id if issue was assigned to one
 | 
					// 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
 | 
						var ip project_model.ProjectIssue
 | 
				
			||||||
	has, err := db.GetEngine(ctx).Where("issue_id=?", issue.ID).Get(&ip)
 | 
						has, err := db.GetEngine(ctx).Where("issue_id=?", issue.ID).Get(&ip)
 | 
				
			||||||
	if err != nil || !has {
 | 
						if err != nil {
 | 
				
			||||||
		return 0
 | 
							return 0, err
 | 
				
			||||||
 | 
						} else if !has {
 | 
				
			||||||
 | 
							return 0, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return ip.ProjectColumnID
 | 
						return ip.ProjectColumnID, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// LoadIssuesFromColumn load issues assigned to this column
 | 
					// LoadIssuesFromColumn load issues assigned to this column
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -349,6 +349,17 @@ func GetLabelIDsInRepoByNames(ctx context.Context, repoID int64, labelNames []st
 | 
				
			|||||||
		Find(&labelIDs)
 | 
							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
 | 
					// BuildLabelNamesIssueIDsCondition returns a builder where get issue ids match label names
 | 
				
			||||||
func BuildLabelNamesIssueIDsCondition(labelNames []string) *builder.Builder {
 | 
					func BuildLabelNamesIssueIDsCondition(labelNames []string) *builder.Builder {
 | 
				
			||||||
	return builder.Select("issue_label.issue_id").
 | 
						return builder.Select("issue_label.issue_id").
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -301,7 +301,7 @@ func (pr *PullRequest) LoadRequestedReviewers(ctx context.Context) error {
 | 
				
			|||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	reviews, err := GetReviewsByIssueID(ctx, pr.Issue.ID)
 | 
						reviews, _, err := GetReviewsByIssueID(ctx, pr.Issue.ID)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -320,7 +320,7 @@ func (pr *PullRequest) LoadRequestedReviewers(ctx context.Context) error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// LoadRequestedReviewersTeams loads the requested reviewers teams.
 | 
					// LoadRequestedReviewersTeams loads the requested reviewers teams.
 | 
				
			||||||
func (pr *PullRequest) LoadRequestedReviewersTeams(ctx context.Context) error {
 | 
					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 {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -639,6 +639,10 @@ func InsertReviews(ctx context.Context, reviews []*Review) error {
 | 
				
			|||||||
				return err
 | 
									return err
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := UpdateIssueNumComments(ctx, review.IssueID); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return committer.Commit()
 | 
						return committer.Commit()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,6 +5,8 @@ package issues
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
 | 
						"slices"
 | 
				
			||||||
 | 
						"sort"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models/db"
 | 
						"code.gitea.io/gitea/models/db"
 | 
				
			||||||
	organization_model "code.gitea.io/gitea/models/organization"
 | 
						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{})
 | 
						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
 | 
					// 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)
 | 
						reviews := make([]*Review, 0, 10)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	sess := db.GetEngine(ctx)
 | 
						// Get all reviews for the issue id
 | 
				
			||||||
 | 
						if err := db.GetEngine(ctx).Where("issue_id=?", issueID).OrderBy("updated_unix ASC").Find(&reviews); err != nil {
 | 
				
			||||||
	// Get latest review of each reviewer, sorted in order they were made
 | 
							return nil, nil, err
 | 
				
			||||||
	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
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 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)
 | 
						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",
 | 
						for _, reviews := range reviewTeamsMap {
 | 
				
			||||||
		issueID).
 | 
							teamReviewRequests = append(teamReviewRequests, reviews[len(reviews)-1])
 | 
				
			||||||
		Find(&teamReviewRequests); err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						sort.Slice(teamReviewRequests, func(i, j int) bool {
 | 
				
			||||||
 | 
							return teamReviewRequests[i].UpdatedUnix < teamReviewRequests[j].UpdatedUnix
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if len(teamReviewRequests) > 0 {
 | 
						return append(individualReviews, teamReviewRequests...), originalReviews, nil
 | 
				
			||||||
		reviews = append(reviews, teamReviewRequests...)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return reviews, 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.NoError(t, err)
 | 
				
			||||||
 | 
						assert.Empty(t, migratedReviews)
 | 
				
			||||||
	for _, review := range allReviews {
 | 
						for _, review := range allReviews {
 | 
				
			||||||
		assert.NoError(t, review.LoadReviewer(db.DefaultContext))
 | 
							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
 | 
					// Duration returns a human-readable duration string based on local server time
 | 
				
			||||||
func (s Stopwatch) Duration() string {
 | 
					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) {
 | 
					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,
 | 
							Doer:    user,
 | 
				
			||||||
		Issue:   issue,
 | 
							Issue:   issue,
 | 
				
			||||||
		Repo:    issue.Repo,
 | 
							Repo:    issue.Repo,
 | 
				
			||||||
		Content: util.SecToTime(timediff),
 | 
							Content: util.SecToHours(timediff),
 | 
				
			||||||
		Type:    CommentTypeStopTracking,
 | 
							Type:    CommentTypeStopTracking,
 | 
				
			||||||
		TimeID:  tt.ID,
 | 
							TimeID:  tt.ID,
 | 
				
			||||||
	}); err != nil {
 | 
						}); err != nil {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -248,6 +248,18 @@ func GetPackageByID(ctx context.Context, packageID int64) (*Package, error) {
 | 
				
			|||||||
	return p, nil
 | 
						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
 | 
					// GetPackageByName gets a package by name
 | 
				
			||||||
func GetPackageByName(ctx context.Context, ownerID int64, packageType Type, name string) (*Package, error) {
 | 
					func GetPackageByName(ctx context.Context, ownerID int64, packageType Type, name string) (*Package, error) {
 | 
				
			||||||
	var cond builder.Cond = builder.Eq{
 | 
						var cond builder.Cond = builder.Eq{
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -126,6 +126,14 @@ func (p *Project) LoadRepo(ctx context.Context) (err error) {
 | 
				
			|||||||
	return err
 | 
						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.
 | 
					// Link returns the project's relative URL.
 | 
				
			||||||
func (p *Project) Link(ctx context.Context) string {
 | 
					func (p *Project) Link(ctx context.Context) string {
 | 
				
			||||||
	if p.OwnerID > 0 {
 | 
						if p.OwnerID > 0 {
 | 
				
			||||||
@@ -134,7 +142,7 @@ func (p *Project) Link(ctx context.Context) string {
 | 
				
			|||||||
			log.Error("LoadOwner: %v", err)
 | 
								log.Error("LoadOwner: %v", err)
 | 
				
			||||||
			return ""
 | 
								return ""
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return fmt.Sprintf("%s/-/projects/%d", p.Owner.HomeLink(), p.ID)
 | 
							return ProjectLinkForOrg(p.Owner, p.ID)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if p.RepoID > 0 {
 | 
						if p.RepoID > 0 {
 | 
				
			||||||
		err := p.LoadRepo(ctx)
 | 
							err := p.LoadRepo(ctx)
 | 
				
			||||||
@@ -142,7 +150,7 @@ func (p *Project) Link(ctx context.Context) string {
 | 
				
			|||||||
			log.Error("LoadRepo: %v", err)
 | 
								log.Error("LoadRepo: %v", err)
 | 
				
			||||||
			return ""
 | 
								return ""
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return fmt.Sprintf("%s/projects/%d", p.Repo.Link(), p.ID)
 | 
							return ProjectLinkForRepo(p.Repo, p.ID)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return ""
 | 
						return ""
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,6 +19,8 @@ import (
 | 
				
			|||||||
	"code.gitea.io/gitea/models/unit"
 | 
						"code.gitea.io/gitea/models/unit"
 | 
				
			||||||
	user_model "code.gitea.io/gitea/models/user"
 | 
						user_model "code.gitea.io/gitea/models/user"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/log"
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"xorm.io/builder"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Init initialize model
 | 
					// Init initialize model
 | 
				
			||||||
@@ -27,7 +29,7 @@ func Init(ctx context.Context) error {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type repoChecker struct {
 | 
					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
 | 
						correctSQL func(ctx context.Context, id int64) error
 | 
				
			||||||
	desc       string
 | 
						desc       string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -38,8 +40,7 @@ func repoStatsCheck(ctx context.Context, checker *repoChecker) {
 | 
				
			|||||||
		log.Error("Select %s: %v", checker.desc, err)
 | 
							log.Error("Select %s: %v", checker.desc, err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	for _, result := range results {
 | 
						for _, id := range results {
 | 
				
			||||||
		id, _ := strconv.ParseInt(string(result["id"]), 10, 64)
 | 
					 | 
				
			||||||
		select {
 | 
							select {
 | 
				
			||||||
		case <-ctx.Done():
 | 
							case <-ctx.Done():
 | 
				
			||||||
			log.Warn("CheckRepoStats: Cancelled before checking %s for with id=%d", checker.desc, id)
 | 
								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 {
 | 
					func StatsCorrectSQL(ctx context.Context, sql any, ids ...any) error {
 | 
				
			||||||
	_, err := db.GetEngine(ctx).Exec(sql, id, id)
 | 
						args := []any{sql}
 | 
				
			||||||
 | 
						args = append(args, ids...)
 | 
				
			||||||
 | 
						_, err := db.GetEngine(ctx).Exec(args...)
 | 
				
			||||||
	return err
 | 
						return err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func repoStatsCorrectNumWatches(ctx context.Context, id int64) error {
 | 
					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 {
 | 
					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 {
 | 
					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 {
 | 
					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 {
 | 
					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 {
 | 
					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 {
 | 
					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)
 | 
						return repo_model.UpdateRepoIssueNumbers(ctx, id, true, true)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func statsQuery(args ...any) func(context.Context) ([]map[string][]byte, error) {
 | 
					// statsQuery returns a function that queries the database for a list of IDs
 | 
				
			||||||
	return func(ctx context.Context) ([]map[string][]byte, error) {
 | 
					// sql could be a string or a *builder.Builder
 | 
				
			||||||
		return db.GetEngine(ctx).Query(args...)
 | 
					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
 | 
							// 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,
 | 
								repoStatsCorrectIssueNumComments,
 | 
				
			||||||
			"issue count 'num_comments'",
 | 
								"issue count 'num_comments'",
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -54,6 +54,7 @@ func UpdateRepoLicenses(ctx context.Context, repo *Repository, commitID string,
 | 
				
			|||||||
		for _, o := range oldLicenses {
 | 
							for _, o := range oldLicenses {
 | 
				
			||||||
			// Update already existing license
 | 
								// Update already existing license
 | 
				
			||||||
			if o.License == license {
 | 
								if o.License == license {
 | 
				
			||||||
 | 
									o.CommitID = commitID
 | 
				
			||||||
				if _, err := db.GetEngine(ctx).ID(o.ID).Cols("`commit_id`").Update(o); err != nil {
 | 
									if _, err := db.GetEngine(ctx).ID(o.ID).Cols("`commit_id`").Update(o); err != nil {
 | 
				
			||||||
					return err
 | 
										return err
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -276,6 +276,8 @@ func (repo *Repository) IsBroken() bool {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// MarkAsBrokenEmpty marks the repo as broken and empty
 | 
					// 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() {
 | 
					func (repo *Repository) MarkAsBrokenEmpty() {
 | 
				
			||||||
	repo.Status = RepositoryBroken
 | 
						repo.Status = RepositoryBroken
 | 
				
			||||||
	repo.IsEmpty = true
 | 
						repo.IsEmpty = true
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -46,6 +46,12 @@ func UpdateRepositoryCols(ctx context.Context, repo *Repository, cols ...string)
 | 
				
			|||||||
	return err
 | 
						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.
 | 
					// ErrReachLimitOfRepo represents a "ReachLimitOfRepo" kind of error.
 | 
				
			||||||
type ErrReachLimitOfRepo struct {
 | 
					type ErrReachLimitOfRepo struct {
 | 
				
			||||||
	Limit int
 | 
						Limit int
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,7 @@ import (
 | 
				
			|||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models/db"
 | 
						"code.gitea.io/gitea/models/db"
 | 
				
			||||||
 | 
						issues_model "code.gitea.io/gitea/models/issues"
 | 
				
			||||||
	"code.gitea.io/gitea/models/unittest"
 | 
						"code.gitea.io/gitea/models/unittest"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/stretchr/testify/assert"
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
@@ -22,3 +23,16 @@ func TestDoctorUserStarNum(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	assert.NoError(t, DoctorUserStarNum(db.DefaultContext))
 | 
						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 (
 | 
					import (
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"os"
 | 
					 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models/db"
 | 
						"code.gitea.io/gitea/models/db"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/auth/password/hash"
 | 
						"code.gitea.io/gitea/modules/auth/password/hash"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/setting"
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/go-testfixtures/testfixtures/v3"
 | 
					 | 
				
			||||||
	"xorm.io/xorm"
 | 
						"xorm.io/xorm"
 | 
				
			||||||
	"xorm.io/xorm/schemas"
 | 
						"xorm.io/xorm/schemas"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var fixturesLoader *testfixtures.Loader
 | 
					type FixturesLoader interface {
 | 
				
			||||||
 | 
						Load() error
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var fixturesLoader FixturesLoader
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetXORMEngine gets the XORM engine
 | 
					// GetXORMEngine gets the XORM engine
 | 
				
			||||||
func GetXORMEngine(engine ...*xorm.Engine) (x *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
 | 
					// InitFixtures initialize test fixtures for a test database
 | 
				
			||||||
func InitFixtures(opts FixturesOptions, engine ...*xorm.Engine) (err error) {
 | 
					func InitFixtures(opts FixturesOptions, engine ...*xorm.Engine) (err error) {
 | 
				
			||||||
	e := GetXORMEngine(engine...)
 | 
						e := GetXORMEngine(engine...)
 | 
				
			||||||
	var fixtureOptionFiles func(*testfixtures.Loader) error
 | 
						fixturesLoader, err = NewFixturesLoader(e, opts)
 | 
				
			||||||
	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...)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							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)
 | 
						u.Avatar = avatars.HashEmail(seed)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, 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
 | 
							// 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 := storage.SaveFrom(storage.Avatars, u.CustomAvatarRelativePath(), func(w io.Writer) error {
 | 
				
			||||||
			if err := png.Encode(w, img); err != nil {
 | 
								if err := png.Encode(w, img); err != nil {
 | 
				
			||||||
				log.Error("Encode: %v", err)
 | 
									log.Error("Encode: %v", err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		return err
 | 
								return nil
 | 
				
			||||||
		}); err != nil {
 | 
							}); err != nil {
 | 
				
			||||||
		return fmt.Errorf("Failed to create dir %s: %w", u.CustomAvatarRelativePath(), err)
 | 
								return fmt.Errorf("failed to save avatar %s: %w", u.CustomAvatarRelativePath(), err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if _, err := db.GetEngine(ctx).ID(u.ID).Cols("avatar").Update(u); err != nil {
 | 
						if _, err := db.GetEngine(ctx).ID(u.ID).Cols("avatar").Update(u); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	log.Info("New random avatar created: %d", u.ID)
 | 
					 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// AvatarLinkWithSize returns a link to the user's avatar with size. size <= 0 means default size
 | 
					// 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 {
 | 
					func (u *User) AvatarLinkWithSize(ctx context.Context, size int) string {
 | 
				
			||||||
	if u.IsGhost() {
 | 
						if u.IsGhost() || u.IsGiteaActions() {
 | 
				
			||||||
		return avatars.DefaultAvatarLink()
 | 
							return avatars.DefaultAvatarLink()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,13 +4,19 @@
 | 
				
			|||||||
package user
 | 
					package user
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models/db"
 | 
						"code.gitea.io/gitea/models/db"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/models/unittest"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/setting"
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/storage"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/test"
 | 
						"code.gitea.io/gitea/modules/test"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/stretchr/testify/assert"
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/require"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestUserAvatarLink(t *testing.T) {
 | 
					func TestUserAvatarLink(t *testing.T) {
 | 
				
			||||||
@@ -26,3 +32,37 @@ func TestUserAvatarLink(t *testing.T) {
 | 
				
			|||||||
	link = u.AvatarLink(db.DefaultContext)
 | 
						link = u.AvatarLink(db.DefaultContext)
 | 
				
			||||||
	assert.Equal(t, "https://localhost/sub-path/avatars/avatar.png", link)
 | 
						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 {
 | 
						if user := GetVerifyUser(ctx, code); user != nil {
 | 
				
			||||||
		// time limit code
 | 
							// time limit code
 | 
				
			||||||
		prefix := code[:base.TimeLimitCodeLength]
 | 
							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) {
 | 
							if base.VerifyTimeLimitCode(time.Now(), data, setting.Service.ActiveCodeLives, prefix) {
 | 
				
			||||||
			emailAddress := &EmailAddress{UID: user.ID, Email: email}
 | 
								emailAddress := &EmailAddress{UID: user.ID, Email: email}
 | 
				
			||||||
			if has, _ := db.GetEngine(ctx).Get(emailAddress); has {
 | 
								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
 | 
						// Activate/deactivate a user's primary email address and account
 | 
				
			||||||
	if addr.IsPrimary {
 | 
						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 {
 | 
							if err != nil {
 | 
				
			||||||
			return err
 | 
								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)
 | 
								return fmt.Errorf("no user with ID: %d and Email: %s", userID, email)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -181,7 +181,8 @@ func (u *User) BeforeUpdate() {
 | 
				
			|||||||
		u.MaxRepoCreation = -1
 | 
							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)
 | 
						u.Email = strings.ToLower(u.Email)
 | 
				
			||||||
	if !u.IsOrganization() {
 | 
						if !u.IsOrganization() {
 | 
				
			||||||
		if len(u.AvatarEmail) == 0 {
 | 
							if len(u.AvatarEmail) == 0 {
 | 
				
			||||||
@@ -310,17 +311,6 @@ func (u *User) OrganisationLink() string {
 | 
				
			|||||||
	return setting.AppSubURL + "/org/" + url.PathEscape(u.Name)
 | 
						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.
 | 
					// GetUserFollowers returns range of user's followers.
 | 
				
			||||||
func GetUserFollowers(ctx context.Context, u, viewer *User, listOptions db.ListOptions) ([]*User, int64, error) {
 | 
					func GetUserFollowers(ctx context.Context, u, viewer *User, listOptions db.ListOptions) ([]*User, int64, error) {
 | 
				
			||||||
	sess := db.GetEngine(ctx).
 | 
						sess := db.GetEngine(ctx).
 | 
				
			||||||
@@ -848,12 +838,38 @@ func GetVerifyUser(ctx context.Context, code string) (user *User) {
 | 
				
			|||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// VerifyUserActiveCode verifies active code when active account
 | 
					type TimeLimitCodePurpose string
 | 
				
			||||||
func VerifyUserActiveCode(ctx context.Context, code string) (user *User) {
 | 
					
 | 
				
			||||||
 | 
					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 {
 | 
						if user = GetVerifyUser(ctx, code); user != nil {
 | 
				
			||||||
		// time limit code
 | 
							// time limit code
 | 
				
			||||||
		prefix := code[:base.TimeLimitCodeLength]
 | 
							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) {
 | 
							if base.VerifyTimeLimitCode(time.Now(), data, setting.Service.ActiveCodeLives, prefix) {
 | 
				
			||||||
			return user
 | 
								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
 | 
					// IsGhost check if user is fake user for a deleted account
 | 
				
			||||||
func (u *User) IsGhost() bool {
 | 
					func (u *User) IsGhost() bool {
 | 
				
			||||||
	if u == nil {
 | 
						if u == nil {
 | 
				
			||||||
@@ -48,6 +52,10 @@ const (
 | 
				
			|||||||
	ActionsEmail    = "teabot@gitea.io"
 | 
						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.
 | 
					// NewActionsUser creates and returns a fake user for running the actions.
 | 
				
			||||||
func NewActionsUser() *User {
 | 
					func NewActionsUser() *User {
 | 
				
			||||||
	return &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
 | 
						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)
 | 
							(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.
 | 
					// HasPullRequestReviewRequestEvent returns true if hook enabled pull request review request event.
 | 
				
			||||||
func (w *Webhook) HasPullRequestReviewRequestEvent() bool {
 | 
					func (w *Webhook) HasPullRequestReviewRequestEvent() bool {
 | 
				
			||||||
	return w.SendEverything ||
 | 
						return w.SendEverything ||
 | 
				
			||||||
@@ -337,6 +342,7 @@ func (w *Webhook) EventCheckers() []struct {
 | 
				
			|||||||
		{w.HasReleaseEvent, webhook_module.HookEventRelease},
 | 
							{w.HasReleaseEvent, webhook_module.HookEventRelease},
 | 
				
			||||||
		{w.HasPackageEvent, webhook_module.HookEventPackage},
 | 
							{w.HasPackageEvent, webhook_module.HookEventPackage},
 | 
				
			||||||
		{w.HasPullRequestReviewRequestEvent, webhook_module.HookEventPullRequestReviewRequest},
 | 
							{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", "pull_request_assign", "pull_request_label", "pull_request_milestone",
 | 
				
			||||||
		"pull_request_comment", "pull_request_review_approved", "pull_request_review_rejected",
 | 
							"pull_request_comment", "pull_request_review_approved", "pull_request_review_rejected",
 | 
				
			||||||
		"pull_request_review_comment", "pull_request_sync", "wiki", "repository", "release",
 | 
							"pull_request_review_comment", "pull_request_sync", "wiki", "repository", "release",
 | 
				
			||||||
		"package", "pull_request_review_request",
 | 
							"package", "pull_request_review_request", "status",
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
		(&Webhook{
 | 
							(&Webhook{
 | 
				
			||||||
			HookEvent: &webhook_module.HookEvent{SendEverything: true},
 | 
								HookEvent: &webhook_module.HookEvent{SendEverything: true},
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,5 +15,5 @@ func TestPamAuth(t *testing.T) {
 | 
				
			|||||||
	result, err := Auth("gitea", "user1", "false-pwd")
 | 
						result, err := Auth("gitea", "user1", "false-pwd")
 | 
				
			||||||
	assert.Error(t, err)
 | 
						assert.Error(t, err)
 | 
				
			||||||
	assert.EqualError(t, err, "Authentication failure")
 | 
						assert.EqualError(t, err, "Authentication failure")
 | 
				
			||||||
	assert.Len(t, result)
 | 
						assert.Empty(t, result)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										7
									
								
								modules/cache/cache.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								modules/cache/cache.go
									
									
									
									
										vendored
									
									
								
							@@ -38,9 +38,14 @@ func Init() error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
	testCacheKey = "DefaultCache.TestKey"
 | 
						testCacheKey = "DefaultCache.TestKey"
 | 
				
			||||||
	SlowCacheThreshold = 100 * time.Microsecond
 | 
						// 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) {
 | 
					func Test() (time.Duration, error) {
 | 
				
			||||||
	if defaultCache == nil {
 | 
						if defaultCache == nil {
 | 
				
			||||||
		return 0, fmt.Errorf("default cache not initialized")
 | 
							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()
 | 
						elapsed, err := Test()
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						assert.NoError(t, err)
 | 
				
			||||||
	// mem cache should take from 300ns up to 1ms on modern hardware ...
 | 
						// 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) {
 | 
					func TestGetCache(t *testing.T) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -253,7 +253,7 @@ func BinToHex(objectFormat ObjectFormat, sha, out []byte) []byte {
 | 
				
			|||||||
	return out
 | 
						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.
 | 
					// 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
 | 
					// 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>
 | 
					// <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
 | 
					// 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
 | 
						var readBytes []byte
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Read the Mode & fname
 | 
						// Read the Mode & fname
 | 
				
			||||||
@@ -271,7 +271,7 @@ func ParseTreeLine(objectFormat ObjectFormat, rd *bufio.Reader, modeBuf, fnameBu
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	idx := bytes.IndexByte(readBytes, ' ')
 | 
						idx := bytes.IndexByte(readBytes, ' ')
 | 
				
			||||||
	if idx < 0 {
 | 
						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{}
 | 
							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())
 | 
						startCommitID, err := repo.GetCommitBranchStart(os.Environ(), "branch1", commit.ID.String())
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						assert.NoError(t, err)
 | 
				
			||||||
	assert.NotEmpty(t, startCommitID)
 | 
						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"
 | 
						"bytes"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"io"
 | 
						"io"
 | 
				
			||||||
	"strconv"
 | 
					 | 
				
			||||||
	"strings"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/modules/log"
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@@ -21,71 +19,30 @@ func ParseTreeEntries(data []byte) ([]*TreeEntry, error) {
 | 
				
			|||||||
	return parseTreeEntries(data, nil)
 | 
						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) {
 | 
					func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) {
 | 
				
			||||||
	var err error
 | 
					 | 
				
			||||||
	entries := make([]*TreeEntry, 0, bytes.Count(data, []byte{'\n'})+1)
 | 
						entries := make([]*TreeEntry, 0, bytes.Count(data, []byte{'\n'})+1)
 | 
				
			||||||
	for pos := 0; pos < len(data); {
 | 
						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')
 | 
							posEnd := bytes.IndexByte(data[pos:], '\n')
 | 
				
			||||||
		if posEnd == -1 {
 | 
							if posEnd == -1 {
 | 
				
			||||||
			posEnd = len(data)
 | 
								posEnd = len(data)
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			posEnd += pos
 | 
								posEnd += pos
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		line := data[pos:posEnd]
 | 
							line := data[pos:posEnd]
 | 
				
			||||||
		posTab := bytes.IndexByte(line, '\t')
 | 
							lsTreeLine, err := parseLsTreeLine(line)
 | 
				
			||||||
		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))
 | 
					 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return nil, fmt.Errorf("invalid ls-tree output (invalid object id): %q, err: %w", line, err)
 | 
								return nil, err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							entry := &TreeEntry{
 | 
				
			||||||
		if len(entryName) > 0 && entryName[0] == '"' {
 | 
								ptree:     ptree,
 | 
				
			||||||
			entry.name, err = strconv.Unquote(string(entryName))
 | 
								ID:        lsTreeLine.ID,
 | 
				
			||||||
			if err != nil {
 | 
								entryMode: lsTreeLine.EntryMode,
 | 
				
			||||||
				return nil, fmt.Errorf("invalid ls-tree output (invalid name): %q, err: %w", line, err)
 | 
								name:      lsTreeLine.Name,
 | 
				
			||||||
 | 
								size:      lsTreeLine.Size.Value(),
 | 
				
			||||||
 | 
								sized:     lsTreeLine.Size.Has(),
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			entry.name = string(entryName)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		pos = posEnd + 1
 | 
							pos = posEnd + 1
 | 
				
			||||||
		entries = append(entries, entry)
 | 
							entries = append(entries, entry)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -100,7 +57,7 @@ func catBatchParseTreeEntries(objectFormat ObjectFormat, ptree *Tree, rd *bufio.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
loop:
 | 
					loop:
 | 
				
			||||||
	for sz > 0 {
 | 
						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 != nil {
 | 
				
			||||||
			if err == io.EOF {
 | 
								if err == io.EOF {
 | 
				
			||||||
				break loop
 | 
									break loop
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -114,7 +114,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
 | 
				
			|||||||
			case "tree":
 | 
								case "tree":
 | 
				
			||||||
				var n int64
 | 
									var n int64
 | 
				
			||||||
				for n < size {
 | 
									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 {
 | 
										if err != nil {
 | 
				
			||||||
						return nil, err
 | 
											return nil, err
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,6 +20,8 @@ func TestRefName(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// Test pull names
 | 
						// Test pull names
 | 
				
			||||||
	assert.Equal(t, "1", RefName("refs/pull/1/head").PullName())
 | 
						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())
 | 
						assert.Equal(t, "my/pull", RefName("refs/pull/my/pull/head").PullName())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Test for branch names
 | 
						// Test for branch names
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,6 +5,7 @@ package git
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	giturl "code.gitea.io/gitea/modules/git/url"
 | 
						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)
 | 
						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
 | 
					// CommitsByFileAndRange return the commits according revision file and the page
 | 
				
			||||||
func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions) ([]*Commit, error) {
 | 
					func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions) ([]*Commit, error) {
 | 
				
			||||||
	skip := (opts.Page - 1) * setting.Git.CommitsRangeSize
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	stdoutReader, stdoutWriter := io.Pipe()
 | 
						stdoutReader, stdoutWriter := io.Pipe()
 | 
				
			||||||
	defer func() {
 | 
						defer func() {
 | 
				
			||||||
		_ = stdoutReader.Close()
 | 
							_ = stdoutReader.Close()
 | 
				
			||||||
@@ -226,8 +224,8 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions)
 | 
				
			|||||||
	go func() {
 | 
						go func() {
 | 
				
			||||||
		stderr := strings.Builder{}
 | 
							stderr := strings.Builder{}
 | 
				
			||||||
		gitCmd := NewCommand(repo.Ctx, "rev-list").
 | 
							gitCmd := NewCommand(repo.Ctx, "rev-list").
 | 
				
			||||||
			AddOptionFormat("--max-count=%d", setting.Git.CommitsRangeSize*opts.Page).
 | 
								AddOptionFormat("--max-count=%d", setting.Git.CommitsRangeSize).
 | 
				
			||||||
			AddOptionFormat("--skip=%d", skip)
 | 
								AddOptionFormat("--skip=%d", (opts.Page-1)*setting.Git.CommitsRangeSize)
 | 
				
			||||||
		gitCmd.AddDynamicArguments(opts.Revision)
 | 
							gitCmd.AddDynamicArguments(opts.Revision)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if opts.Not != "" {
 | 
							if opts.Not != "" {
 | 
				
			||||||
@@ -521,6 +519,7 @@ func (repo *Repository) AddLastCommitCache(cacheKey, fullName, sha string) error
 | 
				
			|||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetCommitBranchStart returns the commit where the branch diverged
 | 
				
			||||||
func (repo *Repository) GetCommitBranchStart(env []string, branch, endCommitID string) (string, error) {
 | 
					func (repo *Repository) GetCommitBranchStart(env []string, branch, endCommitID string) (string, error) {
 | 
				
			||||||
	cmd := NewCommand(repo.Ctx, "log", prettyLogFormat)
 | 
						cmd := NewCommand(repo.Ctx, "log", prettyLogFormat)
 | 
				
			||||||
	cmd.AddDynamicArguments(endCommitID)
 | 
						cmd.AddDynamicArguments(endCommitID)
 | 
				
			||||||
@@ -535,7 +534,8 @@ func (repo *Repository) GetCommitBranchStart(env []string, branch, endCommitID s
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	parts := bytes.Split(bytes.TrimSpace(stdout), []byte{'\n'})
 | 
						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 {
 | 
						for _, commitID := range parts {
 | 
				
			||||||
		branches, err := repo.getBranches(env, string(commitID), 2)
 | 
							branches, err := repo.getBranches(env, string(commitID), 2)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
@@ -543,11 +543,9 @@ func (repo *Repository) GetCommitBranchStart(env []string, branch, endCommitID s
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
		for _, b := range branches {
 | 
							for _, b := range branches {
 | 
				
			||||||
			if b != branch {
 | 
								if b != branch {
 | 
				
			||||||
				return startCommitID, nil
 | 
									return string(commitID), nil
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					 | 
				
			||||||
		startCommitID = string(commitID)
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return "", nil
 | 
						return "", nil
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,11 @@ import (
 | 
				
			|||||||
	"path/filepath"
 | 
						"path/filepath"
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/test"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/stretchr/testify/assert"
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/require"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestRepository_GetCommitBranches(t *testing.T) {
 | 
					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) {
 | 
					func (t *Tree) SubTree(rpath string) (*Tree, error) {
 | 
				
			||||||
	if len(rpath) == 0 {
 | 
						if len(rpath) == 0 {
 | 
				
			||||||
		return t, nil
 | 
							return t, nil
 | 
				
			||||||
@@ -62,3 +62,14 @@ func (repo *Repository) LsTree(ref string, filenames ...string) ([]string, error
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	return filelist, err
 | 
						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,
 | 
								ptree:     t,
 | 
				
			||||||
			ID:        t.ID,
 | 
								ID:        t.ID,
 | 
				
			||||||
			name:      "",
 | 
								name:      "",
 | 
				
			||||||
			fullName:  "",
 | 
					 | 
				
			||||||
			entryMode: EntryModeTree,
 | 
								entryMode: EntryModeTree,
 | 
				
			||||||
		}, nil
 | 
							}, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,22 +10,16 @@ import "code.gitea.io/gitea/modules/log"
 | 
				
			|||||||
// TreeEntry the leaf in the git tree
 | 
					// TreeEntry the leaf in the git tree
 | 
				
			||||||
type TreeEntry struct {
 | 
					type TreeEntry struct {
 | 
				
			||||||
	ID    ObjectID
 | 
						ID    ObjectID
 | 
				
			||||||
 | 
					 | 
				
			||||||
	ptree *Tree
 | 
						ptree *Tree
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	entryMode EntryMode
 | 
						entryMode EntryMode
 | 
				
			||||||
	name      string
 | 
						name      string
 | 
				
			||||||
 | 
					 | 
				
			||||||
	size      int64
 | 
						size      int64
 | 
				
			||||||
	sized     bool
 | 
						sized     bool
 | 
				
			||||||
	fullName string
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Name returns the name of the entry
 | 
					// Name returns the name of the entry
 | 
				
			||||||
func (te *TreeEntry) Name() string {
 | 
					func (te *TreeEntry) Name() string {
 | 
				
			||||||
	if te.fullName != "" {
 | 
					 | 
				
			||||||
		return te.fullName
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return te.name
 | 
						return te.name
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,3 +25,18 @@ func TestSubTree_Issue29101(t *testing.T) {
 | 
				
			|||||||
		assert.True(t, IsErrNotExist(err))
 | 
							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
 | 
						return r
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Body adds request raw body.
 | 
					// Body adds request raw body. It supports string, []byte and io.Reader as body.
 | 
				
			||||||
// it supports string and []byte.
 | 
					 | 
				
			||||||
func (r *Request) Body(data any) *Request {
 | 
					func (r *Request) Body(data any) *Request {
 | 
				
			||||||
	switch t := data.(type) {
 | 
						switch t := data.(type) {
 | 
				
			||||||
 | 
						case nil: // do nothing
 | 
				
			||||||
	case string:
 | 
						case string:
 | 
				
			||||||
		bf := bytes.NewBufferString(t)
 | 
							bf := bytes.NewBufferString(t)
 | 
				
			||||||
		r.req.Body = io.NopCloser(bf)
 | 
							r.req.Body = io.NopCloser(bf)
 | 
				
			||||||
@@ -111,6 +111,12 @@ func (r *Request) Body(data any) *Request {
 | 
				
			|||||||
		bf := bytes.NewBuffer(t)
 | 
							bf := bytes.NewBuffer(t)
 | 
				
			||||||
		r.req.Body = io.NopCloser(bf)
 | 
							r.req.Body = io.NopCloser(bf)
 | 
				
			||||||
		r.req.ContentLength = int64(len(t))
 | 
							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
 | 
						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 {
 | 
						} else if r.req.Method == "POST" && r.req.Body == nil && len(paramBody) > 0 {
 | 
				
			||||||
		r.Header("Content-Type", "application/x-www-form-urlencoded")
 | 
							r.Header("Content-Type", "application/x-www-form-urlencoded")
 | 
				
			||||||
		r.Body(paramBody)
 | 
							r.Body(paramBody) // string
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var err error
 | 
						var err error
 | 
				
			||||||
@@ -185,6 +191,7 @@ func (r *Request) getResponse() (*http.Response, error) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Response executes request client gets response manually.
 | 
					// Response executes request client gets response manually.
 | 
				
			||||||
 | 
					// Caller MUST close the response body if no error occurs
 | 
				
			||||||
func (r *Request) Response() (*http.Response, error) {
 | 
					func (r *Request) Response() (*http.Response, error) {
 | 
				
			||||||
	return r.getResponse()
 | 
						return r.getResponse()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -123,13 +123,12 @@ func Init() {
 | 
				
			|||||||
			for _, indexerData := range items {
 | 
								for _, indexerData := range items {
 | 
				
			||||||
				log.Trace("IndexerData Process Repo: %d", indexerData.RepoID)
 | 
									log.Trace("IndexerData Process Repo: %d", indexerData.RepoID)
 | 
				
			||||||
				if err := index(ctx, indexer, indexerData.RepoID); err != nil {
 | 
									if err := index(ctx, indexer, indexerData.RepoID); err != nil {
 | 
				
			||||||
					unhandled = append(unhandled, indexerData)
 | 
					 | 
				
			||||||
					if !setting.IsInTesting {
 | 
										if !setting.IsInTesting {
 | 
				
			||||||
						log.Error("Codes indexer handler: index error for repo %v: %v", indexerData.RepoID, err)
 | 
											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)
 | 
							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/bleve"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/indexer/code/elasticsearch"
 | 
						"code.gitea.io/gitea/modules/indexer/code/elasticsearch"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/indexer/code/internal"
 | 
						"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"
 | 
				
			||||||
	_ "code.gitea.io/gitea/models/actions"
 | 
						_ "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) {
 | 
					func TestBleveIndexAndSearch(t *testing.T) {
 | 
				
			||||||
	unittest.PrepareTestEnv(t)
 | 
						unittest.PrepareTestEnv(t)
 | 
				
			||||||
 | 
						defer test.MockVariableValue(&setting.Indexer.TypeBleveMaxFuzzniess, 2)()
 | 
				
			||||||
	dir := t.TempDir()
 | 
						dir := t.TempDir()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	idx := bleve.NewIndexer(dir)
 | 
						idx := bleve.NewIndexer(dir)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,7 @@ import (
 | 
				
			|||||||
	"unicode"
 | 
						"unicode"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/modules/log"
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/util"
 | 
						"code.gitea.io/gitea/modules/util"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/blevesearch/bleve/v2"
 | 
						"github.com/blevesearch/bleve/v2"
 | 
				
			||||||
@@ -54,9 +55,9 @@ func openIndexer(path string, latestVersion int) (bleve.Index, int, error) {
 | 
				
			|||||||
	return index, 0, nil
 | 
						return index, 0, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// This method test the GuessFuzzinessByKeyword method. The fuzziness is based on the levenshtein distance and determines how many chars
 | 
					// 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.
 | 
					// 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.
 | 
					// Given a phrase, its shortest word determines its fuzziness. If a phrase uses CJK (eg: `갃갃갃` `啊啊啊`), the fuzziness is zero.
 | 
				
			||||||
func GuessFuzzinessByKeyword(s string) int {
 | 
					func GuessFuzzinessByKeyword(s string) int {
 | 
				
			||||||
	tokenizer := unicode_tokenizer.NewUnicodeTokenizer()
 | 
						tokenizer := unicode_tokenizer.NewUnicodeTokenizer()
 | 
				
			||||||
	tokens := tokenizer.Tokenize([]byte(s))
 | 
						tokens := tokenizer.Tokenize([]byte(s))
 | 
				
			||||||
@@ -85,5 +86,5 @@ func guessFuzzinessByKeyword(s string) int {
 | 
				
			|||||||
			return 0
 | 
								return 0
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return min(maxFuzziness, len(s)/4)
 | 
						return min(min(setting.Indexer.TypeBleveMaxFuzzniess, maxFuzziness), len(s)/4)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,10 +7,15 @@ import (
 | 
				
			|||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/test"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/stretchr/testify/assert"
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestBleveGuessFuzzinessByKeyword(t *testing.T) {
 | 
					func TestBleveGuessFuzzinessByKeyword(t *testing.T) {
 | 
				
			||||||
 | 
						defer test.MockVariableValue(&setting.Indexer.TypeBleveMaxFuzzniess, 2)()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	scenarios := []struct {
 | 
						scenarios := []struct {
 | 
				
			||||||
		Input     string
 | 
							Input     string
 | 
				
			||||||
		Fuzziness int // See util.go for the definition of fuzziness in this particular context
 | 
							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 {
 | 
						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))
 | 
								assert.Equal(t, scenario.Fuzziness, GuessFuzzinessByKeyword(scenario.Input))
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -92,6 +92,11 @@ func getIssueIndexerData(ctx context.Context, issueID int64) (*internal.IndexerD
 | 
				
			|||||||
		projectID = issue.Project.ID
 | 
							projectID = issue.Project.ID
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						projectColumnID, err := issue.ProjectColumnID(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, false, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return &internal.IndexerData{
 | 
						return &internal.IndexerData{
 | 
				
			||||||
		ID:                 issue.ID,
 | 
							ID:                 issue.ID,
 | 
				
			||||||
		RepoID:             issue.RepoID,
 | 
							RepoID:             issue.RepoID,
 | 
				
			||||||
@@ -106,7 +111,7 @@ func getIssueIndexerData(ctx context.Context, issueID int64) (*internal.IndexerD
 | 
				
			|||||||
		NoLabel:            len(labels) == 0,
 | 
							NoLabel:            len(labels) == 0,
 | 
				
			||||||
		MilestoneID:        issue.MilestoneID,
 | 
							MilestoneID:        issue.MilestoneID,
 | 
				
			||||||
		ProjectID:          projectID,
 | 
							ProjectID:          projectID,
 | 
				
			||||||
		ProjectColumnID:    issue.ProjectColumnID(ctx),
 | 
							ProjectColumnID:    projectColumnID,
 | 
				
			||||||
		PosterID:           issue.PosterID,
 | 
							PosterID:           issue.PosterID,
 | 
				
			||||||
		AssigneeID:         issue.AssigneeID,
 | 
							AssigneeID:         issue.AssigneeID,
 | 
				
			||||||
		MentionIDs:         mentionIDs,
 | 
							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)
 | 
						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"
 | 
						// `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
 | 
						// 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)
 | 
						payload := new(bytes.Buffer)
 | 
				
			||||||
	err := json.NewEncoder(payload).Encode(request)
 | 
						err := json.NewEncoder(payload).Encode(request)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,6 @@
 | 
				
			|||||||
package backend
 | 
					package backend
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"bytes"
 | 
					 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"encoding/base64"
 | 
						"encoding/base64"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
@@ -29,7 +28,7 @@ var Capabilities = []string{
 | 
				
			|||||||
	"locking",
 | 
						"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
 | 
					// GiteaBackend is an adapter between git-lfs-transfer library and Gitea's internal LFS API
 | 
				
			||||||
type GiteaBackend struct {
 | 
					type GiteaBackend struct {
 | 
				
			||||||
@@ -78,17 +77,17 @@ func (g *GiteaBackend) Batch(_ string, pointers []transfer.BatchItem, args trans
 | 
				
			|||||||
		headerAccept:            mimeGitLFS,
 | 
							headerAccept:            mimeGitLFS,
 | 
				
			||||||
		headerContentType:       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()
 | 
						resp, err := req.Response()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		g.logger.Log("http request error", err)
 | 
							g.logger.Log("http request error", err)
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						defer resp.Body.Close()
 | 
				
			||||||
	if resp.StatusCode != http.StatusOK {
 | 
						if resp.StatusCode != http.StatusOK {
 | 
				
			||||||
		g.logger.Log("http statuscode error", resp.StatusCode, statusCodeToErr(resp.StatusCode))
 | 
							g.logger.Log("http statuscode error", resp.StatusCode, statusCodeToErr(resp.StatusCode))
 | 
				
			||||||
		return nil, statusCodeToErr(resp.StatusCode)
 | 
							return nil, statusCodeToErr(resp.StatusCode)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	defer resp.Body.Close()
 | 
					 | 
				
			||||||
	respBytes, err := io.ReadAll(resp.Body)
 | 
						respBytes, err := io.ReadAll(resp.Body)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		g.logger.Log("http read error", err)
 | 
							g.logger.Log("http read error", err)
 | 
				
			||||||
@@ -158,8 +157,7 @@ func (g *GiteaBackend) Batch(_ string, pointers []transfer.BatchItem, args trans
 | 
				
			|||||||
	return pointers, nil
 | 
						return pointers, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Download implements transfer.Backend. The returned reader must be closed by the
 | 
					// Download implements transfer.Backend. The returned reader must be closed by the caller.
 | 
				
			||||||
// caller.
 | 
					 | 
				
			||||||
func (g *GiteaBackend) Download(oid string, args transfer.Args) (io.ReadCloser, int64, error) {
 | 
					func (g *GiteaBackend) Download(oid string, args transfer.Args) (io.ReadCloser, int64, error) {
 | 
				
			||||||
	idMapStr, exists := args[argID]
 | 
						idMapStr, exists := args[argID]
 | 
				
			||||||
	if !exists {
 | 
						if !exists {
 | 
				
			||||||
@@ -187,25 +185,25 @@ func (g *GiteaBackend) Download(oid string, args transfer.Args) (io.ReadCloser,
 | 
				
			|||||||
		headerGiteaInternalAuth: g.internalAuth,
 | 
							headerGiteaInternalAuth: g.internalAuth,
 | 
				
			||||||
		headerAccept:            mimeOctetStream,
 | 
							headerAccept:            mimeOctetStream,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	req := newInternalRequest(g.ctx, url, http.MethodGet, headers, nil)
 | 
						req := newInternalRequestLFS(g.ctx, url, http.MethodGet, headers, nil)
 | 
				
			||||||
	resp, err := req.Response()
 | 
						resp, err := req.Response()
 | 
				
			||||||
	if err != nil {
 | 
						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 {
 | 
						if resp.StatusCode != http.StatusOK {
 | 
				
			||||||
		return nil, 0, statusCodeToErr(resp.StatusCode)
 | 
							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 {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, 0, err
 | 
							return nil, 0, fmt.Errorf("failed to parse content length: %w", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	respSize := int64(len(respBytes))
 | 
						// transfer.Backend will check io.Closer interface and close this Body reader
 | 
				
			||||||
	respBuf := io.NopCloser(bytes.NewBuffer(respBytes))
 | 
						return resp.Body, respSize, nil
 | 
				
			||||||
	return respBuf, 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 {
 | 
					func (g *GiteaBackend) Upload(oid string, size int64, r io.Reader, args transfer.Args) error {
 | 
				
			||||||
	idMapStr, exists := args[argID]
 | 
						idMapStr, exists := args[argID]
 | 
				
			||||||
	if !exists {
 | 
						if !exists {
 | 
				
			||||||
@@ -234,15 +232,14 @@ func (g *GiteaBackend) Upload(oid string, size int64, r io.Reader, args transfer
 | 
				
			|||||||
		headerContentType:       mimeOctetStream,
 | 
							headerContentType:       mimeOctetStream,
 | 
				
			||||||
		headerContentLength:     strconv.FormatInt(size, 10),
 | 
							headerContentLength:     strconv.FormatInt(size, 10),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	reqBytes, err := io.ReadAll(r)
 | 
					
 | 
				
			||||||
	if err != nil {
 | 
						req := newInternalRequestLFS(g.ctx, url, http.MethodPut, headers, nil)
 | 
				
			||||||
		return err
 | 
						req.Body(r)
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	req := newInternalRequest(g.ctx, url, http.MethodPut, headers, reqBytes)
 | 
					 | 
				
			||||||
	resp, err := req.Response()
 | 
						resp, err := req.Response()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						defer resp.Body.Close()
 | 
				
			||||||
	if resp.StatusCode != http.StatusOK {
 | 
						if resp.StatusCode != http.StatusOK {
 | 
				
			||||||
		return statusCodeToErr(resp.StatusCode)
 | 
							return statusCodeToErr(resp.StatusCode)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -284,11 +281,12 @@ func (g *GiteaBackend) Verify(oid string, size int64, args transfer.Args) (trans
 | 
				
			|||||||
		headerAccept:            mimeGitLFS,
 | 
							headerAccept:            mimeGitLFS,
 | 
				
			||||||
		headerContentType:       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()
 | 
						resp, err := req.Response()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return transfer.NewStatus(transfer.StatusInternalServerError), err
 | 
							return transfer.NewStatus(transfer.StatusInternalServerError), err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						defer resp.Body.Close()
 | 
				
			||||||
	if resp.StatusCode != http.StatusOK {
 | 
						if resp.StatusCode != http.StatusOK {
 | 
				
			||||||
		return transfer.NewStatus(uint32(resp.StatusCode), http.StatusText(resp.StatusCode)), statusCodeToErr(resp.StatusCode)
 | 
							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,
 | 
							headerAccept:            mimeGitLFS,
 | 
				
			||||||
		headerContentType:       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()
 | 
						resp, err := req.Response()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		g.logger.Log("http request error", err)
 | 
							g.logger.Log("http request error", err)
 | 
				
			||||||
@@ -102,7 +102,7 @@ func (g *giteaLockBackend) Unlock(lock transfer.Lock) error {
 | 
				
			|||||||
		headerAccept:            mimeGitLFS,
 | 
							headerAccept:            mimeGitLFS,
 | 
				
			||||||
		headerContentType:       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()
 | 
						resp, err := req.Response()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		g.logger.Log("http request error", err)
 | 
							g.logger.Log("http request error", err)
 | 
				
			||||||
@@ -185,7 +185,7 @@ func (g *giteaLockBackend) queryLocks(v url.Values) ([]transfer.Lock, string, er
 | 
				
			|||||||
		headerAccept:            mimeGitLFS,
 | 
							headerAccept:            mimeGitLFS,
 | 
				
			||||||
		headerContentType:       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()
 | 
						resp, err := req.Response()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		g.logger.Log("http request error", err)
 | 
							g.logger.Log("http request error", err)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,15 +5,12 @@ package backend
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"crypto/tls"
 | 
					 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"net"
 | 
						"io"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"time"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/modules/httplib"
 | 
						"code.gitea.io/gitea/modules/httplib"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/proxyprotocol"
 | 
						"code.gitea.io/gitea/modules/private"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/setting"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/charmbracelet/git-lfs-transfer/transfer"
 | 
						"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 {
 | 
					func newInternalRequestLFS(ctx context.Context, url, method string, headers map[string]string, body any) *httplib.Request {
 | 
				
			||||||
	req := httplib.NewRequest(url, method).
 | 
						req := private.NewInternalRequest(ctx, 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
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for k, v := range headers {
 | 
						for k, v := range headers {
 | 
				
			||||||
		req.Header(k, v)
 | 
							req.Header(k, v)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						switch body := body.(type) {
 | 
				
			||||||
	req.Body(body)
 | 
						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
 | 
						return req
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,7 +13,6 @@ import (
 | 
				
			|||||||
type Event struct {
 | 
					type Event struct {
 | 
				
			||||||
	Time time.Time
 | 
						Time time.Time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	GoroutinePid string
 | 
					 | 
				
			||||||
	Caller   string
 | 
						Caller   string
 | 
				
			||||||
	Filename string
 | 
						Filename string
 | 
				
			||||||
	Line     int
 | 
						Line     int
 | 
				
			||||||
@@ -218,18 +217,17 @@ func EventFormatTextMessage(mode *WriterMode, event *Event, msgFormat string, ms
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if flags&Lgopid == Lgopid {
 | 
						if flags&Lgopid == Lgopid {
 | 
				
			||||||
		if event.GoroutinePid != "" {
 | 
							deprecatedGoroutinePid := "no-gopid" // use a dummy value to avoid breaking the log format
 | 
				
			||||||
		buf = append(buf, '[')
 | 
							buf = append(buf, '[')
 | 
				
			||||||
		if mode.Colorize {
 | 
							if mode.Colorize {
 | 
				
			||||||
			buf = append(buf, ColorBytes(FgHiYellow)...)
 | 
								buf = append(buf, ColorBytes(FgHiYellow)...)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
			buf = append(buf, event.GoroutinePid...)
 | 
							buf = append(buf, deprecatedGoroutinePid...)
 | 
				
			||||||
		if mode.Colorize {
 | 
							if mode.Colorize {
 | 
				
			||||||
			buf = append(buf, resetBytes...)
 | 
								buf = append(buf, resetBytes...)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		buf = append(buf, ']', ' ')
 | 
							buf = append(buf, ']', ' ')
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	buf = append(buf, msg...)
 | 
						buf = append(buf, msg...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if event.Stacktrace != "" && mode.StacktraceLevel <= event.Level {
 | 
						if event.Stacktrace != "" && mode.StacktraceLevel <= event.Level {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,14 +28,13 @@ func TestEventFormatTextMessage(t *testing.T) {
 | 
				
			|||||||
			Caller:     "caller",
 | 
								Caller:     "caller",
 | 
				
			||||||
			Filename:   "filename",
 | 
								Filename:   "filename",
 | 
				
			||||||
			Line:       123,
 | 
								Line:       123,
 | 
				
			||||||
			GoroutinePid: "pid",
 | 
					 | 
				
			||||||
			Level:      ERROR,
 | 
								Level:      ERROR,
 | 
				
			||||||
			Stacktrace: "stacktrace",
 | 
								Stacktrace: "stacktrace",
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		"msg format: %v %v", "arg0", NewColoredValue("arg1", FgBlue),
 | 
							"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
 | 
						stacktrace
 | 
				
			||||||
 | 
					
 | 
				
			||||||
`, string(res))
 | 
					`, string(res))
 | 
				
			||||||
@@ -46,12 +45,11 @@ func TestEventFormatTextMessage(t *testing.T) {
 | 
				
			|||||||
			Caller:     "caller",
 | 
								Caller:     "caller",
 | 
				
			||||||
			Filename:   "filename",
 | 
								Filename:   "filename",
 | 
				
			||||||
			Line:       123,
 | 
								Line:       123,
 | 
				
			||||||
			GoroutinePid: "pid",
 | 
					 | 
				
			||||||
			Level:      ERROR,
 | 
								Level:      ERROR,
 | 
				
			||||||
			Stacktrace: "stacktrace",
 | 
								Stacktrace: "stacktrace",
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		"msg format: %v %v", "arg0", NewColoredValue("arg1", FgBlue),
 | 
							"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
 | 
						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
 | 
						Llevelinitial                     // Initial character of the provided level in brackets, eg. [I] for info
 | 
				
			||||||
	Llevel                            // Provided level in brackets [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
 | 
						Lmedfile  = Lshortfile | Llongfile                                    // last 20 characters of the filename
 | 
				
			||||||
	LstdFlags = Ldate | Ltime | Lmedfile | Lshortfuncname | Llevelinitial // default
 | 
						LstdFlags = Ldate | Ltime | Lmedfile | Lshortfuncname | Llevelinitial // default
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,19 +0,0 @@
 | 
				
			|||||||
// Copyright 2022 The Gitea Authors. All rights reserved.
 | 
					 | 
				
			||||||
// SPDX-License-Identifier: MIT
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package log
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import "unsafe"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//go:linkname runtime_getProfLabel runtime/pprof.runtime_getProfLabel
 | 
					 | 
				
			||||||
func runtime_getProfLabel() unsafe.Pointer //nolint
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type labelMap map[string]string
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func getGoroutineLabels() map[string]string {
 | 
					 | 
				
			||||||
	l := (*labelMap)(runtime_getProfLabel())
 | 
					 | 
				
			||||||
	if l == nil {
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return *l
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,33 +0,0 @@
 | 
				
			|||||||
// Copyright 2022 The Gitea Authors. All rights reserved.
 | 
					 | 
				
			||||||
// SPDX-License-Identifier: MIT
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package log
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"context"
 | 
					 | 
				
			||||||
	"runtime/pprof"
 | 
					 | 
				
			||||||
	"testing"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	"github.com/stretchr/testify/assert"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func Test_getGoroutineLabels(t *testing.T) {
 | 
					 | 
				
			||||||
	pprof.Do(context.Background(), pprof.Labels(), func(ctx context.Context) {
 | 
					 | 
				
			||||||
		currentLabels := getGoroutineLabels()
 | 
					 | 
				
			||||||
		pprof.ForLabels(ctx, func(key, value string) bool {
 | 
					 | 
				
			||||||
			assert.EqualValues(t, value, currentLabels[key])
 | 
					 | 
				
			||||||
			return true
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		pprof.Do(ctx, pprof.Labels("Test_getGoroutineLabels", "Test_getGoroutineLabels_child1"), func(ctx context.Context) {
 | 
					 | 
				
			||||||
			currentLabels := getGoroutineLabels()
 | 
					 | 
				
			||||||
			pprof.ForLabels(ctx, func(key, value string) bool {
 | 
					 | 
				
			||||||
				assert.EqualValues(t, value, currentLabels[key])
 | 
					 | 
				
			||||||
				return true
 | 
					 | 
				
			||||||
			})
 | 
					 | 
				
			||||||
			if assert.NotNil(t, currentLabels) {
 | 
					 | 
				
			||||||
				assert.EqualValues(t, "Test_getGoroutineLabels_child1", currentLabels["Test_getGoroutineLabels"])
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -200,11 +200,6 @@ func (l *LoggerImpl) Log(skip int, level Level, format string, logArgs ...any) {
 | 
				
			|||||||
		event.Stacktrace = Stack(skip + 1)
 | 
							event.Stacktrace = Stack(skip + 1)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	labels := getGoroutineLabels()
 | 
					 | 
				
			||||||
	if labels != nil {
 | 
					 | 
				
			||||||
		event.GoroutinePid = labels["pid"]
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// get a simple text message without color
 | 
						// get a simple text message without color
 | 
				
			||||||
	msgArgs := make([]any, len(logArgs))
 | 
						msgArgs := make([]any, len(logArgs))
 | 
				
			||||||
	copy(msgArgs, logArgs)
 | 
						copy(msgArgs, logArgs)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,7 +24,7 @@ type GlobalVarsType struct {
 | 
				
			|||||||
	LinkRegex   *regexp.Regexp // fast matching a URL link, no any extra validation.
 | 
						LinkRegex   *regexp.Regexp // fast matching a URL link, no any extra validation.
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var GlobalVars = sync.OnceValue[*GlobalVarsType](func() *GlobalVarsType {
 | 
					var GlobalVars = sync.OnceValue(func() *GlobalVarsType {
 | 
				
			||||||
	v := &GlobalVarsType{}
 | 
						v := &GlobalVarsType{}
 | 
				
			||||||
	v.wwwURLRegxp = regexp.MustCompile(`^www\.[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}((?:/|[#?])[-a-zA-Z0-9@:%_\+.~#!?&//=\(\);,'">\^{}\[\]` + "`" + `]*)?`)
 | 
						v.wwwURLRegxp = regexp.MustCompile(`^www\.[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}((?:/|[#?])[-a-zA-Z0-9@:%_\+.~#!?&//=\(\);,'">\^{}\[\]` + "`" + `]*)?`)
 | 
				
			||||||
	v.LinkRegex, _ = xurls.StrictMatchingScheme("https?://")
 | 
						v.LinkRegex, _ = xurls.StrictMatchingScheme("https?://")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -42,7 +42,7 @@ type globalVarsType struct {
 | 
				
			|||||||
	nulCleaner *strings.Replacer
 | 
						nulCleaner *strings.Replacer
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var globalVars = sync.OnceValue[*globalVarsType](func() *globalVarsType {
 | 
					var globalVars = sync.OnceValue(func() *globalVarsType {
 | 
				
			||||||
	v := &globalVarsType{}
 | 
						v := &globalVarsType{}
 | 
				
			||||||
	// NOTE: All below regex matching do not perform any extra validation.
 | 
						// NOTE: All below regex matching do not perform any extra validation.
 | 
				
			||||||
	// Thus a link is produced even if the linked entity does not exist.
 | 
						// Thus a link is produced even if the linked entity does not exist.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,7 @@ import (
 | 
				
			|||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/modules/markup/common"
 | 
						"code.gitea.io/gitea/modules/markup/common"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/util"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"golang.org/x/net/html"
 | 
						"golang.org/x/net/html"
 | 
				
			||||||
	"golang.org/x/net/html/atom"
 | 
						"golang.org/x/net/html/atom"
 | 
				
			||||||
@@ -171,6 +172,10 @@ func linkProcessor(ctx *RenderContext, node *html.Node) {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		uri := node.Data[m[0]:m[1]]
 | 
							uri := node.Data[m[0]:m[1]]
 | 
				
			||||||
 | 
							remaining := node.Data[m[1]:]
 | 
				
			||||||
 | 
							if util.IsLikelySplitLeftPart(remaining) {
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		replaceContent(node, m[0], m[1], createLink(ctx, uri, uri, "" /*link*/))
 | 
							replaceContent(node, m[0], m[1], createLink(ctx, uri, uri, "" /*link*/))
 | 
				
			||||||
		node = node.NextSibling.NextSibling
 | 
							node = node.NextSibling.NextSibling
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -206,6 +206,16 @@ func TestRender_links(t *testing.T) {
 | 
				
			|||||||
	test(
 | 
						test(
 | 
				
			||||||
		"ftps://gitea.com",
 | 
							"ftps://gitea.com",
 | 
				
			||||||
		`<p>ftps://gitea.com</p>`)
 | 
							`<p>ftps://gitea.com</p>`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Run("LinkSplit", func(t *testing.T) {
 | 
				
			||||||
 | 
							input, _ := util.SplitStringAtByteN("http://10.1.2.3", 12)
 | 
				
			||||||
 | 
							assert.Equal(t, "http://10…", input)
 | 
				
			||||||
 | 
							test(input, "<p>http://10…</p>")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							input, _ = util.SplitStringAtByteN("http://10.1.2.3", 13)
 | 
				
			||||||
 | 
							assert.Equal(t, "http://10.…", input)
 | 
				
			||||||
 | 
							test(input, "<p>http://10.…</p>")
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestRender_email(t *testing.T) {
 | 
					func TestRender_email(t *testing.T) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,7 +17,7 @@ import (
 | 
				
			|||||||
	"golang.org/x/net/html"
 | 
						"golang.org/x/net/html"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var reAttrClass = sync.OnceValue[*regexp.Regexp](func() *regexp.Regexp {
 | 
					var reAttrClass = sync.OnceValue(func() *regexp.Regexp {
 | 
				
			||||||
	// TODO: it isn't a problem at the moment because our HTML contents are always well constructed
 | 
						// TODO: it isn't a problem at the moment because our HTML contents are always well constructed
 | 
				
			||||||
	return regexp.MustCompile(`(<[^>]+)\s+class="([^"]+)"([^>]*>)`)
 | 
						return regexp.MustCompile(`(<[^>]+)\s+class="([^"]+)"([^>]*>)`)
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -112,7 +112,7 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// it is copied from old code, which is quite doubtful whether it is correct
 | 
					// it is copied from old code, which is quite doubtful whether it is correct
 | 
				
			||||||
var reValidIconName = sync.OnceValue[*regexp.Regexp](func() *regexp.Regexp {
 | 
					var reValidIconName = sync.OnceValue(func() *regexp.Regexp {
 | 
				
			||||||
	return regexp.MustCompile(`^[-\w]+$`) // old: regexp.MustCompile("^[a-z ]+$")
 | 
						return regexp.MustCompile(`^[-\w]+$`) // old: regexp.MustCompile("^[a-z ]+$")
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -129,7 +129,8 @@ func SpecializedMarkdown(ctx *markup.RenderContext) *GlodmarkRender {
 | 
				
			|||||||
				Enabled:           setting.Markdown.EnableMath,
 | 
									Enabled:           setting.Markdown.EnableMath,
 | 
				
			||||||
				ParseDollarInline: true,
 | 
									ParseDollarInline: true,
 | 
				
			||||||
				ParseDollarBlock:  true,
 | 
									ParseDollarBlock:  true,
 | 
				
			||||||
				ParseSquareBlock:  true, // TODO: this is a bad syntax, it should be deprecated in the future (by some config options)
 | 
									ParseSquareBlock:  true, // TODO: this is a bad syntax "\[ ... \]", it conflicts with normal markdown escaping, it should be deprecated in the future (by some config options)
 | 
				
			||||||
 | 
									// ParseBracketInline: true, // TODO: this is also a bad syntax "\( ... \)", it also conflicts, it should be deprecated in the future
 | 
				
			||||||
			}),
 | 
								}),
 | 
				
			||||||
			meta.Meta,
 | 
								meta.Meta,
 | 
				
			||||||
		),
 | 
							),
 | 
				
			||||||
 
 | 
				
			|||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user