mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-03 08:02:36 +09:00 
			
		
		
		
	Compare commits
	
		
			214 Commits
		
	
	
		
			v1.24.0-rc
			...
			v1.16.8
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					09b76295f1 | ||
| 
						 | 
					38acce2f3f | ||
| 
						 | 
					8f44d00f22 | ||
| 
						 | 
					4386eb751f | ||
| 
						 | 
					d6aab069ed | ||
| 
						 | 
					f4fb8dbc87 | ||
| 
						 | 
					c7c18e0eb2 | ||
| 
						 | 
					0a2d618d85 | ||
| 
						 | 
					c8a83ace59 | ||
| 
						 | 
					59d132f0b3 | ||
| 
						 | 
					18dd49a4ab | ||
| 
						 | 
					46637b1164 | ||
| 
						 | 
					7b18c67ac9 | ||
| 
						 | 
					6eb3c05cb7 | ||
| 
						 | 
					82f24bedc2 | ||
| 
						 | 
					88da50674f | ||
| 
						 | 
					35a7db49b4 | ||
| 
						 | 
					f4729e2418 | ||
| 
						 | 
					f7330fd027 | ||
| 
						 | 
					755d8e21ad | ||
| 
						 | 
					7c0bf06d96 | ||
| 
						 | 
					0d196e29e8 | ||
| 
						 | 
					b86606fa38 | ||
| 
						 | 
					74602bb487 | ||
| 
						 | 
					1465e0cbb2 | ||
| 
						 | 
					928b603d19 | ||
| 
						 | 
					8ff542c1a2 | ||
| 
						 | 
					39a0db6ecf | ||
| 
						 | 
					9cc93c05cd | ||
| 
						 | 
					b31418edd9 | ||
| 
						 | 
					242f7f1a52 | ||
| 
						 | 
					8d7f1e430a | ||
| 
						 | 
					a6b32adc45 | ||
| 
						 | 
					1f0dca4614 | ||
| 
						 | 
					1d665da32f | ||
| 
						 | 
					09adc26eb6 | ||
| 
						 | 
					297346a762 | ||
| 
						 | 
					acd648061d | ||
| 
						 | 
					c5fe0a096d | ||
| 
						 | 
					0c7bf6801f | ||
| 
						 | 
					5863f7e048 | ||
| 
						 | 
					a785c46ca8 | ||
| 
						 | 
					6bddfd3086 | ||
| 
						 | 
					dd8a726b25 | ||
| 
						 | 
					08eecba32b | ||
| 
						 | 
					9c2212df15 | ||
| 
						 | 
					9b4746967c | ||
| 
						 | 
					00da1facc4 | ||
| 
						 | 
					b461993775 | ||
| 
						 | 
					b885e57762 | ||
| 
						 | 
					081449d7a5 | ||
| 
						 | 
					ee3a21a537 | ||
| 
						 | 
					61c7732e12 | ||
| 
						 | 
					57c2ca7f26 | ||
| 
						 | 
					0704009dd7 | ||
| 
						 | 
					14a6aafb50 | ||
| 
						 | 
					471a1e8111 | ||
| 
						 | 
					123c254b84 | ||
| 
						 | 
					db43f63c53 | ||
| 
						 | 
					3ecd520f8e | ||
| 
						 | 
					e9935d358c | ||
| 
						 | 
					8d653b148b | ||
| 
						 | 
					b702f2dac3 | ||
| 
						 | 
					d59b8541f2 | ||
| 
						 | 
					efd34d0d7d | ||
| 
						 | 
					2ec2935f78 | ||
| 
						 | 
					540541caa2 | ||
| 
						 | 
					a13d64bf98 | ||
| 
						 | 
					bab7d885aa | ||
| 
						 | 
					42229dc0b8 | ||
| 
						 | 
					e3d8e92bdc | ||
| 
						 | 
					6fc73a8433 | ||
| 
						 | 
					b1a0a78a51 | ||
| 
						 | 
					9c7d8b3096 | ||
| 
						 | 
					93feb1a666 | ||
| 
						 | 
					bb0e2121a3 | ||
| 
						 | 
					d21b7fd3af | ||
| 
						 | 
					743553f3e9 | ||
| 
						 | 
					a3ccbb5b7f | ||
| 
						 | 
					4b7cb813e6 | ||
| 
						 | 
					23b8214549 | ||
| 
						 | 
					08feb6b664 | ||
| 
						 | 
					1aa5dc75df | ||
| 
						 | 
					ee234aff61 | ||
| 
						 | 
					a3f3e310fb | ||
| 
						 | 
					ea56bdca5f | ||
| 
						 | 
					45c836badc | ||
| 
						 | 
					f9ea4ab69a | ||
| 
						 | 
					e6d46eeb55 | ||
| 
						 | 
					5bb0c92b6c | ||
| 
						 | 
					c1e6be47d7 | ||
| 
						 | 
					79a5e68816 | ||
| 
						 | 
					9bcbbd419f | ||
| 
						 | 
					f460b7543e | ||
| 
						 | 
					1cb649525d | ||
| 
						 | 
					99861e3e06 | ||
| 
						 | 
					66b8a43e5f | ||
| 
						 | 
					d285905826 | ||
| 
						 | 
					4df2320ba6 | ||
| 
						 | 
					0fe99cc00c | ||
| 
						 | 
					580401ecbf | ||
| 
						 | 
					7aa29720f0 | ||
| 
						 | 
					3e5c844a77 | ||
| 
						 | 
					4047c5c068 | ||
| 
						 | 
					03d924238c | ||
| 
						 | 
					bc1248ed9e | ||
| 
						 | 
					dd52c08b74 | ||
| 
						 | 
					b811b819e2 | ||
| 
						 | 
					da985b25ce | ||
| 
						 | 
					ae9c51df7c | ||
| 
						 | 
					ff1c5815bb | ||
| 
						 | 
					87f8d37be5 | ||
| 
						 | 
					f4b96c1041 | ||
| 
						 | 
					a3f72303d1 | ||
| 
						 | 
					4317806ade | ||
| 
						 | 
					578f19a682 | ||
| 
						 | 
					f9b6404950 | ||
| 
						 | 
					52517e3e23 | ||
| 
						 | 
					36e96e3481 | ||
| 
						 | 
					a765410d0f | ||
| 
						 | 
					43fc2e528c | ||
| 
						 | 
					cb90eda213 | ||
| 
						 | 
					5f9c18b2b3 | ||
| 
						 | 
					4384b85046 | ||
| 
						 | 
					e0973a84a0 | ||
| 
						 | 
					054bc55a1c | ||
| 
						 | 
					4fb718d405 | ||
| 
						 | 
					df35049196 | ||
| 
						 | 
					ce75461380 | ||
| 
						 | 
					cea85c30a4 | ||
| 
						 | 
					6039138323 | ||
| 
						 | 
					eb43e73785 | ||
| 
						 | 
					c077a0361a | ||
| 
						 | 
					6f21a94d18 | ||
| 
						 | 
					8ebf0e68ec | ||
| 
						 | 
					3685cc7660 | ||
| 
						 | 
					9d9ccdbe43 | ||
| 
						 | 
					81b29d6263 | ||
| 
						 | 
					6591f87b28 | ||
| 
						 | 
					efc78c18c1 | ||
| 
						 | 
					f5a3c0dd6c | ||
| 
						 | 
					382101ecc7 | ||
| 
						 | 
					86c3481eff | ||
| 
						 | 
					039eb66c8c | ||
| 
						 | 
					36148ed083 | ||
| 
						 | 
					db4c7dcf15 | ||
| 
						 | 
					bec566282e | ||
| 
						 | 
					fa9be55018 | ||
| 
						 | 
					458239b46d | ||
| 
						 | 
					ae85ee1c6f | ||
| 
						 | 
					08d5a836ef | ||
| 
						 | 
					ad789542b8 | ||
| 
						 | 
					1f7802db97 | ||
| 
						 | 
					c876124efe | ||
| 
						 | 
					3a78ac4b32 | ||
| 
						 | 
					7ebc3da7cb | ||
| 
						 | 
					2e36ba0a00 | ||
| 
						 | 
					69a158dcc2 | ||
| 
						 | 
					913d6f3ff3 | ||
| 
						 | 
					044cb09ae8 | ||
| 
						 | 
					9da8e478dd | ||
| 
						 | 
					c8f3672a88 | ||
| 
						 | 
					edf85b820d | ||
| 
						 | 
					c04a4afac1 | ||
| 
						 | 
					65ad6362d7 | ||
| 
						 | 
					f9a0ae1dd4 | ||
| 
						 | 
					fb26b01688 | ||
| 
						 | 
					63628fdf1c | ||
| 
						 | 
					2e317d3f6e | ||
| 
						 | 
					ce69882180 | ||
| 
						 | 
					649abeda40 | ||
| 
						 | 
					4cfd62cddf | ||
| 
						 | 
					38fc6c75f3 | ||
| 
						 | 
					8671602ba9 | ||
| 
						 | 
					3d08e3a08c | ||
| 
						 | 
					d4a075d738 | ||
| 
						 | 
					bb77e6c12d | ||
| 
						 | 
					fabc0ad157 | ||
| 
						 | 
					a13fb154ae | ||
| 
						 | 
					36c66303df | ||
| 
						 | 
					f65e29c077 | ||
| 
						 | 
					a97c8a8966 | ||
| 
						 | 
					69b7776af5 | ||
| 
						 | 
					18c1edf15c | ||
| 
						 | 
					70ffec4509 | ||
| 
						 | 
					bc196a35e1 | ||
| 
						 | 
					8d31cfbfff | ||
| 
						 | 
					e84a432f76 | ||
| 
						 | 
					1fc9f11253 | ||
| 
						 | 
					0dfe5fa2d6 | ||
| 
						 | 
					1d17313949 | ||
| 
						 | 
					9c318a17f5 | ||
| 
						 | 
					72fa108cbc | ||
| 
						 | 
					db134c5d71 | ||
| 
						 | 
					73b68015de | ||
| 
						 | 
					e4919e414f | ||
| 
						 | 
					f7606de13a | ||
| 
						 | 
					483bda4b2d | ||
| 
						 | 
					edd57028a1 | ||
| 
						 | 
					083b85c655 | ||
| 
						 | 
					d5027b6c09 | ||
| 
						 | 
					a044ec8b53 | ||
| 
						 | 
					f93d72c09b | ||
| 
						 | 
					2f22337125 | ||
| 
						 | 
					781ad8a79e | ||
| 
						 | 
					cada7202aa | ||
| 
						 | 
					0b331e2213 | ||
| 
						 | 
					0734ca0132 | ||
| 
						 | 
					0b83cc21be | ||
| 
						 | 
					b68e605d56 | ||
| 
						 | 
					42991dc89a | ||
| 
						 | 
					160de9fbda | ||
| 
						 | 
					d644289fcb | ||
| 
						 | 
					fd9ff7cd6f | 
							
								
								
									
										483
									
								
								.drone.yml
									
									
									
									
									
								
							
							
						
						
									
										483
									
								
								.drone.yml
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -36,6 +36,8 @@ _testmain.go
 | 
			
		||||
coverage.all
 | 
			
		||||
cpu.out
 | 
			
		||||
 | 
			
		||||
/modules/migration/bindata.go
 | 
			
		||||
/modules/migration/bindata.go.hash
 | 
			
		||||
/modules/options/bindata.go
 | 
			
		||||
/modules/options/bindata.go.hash
 | 
			
		||||
/modules/public/bindata.go
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@ linters:
 | 
			
		||||
    #- gocyclo # The cyclomatic complexety of a lot of functions is too high, we should refactor those another time.
 | 
			
		||||
    - gofmt
 | 
			
		||||
    - misspell
 | 
			
		||||
    - gocritic
 | 
			
		||||
    #- gocritic # TODO: disabled until fixed with go 1.18
 | 
			
		||||
    - bidichk
 | 
			
		||||
    - ineffassign
 | 
			
		||||
    - revive
 | 
			
		||||
@@ -22,7 +22,11 @@ linters:
 | 
			
		||||
  fast: false
 | 
			
		||||
 | 
			
		||||
run:
 | 
			
		||||
  timeout: 3m
 | 
			
		||||
  timeout: 10m
 | 
			
		||||
  skip-dirs:
 | 
			
		||||
    - node_modules
 | 
			
		||||
    - public
 | 
			
		||||
    - web_src
 | 
			
		||||
 | 
			
		||||
linters-settings:
 | 
			
		||||
  gocritic:
 | 
			
		||||
@@ -57,6 +61,9 @@ linters-settings:
 | 
			
		||||
      - name: errorf
 | 
			
		||||
      - name: duplicated-imports
 | 
			
		||||
      - name: modifies-value-receiver
 | 
			
		||||
  gofumpt:
 | 
			
		||||
    extra-rules: true
 | 
			
		||||
    lang-version: 1.18
 | 
			
		||||
 | 
			
		||||
issues:
 | 
			
		||||
  exclude-rules:
 | 
			
		||||
@@ -144,3 +151,11 @@ issues:
 | 
			
		||||
    - path: models/user/openid.go
 | 
			
		||||
      linters:
 | 
			
		||||
        - golint
 | 
			
		||||
    - linters: staticcheck
 | 
			
		||||
      text: "strings.Title is deprecated: The rule Title uses for word boundaries does not handle Unicode punctuation properly. Use golang.org/x/text/cases instead."
 | 
			
		||||
    - linters: staticcheck
 | 
			
		||||
      text: "util.FindClosure is deprecated: This function can not handle newlines. Many elements can be existed over multiple lines(e.g. link labels). Use text.Reader.FindClosure."
 | 
			
		||||
    - linters: staticcheck
 | 
			
		||||
      text: "gossh.SigAlgoRSASHA2256 is deprecated: use KeyAlgoRSASHA256."
 | 
			
		||||
    - linters: staticcheck
 | 
			
		||||
      text: "gossh.SigAlgoRSASHA2512 is deprecated: use KeyAlgoRSASHA512."
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										263
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										263
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -4,13 +4,250 @@ This changelog goes through all the changes that have been made in each release
 | 
			
		||||
without substantial changes to our git log; to see the highlights of what has
 | 
			
		||||
been added to each release, please refer to the [blog](https://blog.gitea.io).
 | 
			
		||||
 | 
			
		||||
## [1.16.0-rc1](https://github.com/go-gitea/gitea/releases/tag/v1.16.0-rc1) - 2022-01-19
 | 
			
		||||
## [1.16.8](https://github.com/go-gitea/gitea/releases/tag/v1.16.8) - 2022-05-16
 | 
			
		||||
 | 
			
		||||
* ENHANCEMENTS
 | 
			
		||||
  * Add doctor check/fix for bogus action rows (#19656) (#19669)
 | 
			
		||||
  * Make .cs highlighting legible on dark themes. (#19604) (#19605)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Fix oauth setting list bug (#19681)
 | 
			
		||||
  * Delete user related oauth stuff on user deletion too (#19677) (#19680)
 | 
			
		||||
  * Fix new release from tags list UI (#19670) (#19673)
 | 
			
		||||
  * Prevent NPE when checking repo units if the user is nil (#19625) (#19630)
 | 
			
		||||
  * GetFeeds must always discard actions with dangling repo_id (#19598) (#19629)
 | 
			
		||||
  * Call MultipartForm.RemoveAll when request finishes (#19606) (#19607)
 | 
			
		||||
  * Avoid MoreThanOne error when creating a branch whose name conflicts with other ref names (#19557) (#19591)
 | 
			
		||||
  * Fix sending empty notifications (#19589) (#19590)
 | 
			
		||||
  * Ignore DNS error when doing migration allow/block check (#19566) (#19567)
 | 
			
		||||
  * Fix issue overview for teams (#19652) (#19653)
 | 
			
		||||
 | 
			
		||||
## [1.16.7](https://github.com/go-gitea/gitea/releases/tag/v1.16.7) - 2022-05-02
 | 
			
		||||
 | 
			
		||||
* SECURITY
 | 
			
		||||
  * Escape git fetch remote (#19487) (#19490)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Don't overwrite err with nil (#19572) (#19574)
 | 
			
		||||
  * On Migrations, only write commit-graph if wiki clone was successful (#19563) (#19568)
 | 
			
		||||
  * Respect DefaultUserIsRestricted system default when creating new user (#19310) (#19560)
 | 
			
		||||
  * Don't error when branch's commit doesn't exist (#19547) (#19548)
 | 
			
		||||
  * Support `hostname:port` to pass host matcher's check (#19543) (#19544)
 | 
			
		||||
  * Prevent intermittent race in attribute reader close (#19537) (#19539)
 | 
			
		||||
  * Fix 64-bit atomic operations on 32-bit machines (#19531) (#19532)
 | 
			
		||||
  * Prevent dangling archiver goroutine (#19516) (#19526)
 | 
			
		||||
  * Fix migrate release from github (#19510) (#19523)
 | 
			
		||||
  * When view _Siderbar or _Footer, just display once (#19501) (#19522)
 | 
			
		||||
  * Fix blame page select range error and some typos (#19503)
 | 
			
		||||
  * Fix name of doctor fix "authorized-keys" in hints (#19464) (#19484)
 | 
			
		||||
  * User specific repoID or xorm builder conditions for issue search (#19475) (#19476)
 | 
			
		||||
  * Prevent dangling cat-file calls (goroutine alternative) (#19454) (#19466)
 | 
			
		||||
  * RepoAssignment ensure to close before overwrite (#19449) (#19460)
 | 
			
		||||
  * Set correct PR status on 3way on conflict checking (#19457) (#19458)
 | 
			
		||||
  * Mark TemplateLoading error as "UnprocessableEntity" (#19445) (#19446)
 | 
			
		||||
 | 
			
		||||
## [1.16.6](https://github.com/go-gitea/gitea/releases/tag/v1.16.6) - 2022-04-20
 | 
			
		||||
 | 
			
		||||
* ENHANCEMENTS
 | 
			
		||||
 * Only request write when necessary (#18657) (#19422)
 | 
			
		||||
 * Disable service worker by default (#18914) (#19342)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * When dumping trim the standard suffices instead of a random suffix (#19440) (#19447)
 | 
			
		||||
  * Fix DELETE request for non-existent public key (#19443) (#19444)
 | 
			
		||||
  * Don't panic on ErrEmailInvalid (#19441) (#19442)
 | 
			
		||||
  * Add uploadpack.allowAnySHA1InWant to allow --filter=blob:none with older git clients (#19430) (#19438)
 | 
			
		||||
  * Warn on SSH connection for incorrect configuration (#19317) (#19437)
 | 
			
		||||
  * Search Issues via API, dont show 500 if filter result in empty list (#19244) (#19436)
 | 
			
		||||
  * When updating mirror repo intervals by API reschedule next update too (#19429) (#19433)
 | 
			
		||||
  * Fix nil error when some pages are rendered outside request context (#19427) (#19428)
 | 
			
		||||
  * Fix double blob-hunk on diff page (#19404) (#19405)
 | 
			
		||||
  * Don't allow merging PR's which are being conflict checked (#19357) (#19358)
 | 
			
		||||
  * Fix middleware function's placements (#19377) (#19378)
 | 
			
		||||
  * Fix invalid CSRF token bug, make sure CSRF tokens can be up-to-date (#19338)
 | 
			
		||||
  * Restore user autoregistration with email addresses (#19261) (#19312)
 | 
			
		||||
  * Move checks for pulls before merge into own function (#19271) (#19277)
 | 
			
		||||
  * Granular webhook events in editHook (#19251) (#19257)
 | 
			
		||||
  * Only send webhook events to active system webhooks and only deliver to active hooks (#19234) (#19248)
 | 
			
		||||
  * Use full output of git show-ref --tags to get tags for PushUpdateAddTag (#19235) (#19236)
 | 
			
		||||
  * Touch mirrors on even on fail to update (#19217) (#19233)
 | 
			
		||||
  * Hide sensitive content on admin panel progress monitor (#19218 & #19226) (#19231)
 | 
			
		||||
  * Fix clone url JS error for the empty repo page (#19209)
 | 
			
		||||
  * Bump goldmark to v1.4.11 (#19201) (#19203)
 | 
			
		||||
* TESTING
 | 
			
		||||
  * Prevent intermittent failures in RepoIndexerTest (#19225 #19229) (#19228)
 | 
			
		||||
* BUILD
 | 
			
		||||
  * Revert the minimal golang version requirement from 1.17 to 1.16 and add a warning in Makefile (#19319)
 | 
			
		||||
* MISC
 | 
			
		||||
  * Performance improvement for add team user when org has more than 1000 repositories (#19227) (#19289)
 | 
			
		||||
  * Check go and nodejs version by go.mod and package.json (#19197) (#19254)
 | 
			
		||||
 | 
			
		||||
## [1.16.5](https://github.com/go-gitea/gitea/releases/tag/v1.16.5) - 2022-03-23
 | 
			
		||||
 | 
			
		||||
* BREAKING
 | 
			
		||||
  * Bump to build with go1.18 (#19120 et al) (#19127)
 | 
			
		||||
* SECURITY
 | 
			
		||||
  * Prevent redirect to Host (2) (#19175) (#19186)
 | 
			
		||||
  * Try to prevent autolinking of displaynames by email readers (#19169) (#19183)
 | 
			
		||||
  * Clean paths when looking in Storage (#19124) (#19179)
 | 
			
		||||
  * Do not send notification emails to inactive users (#19131) (#19139)
 | 
			
		||||
  * Do not send activation email if manual confirm is set (#19119) (#19122)
 | 
			
		||||
* ENHANCEMENTS
 | 
			
		||||
  * Use the new/choose link for New Issue on project page (#19172) (#19176)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Fix showing issues in your repositories (#18916) (#19191)
 | 
			
		||||
  * Fix compare link in active feeds for new branch (#19149) (#19185)
 | 
			
		||||
  * Redirect .wiki/* ui link to /wiki (#18831) (#19184)
 | 
			
		||||
  * Ensure deploy keys with write access can push (#19010) (#19182)
 | 
			
		||||
  * Ensure that setting.LocalURL always has a trailing slash (#19171) (#19177)
 | 
			
		||||
  * Cleanup protected branches when deleting users & teams (#19158) (#19174)
 | 
			
		||||
  * Use IterateBufferSize whilst querying repositories during adoption check (#19140) (#19160)
 | 
			
		||||
  * Fix NPE /repos/issues/search when not signed in (#19154) (#19155)
 | 
			
		||||
  * Use custom favicon when viewing static files if it exists (#19130) (#19152)
 | 
			
		||||
  * Fix the editor height in review box (#19003) (#19147)
 | 
			
		||||
  * Ensure isSSH is set whenever DISABLE_HTTP_GIT is set (#19028) (#19146)
 | 
			
		||||
  * Fix wrong scopes caused by empty scope input (#19029) (#19145)
 | 
			
		||||
  * Make migrations SKIP_TLS_VERIFY apply to git too (#19132) (#19141)
 | 
			
		||||
  * Handle email address not exist (#19089) (#19121)
 | 
			
		||||
* MISC
 | 
			
		||||
  * Update json-iterator to allow compilation with go1.18 (#18644) (#19100)
 | 
			
		||||
  * Update golang.org/x/crypto (#19097) (#19098)
 | 
			
		||||
 | 
			
		||||
## [1.16.4](https://github.com/go-gitea/gitea/releases/tag/v1.16.4) - 2022-03-14
 | 
			
		||||
 | 
			
		||||
* SECURITY
 | 
			
		||||
  * Restrict email address validation (#17688) (#19085)
 | 
			
		||||
  * Fix lfs bug (#19072) (#19080)
 | 
			
		||||
* ENHANCEMENTS
 | 
			
		||||
  * Improve SyncMirrors logging (#19045) (#19050)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Refactor mirror code & fix `StartToMirror` (#18904) (#19075)
 | 
			
		||||
  * Update the webauthn_credential_id_sequence in Postgres (#19048) (#19060)
 | 
			
		||||
  * Prevent 500 when there is an error during new auth source post (#19041) (#19059)
 | 
			
		||||
  * If rendering has failed due to a net.OpError stop rendering (attempt 2) (#19049) (#19056)
 | 
			
		||||
  * Fix flag validation (#19046) (#19051)
 | 
			
		||||
  * Add pam account authorization check (#19040) (#19047)
 | 
			
		||||
  * Ignore missing comment for user notifications (#18954) (#19043)
 | 
			
		||||
  * Set `rel="nofollow noindex"` on new issue links (#19023) (#19042)
 | 
			
		||||
  * Upgrading binding package (#19034) (#19035)
 | 
			
		||||
  * Don't show context cancelled errors in attribute reader (#19006) (#19027)
 | 
			
		||||
  * Fix update hint bug (#18996) (#19002)
 | 
			
		||||
* MISC
 | 
			
		||||
  *  Fix potential assignee query for repo (#18994) (#18999)
 | 
			
		||||
 | 
			
		||||
## [1.16.3](https://github.com/go-gitea/gitea/releases/tag/v1.16.3) - 2022-03-02
 | 
			
		||||
 | 
			
		||||
* SECURITY
 | 
			
		||||
 * Git backend ignore replace objects (#18979) (#18980) 
 | 
			
		||||
* ENHANCEMENTS
 | 
			
		||||
  * Adjust error for already locked db and prevent level db lock on malformed connstr (#18923) (#18938)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Set max text height to prevent overflow (#18862) (#18977)
 | 
			
		||||
  * Fix newAttachmentPaths deletion for DeleteRepository() (#18973) (#18974)
 | 
			
		||||
  * Accounts with WebAuthn only (no TOTP) now exist ... fix code to handle that case (#18897) (#18964)
 | 
			
		||||
  * Send 404 on `/{org}.gpg` (#18959) (#18962)
 | 
			
		||||
  * Fix admin user list pagination (#18957) (#18960)
 | 
			
		||||
  * Fix lfs management setting (#18947) (#18946)
 | 
			
		||||
  * Fix login with email panic when email is not exist (#18942)
 | 
			
		||||
  * Update go-org to v1.6.1 (#18932) (#18933)
 | 
			
		||||
  * Fix `<strong>` html in translation (#18929) (#18931)
 | 
			
		||||
  * Fix page and missing return on unadopted repos API (#18848) (#18927)
 | 
			
		||||
  * Allow adminstrator teams members to see other teams (#18918) (#18919)
 | 
			
		||||
  * Don't treat BOM escape sequence as hidden character. (#18909) (#18910)
 | 
			
		||||
  * Correctly link URLs to users/repos with dashes, dots or underscores (… (#18908)
 | 
			
		||||
  * Fix redirect when using lowercase repo name (#18775) (#18902)
 | 
			
		||||
  * Fix migration v210 (#18893) (#18892)
 | 
			
		||||
  * Fix team management UI (#18887) (18886)
 | 
			
		||||
  * BeforeSourcePath should point to base commit (#18880) (#18799)
 | 
			
		||||
* TRANSLATION
 | 
			
		||||
  * Backport locales from master (#18944)
 | 
			
		||||
* MISC
 | 
			
		||||
  * Don't update email for organisation (#18905) (#18906)
 | 
			
		||||
 | 
			
		||||
## [1.16.2](https://github.com/go-gitea/gitea/releases/tag/v1.16.2) - 2022-02-24
 | 
			
		||||
 | 
			
		||||
* ENHANCEMENTS
 | 
			
		||||
  * Show fullname on issue edits and gpg/ssh signing info (#18828)
 | 
			
		||||
  * Immediately Hammer if second kill is sent (#18823) (#18826)
 | 
			
		||||
  * Allow mermaid render error to wrap (#18791)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Fix ldap user sync missed email in email_address table (#18786) (#18876) 
 | 
			
		||||
  * Update assignees check to include any writing team and change org sidebar (#18680) (#18873)
 | 
			
		||||
  * Don't report signal: killed errors in serviceRPC (#18850) (#18865)
 | 
			
		||||
  * Fix bug where certain LDAP settings were reverted (#18859)
 | 
			
		||||
  * Update go-org to 1.6.0 (#18824) (#18839)
 | 
			
		||||
  * Fix login with email for ldap users (#18800) (#18836)
 | 
			
		||||
  * Fix bug for get user by email (#18834)
 | 
			
		||||
  * Fix panic in EscapeReader (#18820) (#18821)
 | 
			
		||||
  * Fix ldap loginname (#18789) (#18804)
 | 
			
		||||
  * Remove redundant call to UpdateRepoStats during migration (#18591) (#18794)
 | 
			
		||||
  * In disk_channel queues synchronously push to disk on shutdown (#18415) (#18788)
 | 
			
		||||
  * Fix template bug of LFS lock (#18784) (#18787)
 | 
			
		||||
  * Attempt to fix the webauthn migration again - part 3 (#18770) (#18771)
 | 
			
		||||
  * Send mail to issue/pr assignee/reviewer also when OnMention is set (#18707) (#18765)
 | 
			
		||||
  * Fix a broken link in commits_list_small.tmpl (#18763) (#18764)
 | 
			
		||||
  * Increase the size of the webauthn_credential credential_id field (#18739) (#18756)
 | 
			
		||||
  * Prevent dangling GetAttribute calls (#18754) (#18755)
 | 
			
		||||
  * Fix isempty detection of git repository (#18746) (#18750)
 | 
			
		||||
  * Fix source code line highlighting on external tracker (#18729) (#18740)
 | 
			
		||||
  * Prevent double encoding of branch names in delete branch (#18714) (#18738)
 | 
			
		||||
  * Always set PullRequestWorkInProgressPrefixes in PrepareViewPullInfo (#18713) (#18737)
 | 
			
		||||
  * Fix forked repositories missed tags (#18719) (#18735)
 | 
			
		||||
  * Fix release typo (#18728) (#18731)
 | 
			
		||||
  * Separate the details links of commit-statuses in headers (#18661) (#18730)
 | 
			
		||||
  * Update object repo with the migrated repository (#18684) (#18726)
 | 
			
		||||
  * Fix bug for version update hint (#18701) (#18705)
 | 
			
		||||
  * Fix issue with docker-rootless shimming script (#18690) (#18699)
 | 
			
		||||
  * Let `MinUnitAccessMode` return correct perm (#18675) (#18689)
 | 
			
		||||
  * Prevent security failure due to bad APP_ID (#18678) (#18682)
 | 
			
		||||
  * Restart zero worker if there is still work to do (#18658) (#18672)
 | 
			
		||||
  * If rendering has failed due to a net.OpError stop rendering (#18642) (#18645)
 | 
			
		||||
* TESTING
 | 
			
		||||
  * Ensure git tag tests and others create test repos in tmpdir (#18447) (#18767)
 | 
			
		||||
* BUILD
 | 
			
		||||
  * Reduce CI go module downloads, add make targets (#18708, #18475, #18443) (#18741)
 | 
			
		||||
* MISC
 | 
			
		||||
  * Put buttons back in org dashboard (#18817) (#18825)
 | 
			
		||||
  * Various Mermaid improvements (#18776) (#18780)
 | 
			
		||||
  * C preprocessor colors improvement (#18671) (#18696)
 | 
			
		||||
  * Fix the missing i18n key for update checker (#18646) (#18665)
 | 
			
		||||
 | 
			
		||||
## [1.16.1](https://github.com/go-gitea/gitea/releases/tag/v1.16.1) - 2022-02-06
 | 
			
		||||
 | 
			
		||||
* SECURITY
 | 
			
		||||
  * Update JS dependencies, fix lint (#18389) (#18540)
 | 
			
		||||
* ENHANCEMENTS
 | 
			
		||||
  * Add dropdown icon to label set template dropdown (#18564) (#18571)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Comments on migrated issues/prs must link to the comment ID (#18630) (#18637)
 | 
			
		||||
  * Stop logging an error when notes are not found (#18626) (#18635)
 | 
			
		||||
  * Ensure that blob-excerpt links work for wiki (#18587) (#18624)
 | 
			
		||||
  * Only attempt to flush queue if the underlying worker pool is not finished (#18593) (#18620)
 | 
			
		||||
  * Ensure commit-statuses box is sized correctly in headers (#18538) (#18606)
 | 
			
		||||
  * Prevent merge messages from being sorted to the top of email chains (#18566) (#18588)
 | 
			
		||||
  * Prevent panic on prohibited user login with oauth2 (#18562) (#18563)
 | 
			
		||||
  * Collaborator trust model should trust collaborators (#18539) (#18557)
 | 
			
		||||
  * Detect conflicts with 3way merge (#18536) (#18537)
 | 
			
		||||
  * In docker rootless use $GITEA_APP_INI if provided (#18524) (#18535)
 | 
			
		||||
  * Add `GetUserTeams` (#18499) (#18531)
 | 
			
		||||
  * Fix review excerpt (#18502) (#18530)
 | 
			
		||||
  * Fix for AvatarURL database type (#18487) (#18529)
 | 
			
		||||
  * Use `ImagedProvider` for gplus oauth2 provider (#18504) (#18505)
 | 
			
		||||
  * Fix OAuth Source Edit Page (#18495) (#18503)
 | 
			
		||||
  * Use "read" value for General Access (#18496) (#18500)
 | 
			
		||||
  * Prevent NPE on partial match of compare URL and allow short SHA1 compare URLs (#18472) (#18473)
 | 
			
		||||
* BUILD
 | 
			
		||||
  * Make docker gitea/gitea:v1.16-dev etc refer to the latest build on that branch (#18551) (#18569)
 | 
			
		||||
* DOCS
 | 
			
		||||
  * Update 1.16.0 changelog to set #17846 as breaking (#18533) (#18534)
 | 
			
		||||
 | 
			
		||||
## [1.16.0](https://github.com/go-gitea/gitea/releases/tag/v1.16.0) - 2022-01-30
 | 
			
		||||
 | 
			
		||||
* BREAKING
 | 
			
		||||
  * Remove golang vendored directory (#18277)
 | 
			
		||||
  * Paginate releases page & set default page size to 10 (#16857)
 | 
			
		||||
  * Use shadowing script for docker (#17846)
 | 
			
		||||
  * Only allow webhook to send requests to allowed hosts (#17482)
 | 
			
		||||
* SECURITY
 | 
			
		||||
  * Disable content sniffing on `PlainTextBytes` (#18359) (#18365)
 | 
			
		||||
  * Only view milestones from current repo (#18414) (#18417)
 | 
			
		||||
  * Sanitize user-input on file name (#17666)
 | 
			
		||||
  * Use `hostmatcher` to replace `matchlist` to improve blocking of bad hosts in Webhooks (#17605)
 | 
			
		||||
* FEATURES
 | 
			
		||||
@@ -228,6 +465,16 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
 | 
			
		||||
  * Add left padding for chunk header of split diff view (#13397)
 | 
			
		||||
  * Allow U2F 2FA without TOTP (#11573)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * GitLab reviews may not have the updated_at field set (#18450) (#18461)
 | 
			
		||||
  * Fix detection of no commits when the default branch is not master (#18422) (#18423)
 | 
			
		||||
  * Fix broken oauth2 authentication source edit page (#18412) (#18419)
 | 
			
		||||
  * Place inline diff comment dialogs on split diff in 4th and 8th columns (#18403) (#18404)
 | 
			
		||||
  * Fix restore without topic failure (#18387) (#18400)
 | 
			
		||||
  * Fix commit's time (#18375) (#18392)
 | 
			
		||||
  * Fix partial cloning a repo (#18373) (#18377)
 | 
			
		||||
  * Stop trimming preceding and suffixing spaces from editor filenames (#18334)
 | 
			
		||||
  * Prevent showing webauthn error for every time visiting `/user/settings/security` (#18386)
 | 
			
		||||
  * Fix mime-type detection for HTTP server (#18370) (#18371)
 | 
			
		||||
  * Stop trimming preceding and suffixing spaces from editor filenames (#18334)
 | 
			
		||||
  * Restore propagation of ErrDependenciesLeft (#18325)
 | 
			
		||||
  * Fix PR comments UI (#18323)
 | 
			
		||||
@@ -295,10 +542,22 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
 | 
			
		||||
* BUILD
 | 
			
		||||
  * Add lockfile-check (#18285)
 | 
			
		||||
  * Don't store assets modified time into generated files (#18193)
 | 
			
		||||
  * Use shadowing script for docker (#17846)
 | 
			
		||||
* MISC
 | 
			
		||||
  * Update JS dependencies (#17611)
 | 
			
		||||
 | 
			
		||||
## [1.15.11](https://github.com/go-gitea/gitea/releases/tag/v1.15.11) - 2022-01-29
 | 
			
		||||
 | 
			
		||||
* SECURITY
 | 
			
		||||
  * Only view milestones from current repo (#18414) (#18418)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Fix broken when no commits and default branch is not master (#18422) (#18424)
 | 
			
		||||
  * Fix commit's time (#18375) (#18409)
 | 
			
		||||
  * Fix restore without topic failure (#18387) (#18401)
 | 
			
		||||
  * Fix mermaid import in 1.15 (it uses ESModule now) (#18382)
 | 
			
		||||
  * Update to go/text 0.3.7 (#18336)
 | 
			
		||||
* MISC
 | 
			
		||||
  * Upgrade EasyMDE to 2.16.1 (#18278) (#18279)
 | 
			
		||||
 | 
			
		||||
## [1.15.10](https://github.com/go-gitea/gitea/releases/tag/v1.15.10) - 2022-01-14
 | 
			
		||||
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,5 @@
 | 
			
		||||
 | 
			
		||||
###################################
 | 
			
		||||
#Build stage - temporarily using techknowlogick image until we upgrade to latest official alpine/go image
 | 
			
		||||
FROM techknowlogick/go:1.17-alpine3.13 AS build-env
 | 
			
		||||
#Build stage
 | 
			
		||||
FROM golang:1.18-alpine3.15 AS build-env
 | 
			
		||||
 | 
			
		||||
ARG GOPROXY
 | 
			
		||||
ENV GOPROXY ${GOPROXY:-direct}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,5 @@
 | 
			
		||||
 | 
			
		||||
###################################
 | 
			
		||||
#Build stage - temporarily using techknowlogick image until we upgrade to latest official alpine/go image
 | 
			
		||||
FROM techknowlogick/go:1.17-alpine3.13 AS build-env
 | 
			
		||||
#Build stage
 | 
			
		||||
FROM golang:1.18-alpine3.15 AS build-env
 | 
			
		||||
 | 
			
		||||
ARG GOPROXY
 | 
			
		||||
ENV GOPROXY ${GOPROXY:-direct}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										127
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										127
									
								
								Makefile
									
									
									
									
									
								
							@@ -24,10 +24,17 @@ SHASUM ?= shasum -a 256
 | 
			
		||||
HAS_GO = $(shell hash $(GO) > /dev/null 2>&1 && echo "GO" || echo "NOGO" )
 | 
			
		||||
COMMA := ,
 | 
			
		||||
 | 
			
		||||
XGO_VERSION := go-1.17.x
 | 
			
		||||
MIN_GO_VERSION := 001016000
 | 
			
		||||
MIN_NODE_VERSION := 012017000
 | 
			
		||||
MIN_GOLANGCI_LINT_VERSION := 001043000
 | 
			
		||||
XGO_VERSION := go-1.18.x
 | 
			
		||||
 | 
			
		||||
AIR_PACKAGE ?= github.com/cosmtrek/air@v1.29.0
 | 
			
		||||
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/cmd/editorconfig-checker@2.4.0
 | 
			
		||||
ERRCHECK_PACKAGE ?= github.com/kisielk/errcheck@v1.6.0
 | 
			
		||||
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.3.0
 | 
			
		||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.44.2
 | 
			
		||||
GXZ_PAGAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.10
 | 
			
		||||
MISSPELL_PACKAGE ?= github.com/client9/misspell/cmd/misspell@v0.3.4
 | 
			
		||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.29.0
 | 
			
		||||
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
 | 
			
		||||
 | 
			
		||||
DOCKER_IMAGE ?= gitea/gitea
 | 
			
		||||
DOCKER_TAG ?= latest
 | 
			
		||||
@@ -125,8 +132,6 @@ ifeq ($(filter $(TAGS_SPLIT),bindata),bindata)
 | 
			
		||||
	GO_SOURCES += $(BINDATA_DEST)
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
#To update swagger use: GO111MODULE=on go get -u github.com/go-swagger/go-swagger/cmd/swagger
 | 
			
		||||
SWAGGER := $(GO) run github.com/go-swagger/go-swagger/cmd/swagger
 | 
			
		||||
SWAGGER_SPEC := templates/swagger/v1_json.tmpl
 | 
			
		||||
SWAGGER_SPEC_S_TMPL := s|"basePath": *"/api/v1"|"basePath": "{{AppSubUrl \| JSEscape \| Safe}}/api/v1"|g
 | 
			
		||||
SWAGGER_SPEC_S_JSON := s|"basePath": *"{{AppSubUrl \| JSEscape \| Safe}}/api/v1"|"basePath": "/api/v1"|g
 | 
			
		||||
@@ -166,6 +171,9 @@ help:
 | 
			
		||||
	@echo " - watch-backend                    watch backend files and continuously rebuild"
 | 
			
		||||
	@echo " - clean                            delete backend and integration files"
 | 
			
		||||
	@echo " - clean-all                        delete backend, frontend and integration files"
 | 
			
		||||
	@echo " - deps                             install dependencies"
 | 
			
		||||
	@echo " - deps-frontend                    install frontend dependencies"
 | 
			
		||||
	@echo " - deps-backend                     install backend dependencies"
 | 
			
		||||
	@echo " - lint                             lint everything"
 | 
			
		||||
	@echo " - lint-frontend                    lint frontend files"
 | 
			
		||||
	@echo " - lint-backend                     lint backend files"
 | 
			
		||||
@@ -193,10 +201,15 @@ help:
 | 
			
		||||
 | 
			
		||||
.PHONY: go-check
 | 
			
		||||
go-check:
 | 
			
		||||
	$(eval GO_VERSION := $(shell printf "%03d%03d%03d" $(shell $(GO) version | grep -Eo '[0-9]+\.[0-9.]+' | tr '.' ' ');))
 | 
			
		||||
	$(eval MIN_GO_VERSION_STR := $(shell grep -Eo '^go\s+[0-9]+\.[0-9.]+' go.mod | cut -d' ' -f2))
 | 
			
		||||
	$(eval MIN_GO_VERSION := $(shell printf "%03d%03d%03d" $(shell echo '$(MIN_GO_VERSION_STR)' | tr '.' ' ')))
 | 
			
		||||
	$(eval GO_VERSION_STR := $(shell $(GO) version | grep -Eo '[0-9]+\.[0-9.]+'))
 | 
			
		||||
	$(eval GO_VERSION := $(shell printf "%03d%03d%03d" $(shell echo '$(GO_VERSION_STR)' | tr '.' ' ')))
 | 
			
		||||
	@if [ "$(GO_VERSION)" -lt "$(MIN_GO_VERSION)" ]; then \
 | 
			
		||||
		echo "Gitea requires Go 1.16 or greater to build. You can get it at https://golang.org/dl/"; \
 | 
			
		||||
		echo "Gitea requires Go $(MIN_GO_VERSION_STR) or greater to build, but $(GO_VERSION) was found. You can get an updated version at https://go.dev/dl/"; \
 | 
			
		||||
		exit 1; \
 | 
			
		||||
	else \
 | 
			
		||||
		echo "WARNING: Please ensure Go $(GO_VERSION_STR) is still maintained to avoid possible security problems. You can check it at https://go.dev/dl/"; \
 | 
			
		||||
	fi
 | 
			
		||||
 | 
			
		||||
.PHONY: git-check
 | 
			
		||||
@@ -208,11 +221,12 @@ git-check:
 | 
			
		||||
 | 
			
		||||
.PHONY: node-check
 | 
			
		||||
node-check:
 | 
			
		||||
	$(eval MIN_NODE_VERSION_STR := $(shell grep -Eo '"node":.*[0-9.]+"' package.json | sed -n 's/.*[^0-9.]\([0-9.]*\)"/\1/p'))
 | 
			
		||||
	$(eval MIN_NODE_VERSION := $(shell printf "%03d%03d%03d" $(shell echo '$(MIN_NODE_VERSION_STR)' | tr '.' ' ')))
 | 
			
		||||
	$(eval NODE_VERSION := $(shell printf "%03d%03d%03d" $(shell node -v | cut -c2- | tr '.' ' ');))
 | 
			
		||||
	$(eval MIN_NODE_VER_FMT := $(shell printf "%g.%g.%g" $(shell echo $(MIN_NODE_VERSION) | grep -o ...)))
 | 
			
		||||
	$(eval NPM_MISSING := $(shell hash npm > /dev/null 2>&1 || echo 1))
 | 
			
		||||
	@if [ "$(NODE_VERSION)" -lt "$(MIN_NODE_VERSION)" -o "$(NPM_MISSING)" = "1" ]; then \
 | 
			
		||||
		echo "Gitea requires Node.js $(MIN_NODE_VER_FMT) or greater and npm to build. You can get it at https://nodejs.org/en/download/"; \
 | 
			
		||||
		echo "Gitea requires Node.js $(MIN_NODE_VERSION_STR) or greater and npm to build. You can get it at https://nodejs.org/en/download/"; \
 | 
			
		||||
		exit 1; \
 | 
			
		||||
	fi
 | 
			
		||||
 | 
			
		||||
@@ -231,8 +245,8 @@ clean:
 | 
			
		||||
 | 
			
		||||
.PHONY: fmt
 | 
			
		||||
fmt:
 | 
			
		||||
	@echo "Running gitea-fmt(with gofmt)..."
 | 
			
		||||
	@$(GO) run build/code-batch-process.go gitea-fmt -s -w '{file-list}'
 | 
			
		||||
	@echo "Running gitea-fmt (with gofumpt)..."
 | 
			
		||||
	@MISSPELL_PACKAGE=$(MISSPELL_PACKAGE) GOFUMPT_PACKAGE=$(GOFUMPT_PACKAGE) $(GO) run build/code-batch-process.go gitea-fmt -w '{file-list}'
 | 
			
		||||
 | 
			
		||||
.PHONY: vet
 | 
			
		||||
vet:
 | 
			
		||||
@@ -251,7 +265,7 @@ endif
 | 
			
		||||
 | 
			
		||||
.PHONY: generate-swagger
 | 
			
		||||
generate-swagger:
 | 
			
		||||
	$(SWAGGER) generate spec -x "$(SWAGGER_EXCLUDE)" -o './$(SWAGGER_SPEC)'
 | 
			
		||||
	$(GO) run $(SWAGGER_PACKAGE) generate spec -x "$(SWAGGER_EXCLUDE)" -o './$(SWAGGER_SPEC)'
 | 
			
		||||
	$(SED_INPLACE) '$(SWAGGER_SPEC_S_TMPL)' './$(SWAGGER_SPEC)'
 | 
			
		||||
	$(SED_INPLACE) $(SWAGGER_NEWLINE_COMMAND) './$(SWAGGER_SPEC)'
 | 
			
		||||
 | 
			
		||||
@@ -267,21 +281,18 @@ swagger-check: generate-swagger
 | 
			
		||||
.PHONY: swagger-validate
 | 
			
		||||
swagger-validate:
 | 
			
		||||
	$(SED_INPLACE) '$(SWAGGER_SPEC_S_JSON)' './$(SWAGGER_SPEC)'
 | 
			
		||||
	$(SWAGGER) validate './$(SWAGGER_SPEC)'
 | 
			
		||||
	$(GO) run $(SWAGGER_PACKAGE) validate './$(SWAGGER_SPEC)'
 | 
			
		||||
	$(SED_INPLACE) '$(SWAGGER_SPEC_S_TMPL)' './$(SWAGGER_SPEC)'
 | 
			
		||||
 | 
			
		||||
.PHONY: errcheck
 | 
			
		||||
errcheck:
 | 
			
		||||
	@hash errcheck > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
 | 
			
		||||
		$(GO) install github.com/kisielk/errcheck@8ddee489636a8311a376fc92e27a6a13c6658344; \
 | 
			
		||||
	fi
 | 
			
		||||
	@echo "Running errcheck..."
 | 
			
		||||
	@errcheck $(GO_PACKAGES)
 | 
			
		||||
	$(GO) run $(ERRCHECK_PACKAGE) $(GO_PACKAGES)
 | 
			
		||||
 | 
			
		||||
.PHONY: fmt-check
 | 
			
		||||
fmt-check:
 | 
			
		||||
	# get all go files and run gitea-fmt (with gofmt) on them
 | 
			
		||||
	@diff=$$($(GO) run build/code-batch-process.go gitea-fmt -s -d '{file-list}'); \
 | 
			
		||||
	@diff=$$(MISSPELL_PACKAGE=$(MISSPELL_PACKAGE) GOFUMPT_PACKAGE=$(GOFUMPT_PACKAGE) $(GO) run build/code-batch-process.go gitea-fmt -l '{file-list}'); \
 | 
			
		||||
	if [ -n "$$diff" ]; then \
 | 
			
		||||
		echo "Please run 'make fmt' and commit the result:"; \
 | 
			
		||||
		echo "$${diff}"; \
 | 
			
		||||
@@ -320,10 +331,7 @@ watch-frontend: node-check node_modules
 | 
			
		||||
 | 
			
		||||
.PHONY: watch-backend
 | 
			
		||||
watch-backend: go-check
 | 
			
		||||
	@hash air > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
 | 
			
		||||
		$(GO) install github.com/cosmtrek/air@bedc18201271882c2be66d216d0e1a275b526ec4; \
 | 
			
		||||
	fi
 | 
			
		||||
	air -c .air.toml
 | 
			
		||||
	$(GO) run $(AIR_PACKAGE) -c .air.toml
 | 
			
		||||
 | 
			
		||||
.PHONY: test
 | 
			
		||||
test: test-frontend test-backend
 | 
			
		||||
@@ -396,6 +404,11 @@ test-sqlite-migration:  migrations.sqlite.test migrations.individual.sqlite.test
 | 
			
		||||
	GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/sqlite.ini ./migrations.sqlite.test
 | 
			
		||||
	GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/sqlite.ini ./migrations.individual.sqlite.test
 | 
			
		||||
 | 
			
		||||
.PHONY: test-sqlite-migration\#%
 | 
			
		||||
test-sqlite-migration\#%:  migrations.sqlite.test migrations.individual.sqlite.test generate-ini-sqlite
 | 
			
		||||
	GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/sqlite.ini ./migrations.individual.sqlite.test -test.run $(subst .,/,$*)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
generate-ini-mysql:
 | 
			
		||||
	sed -e 's|{{TEST_MYSQL_HOST}}|${TEST_MYSQL_HOST}|g' \
 | 
			
		||||
		-e 's|{{TEST_MYSQL_DBNAME}}|${TEST_MYSQL_DBNAME}|g' \
 | 
			
		||||
@@ -591,12 +604,9 @@ $(DIST_DIRS):
 | 
			
		||||
 | 
			
		||||
.PHONY: release-windows
 | 
			
		||||
release-windows: | $(DIST_DIRS)
 | 
			
		||||
	@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
 | 
			
		||||
		$(GO) install src.techknowlogick.com/xgo@latest; \
 | 
			
		||||
	fi
 | 
			
		||||
	CGO_CFLAGS="$(CGO_CFLAGS)" xgo -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'netgo 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 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) .
 | 
			
		||||
ifeq (,$(findstring gogit,$(TAGS)))
 | 
			
		||||
	CGO_CFLAGS="$(CGO_CFLAGS)" xgo -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'netgo 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 'netgo osusergo gogit $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION)-gogit .
 | 
			
		||||
endif
 | 
			
		||||
ifeq ($(CI),drone)
 | 
			
		||||
	cp /build/* $(DIST)/binaries
 | 
			
		||||
@@ -604,20 +614,14 @@ endif
 | 
			
		||||
 | 
			
		||||
.PHONY: release-linux
 | 
			
		||||
release-linux: | $(DIST_DIRS)
 | 
			
		||||
	@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
 | 
			
		||||
		$(GO) install src.techknowlogick.com/xgo@latest; \
 | 
			
		||||
	fi
 | 
			
		||||
	CGO_CFLAGS="$(CGO_CFLAGS)" xgo -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 '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets '$(LINUX_ARCHS)' -out gitea-$(VERSION) .
 | 
			
		||||
ifeq ($(CI),drone)
 | 
			
		||||
	cp /build/* $(DIST)/binaries
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
.PHONY: release-darwin
 | 
			
		||||
release-darwin: | $(DIST_DIRS)
 | 
			
		||||
	@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
 | 
			
		||||
		$(GO) install src.techknowlogick.com/xgo@latest; \
 | 
			
		||||
	fi
 | 
			
		||||
	CGO_CFLAGS="$(CGO_CFLAGS)" xgo -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 '$(LDFLAGS)' -targets 'darwin-10.12/amd64,darwin-10.12/arm64' -out gitea-$(VERSION) .
 | 
			
		||||
ifeq ($(CI),drone)
 | 
			
		||||
	cp /build/* $(DIST)/binaries
 | 
			
		||||
endif
 | 
			
		||||
@@ -632,10 +636,7 @@ release-check: | $(DIST_DIRS)
 | 
			
		||||
 | 
			
		||||
.PHONY: release-compress
 | 
			
		||||
release-compress: | $(DIST_DIRS)
 | 
			
		||||
	@hash gxz > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
 | 
			
		||||
		$(GO) install github.com/ulikunitz/xz/cmd/gxz@v0.5.10; \
 | 
			
		||||
	fi
 | 
			
		||||
	cd $(DIST)/release/; for file in `find . -type f -name "*"`; do echo "compressing $${file}" && gxz -k -9 $${file}; done;
 | 
			
		||||
	cd $(DIST)/release/; for file in `find . -type f -name "*"`; do echo "compressing $${file}" && $(GO) run $(GXZ_PAGAGE) -k -9 $${file}; done;
 | 
			
		||||
 | 
			
		||||
.PHONY: release-sources
 | 
			
		||||
release-sources: | $(DIST_DIRS)
 | 
			
		||||
@@ -656,6 +657,25 @@ docs:
 | 
			
		||||
	fi
 | 
			
		||||
	cd docs; make trans-copy clean build-offline;
 | 
			
		||||
 | 
			
		||||
.PHONY: deps
 | 
			
		||||
deps: deps-frontend deps-backend
 | 
			
		||||
 | 
			
		||||
.PHONY: deps-frontend
 | 
			
		||||
deps-frontend: node_modules
 | 
			
		||||
 | 
			
		||||
.PHONY: deps-backend
 | 
			
		||||
deps-backend:
 | 
			
		||||
	$(GO) mod download
 | 
			
		||||
	$(GO) install $(AIR_PACKAGE)
 | 
			
		||||
	$(GO) install $(EDITORCONFIG_CHECKER_PACKAGE)
 | 
			
		||||
	$(GO) install $(ERRCHECK_PACKAGE)
 | 
			
		||||
	$(GO) install $(GOFUMPT_PACKAGE)
 | 
			
		||||
	$(GO) install $(GOLANGCI_LINT_PACKAGE)
 | 
			
		||||
	$(GO) install $(GXZ_PAGAGE)
 | 
			
		||||
	$(GO) install $(MISSPELL_PACKAGE)
 | 
			
		||||
	$(GO) install $(SWAGGER_PACKAGE)
 | 
			
		||||
	$(GO) install $(XGO_PACKAGE)
 | 
			
		||||
 | 
			
		||||
node_modules: package-lock.json
 | 
			
		||||
	npm install --no-save
 | 
			
		||||
	@touch node_modules
 | 
			
		||||
@@ -748,22 +768,19 @@ pr\#%: clean-all
 | 
			
		||||
	$(GO) run contrib/pr/checkout.go $*
 | 
			
		||||
 | 
			
		||||
.PHONY: golangci-lint
 | 
			
		||||
golangci-lint: golangci-lint-check
 | 
			
		||||
	golangci-lint run --timeout 10m
 | 
			
		||||
golangci-lint:
 | 
			
		||||
	$(GO) run $(GOLANGCI_LINT_PACKAGE) run
 | 
			
		||||
 | 
			
		||||
.PHONY: golangci-lint-check
 | 
			
		||||
golangci-lint-check:
 | 
			
		||||
	$(eval GOLANGCI_LINT_VERSION := $(shell printf "%03d%03d%03d" $(shell golangci-lint --version | grep -Eo '[0-9]+\.[0-9.]+' | tr '.' ' ');))
 | 
			
		||||
	$(eval MIN_GOLANGCI_LINT_VER_FMT := $(shell printf "%g.%g.%g" $(shell echo $(MIN_GOLANGCI_LINT_VERSION) | grep -o ...)))
 | 
			
		||||
	@hash golangci-lint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
 | 
			
		||||
		echo "Downloading golangci-lint v${MIN_GOLANGCI_LINT_VER_FMT}"; \
 | 
			
		||||
		export BINARY="golangci-lint"; \
 | 
			
		||||
		curl -sfL "https://raw.githubusercontent.com/golangci/golangci-lint/v${MIN_GOLANGCI_LINT_VER_FMT}/install.sh" | sh -s -- -b $(GOPATH)/bin v$(MIN_GOLANGCI_LINT_VER_FMT); \
 | 
			
		||||
	elif [ "$(GOLANGCI_LINT_VERSION)" -lt "$(MIN_GOLANGCI_LINT_VERSION)" ]; then \
 | 
			
		||||
		echo "Downloading newer version of golangci-lint v${MIN_GOLANGCI_LINT_VER_FMT}"; \
 | 
			
		||||
		export BINARY="golangci-lint"; \
 | 
			
		||||
		curl -sfL "https://raw.githubusercontent.com/golangci/golangci-lint/v${MIN_GOLANGCI_LINT_VER_FMT}/install.sh" | sh -s -- -b $(GOPATH)/bin v$(MIN_GOLANGCI_LINT_VER_FMT); \
 | 
			
		||||
	fi
 | 
			
		||||
# workaround step for the lint-backend-windows CI task because 'go run' can not
 | 
			
		||||
# have distinct GOOS/GOARCH for its build and run steps
 | 
			
		||||
.PHONY: golangci-lint-windows
 | 
			
		||||
golangci-lint-windows:
 | 
			
		||||
	@GOOS= GOARCH= $(GO) install $(GOLANGCI_LINT_PACKAGE)
 | 
			
		||||
	golangci-lint run
 | 
			
		||||
 | 
			
		||||
.PHONY: editorconfig-checker
 | 
			
		||||
editorconfig-checker:
 | 
			
		||||
	$(GO) run $(EDITORCONFIG_CHECKER_PACKAGE) templates
 | 
			
		||||
 | 
			
		||||
.PHONY: docker
 | 
			
		||||
docker:
 | 
			
		||||
 
 | 
			
		||||
@@ -73,7 +73,7 @@ or if SQLite support is required:
 | 
			
		||||
 | 
			
		||||
The `build` target is split into two sub-targets:
 | 
			
		||||
 | 
			
		||||
- `make backend` which requires [Go 1.16](https://golang.org/dl/) or greater.
 | 
			
		||||
- `make backend` which requires [Go 1.17](https://go.dev/dl/) or greater.
 | 
			
		||||
- `make frontend` which requires [Node.js LTS](https://nodejs.org/en/download/) or greater and Internet connectivity to download npm dependencies.
 | 
			
		||||
 | 
			
		||||
When building from the official source tarballs which include pre-built frontend files, the `frontend` target will not be triggered, making it possible to build without Node.js and Internet connectivity.
 | 
			
		||||
 
 | 
			
		||||
@@ -40,7 +40,7 @@ func passThroughCmd(cmd string, args []string) error {
 | 
			
		||||
	}
 | 
			
		||||
	c := exec.Cmd{
 | 
			
		||||
		Path:   foundCmd,
 | 
			
		||||
		Args:   args,
 | 
			
		||||
		Args:   append([]string{cmd}, args...),
 | 
			
		||||
		Stdin:  os.Stdin,
 | 
			
		||||
		Stdout: os.Stdout,
 | 
			
		||||
		Stderr: os.Stderr,
 | 
			
		||||
@@ -270,9 +270,10 @@ func main() {
 | 
			
		||||
			if containsString(subArgs, "-w") {
 | 
			
		||||
				cmdErrors = append(cmdErrors, giteaFormatGoImports(files))
 | 
			
		||||
			}
 | 
			
		||||
			cmdErrors = append(cmdErrors, passThroughCmd("gofmt", substArgs))
 | 
			
		||||
			cmdErrors = append(cmdErrors, giteaFormatGoImports(files, containsString(subArgs, "-l"), containsString(subArgs, "-w")))
 | 
			
		||||
			cmdErrors = append(cmdErrors, passThroughCmd("go", append([]string{"run", os.Getenv("GOFUMPT_PACKAGE"), "-extra", "-lang", "1.17"}, substArgs...)))
 | 
			
		||||
		case "misspell":
 | 
			
		||||
			cmdErrors = append(cmdErrors, passThroughCmd("misspell", substArgs))
 | 
			
		||||
			cmdErrors = append(cmdErrors, passThroughCmd("go", append([]string{"run", os.Getenv("MISSPELL_PACKAGE")}, substArgs...)))
 | 
			
		||||
		default:
 | 
			
		||||
			log.Fatalf("unknown cmd: %s %v", subCmd, subArgs)
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										20
									
								
								cmd/admin.go
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								cmd/admin.go
									
									
									
									
									
								
							@@ -25,6 +25,7 @@ import (
 | 
			
		||||
	repo_module "code.gitea.io/gitea/modules/repository"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/modules/storage"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
	auth_service "code.gitea.io/gitea/services/auth"
 | 
			
		||||
	"code.gitea.io/gitea/services/auth/source/oauth2"
 | 
			
		||||
	"code.gitea.io/gitea/services/auth/source/smtp"
 | 
			
		||||
@@ -113,6 +114,10 @@ var (
 | 
			
		||||
				Name:  "access-token",
 | 
			
		||||
				Usage: "Generate access token for the user",
 | 
			
		||||
			},
 | 
			
		||||
			cli.BoolFlag{
 | 
			
		||||
				Name:  "restricted",
 | 
			
		||||
				Usage: "Make a restricted user account",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -537,17 +542,26 @@ func runCreateUser(c *cli.Context) error {
 | 
			
		||||
		changePassword = c.Bool("must-change-password")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	restricted := util.OptionalBoolNone
 | 
			
		||||
 | 
			
		||||
	if c.IsSet("restricted") {
 | 
			
		||||
		restricted = util.OptionalBoolOf(c.Bool("restricted"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	u := &user_model.User{
 | 
			
		||||
		Name:               username,
 | 
			
		||||
		Email:              c.String("email"),
 | 
			
		||||
		Passwd:             password,
 | 
			
		||||
		IsActive:           true,
 | 
			
		||||
		IsAdmin:            c.Bool("admin"),
 | 
			
		||||
		MustChangePassword: changePassword,
 | 
			
		||||
		Theme:              setting.UI.DefaultTheme,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := user_model.CreateUser(u); err != nil {
 | 
			
		||||
	overwriteDefault := &user_model.CreateUserOverwriteOptions{
 | 
			
		||||
		IsActive:     util.OptionalBoolTrue,
 | 
			
		||||
		IsRestricted: restricted,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := user_model.CreateUser(u, overwriteDefault); err != nil {
 | 
			
		||||
		return fmt.Errorf("CreateUser: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -31,7 +31,7 @@ func argsSet(c *cli.Context, args ...string) error {
 | 
			
		||||
			return errors.New(a + " is not set")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if util.IsEmptyString(a) {
 | 
			
		||||
		if util.IsEmptyString(c.String(a)) {
 | 
			
		||||
			return errors.New(a + " is required")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -86,7 +86,7 @@ func (o outputType) String() string {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var outputTypeEnum = &outputType{
 | 
			
		||||
	Enum:    []string{"zip", "rar", "tar", "sz", "tar.gz", "tar.xz", "tar.bz2", "tar.br", "tar.lz4"},
 | 
			
		||||
	Enum:    []string{"zip", "tar", "tar.sz", "tar.gz", "tar.xz", "tar.bz2", "tar.br", "tar.lz4"},
 | 
			
		||||
	Default: "zip",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -160,7 +160,12 @@ func runDump(ctx *cli.Context) error {
 | 
			
		||||
			fatal("Deleting default logger failed. Can not write to stdout: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		fileName = strings.TrimSuffix(fileName, path.Ext(fileName))
 | 
			
		||||
		for _, suffix := range outputTypeEnum.Enum {
 | 
			
		||||
			if strings.HasSuffix(fileName, "."+suffix) {
 | 
			
		||||
				fileName = strings.TrimSuffix(fileName, "."+suffix)
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		fileName += "." + outType
 | 
			
		||||
	}
 | 
			
		||||
	setting.LoadFromExisting()
 | 
			
		||||
 
 | 
			
		||||
@@ -185,7 +185,7 @@ Gitea or set your environment appropriately.`, "")
 | 
			
		||||
	reponame := os.Getenv(models.EnvRepoName)
 | 
			
		||||
	userID, _ := strconv.ParseInt(os.Getenv(models.EnvPusherID), 10, 64)
 | 
			
		||||
	prID, _ := strconv.ParseInt(os.Getenv(models.EnvPRID), 10, 64)
 | 
			
		||||
	isDeployKey, _ := strconv.ParseBool(os.Getenv(models.EnvIsDeployKey))
 | 
			
		||||
	deployKeyID, _ := strconv.ParseInt(os.Getenv(models.EnvDeployKeyID), 10, 64)
 | 
			
		||||
 | 
			
		||||
	hookOptions := private.HookOptions{
 | 
			
		||||
		UserID:                          userID,
 | 
			
		||||
@@ -194,7 +194,7 @@ Gitea or set your environment appropriately.`, "")
 | 
			
		||||
		GitQuarantinePath:               os.Getenv(private.GitQuarantinePath),
 | 
			
		||||
		GitPushOptions:                  pushOptions(),
 | 
			
		||||
		PullRequestID:                   prID,
 | 
			
		||||
		IsDeployKey:                     isDeployKey,
 | 
			
		||||
		DeployKeyID:                     deployKeyID,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	scanner := bufio.NewScanner(os.Stdin)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								cmd/serv.go
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								cmd/serv.go
									
									
									
									
									
								
							@@ -243,7 +243,7 @@ func runServ(c *cli.Context) error {
 | 
			
		||||
	os.Setenv(models.EnvPusherID, strconv.FormatInt(results.UserID, 10))
 | 
			
		||||
	os.Setenv(models.EnvRepoID, strconv.FormatInt(results.RepoID, 10))
 | 
			
		||||
	os.Setenv(models.EnvPRID, fmt.Sprintf("%d", 0))
 | 
			
		||||
	os.Setenv(models.EnvIsDeployKey, fmt.Sprintf("%t", results.IsDeployKey))
 | 
			
		||||
	os.Setenv(models.EnvDeployKeyID, fmt.Sprintf("%d", results.DeployKeyID))
 | 
			
		||||
	os.Setenv(models.EnvKeyID, fmt.Sprintf("%d", results.KeyID))
 | 
			
		||||
	os.Setenv(models.EnvAppURL, setting.AppURL)
 | 
			
		||||
 | 
			
		||||
@@ -297,6 +297,15 @@ func runServ(c *cli.Context) error {
 | 
			
		||||
		gitcmd = exec.CommandContext(ctx, verb, repoPath)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Check if setting.RepoRootPath exists. It could be the case that it doesn't exist, this can happen when
 | 
			
		||||
	// `[repository]` `ROOT` is a relative path and $GITEA_WORK_DIR isn't passed to the SSH connection.
 | 
			
		||||
	if _, err := os.Stat(setting.RepoRootPath); err != nil {
 | 
			
		||||
		if os.IsNotExist(err) {
 | 
			
		||||
			return fail("Incorrect configuration.",
 | 
			
		||||
				"Directory `[repository]` `ROOT` was not found, please check if $GITEA_WORK_DIR is passed to the SSH connection or make `[repository]` `ROOT` an absolute value.")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	gitcmd.Dir = setting.RepoRootPath
 | 
			
		||||
	gitcmd.Stdout = os.Stdout
 | 
			
		||||
	gitcmd.Stdin = os.Stdin
 | 
			
		||||
 
 | 
			
		||||
@@ -33,10 +33,8 @@ for i in "$@"; do
 | 
			
		||||
done
 | 
			
		||||
 | 
			
		||||
if [ -z "$APP_INI_SET" ]; then
 | 
			
		||||
	CONF_ARG="-c \"$APP_INI\""
 | 
			
		||||
	CONF_ARG=("-c" "${GITEA_APP_INI:-$APP_INI}")
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# Provide FHS compliant defaults
 | 
			
		||||
GITEA_WORK_DIR="${GITEA_WORK_DIR:-$WORK_DIR}" exec -a "$0" "$GITEA" $CONF_ARG "$@"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
GITEA_WORK_DIR="${GITEA_WORK_DIR:-$WORK_DIR}" exec -a "$0" "$GITEA" "${CONF_ARG[@]}"  "$@"
 | 
			
		||||
 
 | 
			
		||||
@@ -1075,7 +1075,7 @@ PATH =
 | 
			
		||||
;SEARCH_REPO_DESCRIPTION = true
 | 
			
		||||
;;
 | 
			
		||||
;; Whether to enable a Service Worker to cache frontend assets
 | 
			
		||||
;USE_SERVICE_WORKER = true
 | 
			
		||||
;USE_SERVICE_WORKER = false
 | 
			
		||||
 | 
			
		||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 | 
			
		||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}dev{{/if}}-rootless
 | 
			
		||||
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}dev{{/if}}-rootless
 | 
			
		||||
{{#if build.tags}}
 | 
			
		||||
tags:
 | 
			
		||||
{{#each build.tags}}
 | 
			
		||||
@@ -8,12 +8,12 @@ tags:
 | 
			
		||||
{{/if}}
 | 
			
		||||
manifests:
 | 
			
		||||
  -
 | 
			
		||||
    image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}dev{{/if}}-linux-amd64-rootless
 | 
			
		||||
    image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}dev{{/if}}-linux-amd64-rootless
 | 
			
		||||
    platform:
 | 
			
		||||
      architecture: amd64
 | 
			
		||||
      os: linux
 | 
			
		||||
  -
 | 
			
		||||
    image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}dev{{/if}}-linux-arm64-rootless
 | 
			
		||||
    image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}dev{{/if}}-linux-arm64-rootless
 | 
			
		||||
    platform:
 | 
			
		||||
      architecture: arm64
 | 
			
		||||
      os: linux
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}dev{{/if}}
 | 
			
		||||
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}dev{{/if}}
 | 
			
		||||
{{#if build.tags}}
 | 
			
		||||
tags:
 | 
			
		||||
{{#each build.tags}}
 | 
			
		||||
@@ -8,12 +8,12 @@ tags:
 | 
			
		||||
{{/if}}
 | 
			
		||||
manifests:
 | 
			
		||||
  -
 | 
			
		||||
    image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{else}}dev-{{/if}}linux-amd64
 | 
			
		||||
    image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}dev{{/if}}-linux-amd64
 | 
			
		||||
    platform:
 | 
			
		||||
      architecture: amd64
 | 
			
		||||
      os: linux
 | 
			
		||||
  -
 | 
			
		||||
    image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{else}}dev-{{/if}}linux-arm64
 | 
			
		||||
    image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}dev{{/if}}-linux-arm64
 | 
			
		||||
    platform:
 | 
			
		||||
      architecture: arm64
 | 
			
		||||
      os: linux
 | 
			
		||||
 
 | 
			
		||||
@@ -32,11 +32,9 @@ for i in "$@"; do
 | 
			
		||||
done
 | 
			
		||||
 | 
			
		||||
if [ -z "$APP_INI_SET" ]; then
 | 
			
		||||
	CONF_ARG="-c \"$APP_INI\""
 | 
			
		||||
	CONF_ARG=("-c" "${GITEA_APP_INI:-$APP_INI}")
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Provide docker defaults
 | 
			
		||||
GITEA_WORK_DIR="${GITEA_WORK_DIR:-$WORK_DIR}" exec -a "$0" "$GITEA" $CONF_ARG "$@"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
GITEA_WORK_DIR="${GITEA_WORK_DIR:-$WORK_DIR}" exec -a "$0" "$GITEA" "${CONF_ARG[@]}" "$@"
 | 
			
		||||
 
 | 
			
		||||
@@ -18,9 +18,9 @@ params:
 | 
			
		||||
  description: Git with a cup of tea
 | 
			
		||||
  author: The Gitea Authors
 | 
			
		||||
  website: https://docs.gitea.io
 | 
			
		||||
  version: 1.15.10
 | 
			
		||||
  version: 1.16.4
 | 
			
		||||
  minGoVersion: 1.16
 | 
			
		||||
  goVersion: 1.17
 | 
			
		||||
  goVersion: 1.18
 | 
			
		||||
  minNodeVersion: 12.17
 | 
			
		||||
 | 
			
		||||
outputs:
 | 
			
		||||
 
 | 
			
		||||
@@ -189,7 +189,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a
 | 
			
		||||
    add it to this config.
 | 
			
		||||
- `DEFAULT_SHOW_FULL_NAME`: **false**: Whether the full name of the users should be shown where possible. If the full name isn't set, the username will be used.
 | 
			
		||||
- `SEARCH_REPO_DESCRIPTION`: **true**: Whether to search within description at repository search on explore page.
 | 
			
		||||
- `USE_SERVICE_WORKER`: **true**: Whether to enable a Service Worker to cache frontend assets.
 | 
			
		||||
- `USE_SERVICE_WORKER`: **false**: Whether to enable a Service Worker to cache frontend assets.
 | 
			
		||||
 | 
			
		||||
### UI - Admin (`ui.admin`)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -42,7 +42,7 @@ To maintain understandable code and avoid circular dependencies it is important
 | 
			
		||||
  - `modules/setting`: Store all system configurations read from ini files and has been referenced by everywhere. But they should be used as function parameters when possible.
 | 
			
		||||
  - `modules/git`: Package to interactive with `Git` command line or Gogit package.
 | 
			
		||||
- `public`: Compiled frontend files (javascript, images, css, etc.)
 | 
			
		||||
- `routers`: Handling of server requests. As it uses other Gitea packages to serve the request, other packages (models, modules or services) shall not depend on routers.
 | 
			
		||||
- `routers`: Handling of server requests. As it uses other Gitea packages to serve the request, other packages (models, modules or services) must not depend on routers.
 | 
			
		||||
  - `routers/api` Contains routers for `/api/v1` aims to handle RESTful API requests. 
 | 
			
		||||
  - `routers/install` Could only respond when system is in INSTALL mode (INSTALL_LOCK=false). 
 | 
			
		||||
  - `routers/private` will only be invoked by internal sub commands, especially `serv` and `hooks`. 
 | 
			
		||||
@@ -106,10 +106,20 @@ i.e. `servcies/user`, `models/repository`.
 | 
			
		||||
Since there are some packages which use the same package name, it is possible that you find packages like `modules/user`, `models/user`, and `services/user`. When these packages are imported in one Go file, it's difficult to know which package we are using and if it's a variable name or an import name. So, we always recommend to use import aliases. To differ from package variables which are commonly in camelCase, just use **snake_case** for import aliases.
 | 
			
		||||
i.e. `import user_service "code.gitea.io/gitea/services/user"`
 | 
			
		||||
 | 
			
		||||
### Important Gotchas
 | 
			
		||||
 | 
			
		||||
- Never write `x.Update(exemplar)` without an explicit `WHERE` clause:
 | 
			
		||||
  - This will cause all rows in the table to be updated with the non-zero values of the exemplar - including IDs.
 | 
			
		||||
  - You should usually write `x.ID(id).Update(exemplar)`.
 | 
			
		||||
- If during a migration you are inserting into a table using `x.Insert(exemplar)` where the ID is preset:
 | 
			
		||||
  - You will need to ``SET IDENTITY_INSERT `table` ON`` for the MSSQL variant (the migration will fail otherwise)
 | 
			
		||||
  - However, you will also need to update the id sequence for postgres - the migration will silently pass here but later insertions will fail:
 | 
			
		||||
    ``SELECT setval('table_name_id_seq', COALESCE((SELECT MAX(id)+1 FROM `table_name`), 1), false)``
 | 
			
		||||
 | 
			
		||||
### Future Tasks
 | 
			
		||||
 | 
			
		||||
Currently, we are creating some refactors to do the following things:
 | 
			
		||||
 | 
			
		||||
- Correct that codes which doesn't follow the rules.
 | 
			
		||||
- There are too many files in `models`, so we are moving some of them into a sub package `models/xxx`.
 | 
			
		||||
- Some `modules` sub packages should be moved to `services` because they depends on `models`.
 | 
			
		||||
- Some `modules` sub packages should be moved to `services` because they depend on `models`.
 | 
			
		||||
 
 | 
			
		||||
@@ -185,8 +185,6 @@ Before committing, make sure the linters pass:
 | 
			
		||||
make lint-frontend
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Note: When working on frontend code, set `USE_SERVICE_WORKER` to `false` in `app.ini` to prevent undesirable caching of frontend assets.
 | 
			
		||||
 | 
			
		||||
### Building and adding SVGs
 | 
			
		||||
 | 
			
		||||
SVG icons are built using the `make svg` target which compiles the icon sources defined in `build/generate-svg.js` into the output directory `public/img/svg`. Custom icons can be added in the `web_src/svg` directory.
 | 
			
		||||
 
 | 
			
		||||
@@ -32,7 +32,7 @@ image as a service. Since there is no database available, one can be initialized
 | 
			
		||||
Create a directory for `data` and `config` then paste the following content into a file named `docker-compose.yml`.
 | 
			
		||||
Note that the volume should be owned by the user/group with the UID/GID specified in the config file. By default Gitea in docker will use uid:1000 gid:1000. If needed you can set ownership on those folders with the command: `sudo chown 1000:1000 config/ data/`
 | 
			
		||||
If you don't give the volume correct permissions, the container may not start.
 | 
			
		||||
For a stable release you could use `:latest-rootless`, `:1-rootless` or specify a certain release like `:{{< version >}}-rootless`, but if you'd like to use the latest development version then `:dev-rootless` would be an appropriate tag.
 | 
			
		||||
For a stable release you could use `:latest-rootless`, `:1-rootless` or specify a certain release like `:{{< version >}}-rootless`, but if you'd like to use the latest development version then `:dev-rootless` would be an appropriate tag. If you'd like to run the latest commit from a release branch you can use the `:1.x-dev-rootless` tag, where x is the minor version of Gitea. (e.g. `:1.16-dev-rootless`)
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
version: "2"
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,7 @@ image as a service. Since there is no database available, one can be initialized
 | 
			
		||||
Create a directory like `gitea` and paste the following content into a file named `docker-compose.yml`.
 | 
			
		||||
Note that the volume should be owned by the user/group with the UID/GID specified in the config file.
 | 
			
		||||
If you don't give the volume correct permissions, the container may not start.
 | 
			
		||||
For a stable release you can use `:latest`, `:1` or specify a certain release like `:{{< version >}}`, but if you'd like to use the latest development version of Gitea then you could use the `:dev` tag.
 | 
			
		||||
For a stable release you can use `:latest`, `:1` or specify a certain release like `:{{< version >}}`, but if you'd like to use the latest development version of Gitea then you could use the `:dev` tag. If you'd like to run the latest commit from a release branch you can use the `:1.x-dev` tag, where x is the minor version of Gitea. (e.g. `:1.16-dev`)
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
version: "3"
 | 
			
		||||
 
 | 
			
		||||
@@ -43,7 +43,7 @@ Vous devriez avoir une instance fonctionnelle de Gitea. Pour accèder à l'inter
 | 
			
		||||
 | 
			
		||||
## Named Volumes
 | 
			
		||||
 | 
			
		||||
Ce guide aboutira à une installation avec les données Gita et PostgreSQL stockées dans des volumes nommés. Cela permet une sauvegarde, une restauration et des mises à niveau en toute simplicité.
 | 
			
		||||
Ce guide aboutira à une installation avec les données Gitea et PostgreSQL stockées dans des volumes nommés. Cela permet une sauvegarde, une restauration et des mises à niveau en toute simplicité.
 | 
			
		||||
 | 
			
		||||
### The Database
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										160
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										160
									
								
								go.mod
									
									
									
									
									
								
							@@ -3,41 +3,30 @@ module code.gitea.io/gitea
 | 
			
		||||
go 1.16
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	cloud.google.com/go v0.78.0 // indirect
 | 
			
		||||
	code.gitea.io/gitea-vet v0.2.1
 | 
			
		||||
	code.gitea.io/sdk/gitea v0.15.1
 | 
			
		||||
	gitea.com/go-chi/binding v0.0.0-20211013065440-d16dc407c2be
 | 
			
		||||
	gitea.com/go-chi/binding v0.0.0-20220309004920-114340dabecb
 | 
			
		||||
	gitea.com/go-chi/cache v0.0.0-20211013020926-78790b11abf1
 | 
			
		||||
	gitea.com/go-chi/captcha v0.0.0-20211013065431-70641c1a35d5
 | 
			
		||||
	gitea.com/go-chi/session v0.0.0-20211218221615-e3605d8b28b8
 | 
			
		||||
	gitea.com/lunny/levelqueue v0.4.1
 | 
			
		||||
	github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121
 | 
			
		||||
	github.com/Microsoft/go-winio v0.5.0 // indirect
 | 
			
		||||
	github.com/NYTimes/gziphandler v1.1.1
 | 
			
		||||
	github.com/ProtonMail/go-crypto v0.0.0-20210705153151-cc34b1f6908b // indirect
 | 
			
		||||
	github.com/PuerkitoBio/goquery v1.7.0
 | 
			
		||||
	github.com/alecthomas/chroma v0.9.4
 | 
			
		||||
	github.com/andybalholm/brotli v1.0.3 // indirect
 | 
			
		||||
	github.com/andybalholm/cascadia v1.2.0 // indirect
 | 
			
		||||
	github.com/blevesearch/bleve/v2 v2.3.0
 | 
			
		||||
	github.com/boombuler/barcode v1.0.1 // indirect
 | 
			
		||||
	github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b // indirect
 | 
			
		||||
	github.com/caddyserver/certmagic v0.15.2
 | 
			
		||||
	github.com/PuerkitoBio/goquery v1.8.0
 | 
			
		||||
	github.com/alecthomas/chroma v0.10.0
 | 
			
		||||
	github.com/blevesearch/bleve/v2 v2.3.1
 | 
			
		||||
	github.com/caddyserver/certmagic v0.15.4
 | 
			
		||||
	github.com/chi-middleware/proxy v1.1.1
 | 
			
		||||
	github.com/couchbase/go-couchbase v0.0.0-20210224140812-5740cd35f448 // indirect
 | 
			
		||||
	github.com/couchbase/gomemcached v0.1.2 // indirect
 | 
			
		||||
	github.com/couchbase/goutils v0.0.0-20210118111533-e33d3ffb5401 // indirect
 | 
			
		||||
	github.com/denisenkom/go-mssqldb v0.10.0
 | 
			
		||||
	github.com/denisenkom/go-mssqldb v0.12.0
 | 
			
		||||
	github.com/djherbis/buffer v1.2.0
 | 
			
		||||
	github.com/djherbis/nio/v3 v3.0.1
 | 
			
		||||
	github.com/duo-labs/webauthn v0.0.0-20211221191814-a22482edaa3b
 | 
			
		||||
	github.com/duo-labs/webauthn v0.0.0-20220122034320-81aea484c951
 | 
			
		||||
	github.com/dustin/go-humanize v1.0.0
 | 
			
		||||
	github.com/editorconfig/editorconfig-core-go/v2 v2.4.2
 | 
			
		||||
	github.com/emirpasic/gods v1.12.0
 | 
			
		||||
	github.com/ethantkoenig/rupture v1.0.0
 | 
			
		||||
	github.com/gliderlabs/ssh v0.3.3
 | 
			
		||||
	github.com/go-asn1-ber/asn1-ber v1.5.3 // indirect
 | 
			
		||||
	github.com/go-chi/chi/v5 v5.0.4
 | 
			
		||||
	github.com/go-chi/chi/v5 v5.0.7
 | 
			
		||||
	github.com/go-chi/cors v1.2.0
 | 
			
		||||
	github.com/go-enry/go-enry/v2 v2.7.1
 | 
			
		||||
	github.com/go-git/go-billy/v5 v5.3.1
 | 
			
		||||
@@ -48,105 +37,130 @@ require (
 | 
			
		||||
	github.com/go-swagger/go-swagger v0.27.0
 | 
			
		||||
	github.com/go-testfixtures/testfixtures/v3 v3.6.1
 | 
			
		||||
	github.com/gobwas/glob v0.2.3
 | 
			
		||||
	github.com/gogs/chardet v0.0.0-20191104214054-4b6791f73a28
 | 
			
		||||
	github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
 | 
			
		||||
	github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14
 | 
			
		||||
	github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
 | 
			
		||||
	github.com/golang-jwt/jwt/v4 v4.2.0
 | 
			
		||||
	github.com/golang/snappy v0.0.4 // indirect
 | 
			
		||||
	github.com/golang-jwt/jwt/v4 v4.3.0
 | 
			
		||||
	github.com/google/go-github/v39 v39.2.0
 | 
			
		||||
	github.com/google/uuid v1.2.0
 | 
			
		||||
	github.com/google/uuid v1.3.0
 | 
			
		||||
	github.com/gorilla/feeds v1.1.1
 | 
			
		||||
	github.com/gorilla/mux v1.8.0 // indirect
 | 
			
		||||
	github.com/gorilla/sessions v1.2.1
 | 
			
		||||
	github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
 | 
			
		||||
	github.com/hashicorp/go-retryablehttp v0.7.0 // indirect
 | 
			
		||||
	github.com/hashicorp/go-version v1.3.1
 | 
			
		||||
	github.com/hashicorp/go-version v1.4.0
 | 
			
		||||
	github.com/hashicorp/golang-lru v0.5.4
 | 
			
		||||
	github.com/huandu/xstrings v1.3.2
 | 
			
		||||
	github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7
 | 
			
		||||
	github.com/json-iterator/go v1.1.11
 | 
			
		||||
	github.com/json-iterator/go v1.1.12
 | 
			
		||||
	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
 | 
			
		||||
	github.com/kevinburke/ssh_config v1.1.0 // indirect
 | 
			
		||||
	github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4
 | 
			
		||||
	github.com/klauspost/compress v1.13.1
 | 
			
		||||
	github.com/klauspost/compress v1.13.6
 | 
			
		||||
	github.com/klauspost/cpuid/v2 v2.0.9
 | 
			
		||||
	github.com/klauspost/pgzip v1.2.5 // indirect
 | 
			
		||||
	github.com/lib/pq v1.10.2
 | 
			
		||||
	github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96
 | 
			
		||||
	github.com/markbates/goth v1.68.0
 | 
			
		||||
	github.com/mattn/go-isatty v0.0.13
 | 
			
		||||
	github.com/mattn/go-runewidth v0.0.13 // indirect
 | 
			
		||||
	github.com/mattn/go-sqlite3 v1.14.8
 | 
			
		||||
	github.com/mholt/archiver/v3 v3.5.0
 | 
			
		||||
	github.com/microcosm-cc/bluemonday v1.0.16
 | 
			
		||||
	github.com/minio/md5-simd v1.1.2 // indirect
 | 
			
		||||
	github.com/minio/minio-go/v7 v7.0.12
 | 
			
		||||
	github.com/minio/sha256-simd v1.0.0 // indirect
 | 
			
		||||
	github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect
 | 
			
		||||
	github.com/msteinert/pam v0.0.0-20201130170657-e61372126161
 | 
			
		||||
	github.com/markbates/goth v1.69.0
 | 
			
		||||
	github.com/mattn/go-isatty v0.0.14
 | 
			
		||||
	github.com/mattn/go-sqlite3 v1.14.12
 | 
			
		||||
	github.com/mholt/archiver/v3 v3.5.1
 | 
			
		||||
	github.com/microcosm-cc/bluemonday v1.0.18
 | 
			
		||||
	github.com/minio/minio-go/v7 v7.0.23
 | 
			
		||||
	github.com/msteinert/pam v1.0.0
 | 
			
		||||
	github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
 | 
			
		||||
	github.com/niklasfasching/go-org v1.5.0
 | 
			
		||||
	github.com/olekukonko/tablewriter v0.0.5 // indirect
 | 
			
		||||
	github.com/niklasfasching/go-org v1.6.2
 | 
			
		||||
	github.com/oliamb/cutter v0.2.2
 | 
			
		||||
	github.com/olivere/elastic/v7 v7.0.25
 | 
			
		||||
	github.com/pelletier/go-toml v1.9.0 // indirect
 | 
			
		||||
	github.com/pierrec/lz4/v4 v4.1.8 // indirect
 | 
			
		||||
	github.com/olivere/elastic/v7 v7.0.31
 | 
			
		||||
	github.com/pkg/errors v0.9.1
 | 
			
		||||
	github.com/pquerna/otp v1.3.0
 | 
			
		||||
	github.com/prometheus/client_golang v1.11.0
 | 
			
		||||
	github.com/quasoft/websspi v1.0.0
 | 
			
		||||
	github.com/rs/xid v1.3.0 // indirect
 | 
			
		||||
	github.com/russross/blackfriday/v2 v2.1.0 // indirect
 | 
			
		||||
	github.com/prometheus/client_golang v1.12.1
 | 
			
		||||
	github.com/quasoft/websspi v1.1.2
 | 
			
		||||
	github.com/sergi/go-diff v1.2.0
 | 
			
		||||
	github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect
 | 
			
		||||
	github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546
 | 
			
		||||
	github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
 | 
			
		||||
	github.com/stretchr/testify v1.7.0
 | 
			
		||||
	github.com/syndtr/goleveldb v1.0.0
 | 
			
		||||
	github.com/tstranex/u2f v1.0.0
 | 
			
		||||
	github.com/ulikunitz/xz v0.5.10 // indirect
 | 
			
		||||
	github.com/unknwon/com v1.0.1
 | 
			
		||||
	github.com/unknwon/i18n v0.0.0-20210321134014-0ebbf2df1c44
 | 
			
		||||
	github.com/unknwon/paginater v0.0.0-20200328080006-042474bd0eae
 | 
			
		||||
	github.com/unrolled/render v1.4.0
 | 
			
		||||
	github.com/urfave/cli v1.22.5
 | 
			
		||||
	github.com/xanzy/go-gitlab v0.50.1
 | 
			
		||||
	github.com/xanzy/go-gitlab v0.58.0
 | 
			
		||||
	github.com/yohcop/openid-go v1.0.0
 | 
			
		||||
	github.com/yuin/goldmark v1.4.0
 | 
			
		||||
	github.com/yuin/goldmark-highlighting v0.0.0-20210516132338-9216f9c5aa01
 | 
			
		||||
	github.com/yuin/goldmark-meta v1.0.0
 | 
			
		||||
	go.etcd.io/bbolt v1.3.6 // indirect
 | 
			
		||||
	github.com/yuin/goldmark v1.4.11
 | 
			
		||||
	github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594
 | 
			
		||||
	github.com/yuin/goldmark-meta v1.1.0
 | 
			
		||||
	go.jolheiser.com/hcaptcha v0.0.4
 | 
			
		||||
	go.jolheiser.com/pwn v0.0.3
 | 
			
		||||
	go.uber.org/atomic v1.9.0 // indirect
 | 
			
		||||
	go.uber.org/multierr v1.7.0 // indirect
 | 
			
		||||
	go.uber.org/zap v1.19.0 // indirect
 | 
			
		||||
	golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871
 | 
			
		||||
	golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
 | 
			
		||||
	golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914
 | 
			
		||||
	golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1
 | 
			
		||||
	go.uber.org/multierr v1.8.0 // indirect
 | 
			
		||||
	go.uber.org/zap v1.21.0 // indirect
 | 
			
		||||
	golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4
 | 
			
		||||
	golang.org/x/net v0.0.0-20220225172249-27dd8689420f
 | 
			
		||||
	golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b
 | 
			
		||||
	golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9
 | 
			
		||||
	golang.org/x/text v0.3.7
 | 
			
		||||
	golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 // indirect
 | 
			
		||||
	golang.org/x/tools v0.1.0
 | 
			
		||||
	google.golang.org/protobuf v1.27.1 // indirect
 | 
			
		||||
	gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
 | 
			
		||||
	golang.org/x/tools v0.1.9
 | 
			
		||||
	gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
 | 
			
		||||
	gopkg.in/ini.v1 v1.62.0
 | 
			
		||||
	gopkg.in/ini.v1 v1.66.2
 | 
			
		||||
	gopkg.in/yaml.v2 v2.4.0
 | 
			
		||||
	mvdan.cc/xurls/v2 v2.2.0
 | 
			
		||||
	strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251
 | 
			
		||||
	xorm.io/builder v0.3.9
 | 
			
		||||
	xorm.io/builder v0.3.10
 | 
			
		||||
	xorm.io/xorm v1.2.5
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e // indirect
 | 
			
		||||
	github.com/Microsoft/go-winio v0.5.2 // indirect
 | 
			
		||||
	github.com/ProtonMail/go-crypto v0.0.0-20220113124808-70ae35bab23f // indirect
 | 
			
		||||
	github.com/andybalholm/brotli v1.0.4 // indirect
 | 
			
		||||
	github.com/bits-and-blooms/bitset v1.2.1 // indirect
 | 
			
		||||
	github.com/boombuler/barcode v1.0.1 // indirect
 | 
			
		||||
	github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b // indirect
 | 
			
		||||
	github.com/cloudflare/cfssl v1.6.1 // indirect
 | 
			
		||||
	github.com/couchbase/go-couchbase v0.0.0-20210224140812-5740cd35f448 // indirect
 | 
			
		||||
	github.com/couchbase/gomemcached v0.1.2 // indirect
 | 
			
		||||
	github.com/couchbase/goutils v0.0.0-20210118111533-e33d3ffb5401 // indirect
 | 
			
		||||
	github.com/felixge/httpsnoop v1.0.2 // indirect
 | 
			
		||||
	github.com/fxamacker/cbor/v2 v2.4.0 // indirect
 | 
			
		||||
	github.com/go-asn1-ber/asn1-ber v1.5.3 // indirect
 | 
			
		||||
	github.com/go-openapi/analysis v0.21.2 // indirect
 | 
			
		||||
	github.com/go-openapi/errors v0.20.2 // indirect
 | 
			
		||||
	github.com/go-openapi/runtime v0.21.1 // indirect
 | 
			
		||||
	github.com/go-openapi/strfmt v0.21.1 // indirect
 | 
			
		||||
	github.com/go-stack/stack v1.8.1 // indirect
 | 
			
		||||
	github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
 | 
			
		||||
	github.com/golang/snappy v0.0.4 // indirect
 | 
			
		||||
	github.com/gorilla/mux v1.8.0 // indirect
 | 
			
		||||
	github.com/hashicorp/go-retryablehttp v0.7.0 // indirect
 | 
			
		||||
	github.com/kevinburke/ssh_config v1.1.0 // indirect
 | 
			
		||||
	github.com/kr/pretty v0.3.0 // indirect
 | 
			
		||||
	github.com/mattn/go-runewidth v0.0.13 // indirect
 | 
			
		||||
	github.com/mholt/acmez v1.0.2 // indirect
 | 
			
		||||
	github.com/miekg/dns v1.1.46 // indirect
 | 
			
		||||
	github.com/minio/md5-simd v1.1.2 // indirect
 | 
			
		||||
	github.com/minio/sha256-simd v1.0.0 // indirect
 | 
			
		||||
	github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect
 | 
			
		||||
	github.com/nwaples/rardecode v1.1.3 // indirect
 | 
			
		||||
	github.com/pierrec/lz4/v4 v4.1.14 // indirect
 | 
			
		||||
	github.com/rogpeppe/go-internal v1.8.1 // indirect
 | 
			
		||||
	github.com/rs/xid v1.3.0 // indirect
 | 
			
		||||
	github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect
 | 
			
		||||
	github.com/spf13/afero v1.8.0 // indirect
 | 
			
		||||
	github.com/spf13/cobra v1.3.0 // indirect
 | 
			
		||||
	github.com/spf13/viper v1.10.1 // indirect
 | 
			
		||||
	github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
 | 
			
		||||
	github.com/ulikunitz/xz v0.5.10 // indirect
 | 
			
		||||
	github.com/xanzy/ssh-agent v0.3.1 // indirect
 | 
			
		||||
	go.etcd.io/bbolt v1.3.6 // indirect
 | 
			
		||||
	go.mongodb.org/mongo-driver v1.8.2 // indirect
 | 
			
		||||
	golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect
 | 
			
		||||
	gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1
 | 
			
		||||
 | 
			
		||||
replace github.com/markbates/goth v1.68.0 => github.com/zeripath/goth v1.68.1-0.20220109111530-754359885dce
 | 
			
		||||
 | 
			
		||||
replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0
 | 
			
		||||
 | 
			
		||||
replace github.com/duo-labs/webauthn => github.com/authelia/webauthn v0.0.0-20211225121951-80d1f2a572e4
 | 
			
		||||
 | 
			
		||||
replace github.com/satori/go.uuid v1.2.0 => github.com/gofrs/uuid v4.2.0+incompatible
 | 
			
		||||
 | 
			
		||||
exclude github.com/gofrs/uuid v3.2.0+incompatible
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@ import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/http/httptest"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"os"
 | 
			
		||||
	"testing"
 | 
			
		||||
@@ -262,23 +263,26 @@ func doAPIMergePullRequest(ctx APITestContext, owner, repo string, index int64)
 | 
			
		||||
	return func(t *testing.T) {
 | 
			
		||||
		urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge?token=%s",
 | 
			
		||||
			owner, repo, index, ctx.Token)
 | 
			
		||||
		req := NewRequestWithJSON(t, http.MethodPost, urlStr, &forms.MergePullRequestForm{
 | 
			
		||||
			MergeMessageField: "doAPIMergePullRequest Merge",
 | 
			
		||||
			Do:                string(repo_model.MergeStyleMerge),
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		resp := ctx.Session.MakeRequest(t, req, NoExpectedStatus)
 | 
			
		||||
		var req *http.Request
 | 
			
		||||
		var resp *httptest.ResponseRecorder
 | 
			
		||||
 | 
			
		||||
		if resp.Code == http.StatusMethodNotAllowed {
 | 
			
		||||
			err := api.APIError{}
 | 
			
		||||
			DecodeJSON(t, resp, &err)
 | 
			
		||||
			assert.EqualValues(t, "Please try again later", err.Message)
 | 
			
		||||
			queue.GetManager().FlushAll(context.Background(), 5*time.Second)
 | 
			
		||||
		for i := 0; i < 6; i++ {
 | 
			
		||||
			req = NewRequestWithJSON(t, http.MethodPost, urlStr, &forms.MergePullRequestForm{
 | 
			
		||||
				MergeMessageField: "doAPIMergePullRequest Merge",
 | 
			
		||||
				Do:                string(repo_model.MergeStyleMerge),
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			resp = ctx.Session.MakeRequest(t, req, NoExpectedStatus)
 | 
			
		||||
 | 
			
		||||
			if resp.Code != http.StatusMethodNotAllowed {
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
			err := api.APIError{}
 | 
			
		||||
			DecodeJSON(t, resp, &err)
 | 
			
		||||
			assert.EqualValues(t, "Please try again later", err.Message)
 | 
			
		||||
			queue.GetManager().FlushAll(context.Background(), 5*time.Second)
 | 
			
		||||
			<-time.After(1 * time.Second)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		expected := ctx.ExpectedCode
 | 
			
		||||
 
 | 
			
		||||
@@ -47,7 +47,7 @@ func TestAPIPrivateServ(t *testing.T) {
 | 
			
		||||
		results, err := private.ServCommand(ctx, 1, "user2", "repo1", perm.AccessModeWrite, "git-upload-pack", "")
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		assert.False(t, results.IsWiki)
 | 
			
		||||
		assert.False(t, results.IsDeployKey)
 | 
			
		||||
		assert.Zero(t, results.DeployKeyID)
 | 
			
		||||
		assert.Equal(t, int64(1), results.KeyID)
 | 
			
		||||
		assert.Equal(t, "user2@localhost", results.KeyName)
 | 
			
		||||
		assert.Equal(t, "user2", results.UserName)
 | 
			
		||||
@@ -70,7 +70,7 @@ func TestAPIPrivateServ(t *testing.T) {
 | 
			
		||||
		results, err = private.ServCommand(ctx, 1, "user15", "big_test_public_1", perm.AccessModeRead, "git-upload-pack", "")
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		assert.False(t, results.IsWiki)
 | 
			
		||||
		assert.False(t, results.IsDeployKey)
 | 
			
		||||
		assert.Zero(t, results.DeployKeyID)
 | 
			
		||||
		assert.Equal(t, int64(1), results.KeyID)
 | 
			
		||||
		assert.Equal(t, "user2@localhost", results.KeyName)
 | 
			
		||||
		assert.Equal(t, "user2", results.UserName)
 | 
			
		||||
@@ -92,7 +92,7 @@ func TestAPIPrivateServ(t *testing.T) {
 | 
			
		||||
		results, err = private.ServCommand(ctx, deployKey.KeyID, "user15", "big_test_private_1", perm.AccessModeRead, "git-upload-pack", "")
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		assert.False(t, results.IsWiki)
 | 
			
		||||
		assert.True(t, results.IsDeployKey)
 | 
			
		||||
		assert.NotZero(t, results.DeployKeyID)
 | 
			
		||||
		assert.Equal(t, deployKey.KeyID, results.KeyID)
 | 
			
		||||
		assert.Equal(t, "test-deploy", results.KeyName)
 | 
			
		||||
		assert.Equal(t, "user15", results.UserName)
 | 
			
		||||
@@ -129,7 +129,7 @@ func TestAPIPrivateServ(t *testing.T) {
 | 
			
		||||
		results, err = private.ServCommand(ctx, deployKey.KeyID, "user15", "big_test_private_2", perm.AccessModeRead, "git-upload-pack", "")
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		assert.False(t, results.IsWiki)
 | 
			
		||||
		assert.True(t, results.IsDeployKey)
 | 
			
		||||
		assert.NotZero(t, results.DeployKeyID)
 | 
			
		||||
		assert.Equal(t, deployKey.KeyID, results.KeyID)
 | 
			
		||||
		assert.Equal(t, "test-deploy", results.KeyName)
 | 
			
		||||
		assert.Equal(t, "user15", results.UserName)
 | 
			
		||||
@@ -142,7 +142,7 @@ func TestAPIPrivateServ(t *testing.T) {
 | 
			
		||||
		results, err = private.ServCommand(ctx, deployKey.KeyID, "user15", "big_test_private_2", perm.AccessModeWrite, "git-upload-pack", "")
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		assert.False(t, results.IsWiki)
 | 
			
		||||
		assert.True(t, results.IsDeployKey)
 | 
			
		||||
		assert.NotZero(t, results.DeployKeyID)
 | 
			
		||||
		assert.Equal(t, deployKey.KeyID, results.KeyID)
 | 
			
		||||
		assert.Equal(t, "test-deploy", results.KeyName)
 | 
			
		||||
		assert.Equal(t, "user15", results.UserName)
 | 
			
		||||
 
 | 
			
		||||
@@ -445,7 +445,6 @@ func TestAPIRepoTransfer(t *testing.T) {
 | 
			
		||||
		expectedStatus int
 | 
			
		||||
	}{
 | 
			
		||||
		// Disclaimer for test story: "user1" is an admin, "user2" is normal user and part of in owner team of org "user3"
 | 
			
		||||
 | 
			
		||||
		// Transfer to a user with teams in another org should fail
 | 
			
		||||
		{ctxUserID: 1, newOwner: "user3", teams: &[]int64{5}, expectedStatus: http.StatusForbidden},
 | 
			
		||||
		// Transfer to a user with non-existent team IDs should fail
 | 
			
		||||
 
 | 
			
		||||
@@ -69,6 +69,12 @@ func TestAPIAddEmail(t *testing.T) {
 | 
			
		||||
			Primary:  false,
 | 
			
		||||
		},
 | 
			
		||||
	}, emails)
 | 
			
		||||
 | 
			
		||||
	opts = api.CreateEmailOption{
 | 
			
		||||
		Emails: []string{"notAEmail"},
 | 
			
		||||
	}
 | 
			
		||||
	req = NewRequestWithJSON(t, "POST", "/api/v1/user/emails?token="+token, &opts)
 | 
			
		||||
	session.MakeRequest(t, req, http.StatusUnprocessableEntity)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAPIDeleteEmail(t *testing.T) {
 | 
			
		||||
 
 | 
			
		||||
@@ -123,6 +123,17 @@ func doGitClone(dstLocalPath string, u *url.URL) func(*testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func doPartialGitClone(dstLocalPath string, u *url.URL) func(*testing.T) {
 | 
			
		||||
	return func(t *testing.T) {
 | 
			
		||||
		assert.NoError(t, git.CloneWithArgs(context.Background(), u.String(), dstLocalPath, allowLFSFilters(), git.CloneRepoOptions{
 | 
			
		||||
			Filter: "blob:none",
 | 
			
		||||
		}))
 | 
			
		||||
		exist, err := util.IsExist(filepath.Join(dstLocalPath, "README.md"))
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		assert.True(t, exist)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func doGitCloneFail(u *url.URL) func(*testing.T) {
 | 
			
		||||
	return func(t *testing.T) {
 | 
			
		||||
		tmpDir, err := os.MkdirTemp("", "doGitCloneFail")
 | 
			
		||||
 
 | 
			
		||||
@@ -69,6 +69,12 @@ func testGit(t *testing.T, u *url.URL) {
 | 
			
		||||
 | 
			
		||||
		t.Run("Clone", doGitClone(dstPath, u))
 | 
			
		||||
 | 
			
		||||
		dstPath2, err := os.MkdirTemp("", httpContext.Reponame)
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		defer util.RemoveAll(dstPath2)
 | 
			
		||||
 | 
			
		||||
		t.Run("Partial Clone", doPartialGitClone(dstPath2, u))
 | 
			
		||||
 | 
			
		||||
		little, big := standardCommitAndPushTest(t, dstPath)
 | 
			
		||||
		littleLFS, bigLFS := lfsCommitAndPushTest(t, dstPath)
 | 
			
		||||
		rawTest(t, &httpContext, little, big, littleLFS, bigLFS)
 | 
			
		||||
 
 | 
			
		||||
@@ -112,6 +112,13 @@ func TestMain(m *testing.M) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	os.Unsetenv("GIT_AUTHOR_NAME")
 | 
			
		||||
	os.Unsetenv("GIT_AUTHOR_EMAIL")
 | 
			
		||||
	os.Unsetenv("GIT_AUTHOR_DATE")
 | 
			
		||||
	os.Unsetenv("GIT_COMMITTER_NAME")
 | 
			
		||||
	os.Unsetenv("GIT_COMMITTER_EMAIL")
 | 
			
		||||
	os.Unsetenv("GIT_COMMITTER_DATE")
 | 
			
		||||
 | 
			
		||||
	err := unittest.InitFixtures(
 | 
			
		||||
		unittest.FixturesOptions{
 | 
			
		||||
			Dir: filepath.Join(filepath.Dir(setting.AppPath), "models/fixtures/"),
 | 
			
		||||
 
 | 
			
		||||
@@ -83,7 +83,7 @@ PROVIDER_CONFIG = integrations/gitea-integration-mssql/data/sessions
 | 
			
		||||
 | 
			
		||||
[log]
 | 
			
		||||
MODE                 = test,file
 | 
			
		||||
ROOT_PATH            = mssql-log
 | 
			
		||||
ROOT_PATH            = {{REPO_TEST_DIR}}mssql-log
 | 
			
		||||
ROUTER               = ,
 | 
			
		||||
XORM                 = file
 | 
			
		||||
ENABLE_SSH_LOG       = true
 | 
			
		||||
 
 | 
			
		||||
@@ -101,7 +101,7 @@ PROVIDER_CONFIG = integrations/gitea-integration-mysql/data/sessions
 | 
			
		||||
 | 
			
		||||
[log]
 | 
			
		||||
MODE                 = test,file
 | 
			
		||||
ROOT_PATH            = mysql-log
 | 
			
		||||
ROOT_PATH            = {{REPO_TEST_DIR}}mysql-log
 | 
			
		||||
ROUTER               = ,
 | 
			
		||||
XORM                 = file
 | 
			
		||||
ENABLE_SSH_LOG       = true
 | 
			
		||||
 
 | 
			
		||||
@@ -80,7 +80,7 @@ PROVIDER_CONFIG = integrations/gitea-integration-mysql8/data/sessions
 | 
			
		||||
 | 
			
		||||
[log]
 | 
			
		||||
MODE                 = test,file
 | 
			
		||||
ROOT_PATH            = mysql8-log
 | 
			
		||||
ROOT_PATH            = {{REPO_TEST_DIR}}mysql8-log
 | 
			
		||||
ROUTER               = ,
 | 
			
		||||
XORM                 = file
 | 
			
		||||
ENABLE_SSH_LOG       = true
 | 
			
		||||
 
 | 
			
		||||
@@ -84,7 +84,7 @@ PROVIDER_CONFIG = integrations/gitea-integration-pgsql/data/sessions
 | 
			
		||||
 | 
			
		||||
[log]
 | 
			
		||||
MODE                 = test,file
 | 
			
		||||
ROOT_PATH            = pgsql-log
 | 
			
		||||
ROOT_PATH            = {{REPO_TEST_DIR}}pgsql-log
 | 
			
		||||
ROUTER               = ,
 | 
			
		||||
XORM                 = file
 | 
			
		||||
ENABLE_SSH_LOG       = true
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,8 @@ import (
 | 
			
		||||
	api "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
	"code.gitea.io/gitea/modules/test"
 | 
			
		||||
	"code.gitea.io/gitea/services/pull"
 | 
			
		||||
	repo_service "code.gitea.io/gitea/services/repository"
 | 
			
		||||
	files_service "code.gitea.io/gitea/services/repository/files"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"github.com/unknwon/i18n"
 | 
			
		||||
@@ -335,3 +337,74 @@ func TestCantMergeUnrelated(t *testing.T) {
 | 
			
		||||
		gitRepo.Close()
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestConflictChecking(t *testing.T) {
 | 
			
		||||
	onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
 | 
			
		||||
		user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
 | 
			
		||||
 | 
			
		||||
		// Create new clean repo to test conflict checking.
 | 
			
		||||
		baseRepo, err := repo_service.CreateRepository(user, user, models.CreateRepoOptions{
 | 
			
		||||
			Name:          "conflict-checking",
 | 
			
		||||
			Description:   "Tempo repo",
 | 
			
		||||
			AutoInit:      true,
 | 
			
		||||
			Readme:        "Default",
 | 
			
		||||
			DefaultBranch: "main",
 | 
			
		||||
		})
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		assert.NotEmpty(t, baseRepo)
 | 
			
		||||
 | 
			
		||||
		// create a commit on new branch.
 | 
			
		||||
		_, err = files_service.CreateOrUpdateRepoFile(baseRepo, user, &files_service.UpdateRepoFileOptions{
 | 
			
		||||
			TreePath:  "important_file",
 | 
			
		||||
			Message:   "Add a important file",
 | 
			
		||||
			Content:   "Just a non-important file",
 | 
			
		||||
			IsNewFile: true,
 | 
			
		||||
			OldBranch: "main",
 | 
			
		||||
			NewBranch: "important-secrets",
 | 
			
		||||
		})
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
		// create a commit on main branch.
 | 
			
		||||
		_, err = files_service.CreateOrUpdateRepoFile(baseRepo, user, &files_service.UpdateRepoFileOptions{
 | 
			
		||||
			TreePath:  "important_file",
 | 
			
		||||
			Message:   "Add a important file",
 | 
			
		||||
			Content:   "Not the same content :P",
 | 
			
		||||
			IsNewFile: true,
 | 
			
		||||
			OldBranch: "main",
 | 
			
		||||
			NewBranch: "main",
 | 
			
		||||
		})
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
		// create Pull to merge the important-secrets branch into main branch.
 | 
			
		||||
		pullIssue := &models.Issue{
 | 
			
		||||
			RepoID:   baseRepo.ID,
 | 
			
		||||
			Title:    "PR with conflict!",
 | 
			
		||||
			PosterID: user.ID,
 | 
			
		||||
			Poster:   user,
 | 
			
		||||
			IsPull:   true,
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		pullRequest := &models.PullRequest{
 | 
			
		||||
			HeadRepoID: baseRepo.ID,
 | 
			
		||||
			BaseRepoID: baseRepo.ID,
 | 
			
		||||
			HeadBranch: "important-secrets",
 | 
			
		||||
			BaseBranch: "main",
 | 
			
		||||
			HeadRepo:   baseRepo,
 | 
			
		||||
			BaseRepo:   baseRepo,
 | 
			
		||||
			Type:       models.PullRequestGitea,
 | 
			
		||||
		}
 | 
			
		||||
		err = pull.NewPullRequest(baseRepo, pullIssue, nil, nil, pullRequest, nil)
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
		issue := unittest.AssertExistsAndLoadBean(t, &models.Issue{Title: "PR with conflict!"}).(*models.Issue)
 | 
			
		||||
		conflictingPR, err := models.GetPullRequestByIssueID(issue.ID)
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
		// Ensure conflictedFiles is populated.
 | 
			
		||||
		assert.Equal(t, 1, len(conflictingPR.ConflictedFiles))
 | 
			
		||||
		// Check if status is correct.
 | 
			
		||||
		assert.Equal(t, models.PullRequestStatusConflict, conflictingPR.Status)
 | 
			
		||||
		// Ensure that mergeable returns false
 | 
			
		||||
		assert.False(t, conflictingPR.Mergeable())
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -51,8 +51,6 @@ func TestSignin(t *testing.T) {
 | 
			
		||||
		{username: "wrongUsername", password: "password", message: i18n.Tr("en", "form.username_password_incorrect")},
 | 
			
		||||
		{username: "user15", password: "wrongPassword", message: i18n.Tr("en", "form.username_password_incorrect")},
 | 
			
		||||
		{username: "user1@example.com", password: "wrongPassword", message: i18n.Tr("en", "form.username_password_incorrect")},
 | 
			
		||||
		// test for duplicate email
 | 
			
		||||
		{username: "user2@example.com", password: "password", message: i18n.Tr("en", "form.email_been_used")},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, s := range samples {
 | 
			
		||||
 
 | 
			
		||||
@@ -79,7 +79,7 @@ PROVIDER_CONFIG = integrations/gitea-integration-sqlite/data/sessions
 | 
			
		||||
 | 
			
		||||
[log]
 | 
			
		||||
MODE                 = test,file
 | 
			
		||||
ROOT_PATH            = sqlite-log
 | 
			
		||||
ROOT_PATH            = {{REPO_TEST_DIR}}sqlite-log
 | 
			
		||||
ROUTER               = ,
 | 
			
		||||
XORM                 = file
 | 
			
		||||
ENABLE_SSH_LOG       = true
 | 
			
		||||
 
 | 
			
		||||
@@ -337,7 +337,7 @@ func GetFeeds(opts GetFeedsOptions) ([]*Action, error) {
 | 
			
		||||
 | 
			
		||||
	actions := make([]*Action, 0, setting.UI.FeedPagingNum)
 | 
			
		||||
 | 
			
		||||
	if err := db.GetEngine(db.DefaultContext).Limit(setting.UI.FeedPagingNum).Desc("created_unix").Where(cond).Find(&actions); err != nil {
 | 
			
		||||
	if err := db.GetEngine(db.DefaultContext).Limit(setting.UI.FeedPagingNum).Desc("`action`.created_unix").Where(cond).Join("INNER", "repository", "`repository`.id = `action`.repo_id").Find(&actions); err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Find: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -401,7 +401,7 @@ func activityQueryCondition(opts GetFeedsOptions) (builder.Cond, error) {
 | 
			
		||||
		cond = cond.And(builder.Eq{"act_user_id": opts.RequestedUser.ID})
 | 
			
		||||
	}
 | 
			
		||||
	if !opts.IncludePrivate {
 | 
			
		||||
		cond = cond.And(builder.Eq{"is_private": false})
 | 
			
		||||
		cond = cond.And(builder.Eq{"`action`.is_private": false})
 | 
			
		||||
	}
 | 
			
		||||
	if !opts.IncludeDeleted {
 | 
			
		||||
		cond = cond.And(builder.Eq{"is_deleted": false})
 | 
			
		||||
@@ -414,8 +414,8 @@ func activityQueryCondition(opts GetFeedsOptions) (builder.Cond, error) {
 | 
			
		||||
		} else {
 | 
			
		||||
			dateHigh := dateLow.Add(86399000000000) // 23h59m59s
 | 
			
		||||
 | 
			
		||||
			cond = cond.And(builder.Gte{"created_unix": dateLow.Unix()})
 | 
			
		||||
			cond = cond.And(builder.Lte{"created_unix": dateHigh.Unix()})
 | 
			
		||||
			cond = cond.And(builder.Gte{"`action`.created_unix": dateLow.Unix()})
 | 
			
		||||
			cond = cond.And(builder.Lte{"`action`.created_unix": dateHigh.Unix()})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -129,3 +129,20 @@ func TestNotifyWatchers(t *testing.T) {
 | 
			
		||||
		OpType:    action.OpType,
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetFeedsCorrupted(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
			
		||||
	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
 | 
			
		||||
	unittest.AssertExistsAndLoadBean(t, &Action{
 | 
			
		||||
		ID:     8,
 | 
			
		||||
		RepoID: 1700,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	actions, err := GetFeeds(GetFeedsOptions{
 | 
			
		||||
		RequestedUser:  user,
 | 
			
		||||
		Actor:          user,
 | 
			
		||||
		IncludePrivate: true,
 | 
			
		||||
	})
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Len(t, actions, 0)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -71,7 +71,7 @@ const (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ParseCommitsWithSignature checks if signaute of commits are corresponding to users gpg keys.
 | 
			
		||||
func ParseCommitsWithSignature(oldCommits []*user_model.UserCommit, repoTrustModel repo_model.TrustModelType, isCodeReader func(*user_model.User) (bool, error)) []*SignCommit {
 | 
			
		||||
func ParseCommitsWithSignature(oldCommits []*user_model.UserCommit, repoTrustModel repo_model.TrustModelType, isOwnerMemberCollaborator func(*user_model.User) (bool, error)) []*SignCommit {
 | 
			
		||||
	newCommits := make([]*SignCommit, 0, len(oldCommits))
 | 
			
		||||
	keyMap := map[string]bool{}
 | 
			
		||||
 | 
			
		||||
@@ -81,7 +81,7 @@ func ParseCommitsWithSignature(oldCommits []*user_model.UserCommit, repoTrustMod
 | 
			
		||||
			Verification: ParseCommitWithSignature(c.Commit),
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		_ = CalculateTrustStatus(signCommit.Verification, repoTrustModel, isCodeReader, &keyMap)
 | 
			
		||||
		_ = CalculateTrustStatus(signCommit.Verification, repoTrustModel, isOwnerMemberCollaborator, &keyMap)
 | 
			
		||||
 | 
			
		||||
		newCommits = append(newCommits, signCommit)
 | 
			
		||||
	}
 | 
			
		||||
@@ -455,7 +455,7 @@ func hashAndVerifyForKeyID(sig *packet.Signature, payload string, committer *use
 | 
			
		||||
 | 
			
		||||
// CalculateTrustStatus will calculate the TrustStatus for a commit verification within a repository
 | 
			
		||||
// There are several trust models in Gitea
 | 
			
		||||
func CalculateTrustStatus(verification *CommitVerification, repoTrustModel repo_model.TrustModelType, isCodeReader func(*user_model.User) (bool, error), keyMap *map[string]bool) (err error) {
 | 
			
		||||
func CalculateTrustStatus(verification *CommitVerification, repoTrustModel repo_model.TrustModelType, isOwnerMemberCollaborator func(*user_model.User) (bool, error), keyMap *map[string]bool) (err error) {
 | 
			
		||||
	if !verification.Verified {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
@@ -500,11 +500,11 @@ func CalculateTrustStatus(verification *CommitVerification, repoTrustModel repo_
 | 
			
		||||
			var has bool
 | 
			
		||||
			isMember, has = (*keyMap)[verification.SigningKey.KeyID]
 | 
			
		||||
			if !has {
 | 
			
		||||
				isMember, err = isCodeReader(verification.SigningUser)
 | 
			
		||||
				isMember, err = isOwnerMemberCollaborator(verification.SigningUser)
 | 
			
		||||
				(*keyMap)[verification.SigningKey.KeyID] = isMember
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			isMember, err = isCodeReader(verification.SigningUser)
 | 
			
		||||
			isMember, err = isOwnerMemberCollaborator(verification.SigningUser)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !isMember {
 | 
			
		||||
 
 | 
			
		||||
@@ -58,7 +58,7 @@ func (key *DeployKey) GetContent() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsReadOnly checks if the key can only be used for read operations
 | 
			
		||||
// IsReadOnly checks if the key can only be used for read operations, used by template
 | 
			
		||||
func (key *DeployKey) IsReadOnly() bool {
 | 
			
		||||
	return key.Mode == perm.AccessModeRead
 | 
			
		||||
}
 | 
			
		||||
@@ -203,12 +203,6 @@ func UpdateDeployKeyCols(key *DeployKey, cols ...string) error {
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateDeployKey updates deploy key information.
 | 
			
		||||
func UpdateDeployKey(key *DeployKey) error {
 | 
			
		||||
	_, err := db.GetEngine(db.DefaultContext).ID(key.ID).AllCols().Update(key)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ListDeployKeysOptions are options for ListDeployKeys
 | 
			
		||||
type ListDeployKeysOptions struct {
 | 
			
		||||
	db.ListOptions
 | 
			
		||||
 
 | 
			
		||||
@@ -43,7 +43,7 @@ type WebAuthnCredential struct {
 | 
			
		||||
	Name            string
 | 
			
		||||
	LowerName       string `xorm:"unique(s)"`
 | 
			
		||||
	UserID          int64  `xorm:"INDEX unique(s)"`
 | 
			
		||||
	CredentialID    string `xorm:"INDEX"`
 | 
			
		||||
	CredentialID    string `xorm:"INDEX VARCHAR(410)"`
 | 
			
		||||
	PublicKey       []byte
 | 
			
		||||
	AttestationType string
 | 
			
		||||
	AAGUID          []byte
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,7 @@ func ConvertFromGitCommit(commits []*git.Commit, repo *repo_model.Repository) []
 | 
			
		||||
			user_model.ValidateCommitsWithEmails(commits),
 | 
			
		||||
			repo.GetTrustModel(),
 | 
			
		||||
			func(user *user_model.User) (bool, error) {
 | 
			
		||||
				return IsUserRepoAdmin(repo, user)
 | 
			
		||||
				return IsOwnerMemberCollaborator(repo, user.ID)
 | 
			
		||||
			},
 | 
			
		||||
		),
 | 
			
		||||
		repo,
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
 | 
			
		||||
	"xorm.io/builder"
 | 
			
		||||
)
 | 
			
		||||
@@ -247,3 +248,23 @@ func FixIssueLabelWithOutsideLabels() (int64, error) {
 | 
			
		||||
 | 
			
		||||
	return res.RowsAffected()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CountActionCreatedUnixString count actions where created_unix is an empty string
 | 
			
		||||
func CountActionCreatedUnixString() (int64, error) {
 | 
			
		||||
	if setting.Database.UseSQLite3 {
 | 
			
		||||
		return db.GetEngine(db.DefaultContext).Where(`created_unix = ""`).Count(new(Action))
 | 
			
		||||
	}
 | 
			
		||||
	return 0, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FixActionCreatedUnixString set created_unix to zero if it is an empty string
 | 
			
		||||
func FixActionCreatedUnixString() (int64, error) {
 | 
			
		||||
	if setting.Database.UseSQLite3 {
 | 
			
		||||
		res, err := db.GetEngine(db.DefaultContext).Exec(`UPDATE action SET created_unix = 0 WHERE created_unix = ""`)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
		return res.RowsAffected()
 | 
			
		||||
	}
 | 
			
		||||
	return 0, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@ import (
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	"code.gitea.io/gitea/models/unittest"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
@@ -33,3 +34,46 @@ func TestDeleteOrphanedObjects(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.EqualValues(t, countBefore, countAfter)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestConsistencyUpdateAction(t *testing.T) {
 | 
			
		||||
	if !setting.Database.UseSQLite3 {
 | 
			
		||||
		t.Skip("Test is only for SQLite database.")
 | 
			
		||||
	}
 | 
			
		||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
			
		||||
	id := 8
 | 
			
		||||
	unittest.AssertExistsAndLoadBean(t, &Action{
 | 
			
		||||
		ID: int64(id),
 | 
			
		||||
	})
 | 
			
		||||
	_, err := db.GetEngine(db.DefaultContext).Exec(`UPDATE action SET created_unix = "" WHERE id = ?`, id)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	actions := make([]*Action, 0, 1)
 | 
			
		||||
	//
 | 
			
		||||
	// XORM returns an error when created_unix is a string
 | 
			
		||||
	//
 | 
			
		||||
	err = db.GetEngine(db.DefaultContext).Where("id = ?", id).Find(&actions)
 | 
			
		||||
	if assert.Error(t, err) {
 | 
			
		||||
		assert.Contains(t, err.Error(), "type string to a int64: invalid syntax")
 | 
			
		||||
	}
 | 
			
		||||
	//
 | 
			
		||||
	// Get rid of incorrectly set created_unix
 | 
			
		||||
	//
 | 
			
		||||
	count, err := CountActionCreatedUnixString()
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.EqualValues(t, 1, count)
 | 
			
		||||
	count, err = FixActionCreatedUnixString()
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.EqualValues(t, 1, count)
 | 
			
		||||
 | 
			
		||||
	count, err = CountActionCreatedUnixString()
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.EqualValues(t, 0, count)
 | 
			
		||||
	count, err = FixActionCreatedUnixString()
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.EqualValues(t, 0, count)
 | 
			
		||||
 | 
			
		||||
	//
 | 
			
		||||
	// XORM must be happy now
 | 
			
		||||
	//
 | 
			
		||||
	assert.NoError(t, db.GetEngine(db.DefaultContext).Where("id = ?", id).Find(&actions))
 | 
			
		||||
	unittest.CheckConsistencyFor(t, &Action{})
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -332,7 +332,6 @@ type ErrInvalidCloneAddr struct {
 | 
			
		||||
	IsProtocolInvalid  bool
 | 
			
		||||
	IsPermissionDenied bool
 | 
			
		||||
	LocalPath          bool
 | 
			
		||||
	NotResolvedIP      bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsErrInvalidCloneAddr checks if an error is a ErrInvalidCloneAddr.
 | 
			
		||||
@@ -342,9 +341,6 @@ func IsErrInvalidCloneAddr(err error) bool {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (err *ErrInvalidCloneAddr) Error() string {
 | 
			
		||||
	if err.NotResolvedIP {
 | 
			
		||||
		return fmt.Sprintf("migration/cloning from '%s' is not allowed: unknown hostname", err.Host)
 | 
			
		||||
	}
 | 
			
		||||
	if err.IsInvalidPath {
 | 
			
		||||
		return fmt.Sprintf("migration/cloning from '%s' is not allowed: the provided path is invalid", err.Host)
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -56,3 +56,11 @@
 | 
			
		||||
  repo_id: 8
 | 
			
		||||
  is_private: false
 | 
			
		||||
  created_unix: 1603011540 # grouped with id:7
 | 
			
		||||
 | 
			
		||||
- id: 8
 | 
			
		||||
  user_id: 1
 | 
			
		||||
  op_type: 12 # close issue
 | 
			
		||||
  act_user_id: 1
 | 
			
		||||
  repo_id: 1700 # dangling intentional
 | 
			
		||||
  is_private: false
 | 
			
		||||
  created_unix: 1603011541
 | 
			
		||||
 
 | 
			
		||||
@@ -23,8 +23,8 @@ const (
 | 
			
		||||
	EnvPusherName   = "GITEA_PUSHER_NAME"
 | 
			
		||||
	EnvPusherEmail  = "GITEA_PUSHER_EMAIL"
 | 
			
		||||
	EnvPusherID     = "GITEA_PUSHER_ID"
 | 
			
		||||
	EnvKeyID        = "GITEA_KEY_ID"
 | 
			
		||||
	EnvIsDeployKey  = "GITEA_IS_DEPLOY_KEY"
 | 
			
		||||
	EnvKeyID        = "GITEA_KEY_ID" // public key ID
 | 
			
		||||
	EnvDeployKeyID  = "GITEA_DEPLOY_KEY_ID"
 | 
			
		||||
	EnvPRID         = "GITEA_PR_ID"
 | 
			
		||||
	EnvIsInternal   = "GITEA_INTERNAL_PUSH"
 | 
			
		||||
	EnvAppURL       = "GITEA_ROOT_URL"
 | 
			
		||||
 
 | 
			
		||||
@@ -1165,7 +1165,8 @@ func GetIssuesByIDs(issueIDs []int64) ([]*Issue, error) {
 | 
			
		||||
// IssuesOptions represents options of an issue.
 | 
			
		||||
type IssuesOptions struct {
 | 
			
		||||
	db.ListOptions
 | 
			
		||||
	RepoIDs            []int64 // include all repos if empty
 | 
			
		||||
	RepoID             int64 // overwrites RepoCond if not 0
 | 
			
		||||
	RepoCond           builder.Cond
 | 
			
		||||
	AssigneeID         int64
 | 
			
		||||
	PosterID           int64
 | 
			
		||||
	MentionedID        int64
 | 
			
		||||
@@ -1238,7 +1239,7 @@ func sortIssuesSession(sess *xorm.Session, sortType string, priorityRepoID int64
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (opts *IssuesOptions) setupSession(sess *xorm.Session) {
 | 
			
		||||
func (opts *IssuesOptions) setupSessionWithLimit(sess *xorm.Session) {
 | 
			
		||||
	if opts.Page >= 0 && opts.PageSize > 0 {
 | 
			
		||||
		var start int
 | 
			
		||||
		if opts.Page == 0 {
 | 
			
		||||
@@ -1248,20 +1249,23 @@ func (opts *IssuesOptions) setupSession(sess *xorm.Session) {
 | 
			
		||||
		}
 | 
			
		||||
		sess.Limit(opts.PageSize, start)
 | 
			
		||||
	}
 | 
			
		||||
	opts.setupSessionNoLimit(sess)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (opts *IssuesOptions) setupSessionNoLimit(sess *xorm.Session) {
 | 
			
		||||
	if len(opts.IssueIDs) > 0 {
 | 
			
		||||
		sess.In("issue.id", opts.IssueIDs)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(opts.RepoIDs) > 0 {
 | 
			
		||||
		applyReposCondition(sess, opts.RepoIDs)
 | 
			
		||||
	if opts.RepoID != 0 {
 | 
			
		||||
		opts.RepoCond = builder.Eq{"issue.repo_id": opts.RepoID}
 | 
			
		||||
	}
 | 
			
		||||
	if opts.RepoCond != nil {
 | 
			
		||||
		sess.And(opts.RepoCond)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch opts.IsClosed {
 | 
			
		||||
	case util.OptionalBoolTrue:
 | 
			
		||||
		sess.And("issue.is_closed=?", true)
 | 
			
		||||
	case util.OptionalBoolFalse:
 | 
			
		||||
		sess.And("issue.is_closed=?", false)
 | 
			
		||||
	if !opts.IsClosed.IsNone() {
 | 
			
		||||
		sess.And("issue.is_closed=?", opts.IsClosed.IsTrue())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if opts.AssigneeID > 0 {
 | 
			
		||||
@@ -1342,9 +1346,7 @@ func (opts *IssuesOptions) setupSession(sess *xorm.Session) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if opts.User != nil {
 | 
			
		||||
		sess.And(
 | 
			
		||||
			issuePullAccessibleRepoCond("issue.repo_id", opts.User.ID, opts.Org, opts.Team, opts.IsPull.IsTrue()),
 | 
			
		||||
		)
 | 
			
		||||
		sess.And(issuePullAccessibleRepoCond("issue.repo_id", opts.User.ID, opts.Org, opts.Team, opts.IsPull.IsTrue()))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -1380,10 +1382,6 @@ func issuePullAccessibleRepoCond(repoIDstr string, userID int64, org *Organizati
 | 
			
		||||
	return cond
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func applyReposCondition(sess *xorm.Session, repoIDs []int64) *xorm.Session {
 | 
			
		||||
	return sess.In("issue.repo_id", repoIDs)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func applyAssigneeCondition(sess *xorm.Session, assigneeID int64) *xorm.Session {
 | 
			
		||||
	return sess.Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id").
 | 
			
		||||
		And("issue_assignees.assignee_id = ?", assigneeID)
 | 
			
		||||
@@ -1413,8 +1411,7 @@ func CountIssuesByRepo(opts *IssuesOptions) (map[int64]int64, error) {
 | 
			
		||||
	e := db.GetEngine(db.DefaultContext)
 | 
			
		||||
 | 
			
		||||
	sess := e.Join("INNER", "repository", "`issue`.repo_id = `repository`.id")
 | 
			
		||||
 | 
			
		||||
	opts.setupSession(sess)
 | 
			
		||||
	opts.setupSessionNoLimit(sess)
 | 
			
		||||
 | 
			
		||||
	countsSlice := make([]*struct {
 | 
			
		||||
		RepoID int64
 | 
			
		||||
@@ -1424,7 +1421,7 @@ func CountIssuesByRepo(opts *IssuesOptions) (map[int64]int64, error) {
 | 
			
		||||
		Select("issue.repo_id AS repo_id, COUNT(*) AS count").
 | 
			
		||||
		Table("issue").
 | 
			
		||||
		Find(&countsSlice); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
		return nil, fmt.Errorf("unable to CountIssuesByRepo: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	countMap := make(map[int64]int64, len(countsSlice))
 | 
			
		||||
@@ -1440,15 +1437,14 @@ func GetRepoIDsForIssuesOptions(opts *IssuesOptions, user *user_model.User) ([]i
 | 
			
		||||
	e := db.GetEngine(db.DefaultContext)
 | 
			
		||||
 | 
			
		||||
	sess := e.Join("INNER", "repository", "`issue`.repo_id = `repository`.id")
 | 
			
		||||
 | 
			
		||||
	opts.setupSession(sess)
 | 
			
		||||
	opts.setupSessionNoLimit(sess)
 | 
			
		||||
 | 
			
		||||
	accessCond := accessibleRepositoryCondition(user)
 | 
			
		||||
	if err := sess.Where(accessCond).
 | 
			
		||||
		Distinct("issue.repo_id").
 | 
			
		||||
		Table("issue").
 | 
			
		||||
		Find(&repoIDs); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
		return nil, fmt.Errorf("unable to GetRepoIDsForIssuesOptions: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return repoIDs, nil
 | 
			
		||||
@@ -1459,17 +1455,16 @@ func Issues(opts *IssuesOptions) ([]*Issue, error) {
 | 
			
		||||
	e := db.GetEngine(db.DefaultContext)
 | 
			
		||||
 | 
			
		||||
	sess := e.Join("INNER", "repository", "`issue`.repo_id = `repository`.id")
 | 
			
		||||
	opts.setupSession(sess)
 | 
			
		||||
	opts.setupSessionWithLimit(sess)
 | 
			
		||||
	sortIssuesSession(sess, opts.SortType, opts.PriorityRepoID)
 | 
			
		||||
 | 
			
		||||
	issues := make([]*Issue, 0, opts.ListOptions.PageSize)
 | 
			
		||||
	if err := sess.Find(&issues); err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Find: %v", err)
 | 
			
		||||
		return nil, fmt.Errorf("unable to query Issues: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
	sess.Close()
 | 
			
		||||
 | 
			
		||||
	if err := IssueList(issues).LoadAttributes(); err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("LoadAttributes: %v", err)
 | 
			
		||||
		return nil, fmt.Errorf("unable to LoadAttributes for Issues: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return issues, nil
 | 
			
		||||
@@ -1480,18 +1475,18 @@ func CountIssues(opts *IssuesOptions) (int64, error) {
 | 
			
		||||
	e := db.GetEngine(db.DefaultContext)
 | 
			
		||||
 | 
			
		||||
	countsSlice := make([]*struct {
 | 
			
		||||
		RepoID int64
 | 
			
		||||
		Count int64
 | 
			
		||||
	}, 0, 1)
 | 
			
		||||
 | 
			
		||||
	sess := e.Select("COUNT(issue.id) AS count").Table("issue")
 | 
			
		||||
	sess.Join("INNER", "repository", "`issue`.repo_id = `repository`.id")
 | 
			
		||||
	opts.setupSession(sess)
 | 
			
		||||
	opts.setupSessionNoLimit(sess)
 | 
			
		||||
 | 
			
		||||
	if err := sess.Find(&countsSlice); err != nil {
 | 
			
		||||
		return 0, fmt.Errorf("Find: %v", err)
 | 
			
		||||
		return 0, fmt.Errorf("unable to CountIssues: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
	if len(countsSlice) < 1 {
 | 
			
		||||
		return 0, fmt.Errorf("there is less than one result sql record")
 | 
			
		||||
	if len(countsSlice) != 1 {
 | 
			
		||||
		return 0, fmt.Errorf("unable to get one row result when CountIssues, row count=%d", len(countsSlice))
 | 
			
		||||
	}
 | 
			
		||||
	return countsSlice[0].Count, nil
 | 
			
		||||
}
 | 
			
		||||
@@ -1551,6 +1546,7 @@ const (
 | 
			
		||||
	FilterModeCreate
 | 
			
		||||
	FilterModeMention
 | 
			
		||||
	FilterModeReviewRequested
 | 
			
		||||
	FilterModeYourRepositories
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func parseCountResult(results []map[string][]byte) int64 {
 | 
			
		||||
@@ -1695,6 +1691,7 @@ type UserIssueStatsOptions struct {
 | 
			
		||||
	IssueIDs   []int64
 | 
			
		||||
	IsArchived util.OptionalBool
 | 
			
		||||
	LabelIDs   []int64
 | 
			
		||||
	RepoCond   builder.Cond
 | 
			
		||||
	Org        *Organization
 | 
			
		||||
	Team       *Team
 | 
			
		||||
}
 | 
			
		||||
@@ -1712,6 +1709,9 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) {
 | 
			
		||||
	if len(opts.IssueIDs) > 0 {
 | 
			
		||||
		cond = cond.And(builder.In("issue.id", opts.IssueIDs))
 | 
			
		||||
	}
 | 
			
		||||
	if opts.RepoCond != nil {
 | 
			
		||||
		cond = cond.And(opts.RepoCond)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if opts.UserID > 0 {
 | 
			
		||||
		cond = cond.And(issuePullAccessibleRepoCond("issue.repo_id", opts.UserID, opts.Org, opts.Team, opts.IsPull))
 | 
			
		||||
@@ -1733,7 +1733,7 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch opts.FilterMode {
 | 
			
		||||
	case FilterModeAll:
 | 
			
		||||
	case FilterModeAll, FilterModeYourRepositories:
 | 
			
		||||
		stats.OpenCount, err = sess(cond).
 | 
			
		||||
			And("issue.is_closed = ?", false).
 | 
			
		||||
			Count(new(Issue))
 | 
			
		||||
 
 | 
			
		||||
@@ -101,12 +101,9 @@ func (label *Label) CalOpenIssues() {
 | 
			
		||||
 | 
			
		||||
// CalOpenOrgIssues calculates the open issues of a label for a specific repo
 | 
			
		||||
func (label *Label) CalOpenOrgIssues(repoID, labelID int64) {
 | 
			
		||||
	repoIDs := []int64{repoID}
 | 
			
		||||
	labelIDs := []int64{labelID}
 | 
			
		||||
 | 
			
		||||
	counts, _ := CountIssuesByRepo(&IssuesOptions{
 | 
			
		||||
		RepoIDs:  repoIDs,
 | 
			
		||||
		LabelIDs: labelIDs,
 | 
			
		||||
		RepoID:   repoID,
 | 
			
		||||
		LabelIDs: []int64{labelID},
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	for _, count := range counts {
 | 
			
		||||
 
 | 
			
		||||
@@ -134,22 +134,6 @@ func GetMilestoneByRepoIDANDName(repoID int64, name string) (*Milestone, error)
 | 
			
		||||
	return &mile, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetMilestoneByID returns the milestone via id .
 | 
			
		||||
func GetMilestoneByID(id int64) (*Milestone, error) {
 | 
			
		||||
	return getMilestoneByID(db.GetEngine(db.DefaultContext), id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getMilestoneByID(e db.Engine, id int64) (*Milestone, error) {
 | 
			
		||||
	var m Milestone
 | 
			
		||||
	has, err := e.ID(id).Get(&m)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	} else if !has {
 | 
			
		||||
		return nil, ErrMilestoneNotExist{ID: id, RepoID: 0}
 | 
			
		||||
	}
 | 
			
		||||
	return &m, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateMilestone updates information of given milestone.
 | 
			
		||||
func UpdateMilestone(m *Milestone, oldIsClosed bool) error {
 | 
			
		||||
	ctx, committer, err := db.TxContext()
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,7 @@ import (
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"xorm.io/builder"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestIssue_ReplaceLabels(t *testing.T) {
 | 
			
		||||
@@ -153,7 +154,7 @@ func TestIssues(t *testing.T) {
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			IssuesOptions{
 | 
			
		||||
				RepoIDs:  []int64{1, 3},
 | 
			
		||||
				RepoCond: builder.In("repo_id", 1, 3),
 | 
			
		||||
				SortType: "oldest",
 | 
			
		||||
				ListOptions: db.ListOptions{
 | 
			
		||||
					Page:     1,
 | 
			
		||||
@@ -340,7 +341,7 @@ func TestGetRepoIDsForIssuesOptions(t *testing.T) {
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			IssuesOptions{
 | 
			
		||||
				RepoIDs: []int64{1, 2},
 | 
			
		||||
				RepoCond: builder.In("repo_id", 1, 2),
 | 
			
		||||
			},
 | 
			
		||||
			[]int64{1, 2},
 | 
			
		||||
		},
 | 
			
		||||
 
 | 
			
		||||
@@ -137,6 +137,7 @@ func QueryIssueContentHistoryEditedCountMap(dbCtx context.Context, issueID int64
 | 
			
		||||
type IssueContentListItem struct {
 | 
			
		||||
	UserID         int64
 | 
			
		||||
	UserName       string
 | 
			
		||||
	UserFullName   string
 | 
			
		||||
	UserAvatarLink string
 | 
			
		||||
 | 
			
		||||
	HistoryID      int64
 | 
			
		||||
@@ -148,7 +149,7 @@ type IssueContentListItem struct {
 | 
			
		||||
// FetchIssueContentHistoryList fetch list
 | 
			
		||||
func FetchIssueContentHistoryList(dbCtx context.Context, issueID, commentID int64) ([]*IssueContentListItem, error) {
 | 
			
		||||
	res := make([]*IssueContentListItem, 0)
 | 
			
		||||
	err := db.GetEngine(dbCtx).Select("u.id as user_id, u.name as user_name,"+
 | 
			
		||||
	err := db.GetEngine(dbCtx).Select("u.id as user_id, u.name as user_name, u.full_name as user_full_name,"+
 | 
			
		||||
		"h.id as history_id, h.edited_unix, h.is_first_created, h.is_deleted").
 | 
			
		||||
		Table([]string{"issue_content_history", "h"}).
 | 
			
		||||
		Join("LEFT", []string{"user", "u"}, "h.poster_id = u.id").
 | 
			
		||||
 
 | 
			
		||||
@@ -45,6 +45,7 @@ func TestContentHistory(t *testing.T) {
 | 
			
		||||
	type User struct {
 | 
			
		||||
		ID       int64
 | 
			
		||||
		Name     string
 | 
			
		||||
		FullName string
 | 
			
		||||
	}
 | 
			
		||||
	_ = dbEngine.Sync2(&User{})
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -193,12 +193,13 @@ func LFSAutoAssociate(metas []*LFSMetaObject, user *user_model.User, repoID int6
 | 
			
		||||
		// admin can associate any LFS object to any repository, and we do not care about errors (eg: duplicated unique key),
 | 
			
		||||
		// even if error occurs, it won't hurt users and won't make things worse
 | 
			
		||||
		for i := range metas {
 | 
			
		||||
			p := lfs.Pointer{Oid: metas[i].Oid, Size: metas[i].Size}
 | 
			
		||||
			_, err = sess.Insert(&LFSMetaObject{
 | 
			
		||||
				Pointer:      lfs.Pointer{Oid: metas[i].Oid, Size: metas[i].Size},
 | 
			
		||||
				Pointer:      p,
 | 
			
		||||
				RepositoryID: repoID,
 | 
			
		||||
			})
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Warn("failed to insert LFS meta object into database, err=%v", err)
 | 
			
		||||
				log.Warn("failed to insert LFS meta object %-v for repo_id: %d into database, err=%v", p, repoID, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -52,10 +52,6 @@ func InsertIssues(issues ...*Issue) error {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	err = UpdateRepoStats(ctx, issues[0].RepoID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return committer.Commit()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -147,11 +143,6 @@ func InsertPullRequests(prs ...*PullRequest) error {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = UpdateRepoStats(ctx, prs[0].Issue.RepoID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return committer.Commit()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -32,8 +32,9 @@ func TestMigrate_InsertMilestones(t *testing.T) {
 | 
			
		||||
	unittest.CheckConsistencyFor(t, &Milestone{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func assertCreateIssues(t *testing.T, reponame string, isPull bool) {
 | 
			
		||||
func assertCreateIssues(t *testing.T, isPull bool) {
 | 
			
		||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
			
		||||
	reponame := "repo1"
 | 
			
		||||
	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame}).(*repo_model.Repository)
 | 
			
		||||
	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}).(*user_model.User)
 | 
			
		||||
	label := unittest.AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label)
 | 
			
		||||
@@ -63,38 +64,14 @@ func assertCreateIssues(t *testing.T, reponame string, isPull bool) {
 | 
			
		||||
 | 
			
		||||
	i := unittest.AssertExistsAndLoadBean(t, &Issue{Title: title}).(*Issue)
 | 
			
		||||
	unittest.AssertExistsAndLoadBean(t, &Reaction{Type: "heart", UserID: owner.ID, IssueID: i.ID})
 | 
			
		||||
 | 
			
		||||
	labelModified := unittest.AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label)
 | 
			
		||||
	assert.EqualValues(t, label.NumIssues+1, labelModified.NumIssues)
 | 
			
		||||
	assert.EqualValues(t, label.NumClosedIssues+1, labelModified.NumClosedIssues)
 | 
			
		||||
 | 
			
		||||
	milestoneModified := unittest.AssertExistsAndLoadBean(t, &Milestone{ID: milestone.ID}).(*Milestone)
 | 
			
		||||
	assert.EqualValues(t, milestone.NumIssues+1, milestoneModified.NumIssues)
 | 
			
		||||
	assert.EqualValues(t, milestone.NumClosedIssues+1, milestoneModified.NumClosedIssues)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMigrate_CreateIssuesIsPullFalse(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
			
		||||
	reponame := "repo1"
 | 
			
		||||
	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame}).(*repo_model.Repository)
 | 
			
		||||
 | 
			
		||||
	assertCreateIssues(t, reponame, false)
 | 
			
		||||
 | 
			
		||||
	repoModified := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID}).(*repo_model.Repository)
 | 
			
		||||
	assert.EqualValues(t, repo.NumIssues+1, repoModified.NumIssues)
 | 
			
		||||
	assert.EqualValues(t, repo.NumClosedIssues+1, repoModified.NumClosedIssues)
 | 
			
		||||
	assertCreateIssues(t, false)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMigrate_CreateIssuesIsPullTrue(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
			
		||||
	reponame := "repo1"
 | 
			
		||||
	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame}).(*repo_model.Repository)
 | 
			
		||||
 | 
			
		||||
	assertCreateIssues(t, reponame, true)
 | 
			
		||||
 | 
			
		||||
	repoModified := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID}).(*repo_model.Repository)
 | 
			
		||||
	assert.EqualValues(t, repo.NumPulls+1, repoModified.NumPulls)
 | 
			
		||||
	assert.EqualValues(t, repo.NumClosedPulls+1, repoModified.NumClosedPulls)
 | 
			
		||||
	assertCreateIssues(t, true)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMigrate_InsertIssueComments(t *testing.T) {
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,12 @@
 | 
			
		||||
-
 | 
			
		||||
  id: 1
 | 
			
		||||
  credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
 | 
			
		||||
-
 | 
			
		||||
  id: 2
 | 
			
		||||
  credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
 | 
			
		||||
-
 | 
			
		||||
  id: 3
 | 
			
		||||
  credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
 | 
			
		||||
-
 | 
			
		||||
  id: 4
 | 
			
		||||
  credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
 | 
			
		||||
@@ -0,0 +1,21 @@
 | 
			
		||||
-
 | 
			
		||||
  id: 1
 | 
			
		||||
  name: "u2fkey-correctly-migrated"
 | 
			
		||||
  user_id: 1
 | 
			
		||||
  raw: 0x05040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf240efe2e213b889daf3fc88e3952e8dd6b4cfd82f1a1212e2ab4b19389455ecf3e67f0aeafc91b9c0d413c9d6215a45177c1d5076358aa6ee20e1b30e3d7467cae2308202bd308201a5a00302010202041e8f8734300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a306e310b300906035504061302534531123010060355040a0c0959756269636f20414231223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3127302506035504030c1e59756269636f205532462045452053657269616c203531323732323734303059301306072a8648ce3d020106082a8648ce3d03010703420004a879f82338ed1494bac0704bcc7fc663d1b271715976243101c7605115d7c1529e281c1c67322d384b5cd55dd3e9818d5fd85c22af326e0c64fc20afe33f2366a36c306a302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e373013060b2b0601040182e51c0201010404030204303021060b2b0601040182e51c010104041204102fc0579f811347eab116bb5a8db9202a300c0603551d130101ff04023000300d06092a864886f70d01010b050003820101008693ff62df0d5779d4748d7fc8d10227318a8e580e6a3a57c108e94e03c38568b366894fce5624be4a3efd7f34118b3d993743f792a1989160c8fc9ae0b04e3df9ee15e3e88c04fc82a8dcbf5818e108dcc2968577ae79ff662b94734e3dec4597305d73e6e55ee2beb9cd9678ca0935e533eb638f8e26fabb817cda441fbe9831832ae5f6e2ad992f9ebbdb4c62238b8f8d7ab481d6d3263bcdbf9e4a57550370988ad5813440fa032cadb6723cadd8f8d7ba809f75b43cffa0a5b9add14232ef9d9e14812638233c4ca4a873b9f8ac98e32ba19167606e15909fcddb4a2dffbdae4620249f9a6646ac81e4832d1119febfaa731a882da25a77827d46d190173046022100b579338a44c236d3f214b2e150011a08cf251193ecfae2244edb0a5794e9b301022100fab468862c47d98204d437cf2be8c54a5a4ecd1ebb1c61a6c23da7b9c75f6841
 | 
			
		||||
  counter: 0
 | 
			
		||||
- id: 2
 | 
			
		||||
  name: "u2fkey-incorrectly-migrated"
 | 
			
		||||
  user_id: 1
 | 
			
		||||
  raw: 0x05040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf240efe2e213b889daf3fc88e3952e8dd6b4cfd82f1a1212e2ab4b19389455ecf3e67f0aeafc91b9c0d413c9d6215a45177c1d5076358aa6ee20e1b30e3d7467cae2308202bd308201a5a00302010202041e8f8734300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a306e310b300906035504061302534531123010060355040a0c0959756269636f20414231223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3127302506035504030c1e59756269636f205532462045452053657269616c203531323732323734303059301306072a8648ce3d020106082a8648ce3d03010703420004a879f82338ed1494bac0704bcc7fc663d1b271715976243101c7605115d7c1529e281c1c67322d384b5cd55dd3e9818d5fd85c22af326e0c64fc20afe33f2366a36c306a302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e373013060b2b0601040182e51c0201010404030204303021060b2b0601040182e51c010104041204102fc0579f811347eab116bb5a8db9202a300c0603551d130101ff04023000300d06092a864886f70d01010b050003820101008693ff62df0d5779d4748d7fc8d10227318a8e580e6a3a57c108e94e03c38568b366894fce5624be4a3efd7f34118b3d993743f792a1989160c8fc9ae0b04e3df9ee15e3e88c04fc82a8dcbf5818e108dcc2968577ae79ff662b94734e3dec4597305d73e6e55ee2beb9cd9678ca0935e533eb638f8e26fabb817cda441fbe9831832ae5f6e2ad992f9ebbdb4c62238b8f8d7ab481d6d3263bcdbf9e4a57550370988ad5813440fa032cadb6723cadd8f8d7ba809f75b43cffa0a5b9add14232ef9d9e14812638233c4ca4a873b9f8ac98e32ba19167606e15909fcddb4a2dffbdae4620249f9a6646ac81e4832d1119febfaa731a882da25a77827d46d190173046022100b579338a44c236d3f214b2e150011a08cf251193ecfae2244edb0a5794e9b301022100fab468862c47d98204d437cf2be8c54a5a4ecd1ebb1c61a6c23da7b9c75f6841
 | 
			
		||||
  counter: 0
 | 
			
		||||
- id: 3
 | 
			
		||||
  name: "u2fkey-deleted"
 | 
			
		||||
  user_id: 1
 | 
			
		||||
  raw: 0x05040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf240efe2e213b889daf3fc88e3952e8dd6b4cfd82f1a1212e2ab4b19389455ecf3e67f0aeafc91b9c0d413c9d6215a45177c1d5076358aa6ee20e1b30e3d7467cae2308202bd308201a5a00302010202041e8f8734300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a306e310b300906035504061302534531123010060355040a0c0959756269636f20414231223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3127302506035504030c1e59756269636f205532462045452053657269616c203531323732323734303059301306072a8648ce3d020106082a8648ce3d03010703420004a879f82338ed1494bac0704bcc7fc663d1b271715976243101c7605115d7c1529e281c1c67322d384b5cd55dd3e9818d5fd85c22af326e0c64fc20afe33f2366a36c306a302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e373013060b2b0601040182e51c0201010404030204303021060b2b0601040182e51c010104041204102fc0579f811347eab116bb5a8db9202a300c0603551d130101ff04023000300d06092a864886f70d01010b050003820101008693ff62df0d5779d4748d7fc8d10227318a8e580e6a3a57c108e94e03c38568b366894fce5624be4a3efd7f34118b3d993743f792a1989160c8fc9ae0b04e3df9ee15e3e88c04fc82a8dcbf5818e108dcc2968577ae79ff662b94734e3dec4597305d73e6e55ee2beb9cd9678ca0935e533eb638f8e26fabb817cda441fbe9831832ae5f6e2ad992f9ebbdb4c62238b8f8d7ab481d6d3263bcdbf9e4a57550370988ad5813440fa032cadb6723cadd8f8d7ba809f75b43cffa0a5b9add14232ef9d9e14812638233c4ca4a873b9f8ac98e32ba19167606e15909fcddb4a2dffbdae4620249f9a6646ac81e4832d1119febfaa731a882da25a77827d46d190173046022100b579338a44c236d3f214b2e150011a08cf251193ecfae2244edb0a5794e9b301022100fab468862c47d98204d437cf2be8c54a5a4ecd1ebb1c61a6c23da7b9c75f6841
 | 
			
		||||
  counter: 0
 | 
			
		||||
- id: 4
 | 
			
		||||
  name: "u2fkey-wrong-user-id"
 | 
			
		||||
  user_id: 2
 | 
			
		||||
  raw: 0x05040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf240efe2e213b889daf3fc88e3952e8dd6b4cfd82f1a1212e2ab4b19389455ecf3e67f0aeafc91b9c0d413c9d6215a45177c1d5076358aa6ee20e1b30e3d7467cae2308202bd308201a5a00302010202041e8f8734300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a306e310b300906035504061302534531123010060355040a0c0959756269636f20414231223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3127302506035504030c1e59756269636f205532462045452053657269616c203531323732323734303059301306072a8648ce3d020106082a8648ce3d03010703420004a879f82338ed1494bac0704bcc7fc663d1b271715976243101c7605115d7c1529e281c1c67322d384b5cd55dd3e9818d5fd85c22af326e0c64fc20afe33f2366a36c306a302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e373013060b2b0601040182e51c0201010404030204303021060b2b0601040182e51c010104041204102fc0579f811347eab116bb5a8db9202a300c0603551d130101ff04023000300d06092a864886f70d01010b050003820101008693ff62df0d5779d4748d7fc8d10227318a8e580e6a3a57c108e94e03c38568b366894fce5624be4a3efd7f34118b3d993743f792a1989160c8fc9ae0b04e3df9ee15e3e88c04fc82a8dcbf5818e108dcc2968577ae79ff662b94734e3dec4597305d73e6e55ee2beb9cd9678ca0935e533eb638f8e26fabb817cda441fbe9831832ae5f6e2ad992f9ebbdb4c62238b8f8d7ab481d6d3263bcdbf9e4a57550370988ad5813440fa032cadb6723cadd8f8d7ba809f75b43cffa0a5b9add14232ef9d9e14812638233c4ca4a873b9f8ac98e32ba19167606e15909fcddb4a2dffbdae4620249f9a6646ac81e4832d1119febfaa731a882da25a77827d46d190173046022100b579338a44c236d3f214b2e150011a08cf251193ecfae2244edb0a5794e9b301022100fab468862c47d98204d437cf2be8c54a5a4ecd1ebb1c61a6c23da7b9c75f6841
 | 
			
		||||
  counter: 0
 | 
			
		||||
@@ -0,0 +1,30 @@
 | 
			
		||||
-
 | 
			
		||||
  id: 1
 | 
			
		||||
  lower_name: "u2fkey-correctly-migrated"
 | 
			
		||||
  name: "u2fkey-correctly-migrated"
 | 
			
		||||
  user_id: 1
 | 
			
		||||
  credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
 | 
			
		||||
  public_key: 0x040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf2
 | 
			
		||||
  attestation_type: 'fido-u2f'
 | 
			
		||||
  sign_count: 1
 | 
			
		||||
  clone_warning: false
 | 
			
		||||
-
 | 
			
		||||
  id: 2
 | 
			
		||||
  lower_name: "u2fkey-incorrectly-migrated"
 | 
			
		||||
  name: "u2fkey-incorrectly-migrated"
 | 
			
		||||
  user_id: 1
 | 
			
		||||
  credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8A"
 | 
			
		||||
  public_key: 0x040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf2
 | 
			
		||||
  attestation_type: 'fido-u2f'
 | 
			
		||||
  sign_count: 1
 | 
			
		||||
  clone_warning: false
 | 
			
		||||
-
 | 
			
		||||
  id: 4
 | 
			
		||||
  lower_name: "u2fkey-wrong-user-id"
 | 
			
		||||
  name: "u2fkey-wrong-user-id"
 | 
			
		||||
  user_id: 1
 | 
			
		||||
  credential_id: "THIS SHOULD CHANGE"
 | 
			
		||||
  public_key: 0x040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf2
 | 
			
		||||
  attestation_type: 'fido-u2f'
 | 
			
		||||
  sign_count: 1
 | 
			
		||||
  clone_warning: false
 | 
			
		||||
@@ -61,7 +61,6 @@ type Version struct {
 | 
			
		||||
// update minDBVersion accordingly
 | 
			
		||||
var migrations = []Migration{
 | 
			
		||||
	// Gitea 1.5.0 ends at v69
 | 
			
		||||
 | 
			
		||||
	// v70 -> v71
 | 
			
		||||
	NewMigration("add issue_dependencies", addIssueDependencies),
 | 
			
		||||
	// v71 -> v72
 | 
			
		||||
@@ -367,9 +366,13 @@ var migrations = []Migration{
 | 
			
		||||
	// v206 -> v207
 | 
			
		||||
	NewMigration("Add authorize column to team_unit table", addAuthorizeColForTeamUnit),
 | 
			
		||||
	// v207 -> v208
 | 
			
		||||
	NewMigration("Add webauthn table and migrate u2f data to webauthn", addWebAuthnCred),
 | 
			
		||||
	NewMigration("Add webauthn table and migrate u2f data to webauthn - NO-OPED", addWebAuthnCred),
 | 
			
		||||
	// v208 -> v209
 | 
			
		||||
	NewMigration("Use base32.HexEncoding instead of base64 encoding for cred ID as it is case insensitive", useBase32HexForCredIDInWebAuthnCredential),
 | 
			
		||||
	NewMigration("Use base32.HexEncoding instead of base64 encoding for cred ID as it is case insensitive - NO-OPED", useBase32HexForCredIDInWebAuthnCredential),
 | 
			
		||||
	// v209 -> v210
 | 
			
		||||
	NewMigration("Increase WebAuthentication CredentialID size to 410 - NO-OPED", increaseCredentialIDTo410),
 | 
			
		||||
	// v210 -> v211
 | 
			
		||||
	NewMigration("v208 was completely broken - remigrate", remigrateU2FCredentials),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetCurrentDBVersion returns the current db version
 | 
			
		||||
@@ -450,9 +453,12 @@ Please try upgrading to a lower version first (suggested v1.6.4), then upgrade t
 | 
			
		||||
 | 
			
		||||
	// Downgrading Gitea's database version not supported
 | 
			
		||||
	if int(v-minDBVersion) > len(migrations) {
 | 
			
		||||
		msg := fmt.Sprintf("Downgrading database version from '%d' to '%d' is not supported and may result in loss of data integrity.\nIf you really know what you're doing, execute `UPDATE version SET version=%d WHERE id=1;`\n",
 | 
			
		||||
			v, minDBVersion+len(migrations), minDBVersion+len(migrations))
 | 
			
		||||
		fmt.Fprint(os.Stderr, msg)
 | 
			
		||||
		msg := fmt.Sprintf("Your database (migration version: %d) is for a newer Gitea, you can not use the newer database for this old Gitea release (%d).", v, minDBVersion+len(migrations))
 | 
			
		||||
		msg += "\nGitea will exit to keep your database safe and unchanged. Please use the correct Gitea release, do not change the migration version manually (incorrect manual operation may lose data)."
 | 
			
		||||
		if !setting.IsProd {
 | 
			
		||||
			msg += fmt.Sprintf("\nIf you are in development and really know what you're doing, you can force changing the migration version by executing: UPDATE version SET version=%d WHERE id=1;", minDBVersion+len(migrations))
 | 
			
		||||
		}
 | 
			
		||||
		_, _ = fmt.Fprintln(os.Stderr, msg)
 | 
			
		||||
		log.Fatal(msg)
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,87 +5,11 @@
 | 
			
		||||
package migrations
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/elliptic"
 | 
			
		||||
	"encoding/base64"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/timeutil"
 | 
			
		||||
 | 
			
		||||
	"github.com/tstranex/u2f"
 | 
			
		||||
	"xorm.io/xorm"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func addWebAuthnCred(x *xorm.Engine) error {
 | 
			
		||||
 | 
			
		||||
	// Create webauthnCredential table
 | 
			
		||||
	type webauthnCredential struct {
 | 
			
		||||
		ID              int64 `xorm:"pk autoincr"`
 | 
			
		||||
		Name            string
 | 
			
		||||
		LowerName       string `xorm:"unique(s)"`
 | 
			
		||||
		UserID          int64  `xorm:"INDEX unique(s)"`
 | 
			
		||||
		CredentialID    string `xorm:"INDEX"`
 | 
			
		||||
		PublicKey       []byte
 | 
			
		||||
		AttestationType string
 | 
			
		||||
		AAGUID          []byte
 | 
			
		||||
		SignCount       uint32 `xorm:"BIGINT"`
 | 
			
		||||
		CloneWarning    bool
 | 
			
		||||
		CreatedUnix     timeutil.TimeStamp `xorm:"INDEX created"`
 | 
			
		||||
		UpdatedUnix     timeutil.TimeStamp `xorm:"INDEX updated"`
 | 
			
		||||
	}
 | 
			
		||||
	if err := x.Sync2(&webauthnCredential{}); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Now migrate the old u2f registrations to the new format
 | 
			
		||||
	type u2fRegistration struct {
 | 
			
		||||
		ID          int64 `xorm:"pk autoincr"`
 | 
			
		||||
		Name        string
 | 
			
		||||
		UserID      int64 `xorm:"INDEX"`
 | 
			
		||||
		Raw         []byte
 | 
			
		||||
		Counter     uint32             `xorm:"BIGINT"`
 | 
			
		||||
		CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
 | 
			
		||||
		UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var start int
 | 
			
		||||
	regs := make([]*u2fRegistration, 0, 50)
 | 
			
		||||
	for {
 | 
			
		||||
		err := x.OrderBy("id").Limit(50, start).Find(®s)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, reg := range regs {
 | 
			
		||||
			parsed := new(u2f.Registration)
 | 
			
		||||
			err = parsed.UnmarshalBinary(reg.Raw)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			c := &webauthnCredential{
 | 
			
		||||
				ID:              reg.ID,
 | 
			
		||||
				Name:            reg.Name,
 | 
			
		||||
				LowerName:       strings.ToLower(reg.Name),
 | 
			
		||||
				UserID:          reg.UserID,
 | 
			
		||||
				CredentialID:    base64.RawStdEncoding.EncodeToString(parsed.KeyHandle),
 | 
			
		||||
				PublicKey:       elliptic.Marshal(elliptic.P256(), parsed.PubKey.X, parsed.PubKey.Y),
 | 
			
		||||
				AttestationType: "fido-u2f",
 | 
			
		||||
				AAGUID:          []byte{},
 | 
			
		||||
				SignCount:       reg.Counter,
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			_, err := x.Insert(c)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(regs) < 50 {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		start += 50
 | 
			
		||||
		regs = regs[:0]
 | 
			
		||||
	}
 | 
			
		||||
	// NO-OP Don't migrate here - let v210 do this.
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,47 +5,10 @@
 | 
			
		||||
package migrations
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/base32"
 | 
			
		||||
	"encoding/base64"
 | 
			
		||||
 | 
			
		||||
	"xorm.io/xorm"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func useBase32HexForCredIDInWebAuthnCredential(x *xorm.Engine) error {
 | 
			
		||||
 | 
			
		||||
	// Create webauthnCredential table
 | 
			
		||||
	type webauthnCredential struct {
 | 
			
		||||
		ID           int64  `xorm:"pk autoincr"`
 | 
			
		||||
		CredentialID string `xorm:"INDEX"`
 | 
			
		||||
	}
 | 
			
		||||
	if err := x.Sync2(&webauthnCredential{}); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var start int
 | 
			
		||||
	regs := make([]*webauthnCredential, 0, 50)
 | 
			
		||||
	for {
 | 
			
		||||
		err := x.OrderBy("id").Limit(50, start).Find(®s)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, reg := range regs {
 | 
			
		||||
			credID, _ := base64.RawStdEncoding.DecodeString(reg.CredentialID)
 | 
			
		||||
			reg.CredentialID = base32.HexEncoding.EncodeToString(credID)
 | 
			
		||||
 | 
			
		||||
			_, err := x.Update(reg)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(regs) < 50 {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		start += 50
 | 
			
		||||
		regs = regs[:0]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// noop
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										17
									
								
								models/migrations/v209.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								models/migrations/v209.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
// Copyright 2022 The Gitea Authors. All rights reserved.
 | 
			
		||||
// Use of this source code is governed by a MIT-style
 | 
			
		||||
// license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
package migrations
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"xorm.io/xorm"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func increaseCredentialIDTo410(x *xorm.Engine) error {
 | 
			
		||||
	// no-op
 | 
			
		||||
	// V208 is badly broken
 | 
			
		||||
	// So now we have to no-op again.
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										184
									
								
								models/migrations/v210.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										184
									
								
								models/migrations/v210.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,184 @@
 | 
			
		||||
// Copyright 2022 The Gitea Authors. All rights reserved.
 | 
			
		||||
// Use of this source code is governed by a MIT-style
 | 
			
		||||
// license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
package migrations
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/elliptic"
 | 
			
		||||
	"encoding/base32"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/timeutil"
 | 
			
		||||
 | 
			
		||||
	"github.com/tstranex/u2f"
 | 
			
		||||
	"xorm.io/xorm"
 | 
			
		||||
	"xorm.io/xorm/schemas"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// v208 migration was completely broken
 | 
			
		||||
func remigrateU2FCredentials(x *xorm.Engine) error {
 | 
			
		||||
	// Create webauthnCredential table
 | 
			
		||||
	type webauthnCredential struct {
 | 
			
		||||
		ID              int64 `xorm:"pk autoincr"`
 | 
			
		||||
		Name            string
 | 
			
		||||
		LowerName       string `xorm:"unique(s)"`
 | 
			
		||||
		UserID          int64  `xorm:"INDEX unique(s)"`
 | 
			
		||||
		CredentialID    string `xorm:"INDEX VARCHAR(410)"` // CredentalID in U2F is at most 255bytes / 5 * 8 = 408 - add a few extra characters for safety
 | 
			
		||||
		PublicKey       []byte
 | 
			
		||||
		AttestationType string
 | 
			
		||||
		AAGUID          []byte
 | 
			
		||||
		SignCount       uint32 `xorm:"BIGINT"`
 | 
			
		||||
		CloneWarning    bool
 | 
			
		||||
		CreatedUnix     timeutil.TimeStamp `xorm:"INDEX created"`
 | 
			
		||||
		UpdatedUnix     timeutil.TimeStamp `xorm:"INDEX updated"`
 | 
			
		||||
	}
 | 
			
		||||
	if err := x.Sync2(&webauthnCredential{}); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch x.Dialect().URI().DBType {
 | 
			
		||||
	case schemas.MYSQL:
 | 
			
		||||
		_, err := x.Exec("ALTER TABLE webauthn_credential MODIFY COLUMN credential_id VARCHAR(410)")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	case schemas.ORACLE:
 | 
			
		||||
		_, err := x.Exec("ALTER TABLE webauthn_credential MODIFY credential_id VARCHAR(410)")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	case schemas.MSSQL:
 | 
			
		||||
		// This column has an index on it. I could write all of the code to attempt to change the index OR
 | 
			
		||||
		// I could just use recreate table.
 | 
			
		||||
		sess := x.NewSession()
 | 
			
		||||
		if err := sess.Begin(); err != nil {
 | 
			
		||||
			_ = sess.Close()
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := recreateTable(sess, new(webauthnCredential)); err != nil {
 | 
			
		||||
			_ = sess.Close()
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if err := sess.Commit(); err != nil {
 | 
			
		||||
			_ = sess.Close()
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if err := sess.Close(); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	case schemas.POSTGRES:
 | 
			
		||||
		_, err := x.Exec("ALTER TABLE webauthn_credential ALTER COLUMN credential_id TYPE VARCHAR(410)")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		// SQLite doesn't support ALTER COLUMN, and it already makes String _TEXT_ by default so no migration needed
 | 
			
		||||
		// nor is there any need to re-migrate
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	exist, err := x.IsTableExist("u2f_registration")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if !exist {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Now migrate the old u2f registrations to the new format
 | 
			
		||||
	type u2fRegistration struct {
 | 
			
		||||
		ID          int64 `xorm:"pk autoincr"`
 | 
			
		||||
		Name        string
 | 
			
		||||
		UserID      int64 `xorm:"INDEX"`
 | 
			
		||||
		Raw         []byte
 | 
			
		||||
		Counter     uint32             `xorm:"BIGINT"`
 | 
			
		||||
		CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
 | 
			
		||||
		UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var start int
 | 
			
		||||
	regs := make([]*u2fRegistration, 0, 50)
 | 
			
		||||
	for {
 | 
			
		||||
		err := x.OrderBy("id").Limit(50, start).Find(®s)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		err = func() error {
 | 
			
		||||
			sess := x.NewSession()
 | 
			
		||||
			defer sess.Close()
 | 
			
		||||
			if err := sess.Begin(); err != nil {
 | 
			
		||||
				return fmt.Errorf("unable to allow start session. Error: %w", err)
 | 
			
		||||
			}
 | 
			
		||||
			if x.Dialect().URI().DBType == schemas.MSSQL {
 | 
			
		||||
				if _, err := sess.Exec("SET IDENTITY_INSERT `webauthn_credential` ON"); err != nil {
 | 
			
		||||
					return fmt.Errorf("unable to allow identity insert on webauthn_credential. Error: %w", err)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			for _, reg := range regs {
 | 
			
		||||
				parsed := new(u2f.Registration)
 | 
			
		||||
				err = parsed.UnmarshalBinary(reg.Raw)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				remigrated := &webauthnCredential{
 | 
			
		||||
					ID:              reg.ID,
 | 
			
		||||
					Name:            reg.Name,
 | 
			
		||||
					LowerName:       strings.ToLower(reg.Name),
 | 
			
		||||
					UserID:          reg.UserID,
 | 
			
		||||
					CredentialID:    base32.HexEncoding.EncodeToString(parsed.KeyHandle),
 | 
			
		||||
					PublicKey:       elliptic.Marshal(elliptic.P256(), parsed.PubKey.X, parsed.PubKey.Y),
 | 
			
		||||
					AttestationType: "fido-u2f",
 | 
			
		||||
					AAGUID:          []byte{},
 | 
			
		||||
					SignCount:       reg.Counter,
 | 
			
		||||
					UpdatedUnix:     reg.UpdatedUnix,
 | 
			
		||||
					CreatedUnix:     reg.CreatedUnix,
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				has, err := sess.ID(reg.ID).Get(new(webauthnCredential))
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return fmt.Errorf("unable to get webauthn_credential[%d]. Error: %w", reg.ID, err)
 | 
			
		||||
				}
 | 
			
		||||
				if !has {
 | 
			
		||||
					has, err := sess.Where("`lower_name`=?", remigrated.LowerName).And("`user_id`=?", remigrated.UserID).Exist(new(webauthnCredential))
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						return fmt.Errorf("unable to check webauthn_credential[lower_name: %s, user_id:%v]. Error: %w", remigrated.LowerName, remigrated.UserID, err)
 | 
			
		||||
					}
 | 
			
		||||
					if !has {
 | 
			
		||||
						_, err = sess.Insert(remigrated)
 | 
			
		||||
						if err != nil {
 | 
			
		||||
							return fmt.Errorf("unable to (re)insert webauthn_credential[%d]. Error: %w", reg.ID, err)
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
						continue
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				_, err = sess.ID(remigrated.ID).AllCols().Update(remigrated)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return fmt.Errorf("unable to update webauthn_credential[%d]. Error: %w", reg.ID, err)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			return sess.Commit()
 | 
			
		||||
		}()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(regs) < 50 {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		start += 50
 | 
			
		||||
		regs = regs[:0]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if x.Dialect().URI().DBType == schemas.POSTGRES {
 | 
			
		||||
		if _, err := x.Exec("SELECT setval('webauthn_credential_id_seq', COALESCE((SELECT MAX(id)+1 FROM `webauthn_credential`), 1), false)"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										75
									
								
								models/migrations/v210_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								models/migrations/v210_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,75 @@
 | 
			
		||||
// Copyright 2021 The Gitea Authors. All rights reserved.
 | 
			
		||||
// Use of this source code is governed by a MIT-style
 | 
			
		||||
// license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
package migrations
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/timeutil"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"xorm.io/xorm/schemas"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func Test_remigrateU2FCredentials(t *testing.T) {
 | 
			
		||||
	// Create webauthnCredential table
 | 
			
		||||
	type WebauthnCredential struct {
 | 
			
		||||
		ID              int64 `xorm:"pk autoincr"`
 | 
			
		||||
		Name            string
 | 
			
		||||
		LowerName       string `xorm:"unique(s)"`
 | 
			
		||||
		UserID          int64  `xorm:"INDEX unique(s)"`
 | 
			
		||||
		CredentialID    string `xorm:"INDEX VARCHAR(410)"` // CredentalID in U2F is at most 255bytes / 5 * 8 = 408 - add a few extra characters for safety
 | 
			
		||||
		PublicKey       []byte
 | 
			
		||||
		AttestationType string
 | 
			
		||||
		SignCount       uint32 `xorm:"BIGINT"`
 | 
			
		||||
		CloneWarning    bool
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Now migrate the old u2f registrations to the new format
 | 
			
		||||
	type U2fRegistration struct {
 | 
			
		||||
		ID          int64 `xorm:"pk autoincr"`
 | 
			
		||||
		Name        string
 | 
			
		||||
		UserID      int64 `xorm:"INDEX"`
 | 
			
		||||
		Raw         []byte
 | 
			
		||||
		Counter     uint32             `xorm:"BIGINT"`
 | 
			
		||||
		CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
 | 
			
		||||
		UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	type ExpectedWebauthnCredential struct {
 | 
			
		||||
		ID           int64  `xorm:"pk autoincr"`
 | 
			
		||||
		CredentialID string `xorm:"INDEX VARCHAR(410)"` // CredentalID in U2F is at most 255bytes / 5 * 8 = 408 - add a few extra characters for safety
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Prepare and load the testing database
 | 
			
		||||
	x, deferable := prepareTestEnv(t, 0, new(WebauthnCredential), new(U2fRegistration), new(ExpectedWebauthnCredential))
 | 
			
		||||
	if x == nil || t.Failed() {
 | 
			
		||||
		defer deferable()
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	defer deferable()
 | 
			
		||||
 | 
			
		||||
	if x.Dialect().URI().DBType == schemas.SQLITE {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Run the migration
 | 
			
		||||
	if err := remigrateU2FCredentials(x); err != nil {
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	expected := []ExpectedWebauthnCredential{}
 | 
			
		||||
	if err := x.Table("expected_webauthn_credential").Asc("id").Find(&expected); !assert.NoError(t, err) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	got := []ExpectedWebauthnCredential{}
 | 
			
		||||
	if err := x.Table("webauthn_credential").Select("id, credential_id").Asc("id").Find(&got); !assert.NoError(t, err) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	assert.EqualValues(t, expected, got)
 | 
			
		||||
}
 | 
			
		||||
@@ -498,14 +498,15 @@ func (n *Notification) APIURL() string {
 | 
			
		||||
type NotificationList []*Notification
 | 
			
		||||
 | 
			
		||||
// LoadAttributes load Repo Issue User and Comment if not loaded
 | 
			
		||||
func (nl NotificationList) LoadAttributes() (err error) {
 | 
			
		||||
func (nl NotificationList) LoadAttributes() error {
 | 
			
		||||
	var err error
 | 
			
		||||
	for i := 0; i < len(nl); i++ {
 | 
			
		||||
		err = nl[i].LoadAttributes()
 | 
			
		||||
		if err != nil && !IsErrCommentNotExist(err) {
 | 
			
		||||
			return
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (nl NotificationList) getPendingRepoIDs() []int64 {
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@ import (
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
 | 
			
		||||
	"xorm.io/builder"
 | 
			
		||||
)
 | 
			
		||||
@@ -49,22 +50,67 @@ func init() {
 | 
			
		||||
	db.RegisterModel(new(TeamUnit))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SearchTeamOptions holds the search options
 | 
			
		||||
type SearchTeamOptions struct {
 | 
			
		||||
// SearchOrgTeamOptions holds the search options
 | 
			
		||||
type SearchOrgTeamOptions struct {
 | 
			
		||||
	db.ListOptions
 | 
			
		||||
	UserID      int64
 | 
			
		||||
	Keyword     string
 | 
			
		||||
	OrgID       int64
 | 
			
		||||
	IncludeDesc bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetUserTeamOptions holds the search options.
 | 
			
		||||
type GetUserTeamOptions struct {
 | 
			
		||||
	db.ListOptions
 | 
			
		||||
	UserID int64
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SearchMembersOptions holds the search options
 | 
			
		||||
type SearchMembersOptions struct {
 | 
			
		||||
	db.ListOptions
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SearchTeam search for teams. Caller is responsible to check permissions.
 | 
			
		||||
func SearchTeam(opts *SearchTeamOptions) ([]*Team, int64, error) {
 | 
			
		||||
// GetUserTeams search for org teams. Caller is responsible to check permissions.
 | 
			
		||||
func GetUserTeams(opts *GetUserTeamOptions) ([]*Team, int64, error) {
 | 
			
		||||
	if opts.Page <= 0 {
 | 
			
		||||
		opts.Page = 1
 | 
			
		||||
	}
 | 
			
		||||
	if opts.PageSize == 0 {
 | 
			
		||||
		// Default limit
 | 
			
		||||
		opts.PageSize = 10
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sess := db.GetEngine(db.DefaultContext)
 | 
			
		||||
 | 
			
		||||
	sess = sess.Join("INNER", "team_user", "team_user.team_id = team.id").
 | 
			
		||||
		And("team_user.uid=?", opts.UserID)
 | 
			
		||||
 | 
			
		||||
	count, err := sess.
 | 
			
		||||
		Count(new(Team))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, 0, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if opts.PageSize == -1 {
 | 
			
		||||
		opts.PageSize = int(count)
 | 
			
		||||
	} else {
 | 
			
		||||
		sess = sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sess = sess.Join("INNER", "team_user", "team_user.team_id = team.id").
 | 
			
		||||
		And("team_user.uid=?", opts.UserID)
 | 
			
		||||
 | 
			
		||||
	teams := make([]*Team, 0, opts.PageSize)
 | 
			
		||||
	if err = sess.
 | 
			
		||||
		OrderBy("lower_name").
 | 
			
		||||
		Find(&teams); err != nil {
 | 
			
		||||
		return nil, 0, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return teams, count, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SearchOrgTeams search for org teams. Caller is responsible to check permissions.
 | 
			
		||||
func SearchOrgTeams(opts *SearchOrgTeamOptions) ([]*Team, int64, error) {
 | 
			
		||||
	if opts.Page <= 0 {
 | 
			
		||||
		opts.Page = 1
 | 
			
		||||
	}
 | 
			
		||||
@@ -196,7 +242,7 @@ func (t *Team) getRepositories(e db.Engine) error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetRepositories returns paginated repositories in team of organization.
 | 
			
		||||
func (t *Team) GetRepositories(opts *SearchTeamOptions) error {
 | 
			
		||||
func (t *Team) GetRepositories(opts *SearchOrgTeamOptions) error {
 | 
			
		||||
	if opts.Page == 0 {
 | 
			
		||||
		return t.getRepositories(db.GetEngine(db.DefaultContext))
 | 
			
		||||
	}
 | 
			
		||||
@@ -716,7 +762,7 @@ func UpdateTeam(t *Team, authChanged, includeAllChanged bool) (err error) {
 | 
			
		||||
// DeleteTeam deletes given team.
 | 
			
		||||
// It's caller's responsibility to assign organization ID.
 | 
			
		||||
func DeleteTeam(t *Team) error {
 | 
			
		||||
	if err := t.GetRepositories(&SearchTeamOptions{}); err != nil {
 | 
			
		||||
	if err := t.GetRepositories(&SearchOrgTeamOptions{}); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -731,9 +777,46 @@ func DeleteTeam(t *Team) error {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// update branch protections
 | 
			
		||||
	{
 | 
			
		||||
		protections := make([]*ProtectedBranch, 0, 10)
 | 
			
		||||
		err := sess.In("repo_id",
 | 
			
		||||
			builder.Select("id").From("repository").Where(builder.Eq{"owner_id": t.OrgID})).
 | 
			
		||||
			Find(&protections)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("findProtectedBranches: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		for _, p := range protections {
 | 
			
		||||
			var matched1, matched2, matched3 bool
 | 
			
		||||
			if len(p.WhitelistTeamIDs) != 0 {
 | 
			
		||||
				p.WhitelistTeamIDs, matched1 = util.RemoveIDFromList(
 | 
			
		||||
					p.WhitelistTeamIDs, t.ID)
 | 
			
		||||
			}
 | 
			
		||||
			if len(p.ApprovalsWhitelistTeamIDs) != 0 {
 | 
			
		||||
				p.ApprovalsWhitelistTeamIDs, matched2 = util.RemoveIDFromList(
 | 
			
		||||
					p.ApprovalsWhitelistTeamIDs, t.ID)
 | 
			
		||||
			}
 | 
			
		||||
			if len(p.MergeWhitelistTeamIDs) != 0 {
 | 
			
		||||
				p.MergeWhitelistTeamIDs, matched3 = util.RemoveIDFromList(
 | 
			
		||||
					p.MergeWhitelistTeamIDs, t.ID)
 | 
			
		||||
			}
 | 
			
		||||
			if matched1 || matched2 || matched3 {
 | 
			
		||||
				if _, err = sess.ID(p.ID).Cols(
 | 
			
		||||
					"whitelist_team_i_ds",
 | 
			
		||||
					"merge_whitelist_team_i_ds",
 | 
			
		||||
					"approvals_whitelist_team_i_ds",
 | 
			
		||||
				).Update(p); err != nil {
 | 
			
		||||
					return fmt.Errorf("updateProtectedBranches: %v", err)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !t.IncludesAllRepositories {
 | 
			
		||||
		if err := t.removeAllRepositories(ctx); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Delete team-user.
 | 
			
		||||
	if _, err := sess.
 | 
			
		||||
@@ -857,11 +940,6 @@ func AddTeamMember(team *Team, userID int64) error {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Get team and its repositories.
 | 
			
		||||
	if err := team.GetRepositories(&SearchTeamOptions{}); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx, committer, err := db.TxContext()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
@@ -883,15 +961,49 @@ func AddTeamMember(team *Team, userID int64) error {
 | 
			
		||||
	team.NumMembers++
 | 
			
		||||
 | 
			
		||||
	// Give access to team repositories.
 | 
			
		||||
	for _, repo := range team.Repos {
 | 
			
		||||
		if err := recalculateUserAccess(ctx, repo, userID); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
	// update exist access if mode become bigger
 | 
			
		||||
	subQuery := builder.Select("repo_id").From("team_repo").
 | 
			
		||||
		Where(builder.Eq{"team_id": team.ID})
 | 
			
		||||
 | 
			
		||||
	if _, err := sess.Where("user_id=?", userID).
 | 
			
		||||
		In("repo_id", subQuery).
 | 
			
		||||
		And("mode < ?", team.AccessMode).
 | 
			
		||||
		SetExpr("mode", team.AccessMode).
 | 
			
		||||
		Update(new(Access)); err != nil {
 | 
			
		||||
		return fmt.Errorf("update user accesses: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// for not exist access
 | 
			
		||||
	var repoIDs []int64
 | 
			
		||||
	accessSubQuery := builder.Select("repo_id").From("access").Where(builder.Eq{"user_id": userID})
 | 
			
		||||
	if err := sess.SQL(subQuery.And(builder.NotIn("repo_id", accessSubQuery))).Find(&repoIDs); err != nil {
 | 
			
		||||
		return fmt.Errorf("select id accesses: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	accesses := make([]*Access, 0, 100)
 | 
			
		||||
	for i, repoID := range repoIDs {
 | 
			
		||||
		accesses = append(accesses, &Access{RepoID: repoID, UserID: userID, Mode: team.AccessMode})
 | 
			
		||||
		if (i%100 == 0 || i == len(repoIDs)-1) && len(accesses) > 0 {
 | 
			
		||||
			if err = db.Insert(ctx, accesses); err != nil {
 | 
			
		||||
				return fmt.Errorf("insert new user accesses: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
			accesses = accesses[:0]
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// watch could be failed, so run it in a goroutine
 | 
			
		||||
	if setting.Service.AutoWatchNewRepos {
 | 
			
		||||
			if err = repo_model.WatchRepoCtx(ctx, userID, repo.ID, true); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
		// Get team and its repositories.
 | 
			
		||||
		if err := team.GetRepositories(&SearchOrgTeamOptions{}); err != nil {
 | 
			
		||||
			log.Error("getRepositories failed: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		go func(repos []*repo_model.Repository) {
 | 
			
		||||
			for _, repo := range repos {
 | 
			
		||||
				if err = repo_model.WatchRepoCtx(db.DefaultContext, userID, repo.ID, true); err != nil {
 | 
			
		||||
					log.Error("watch repo failed: %v", err)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}(team.Repos)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return committer.Commit()
 | 
			
		||||
 
 | 
			
		||||
@@ -46,7 +46,7 @@ func TestTeam_GetRepositories(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	test := func(teamID int64) {
 | 
			
		||||
		team := unittest.AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team)
 | 
			
		||||
		assert.NoError(t, team.GetRepositories(&SearchTeamOptions{}))
 | 
			
		||||
		assert.NoError(t, team.GetRepositories(&SearchOrgTeamOptions{}))
 | 
			
		||||
		assert.Len(t, team.Repos, team.NumRepos)
 | 
			
		||||
		for _, repo := range team.Repos {
 | 
			
		||||
			unittest.AssertExistsAndLoadBean(t, &TeamRepo{TeamID: teamID, RepoID: repo.ID})
 | 
			
		||||
@@ -292,7 +292,7 @@ func TestGetTeamMembers(t *testing.T) {
 | 
			
		||||
func TestGetUserTeams(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
			
		||||
	test := func(userID int64) {
 | 
			
		||||
		teams, _, err := SearchTeam(&SearchTeamOptions{UserID: userID})
 | 
			
		||||
		teams, _, err := GetUserTeams(&GetUserTeamOptions{UserID: userID})
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		for _, team := range teams {
 | 
			
		||||
			unittest.AssertExistsAndLoadBean(t, &TeamUser{TeamID: team.ID, UID: userID})
 | 
			
		||||
 
 | 
			
		||||
@@ -222,22 +222,19 @@ func (pr *PullRequest) loadProtectedBranch(ctx context.Context) (err error) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetDefaultMergeMessage returns default message used when merging pull request
 | 
			
		||||
func (pr *PullRequest) GetDefaultMergeMessage() string {
 | 
			
		||||
func (pr *PullRequest) GetDefaultMergeMessage() (string, error) {
 | 
			
		||||
	if pr.HeadRepo == nil {
 | 
			
		||||
		var err error
 | 
			
		||||
		pr.HeadRepo, err = repo_model.GetRepositoryByID(pr.HeadRepoID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Error("GetRepositoryById[%d]: %v", pr.HeadRepoID, err)
 | 
			
		||||
			return ""
 | 
			
		||||
			return "", fmt.Errorf("GetRepositoryById[%d]: %v", pr.HeadRepoID, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if err := pr.LoadIssue(); err != nil {
 | 
			
		||||
		log.Error("Cannot load issue %d for PR id %d: Error: %v", pr.IssueID, pr.ID, err)
 | 
			
		||||
		return ""
 | 
			
		||||
		return "", fmt.Errorf("Cannot load issue %d for PR id %d: Error: %v", pr.IssueID, pr.ID, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := pr.LoadBaseRepo(); err != nil {
 | 
			
		||||
		log.Error("LoadBaseRepo: %v", err)
 | 
			
		||||
		return ""
 | 
			
		||||
		return "", fmt.Errorf("LoadBaseRepo: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	issueReference := "#"
 | 
			
		||||
@@ -246,10 +243,10 @@ func (pr *PullRequest) GetDefaultMergeMessage() string {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if pr.BaseRepoID == pr.HeadRepoID {
 | 
			
		||||
		return fmt.Sprintf("Merge pull request '%s' (%s%d) from %s into %s", pr.Issue.Title, issueReference, pr.Issue.Index, pr.HeadBranch, pr.BaseBranch)
 | 
			
		||||
		return fmt.Sprintf("Merge pull request '%s' (%s%d) from %s into %s", pr.Issue.Title, issueReference, pr.Issue.Index, pr.HeadBranch, pr.BaseBranch), nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return fmt.Sprintf("Merge pull request '%s' (%s%d) from %s:%s into %s", pr.Issue.Title, issueReference, pr.Issue.Index, pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseBranch)
 | 
			
		||||
	return fmt.Sprintf("Merge pull request '%s' (%s%d) from %s:%s into %s", pr.Issue.Title, issueReference, pr.Issue.Index, pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseBranch), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReviewCount represents a count of Reviews
 | 
			
		||||
@@ -335,19 +332,17 @@ func (pr *PullRequest) getReviewedByLines(writer io.Writer) error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetDefaultSquashMessage returns default message used when squash and merging pull request
 | 
			
		||||
func (pr *PullRequest) GetDefaultSquashMessage() string {
 | 
			
		||||
func (pr *PullRequest) GetDefaultSquashMessage() (string, error) {
 | 
			
		||||
	if err := pr.LoadIssue(); err != nil {
 | 
			
		||||
		log.Error("LoadIssue: %v", err)
 | 
			
		||||
		return ""
 | 
			
		||||
		return "", fmt.Errorf("LoadIssue: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := pr.LoadBaseRepo(); err != nil {
 | 
			
		||||
		log.Error("LoadBaseRepo: %v", err)
 | 
			
		||||
		return ""
 | 
			
		||||
		return "", fmt.Errorf("LoadBaseRepo: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	if pr.BaseRepo.UnitEnabled(unit.TypeExternalTracker) {
 | 
			
		||||
		return fmt.Sprintf("%s (!%d)", pr.Issue.Title, pr.Issue.Index)
 | 
			
		||||
		return fmt.Sprintf("%s (!%d)", pr.Issue.Title, pr.Issue.Index), nil
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s (#%d)", pr.Issue.Title, pr.Issue.Index)
 | 
			
		||||
	return fmt.Sprintf("%s (#%d)", pr.Issue.Title, pr.Issue.Index), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetGitRefName returns git ref for hidden pull request branch
 | 
			
		||||
@@ -702,3 +697,14 @@ func (pr *PullRequest) GetHeadBranchHTMLURL() string {
 | 
			
		||||
	}
 | 
			
		||||
	return pr.HeadRepo.HTMLURL() + "/src/branch/" + util.PathEscapeSegments(pr.HeadBranch)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Mergeable returns if the pullrequest is mergeable.
 | 
			
		||||
func (pr *PullRequest) Mergeable() bool {
 | 
			
		||||
	// If a pull request isn't mergable if it's:
 | 
			
		||||
	// - Being conflict checked.
 | 
			
		||||
	// - Has a conflict.
 | 
			
		||||
	// - Received a error while being conflict checked.
 | 
			
		||||
	// - Is a work-in-progress pull request.
 | 
			
		||||
	return pr.Status != PullRequestStatusChecking && pr.Status != PullRequestStatusConflict &&
 | 
			
		||||
		pr.Status != PullRequestStatusError && !pr.IsWorkInProgress()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -261,11 +261,15 @@ func TestPullRequest_GetDefaultMergeMessage_InternalTracker(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
			
		||||
	pr := unittest.AssertExistsAndLoadBean(t, &PullRequest{ID: 2}).(*PullRequest)
 | 
			
		||||
 | 
			
		||||
	assert.Equal(t, "Merge pull request 'issue3' (#3) from branch2 into master", pr.GetDefaultMergeMessage())
 | 
			
		||||
	msg, err := pr.GetDefaultMergeMessage()
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, "Merge pull request 'issue3' (#3) from branch2 into master", msg)
 | 
			
		||||
 | 
			
		||||
	pr.BaseRepoID = 1
 | 
			
		||||
	pr.HeadRepoID = 2
 | 
			
		||||
	assert.Equal(t, "Merge pull request 'issue3' (#3) from user2/repo1:branch2 into master", pr.GetDefaultMergeMessage())
 | 
			
		||||
	msg, err = pr.GetDefaultMergeMessage()
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, "Merge pull request 'issue3' (#3) from user2/repo1:branch2 into master", msg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPullRequest_GetDefaultMergeMessage_ExternalTracker(t *testing.T) {
 | 
			
		||||
@@ -283,9 +287,13 @@ func TestPullRequest_GetDefaultMergeMessage_ExternalTracker(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	pr := unittest.AssertExistsAndLoadBean(t, &PullRequest{ID: 2, BaseRepo: baseRepo}).(*PullRequest)
 | 
			
		||||
 | 
			
		||||
	assert.Equal(t, "Merge pull request 'issue3' (!3) from branch2 into master", pr.GetDefaultMergeMessage())
 | 
			
		||||
	msg, err := pr.GetDefaultMergeMessage()
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, "Merge pull request 'issue3' (!3) from branch2 into master", msg)
 | 
			
		||||
 | 
			
		||||
	pr.BaseRepoID = 1
 | 
			
		||||
	pr.HeadRepoID = 2
 | 
			
		||||
	assert.Equal(t, "Merge pull request 'issue3' (!3) from user2/repo1:branch2 into master", pr.GetDefaultMergeMessage())
 | 
			
		||||
	msg, err = pr.GetDefaultMergeMessage()
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, "Merge pull request 'issue3' (!3) from user2/repo1:branch2 into master", msg)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -132,7 +132,7 @@ func CheckRepoUnitUser(repo *repo_model.Repository, user *user_model.User, unitT
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func checkRepoUnitUser(ctx context.Context, repo *repo_model.Repository, user *user_model.User, unitType unit.Type) bool {
 | 
			
		||||
	if user.IsAdmin {
 | 
			
		||||
	if user != nil && user.IsAdmin {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	perm, err := getUserRepoPermission(ctx, repo, user)
 | 
			
		||||
@@ -150,27 +150,56 @@ func getRepoAssignees(ctx context.Context, repo *repo_model.Repository) (_ []*us
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	e := db.GetEngine(ctx)
 | 
			
		||||
	accesses := make([]*Access, 0, 10)
 | 
			
		||||
	if err = e.
 | 
			
		||||
	userIDs := make([]int64, 0, 10)
 | 
			
		||||
	if err = e.Table("access").
 | 
			
		||||
		Where("repo_id = ? AND mode >= ?", repo.ID, perm.AccessModeWrite).
 | 
			
		||||
		Find(&accesses); err != nil {
 | 
			
		||||
		Select("user_id").
 | 
			
		||||
		Find(&userIDs); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Leave a seat for owner itself to append later, but if owner is an organization
 | 
			
		||||
	// and just waste 1 unit is cheaper than re-allocate memory once.
 | 
			
		||||
	users := make([]*user_model.User, 0, len(accesses)+1)
 | 
			
		||||
	if len(accesses) > 0 {
 | 
			
		||||
		userIDs := make([]int64, len(accesses))
 | 
			
		||||
		for i := 0; i < len(accesses); i++ {
 | 
			
		||||
			userIDs[i] = accesses[i].UserID
 | 
			
		||||
	additionalUserIDs := make([]int64, 0, 10)
 | 
			
		||||
	if err = e.Table("team_user").
 | 
			
		||||
		Join("INNER", "team_repo", "`team_repo`.team_id = `team_user`.team_id").
 | 
			
		||||
		Join("INNER", "team_unit", "`team_unit`.team_id = `team_user`.team_id").
 | 
			
		||||
		Where("`team_repo`.repo_id = ? AND `team_unit`.access_mode >= ?", repo.ID, perm.AccessModeWrite).
 | 
			
		||||
		Distinct("`team_user`.uid").
 | 
			
		||||
		Select("`team_user`.uid").
 | 
			
		||||
		Find(&additionalUserIDs); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	uidMap := map[int64]bool{}
 | 
			
		||||
	i := 0
 | 
			
		||||
	for _, uid := range userIDs {
 | 
			
		||||
		if uidMap[uid] {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		uidMap[uid] = true
 | 
			
		||||
		userIDs[i] = uid
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
	userIDs = userIDs[:i]
 | 
			
		||||
	userIDs = append(userIDs, additionalUserIDs...)
 | 
			
		||||
 | 
			
		||||
	for _, uid := range additionalUserIDs {
 | 
			
		||||
		if uidMap[uid] {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		userIDs[i] = uid
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
	userIDs = userIDs[:i]
 | 
			
		||||
 | 
			
		||||
	// Leave a seat for owner itself to append later, but if owner is an organization
 | 
			
		||||
	// and just waste 1 unit is cheaper than re-allocate memory once.
 | 
			
		||||
	users := make([]*user_model.User, 0, len(userIDs)+1)
 | 
			
		||||
	if len(userIDs) > 0 {
 | 
			
		||||
		if err = e.In("id", userIDs).Find(&users); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if !repo.Owner.IsOrganization() {
 | 
			
		||||
	if !repo.Owner.IsOrganization() && !uidMap[repo.OwnerID] {
 | 
			
		||||
		users = append(users, repo.Owner)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -948,7 +977,7 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error {
 | 
			
		||||
 | 
			
		||||
	// Remove attachment with no issue_id and release_id.
 | 
			
		||||
	for i := range newAttachmentPaths {
 | 
			
		||||
		admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete issue attachment", attachmentPaths[i])
 | 
			
		||||
		admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete issue attachment", newAttachmentPaths[i])
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(repo.Avatar) > 0 {
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@
 | 
			
		||||
package repo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"time"
 | 
			
		||||
@@ -115,6 +116,13 @@ func UpdateMirror(m *Mirror) error {
 | 
			
		||||
	return updateMirror(db.GetEngine(db.DefaultContext), m)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TouchMirror updates the mirror updatedUnix
 | 
			
		||||
func TouchMirror(ctx context.Context, m *Mirror) error {
 | 
			
		||||
	m.UpdatedUnix = timeutil.TimeStampNow()
 | 
			
		||||
	_, err := db.GetEngine(ctx).ID(m.ID).Cols("updated_unix").Update(m)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeleteMirrorByRepoID deletes a mirror by repoID
 | 
			
		||||
func DeleteMirrorByRepoID(repoID int64) error {
 | 
			
		||||
	_, err := db.GetEngine(db.DefaultContext).Delete(&Mirror{RepoID: repoID})
 | 
			
		||||
 
 | 
			
		||||
@@ -233,13 +233,27 @@ func teamUnitsRepoCond(id string, userID, orgID, teamID int64, units ...unit.Typ
 | 
			
		||||
		builder.Select("repo_id").From("team_repo").Where(
 | 
			
		||||
			builder.Eq{
 | 
			
		||||
				"team_id": teamID,
 | 
			
		||||
			}.And(
 | 
			
		||||
			}.And(builder.Or(
 | 
			
		||||
				// Check if the user is member of the team.
 | 
			
		||||
				builder.In(
 | 
			
		||||
					"team_id", builder.Select("team_id").From("team_user").Where(
 | 
			
		||||
						builder.Eq{
 | 
			
		||||
							"uid": userID,
 | 
			
		||||
						},
 | 
			
		||||
					),
 | 
			
		||||
				),
 | 
			
		||||
				// Check if the user is in the owner team of the organisation.
 | 
			
		||||
				builder.Exists(builder.Select("team_id").From("team_user").
 | 
			
		||||
					Where(builder.Eq{
 | 
			
		||||
						"org_id": orgID,
 | 
			
		||||
						"team_id": builder.Select("id").From("team").Where(
 | 
			
		||||
							builder.Eq{
 | 
			
		||||
								"org_id":     orgID,
 | 
			
		||||
								"lower_name": strings.ToLower(ownerTeamName),
 | 
			
		||||
							}),
 | 
			
		||||
						"uid": userID,
 | 
			
		||||
					}),
 | 
			
		||||
				),
 | 
			
		||||
			)).And(
 | 
			
		||||
				builder.In(
 | 
			
		||||
					"team_id", builder.Select("team_id").From("team_unit").Where(
 | 
			
		||||
 
 | 
			
		||||
@@ -167,3 +167,21 @@ func TestLinkedRepository(t *testing.T) {
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestRepoAssignees(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
			
		||||
 | 
			
		||||
	repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}).(*repo_model.Repository)
 | 
			
		||||
	users, err := GetRepoAssignees(repo2)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Len(t, users, 1)
 | 
			
		||||
	assert.Equal(t, users[0].ID, int64(2))
 | 
			
		||||
 | 
			
		||||
	repo21 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 21}).(*repo_model.Repository)
 | 
			
		||||
	users, err = GetRepoAssignees(repo21)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Len(t, users, 3)
 | 
			
		||||
	assert.Equal(t, users[0].ID, int64(15))
 | 
			
		||||
	assert.Equal(t, users[1].ID, int64(18))
 | 
			
		||||
	assert.Equal(t, users[2].ID, int64(16))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -328,7 +328,12 @@ func AllUnitKeyNames() []string {
 | 
			
		||||
// MinUnitAccessMode returns the minial permission of the permission map
 | 
			
		||||
func MinUnitAccessMode(unitsMap map[Type]perm.AccessMode) perm.AccessMode {
 | 
			
		||||
	res := perm.AccessModeNone
 | 
			
		||||
	for _, mode := range unitsMap {
 | 
			
		||||
	for t, mode := range unitsMap {
 | 
			
		||||
		// Don't allow `TypeExternal{Tracker,Wiki}` to influence this as they can only be set to READ perms.
 | 
			
		||||
		if t == TypeExternalTracker || t == TypeExternalWiki {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// get the minial permission great than AccessModeNone except all are AccessModeNone
 | 
			
		||||
		if mode > perm.AccessModeNone && (res == perm.AccessModeNone || mode < res) {
 | 
			
		||||
			res = mode
 | 
			
		||||
 
 | 
			
		||||
@@ -175,9 +175,11 @@ func init() {
 | 
			
		||||
 | 
			
		||||
	checkForActionConsistency := func(t assert.TestingT, bean interface{}) {
 | 
			
		||||
		action := reflectionWrap(bean)
 | 
			
		||||
		if action.int("RepoID") != 1700 { // dangling intentional
 | 
			
		||||
			repoRow := AssertExistsAndLoadMap(t, "repository", builder.Eq{"id": action.int("RepoID")})
 | 
			
		||||
			assert.Equal(t, parseBool(repoRow["is_private"]), action.bool("IsPrivate"), "action: %+v", action)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	consistencyCheckMap["user"] = checkForUserConsistency
 | 
			
		||||
	consistencyCheckMap["repository"] = checkForRepoConsistency
 | 
			
		||||
 
 | 
			
		||||
@@ -13,11 +13,13 @@ import (
 | 
			
		||||
	_ "image/jpeg" // Needed for jpeg support
 | 
			
		||||
 | 
			
		||||
	asymkey_model "code.gitea.io/gitea/models/asymkey"
 | 
			
		||||
	auth_model "code.gitea.io/gitea/models/auth"
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/modules/structs"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
 | 
			
		||||
	"xorm.io/builder"
 | 
			
		||||
)
 | 
			
		||||
@@ -82,6 +84,11 @@ func DeleteUser(ctx context.Context, u *user_model.User) (err error) {
 | 
			
		||||
	}
 | 
			
		||||
	// ***** END: Follow *****
 | 
			
		||||
 | 
			
		||||
	if _, err := db.GetEngine(ctx).In("grant_id", builder.Select("id").From("oauth2_grant").Where(builder.Eq{"oauth2_grant.user_id": u.ID})).
 | 
			
		||||
		Delete(&auth_model.OAuth2AuthorizationCode{}); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = deleteBeans(e,
 | 
			
		||||
		&AccessToken{UID: u.ID},
 | 
			
		||||
		&Collaboration{UserID: u.ID},
 | 
			
		||||
@@ -99,6 +106,8 @@ func DeleteUser(ctx context.Context, u *user_model.User) (err error) {
 | 
			
		||||
		&Collaboration{UserID: u.ID},
 | 
			
		||||
		&Stopwatch{UserID: u.ID},
 | 
			
		||||
		&user_model.Setting{UserID: u.ID},
 | 
			
		||||
		&auth_model.OAuth2Application{UID: u.ID},
 | 
			
		||||
		&auth_model.OAuth2Grant{UserID: u.ID},
 | 
			
		||||
	); err != nil {
 | 
			
		||||
		return fmt.Errorf("deleteBeans: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
@@ -130,6 +139,50 @@ func DeleteUser(ctx context.Context, u *user_model.User) (err error) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// ***** START: Branch Protections *****
 | 
			
		||||
	{
 | 
			
		||||
		const batchSize = 50
 | 
			
		||||
		for start := 0; ; start += batchSize {
 | 
			
		||||
			protections := make([]*ProtectedBranch, 0, batchSize)
 | 
			
		||||
			// @perf: We can't filter on DB side by u.ID, as those IDs are serialized as JSON strings.
 | 
			
		||||
			//   We could filter down with `WHERE repo_id IN (reposWithPushPermission(u))`,
 | 
			
		||||
			//   though that query will be quite complex and tricky to maintain (compare `getRepoAssignees()`).
 | 
			
		||||
			// Also, as we didn't update branch protections when removing entries from `access` table,
 | 
			
		||||
			//   it's safer to iterate all protected branches.
 | 
			
		||||
			if err = e.Limit(batchSize, start).Find(&protections); err != nil {
 | 
			
		||||
				return fmt.Errorf("findProtectedBranches: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
			if len(protections) == 0 {
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
			for _, p := range protections {
 | 
			
		||||
				var matched1, matched2, matched3 bool
 | 
			
		||||
				if len(p.WhitelistUserIDs) != 0 {
 | 
			
		||||
					p.WhitelistUserIDs, matched1 = util.RemoveIDFromList(
 | 
			
		||||
						p.WhitelistUserIDs, u.ID)
 | 
			
		||||
				}
 | 
			
		||||
				if len(p.ApprovalsWhitelistUserIDs) != 0 {
 | 
			
		||||
					p.ApprovalsWhitelistUserIDs, matched2 = util.RemoveIDFromList(
 | 
			
		||||
						p.ApprovalsWhitelistUserIDs, u.ID)
 | 
			
		||||
				}
 | 
			
		||||
				if len(p.MergeWhitelistUserIDs) != 0 {
 | 
			
		||||
					p.MergeWhitelistUserIDs, matched3 = util.RemoveIDFromList(
 | 
			
		||||
						p.MergeWhitelistUserIDs, u.ID)
 | 
			
		||||
				}
 | 
			
		||||
				if matched1 || matched2 || matched3 {
 | 
			
		||||
					if _, err = e.ID(p.ID).Cols(
 | 
			
		||||
						"whitelist_user_i_ds",
 | 
			
		||||
						"merge_whitelist_user_i_ds",
 | 
			
		||||
						"approvals_whitelist_user_i_ds",
 | 
			
		||||
					).Update(p); err != nil {
 | 
			
		||||
						return fmt.Errorf("updateProtectedBranches: %v", err)
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// ***** END: Branch Protections *****
 | 
			
		||||
 | 
			
		||||
	// ***** START: PublicKey *****
 | 
			
		||||
	if _, err = e.Delete(&asymkey_model.PublicKey{OwnerID: u.ID}); err != nil {
 | 
			
		||||
		return fmt.Errorf("deletePublicKeys: %v", err)
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/mail"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
@@ -21,10 +22,23 @@ import (
 | 
			
		||||
	"xorm.io/builder"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
// ErrEmailNotActivated e-mail address has not been activated error
 | 
			
		||||
	ErrEmailNotActivated = errors.New("E-mail address has not been activated")
 | 
			
		||||
)
 | 
			
		||||
var ErrEmailNotActivated = errors.New("e-mail address has not been activated")
 | 
			
		||||
 | 
			
		||||
// ErrEmailCharIsNotSupported e-mail address contains unsupported character
 | 
			
		||||
type ErrEmailCharIsNotSupported struct {
 | 
			
		||||
	Email string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsErrEmailCharIsNotSupported checks if an error is an ErrEmailCharIsNotSupported
 | 
			
		||||
func IsErrEmailCharIsNotSupported(err error) bool {
 | 
			
		||||
	_, ok := err.(ErrEmailCharIsNotSupported)
 | 
			
		||||
	return ok
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (err ErrEmailCharIsNotSupported) Error() string {
 | 
			
		||||
	return fmt.Sprintf("e-mail address contains unsupported character [email: %s]", err.Email)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ErrEmailInvalid represents an error where the email address does not comply with RFC 5322
 | 
			
		||||
type ErrEmailInvalid struct {
 | 
			
		||||
@@ -108,12 +122,24 @@ func (email *EmailAddress) BeforeInsert() {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var emailRegexp = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]*@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
 | 
			
		||||
 | 
			
		||||
// ValidateEmail check if email is a allowed address
 | 
			
		||||
func ValidateEmail(email string) error {
 | 
			
		||||
	if len(email) == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !emailRegexp.MatchString(email) {
 | 
			
		||||
		return ErrEmailCharIsNotSupported{email}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !(email[0] >= 'a' && email[0] <= 'z') &&
 | 
			
		||||
		!(email[0] >= 'A' && email[0] <= 'Z') &&
 | 
			
		||||
		!(email[0] >= '0' && email[0] <= '9') {
 | 
			
		||||
		return ErrEmailInvalid{email}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := mail.ParseAddress(email); err != nil {
 | 
			
		||||
		return ErrEmailInvalid{email}
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -252,3 +252,58 @@ func TestListEmails(t *testing.T) {
 | 
			
		||||
	assert.Len(t, emails, 5)
 | 
			
		||||
	assert.Greater(t, count, int64(len(emails)))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestEmailAddressValidate(t *testing.T) {
 | 
			
		||||
	kases := map[string]error{
 | 
			
		||||
		"abc@gmail.com":                  nil,
 | 
			
		||||
		"132@hotmail.com":                nil,
 | 
			
		||||
		"1-3-2@test.org":                 nil,
 | 
			
		||||
		"1.3.2@test.org":                 nil,
 | 
			
		||||
		"a_123@test.org.cn":              nil,
 | 
			
		||||
		`first.last@iana.org`:            nil,
 | 
			
		||||
		`first!last@iana.org`:            nil,
 | 
			
		||||
		`first#last@iana.org`:            nil,
 | 
			
		||||
		`first$last@iana.org`:            nil,
 | 
			
		||||
		`first%last@iana.org`:            nil,
 | 
			
		||||
		`first&last@iana.org`:            nil,
 | 
			
		||||
		`first'last@iana.org`:            nil,
 | 
			
		||||
		`first*last@iana.org`:            nil,
 | 
			
		||||
		`first+last@iana.org`:            nil,
 | 
			
		||||
		`first/last@iana.org`:            nil,
 | 
			
		||||
		`first=last@iana.org`:            nil,
 | 
			
		||||
		`first?last@iana.org`:            nil,
 | 
			
		||||
		`first^last@iana.org`:            nil,
 | 
			
		||||
		"first`last@iana.org":            nil,
 | 
			
		||||
		`first{last@iana.org`:            nil,
 | 
			
		||||
		`first|last@iana.org`:            nil,
 | 
			
		||||
		`first}last@iana.org`:            nil,
 | 
			
		||||
		`first~last@iana.org`:            nil,
 | 
			
		||||
		`first;last@iana.org`:            ErrEmailCharIsNotSupported{`first;last@iana.org`},
 | 
			
		||||
		".233@qq.com":                    ErrEmailInvalid{".233@qq.com"},
 | 
			
		||||
		"!233@qq.com":                    ErrEmailInvalid{"!233@qq.com"},
 | 
			
		||||
		"#233@qq.com":                    ErrEmailInvalid{"#233@qq.com"},
 | 
			
		||||
		"$233@qq.com":                    ErrEmailInvalid{"$233@qq.com"},
 | 
			
		||||
		"%233@qq.com":                    ErrEmailInvalid{"%233@qq.com"},
 | 
			
		||||
		"&233@qq.com":                    ErrEmailInvalid{"&233@qq.com"},
 | 
			
		||||
		"'233@qq.com":                    ErrEmailInvalid{"'233@qq.com"},
 | 
			
		||||
		"*233@qq.com":                    ErrEmailInvalid{"*233@qq.com"},
 | 
			
		||||
		"+233@qq.com":                    ErrEmailInvalid{"+233@qq.com"},
 | 
			
		||||
		"/233@qq.com":                    ErrEmailInvalid{"/233@qq.com"},
 | 
			
		||||
		"=233@qq.com":                    ErrEmailInvalid{"=233@qq.com"},
 | 
			
		||||
		"?233@qq.com":                    ErrEmailInvalid{"?233@qq.com"},
 | 
			
		||||
		"^233@qq.com":                    ErrEmailInvalid{"^233@qq.com"},
 | 
			
		||||
		"`233@qq.com":                    ErrEmailInvalid{"`233@qq.com"},
 | 
			
		||||
		"{233@qq.com":                    ErrEmailInvalid{"{233@qq.com"},
 | 
			
		||||
		"|233@qq.com":                    ErrEmailInvalid{"|233@qq.com"},
 | 
			
		||||
		"}233@qq.com":                    ErrEmailInvalid{"}233@qq.com"},
 | 
			
		||||
		"~233@qq.com":                    ErrEmailInvalid{"~233@qq.com"},
 | 
			
		||||
		";233@qq.com":                    ErrEmailCharIsNotSupported{";233@qq.com"},
 | 
			
		||||
		"Foo <foo@bar.com>":              ErrEmailCharIsNotSupported{"Foo <foo@bar.com>"},
 | 
			
		||||
		string([]byte{0xE2, 0x84, 0xAA}): ErrEmailCharIsNotSupported{string([]byte{0xE2, 0x84, 0xAA})},
 | 
			
		||||
	}
 | 
			
		||||
	for kase, err := range kases {
 | 
			
		||||
		t.Run(kase, func(t *testing.T) {
 | 
			
		||||
			assert.EqualValues(t, err, ValidateEmail(kase))
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -60,7 +60,7 @@ type ExternalLoginUser struct {
 | 
			
		||||
	LastName          string
 | 
			
		||||
	NickName          string
 | 
			
		||||
	Description       string
 | 
			
		||||
	AvatarURL         string
 | 
			
		||||
	AvatarURL         string `xorm:"TEXT"`
 | 
			
		||||
	Location          string
 | 
			
		||||
	AccessToken       string `xorm:"TEXT"`
 | 
			
		||||
	AccessTokenSecret string `xorm:"TEXT"`
 | 
			
		||||
 
 | 
			
		||||
@@ -30,13 +30,19 @@ func (users UserList) GetTwoFaStatus() map[int64]bool {
 | 
			
		||||
	for _, user := range users {
 | 
			
		||||
		results[user.ID] = false // Set default to false
 | 
			
		||||
	}
 | 
			
		||||
	tokenMaps, err := users.loadTwoFactorStatus(db.GetEngine(db.DefaultContext))
 | 
			
		||||
	if err == nil {
 | 
			
		||||
 | 
			
		||||
	if tokenMaps, err := users.loadTwoFactorStatus(db.GetEngine(db.DefaultContext)); err == nil {
 | 
			
		||||
		for _, token := range tokenMaps {
 | 
			
		||||
			results[token.UID] = true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ids, err := users.userIDsWithWebAuthn(db.GetEngine(db.DefaultContext)); err == nil {
 | 
			
		||||
		for _, id := range ids {
 | 
			
		||||
			results[id] = true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return results
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -47,15 +53,23 @@ func (users UserList) loadTwoFactorStatus(e db.Engine) (map[int64]*auth.TwoFacto
 | 
			
		||||
 | 
			
		||||
	userIDs := users.GetUserIDs()
 | 
			
		||||
	tokenMaps := make(map[int64]*auth.TwoFactor, len(userIDs))
 | 
			
		||||
	err := e.
 | 
			
		||||
		In("uid", userIDs).
 | 
			
		||||
		Find(&tokenMaps)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
	if err := e.In("uid", userIDs).Find(&tokenMaps); err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("find two factor: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	return tokenMaps, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (users UserList) userIDsWithWebAuthn(e db.Engine) ([]int64, error) {
 | 
			
		||||
	if len(users) == 0 {
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
	ids := make([]int64, 0, len(users))
 | 
			
		||||
	if err := e.Table(new(auth.WebAuthnCredential)).In("user_id", users.GetUserIDs()).Select("user_id").Distinct("user_id").Find(&ids); err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("find two factor: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	return ids, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetUsersByIDs returns all resolved users from a list of Ids.
 | 
			
		||||
func GetUsersByIDs(ids []int64) (UserList, error) {
 | 
			
		||||
	ous := make([]*User, 0, len(ids))
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,7 @@ import (
 | 
			
		||||
// SearchUserOptions contains the options for searching
 | 
			
		||||
type SearchUserOptions struct {
 | 
			
		||||
	db.ListOptions
 | 
			
		||||
 | 
			
		||||
	Keyword       string
 | 
			
		||||
	Type          UserType
 | 
			
		||||
	UID           int64
 | 
			
		||||
@@ -33,6 +34,8 @@ type SearchUserOptions struct {
 | 
			
		||||
	IsRestricted       util.OptionalBool
 | 
			
		||||
	IsTwoFactorEnabled util.OptionalBool
 | 
			
		||||
	IsProhibitLogin    util.OptionalBool
 | 
			
		||||
 | 
			
		||||
	ExtraParamStrings map[string]string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (opts *SearchUserOptions) toSearchQueryBase() *xorm.Session {
 | 
			
		||||
 
 | 
			
		||||
@@ -622,7 +622,14 @@ func IsUsableUsername(name string) error {
 | 
			
		||||
 | 
			
		||||
// CreateUserOverwriteOptions are an optional options who overwrite system defaults on user creation
 | 
			
		||||
type CreateUserOverwriteOptions struct {
 | 
			
		||||
	Visibility structs.VisibleType
 | 
			
		||||
	KeepEmailPrivate             util.OptionalBool
 | 
			
		||||
	Visibility                   *structs.VisibleType
 | 
			
		||||
	AllowCreateOrganization      util.OptionalBool
 | 
			
		||||
	EmailNotificationsPreference *string
 | 
			
		||||
	MaxRepoCreation              *int
 | 
			
		||||
	Theme                        *string
 | 
			
		||||
	IsRestricted                 util.OptionalBool
 | 
			
		||||
	IsActive                     util.OptionalBool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CreateUser creates record of a new user.
 | 
			
		||||
@@ -638,10 +645,45 @@ func CreateUser(u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err e
 | 
			
		||||
	u.EmailNotificationsPreference = setting.Admin.DefaultEmailNotification
 | 
			
		||||
	u.MaxRepoCreation = -1
 | 
			
		||||
	u.Theme = setting.UI.DefaultTheme
 | 
			
		||||
	u.IsRestricted = setting.Service.DefaultUserIsRestricted
 | 
			
		||||
	u.IsActive = !(setting.Service.RegisterEmailConfirm || setting.Service.RegisterManualConfirm)
 | 
			
		||||
 | 
			
		||||
	// overwrite defaults if set
 | 
			
		||||
	if len(overwriteDefault) != 0 && overwriteDefault[0] != nil {
 | 
			
		||||
		u.Visibility = overwriteDefault[0].Visibility
 | 
			
		||||
		overwrite := overwriteDefault[0]
 | 
			
		||||
		if !overwrite.KeepEmailPrivate.IsNone() {
 | 
			
		||||
			u.KeepEmailPrivate = overwrite.KeepEmailPrivate.IsTrue()
 | 
			
		||||
		}
 | 
			
		||||
		if overwrite.Visibility != nil {
 | 
			
		||||
			u.Visibility = *overwrite.Visibility
 | 
			
		||||
		}
 | 
			
		||||
		if !overwrite.AllowCreateOrganization.IsNone() {
 | 
			
		||||
			u.AllowCreateOrganization = overwrite.AllowCreateOrganization.IsTrue()
 | 
			
		||||
		}
 | 
			
		||||
		if overwrite.EmailNotificationsPreference != nil {
 | 
			
		||||
			u.EmailNotificationsPreference = *overwrite.EmailNotificationsPreference
 | 
			
		||||
		}
 | 
			
		||||
		if overwrite.MaxRepoCreation != nil {
 | 
			
		||||
			u.MaxRepoCreation = *overwrite.MaxRepoCreation
 | 
			
		||||
		}
 | 
			
		||||
		if overwrite.Theme != nil {
 | 
			
		||||
			u.Theme = *overwrite.Theme
 | 
			
		||||
		}
 | 
			
		||||
		if !overwrite.IsRestricted.IsNone() {
 | 
			
		||||
			u.IsRestricted = overwrite.IsRestricted.IsTrue()
 | 
			
		||||
		}
 | 
			
		||||
		if !overwrite.IsActive.IsNone() {
 | 
			
		||||
			u.IsActive = overwrite.IsActive.IsTrue()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// validate data
 | 
			
		||||
	if err := validateUser(u); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := ValidateEmail(u.Email); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx, committer, err := db.TxContext()
 | 
			
		||||
@@ -652,11 +694,6 @@ func CreateUser(u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err e
 | 
			
		||||
 | 
			
		||||
	sess := db.GetEngine(ctx)
 | 
			
		||||
 | 
			
		||||
	// validate data
 | 
			
		||||
	if err := validateUser(u); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	isExist, err := isUserExist(sess, 0, u.Name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
@@ -827,8 +864,9 @@ func validateUser(u *User) error {
 | 
			
		||||
	return ValidateEmail(u.Email)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func updateUser(ctx context.Context, u *User, changePrimaryEmail bool) error {
 | 
			
		||||
	if err := validateUser(u); err != nil {
 | 
			
		||||
func updateUser(ctx context.Context, u *User, changePrimaryEmail bool, cols ...string) error {
 | 
			
		||||
	err := validateUser(u)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -860,15 +898,35 @@ func updateUser(ctx context.Context, u *User, changePrimaryEmail bool) error {
 | 
			
		||||
		}); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	} else if !u.IsOrganization() { // check if primary email in email_address table
 | 
			
		||||
		primaryEmailExist, err := e.Where("uid=? AND is_primary=?", u.ID, true).Exist(&EmailAddress{})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	_, err := e.ID(u.ID).AllCols().Update(u)
 | 
			
		||||
		if !primaryEmailExist {
 | 
			
		||||
			if _, err = e.Insert(&EmailAddress{
 | 
			
		||||
				Email:       u.Email,
 | 
			
		||||
				UID:         u.ID,
 | 
			
		||||
				IsActivated: true,
 | 
			
		||||
				IsPrimary:   true,
 | 
			
		||||
			}); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(cols) == 0 {
 | 
			
		||||
		_, err = e.ID(u.ID).AllCols().Update(u)
 | 
			
		||||
	} else {
 | 
			
		||||
		_, err = e.ID(u.ID).Cols(cols...).Update(u)
 | 
			
		||||
	}
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateUser updates user's information.
 | 
			
		||||
func UpdateUser(u *User, emailChanged bool) error {
 | 
			
		||||
	return updateUser(db.DefaultContext, u, emailChanged)
 | 
			
		||||
func UpdateUser(u *User, emailChanged bool, cols ...string) error {
 | 
			
		||||
	return updateUser(db.DefaultContext, u, emailChanged, cols...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateUserCols update user according special columns
 | 
			
		||||
@@ -1104,19 +1162,9 @@ func GetUserByEmailContext(ctx context.Context, email string) (*User, error) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	email = strings.ToLower(email)
 | 
			
		||||
	// First try to find the user by primary email
 | 
			
		||||
	user := &User{Email: email}
 | 
			
		||||
	has, err := db.GetEngine(ctx).Get(user)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if has {
 | 
			
		||||
		return user, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Otherwise, check in alternative list for activated email addresses
 | 
			
		||||
	emailAddress := &EmailAddress{Email: email, IsActivated: true}
 | 
			
		||||
	has, err = db.GetEngine(ctx).Get(emailAddress)
 | 
			
		||||
	emailAddress := &EmailAddress{LowerEmail: email, IsActivated: true}
 | 
			
		||||
	has, err := db.GetEngine(ctx).Get(emailAddress)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -232,7 +232,21 @@ func TestCreateUserInvalidEmail(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	err := CreateUser(user)
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
	assert.True(t, IsErrEmailInvalid(err))
 | 
			
		||||
	assert.True(t, IsErrEmailCharIsNotSupported(err))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCreateUserEmailAlreadyUsed(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
			
		||||
 | 
			
		||||
	user := unittest.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
 | 
			
		||||
 | 
			
		||||
	// add new user with user2's email
 | 
			
		||||
	user.Name = "testuser"
 | 
			
		||||
	user.LowerName = strings.ToLower(user.Name)
 | 
			
		||||
	user.ID = 0
 | 
			
		||||
	err := CreateUser(user)
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
	assert.True(t, IsErrEmailAlreadyUsed(err))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetUserIDsByNames(t *testing.T) {
 | 
			
		||||
 
 | 
			
		||||
@@ -497,16 +497,21 @@ func GetSystemOrDefaultWebhook(id int64) (*Webhook, error) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetSystemWebhooks returns all admin system webhooks.
 | 
			
		||||
func GetSystemWebhooks() ([]*Webhook, error) {
 | 
			
		||||
	return getSystemWebhooks(db.GetEngine(db.DefaultContext))
 | 
			
		||||
func GetSystemWebhooks(isActive util.OptionalBool) ([]*Webhook, error) {
 | 
			
		||||
	return getSystemWebhooks(db.GetEngine(db.DefaultContext), isActive)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getSystemWebhooks(e db.Engine) ([]*Webhook, error) {
 | 
			
		||||
func getSystemWebhooks(e db.Engine, isActive util.OptionalBool) ([]*Webhook, error) {
 | 
			
		||||
	webhooks := make([]*Webhook, 0, 5)
 | 
			
		||||
	if isActive.IsNone() {
 | 
			
		||||
		return webhooks, e.
 | 
			
		||||
			Where("repo_id=? AND org_id=? AND is_system_webhook=?", 0, 0, true).
 | 
			
		||||
			Find(&webhooks)
 | 
			
		||||
	}
 | 
			
		||||
	return webhooks, e.
 | 
			
		||||
		Where("repo_id=? AND org_id=? AND is_system_webhook=? AND is_active = ?", 0, 0, true, isActive.IsTrue()).
 | 
			
		||||
		Find(&webhooks)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateWebhook updates information of webhook.
 | 
			
		||||
func UpdateWebhook(w *Webhook) error {
 | 
			
		||||
 
 | 
			
		||||
@@ -36,6 +36,10 @@ func Auth(serviceName, userName, passwd string) (string, error) {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	if err = t.AcctMgmt(0); err != nil {
 | 
			
		||||
	  return "", err
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
	// PAM login names might suffer transformations in the PAM stack.
 | 
			
		||||
	// We should take whatever the PAM stack returns for it.
 | 
			
		||||
	return t.GetItem(pam.User)
 | 
			
		||||
 
 | 
			
		||||
@@ -63,6 +63,7 @@ func EscapeControlBytes(text []byte) (EscapeStatus, []byte) {
 | 
			
		||||
func EscapeControlReader(text io.Reader, output io.Writer) (escaped EscapeStatus, err error) {
 | 
			
		||||
	buf := make([]byte, 4096)
 | 
			
		||||
	readStart := 0
 | 
			
		||||
	runeCount := 0
 | 
			
		||||
	var n int
 | 
			
		||||
	var writePos int
 | 
			
		||||
 | 
			
		||||
@@ -74,10 +75,13 @@ readingloop:
 | 
			
		||||
	for err == nil {
 | 
			
		||||
		n, err = text.Read(buf[readStart:])
 | 
			
		||||
		bs := buf[:n+readStart]
 | 
			
		||||
		n = len(bs)
 | 
			
		||||
		i := 0
 | 
			
		||||
 | 
			
		||||
		for i < len(bs) {
 | 
			
		||||
			r, size := utf8.DecodeRune(bs[i:])
 | 
			
		||||
			runeCount++
 | 
			
		||||
 | 
			
		||||
			// Now handle the codepoints
 | 
			
		||||
			switch {
 | 
			
		||||
			case r == utf8.RuneError:
 | 
			
		||||
@@ -112,6 +116,8 @@ readingloop:
 | 
			
		||||
				lineHasRTLScript = false
 | 
			
		||||
				lineHasLTRScript = false
 | 
			
		||||
 | 
			
		||||
			case runeCount == 1 && r == 0xFEFF: // UTF BOM
 | 
			
		||||
				// the first BOM is safe
 | 
			
		||||
			case r == '\r' || r == '\t' || r == ' ':
 | 
			
		||||
				// These are acceptable control characters and space characters
 | 
			
		||||
			case unicode.IsSpace(r):
 | 
			
		||||
@@ -143,7 +149,8 @@ readingloop:
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				writePos = i + size
 | 
			
		||||
			case unicode.Is(unicode.C, r):
 | 
			
		||||
			// 65279 == BOM rune.
 | 
			
		||||
			case unicode.Is(unicode.C, r) && r != rune(65279):
 | 
			
		||||
				escaped.Escaped = true
 | 
			
		||||
				escaped.HasControls = true
 | 
			
		||||
				if writePos < i {
 | 
			
		||||
 
 | 
			
		||||
@@ -129,6 +129,14 @@ then resh (ר), and finally heh (ה) (which should appear leftmost).`,
 | 
			
		||||
			"\n" + `if access_level != "user<span class="escaped-code-point" data-escaped="[U+202E]"><span class="char">` + "\u202e" + `</span></span> <span class="escaped-code-point" data-escaped="[U+2066]"><span class="char">` + "\u2066" + `</span></span>// Check if admin<span class="escaped-code-point" data-escaped="[U+2069]"><span class="char">` + "\u2069" + `</span></span> <span class="escaped-code-point" data-escaped="[U+2066]"><span class="char">` + "\u2066" + `</span></span>" {` + "\n",
 | 
			
		||||
		status: EscapeStatus{Escaped: true, HasBIDI: true, BadBIDI: true, HasLTRScript: true, HasRTLScript: true},
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		// UTF-8/16/32 all use the same codepoint for BOM
 | 
			
		||||
		// Gitea could read UTF-16/32 content and convert into UTF-8 internally then render it, so we only process UTF-8 internally
 | 
			
		||||
		name:   "UTF BOM",
 | 
			
		||||
		text:   "\xef\xbb\xbftest",
 | 
			
		||||
		result: "\xef\xbb\xbftest",
 | 
			
		||||
		status: EscapeStatus{HasLTRScript: true},
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestEscapeControlString(t *testing.T) {
 | 
			
		||||
@@ -163,10 +171,18 @@ func TestEscapeControlReader(t *testing.T) {
 | 
			
		||||
	// lets add some control characters to the tests
 | 
			
		||||
	tests := make([]escapeControlTest, 0, len(escapeControlTests)*3)
 | 
			
		||||
	copy(tests, escapeControlTests)
 | 
			
		||||
 | 
			
		||||
	// if there is a BOM, we should keep the BOM
 | 
			
		||||
	addPrefix := func(prefix, s string) string {
 | 
			
		||||
		if strings.HasPrefix(s, "\xef\xbb\xbf") {
 | 
			
		||||
			return s[:3] + prefix + s[3:]
 | 
			
		||||
		}
 | 
			
		||||
		return prefix + s
 | 
			
		||||
	}
 | 
			
		||||
	for _, test := range escapeControlTests {
 | 
			
		||||
		test.name += " (+Control)"
 | 
			
		||||
		test.text = "\u001E" + test.text
 | 
			
		||||
		test.result = `<span class="escaped-code-point" data-escaped="[U+001E]"><span class="char">` + "\u001e" + `</span></span>` + test.result
 | 
			
		||||
		test.text = addPrefix("\u001E", test.text)
 | 
			
		||||
		test.result = addPrefix(`<span class="escaped-code-point" data-escaped="[U+001E]"><span class="char">`+"\u001e"+`</span></span>`, test.result)
 | 
			
		||||
		test.status.Escaped = true
 | 
			
		||||
		test.status.HasControls = true
 | 
			
		||||
		tests = append(tests, test)
 | 
			
		||||
@@ -174,8 +190,8 @@ func TestEscapeControlReader(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	for _, test := range escapeControlTests {
 | 
			
		||||
		test.name += " (+Mark)"
 | 
			
		||||
		test.text = "\u0300" + test.text
 | 
			
		||||
		test.result = `<span class="escaped-code-point" data-escaped="[U+0300]"><span class="char">` + "\u0300" + `</span></span>` + test.result
 | 
			
		||||
		test.text = addPrefix("\u0300", test.text)
 | 
			
		||||
		test.result = addPrefix(`<span class="escaped-code-point" data-escaped="[U+0300]"><span class="char">`+"\u0300"+`</span></span>`, test.result)
 | 
			
		||||
		test.status.Escaped = true
 | 
			
		||||
		test.status.HasMarks = true
 | 
			
		||||
		tests = append(tests, test)
 | 
			
		||||
@@ -200,3 +216,12 @@ func TestEscapeControlReader(t *testing.T) {
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestEscapeControlReader_panic(t *testing.T) {
 | 
			
		||||
	bs := make([]byte, 0, 20479)
 | 
			
		||||
	bs = append(bs, 'A')
 | 
			
		||||
	for i := 0; i < 6826; i++ {
 | 
			
		||||
		bs = append(bs, []byte("—")...)
 | 
			
		||||
	}
 | 
			
		||||
	_, _ = EscapeControlBytes(bs)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -288,6 +288,7 @@ func APIContexter() func(http.Handler) http.Handler {
 | 
			
		||||
				},
 | 
			
		||||
				Org: &APIOrganization{},
 | 
			
		||||
			}
 | 
			
		||||
			defer ctx.Close()
 | 
			
		||||
 | 
			
		||||
			ctx.Req = WithAPIContext(WithContext(req, ctx.Context), &ctx)
 | 
			
		||||
			ctx.csrf = Csrfer(csrfOpts, ctx.Context)
 | 
			
		||||
 
 | 
			
		||||
@@ -9,9 +9,11 @@ import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"crypto/sha256"
 | 
			
		||||
	"encoding/hex"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"html"
 | 
			
		||||
	"html/template"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"path"
 | 
			
		||||
@@ -69,6 +71,16 @@ type Context struct {
 | 
			
		||||
	Org  *Organization
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Close frees all resources hold by Context
 | 
			
		||||
func (ctx *Context) Close() error {
 | 
			
		||||
	var err error
 | 
			
		||||
	if ctx.Req != nil && ctx.Req.MultipartForm != nil {
 | 
			
		||||
		err = ctx.Req.MultipartForm.RemoveAll() // remove the temp files buffered to tmp directory
 | 
			
		||||
	}
 | 
			
		||||
	// TODO: close opened repo, and more
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TrHTMLEscapeArgs runs Tr but pre-escapes all arguments with html.EscapeString.
 | 
			
		||||
// This is useful if the locale message is intended to only produce HTML content.
 | 
			
		||||
func (ctx *Context) TrHTMLEscapeArgs(msg string, args ...string) string {
 | 
			
		||||
@@ -179,6 +191,12 @@ func (ctx *Context) RedirectToFirst(location ...string) {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Unfortunately browsers consider a redirect Location with preceding "//" and "/\" as meaning redirect to "http(s)://REST_OF_PATH"
 | 
			
		||||
		// Therefore we should ignore these redirect locations to prevent open redirects
 | 
			
		||||
		if len(loc) > 1 && loc[0] == '/' && (loc[1] == '/' || loc[1] == '\\') {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		u, err := url.Parse(loc)
 | 
			
		||||
		if err != nil || ((u.Scheme != "" || u.Host != "") && !strings.HasPrefix(strings.ToLower(loc), strings.ToLower(setting.AppURL))) {
 | 
			
		||||
			continue
 | 
			
		||||
@@ -264,6 +282,12 @@ func (ctx *Context) ServerError(logMsg string, logErr error) {
 | 
			
		||||
func (ctx *Context) serverErrorInternal(logMsg string, logErr error) {
 | 
			
		||||
	if logErr != nil {
 | 
			
		||||
		log.ErrorWithSkip(2, "%s: %v", logMsg, logErr)
 | 
			
		||||
		if _, ok := logErr.(*net.OpError); ok || errors.Is(logErr, &net.OpError{}) {
 | 
			
		||||
			// This is an error within the underlying connection
 | 
			
		||||
			// and further rendering will not work so just return
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !setting.IsProd {
 | 
			
		||||
			ctx.Data["ErrorMsg"] = logErr
 | 
			
		||||
		}
 | 
			
		||||
@@ -291,6 +315,7 @@ func (ctx *Context) PlainTextBytes(status int, bs []byte) {
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Resp.WriteHeader(status)
 | 
			
		||||
	ctx.Resp.Header().Set("Content-Type", "text/plain;charset=utf-8")
 | 
			
		||||
	ctx.Resp.Header().Set("X-Content-Type-Options", "nosniff")
 | 
			
		||||
	if _, err := ctx.Resp.Write(bs); err != nil {
 | 
			
		||||
		log.Error("Write bytes failed: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
@@ -628,6 +653,8 @@ func Contexter() func(next http.Handler) http.Handler {
 | 
			
		||||
					"RunModeIsProd": setting.IsProd,
 | 
			
		||||
				},
 | 
			
		||||
			}
 | 
			
		||||
			defer ctx.Close()
 | 
			
		||||
 | 
			
		||||
			// PageData is passed by reference, and it will be rendered to `window.config.pageData` in `head.tmpl` for JavaScript modules
 | 
			
		||||
			ctx.PageData = map[string]interface{}{}
 | 
			
		||||
			ctx.Data["PageData"] = ctx.PageData
 | 
			
		||||
 
 | 
			
		||||
@@ -229,6 +229,7 @@ func Csrfer(opt CsrfOptions, ctx *Context) CSRF {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	needsNew = needsNew || ctx.Req.Method == "GET" // If this request is a Get request, it will generate a new token, make sure the token is always up-to-date.
 | 
			
		||||
	if needsNew {
 | 
			
		||||
		// FIXME: actionId.
 | 
			
		||||
		x.Token = GenerateToken(x.Secret, x.ID, "POST")
 | 
			
		||||
 
 | 
			
		||||
@@ -70,12 +70,6 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
 | 
			
		||||
	org := ctx.Org.Organization
 | 
			
		||||
	ctx.Data["Org"] = org
 | 
			
		||||
 | 
			
		||||
	teams, err := org.LoadTeams()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("LoadTeams", err)
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Data["OrgTeams"] = teams
 | 
			
		||||
 | 
			
		||||
	// Admin has super access.
 | 
			
		||||
	if ctx.IsSigned && ctx.User.IsAdmin {
 | 
			
		||||
		ctx.Org.IsOwner = true
 | 
			
		||||
@@ -129,7 +123,23 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
 | 
			
		||||
 | 
			
		||||
	// Team.
 | 
			
		||||
	if ctx.Org.IsMember {
 | 
			
		||||
		shouldSeeAllTeams := false
 | 
			
		||||
		if ctx.Org.IsOwner {
 | 
			
		||||
			shouldSeeAllTeams = true
 | 
			
		||||
		} else {
 | 
			
		||||
			teams, err := org.GetUserTeams(ctx.User.ID)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				ctx.ServerError("GetUserTeams", err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			for _, team := range teams {
 | 
			
		||||
				if team.IncludesAllRepositories && team.AccessMode >= perm.AccessModeAdmin {
 | 
			
		||||
					shouldSeeAllTeams = true
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if shouldSeeAllTeams {
 | 
			
		||||
			ctx.Org.Teams, err = org.LoadTeams()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				ctx.ServerError("LoadTeams", err)
 | 
			
		||||
 
 | 
			
		||||
@@ -38,6 +38,8 @@ func PrivateContexter() func(http.Handler) http.Handler {
 | 
			
		||||
					Data: map[string]interface{}{},
 | 
			
		||||
				},
 | 
			
		||||
			}
 | 
			
		||||
			defer ctx.Close()
 | 
			
		||||
 | 
			
		||||
			ctx.Req = WithPrivateContext(req, ctx)
 | 
			
		||||
			next.ServeHTTP(ctx.Resp, ctx.Req)
 | 
			
		||||
		})
 | 
			
		||||
 
 | 
			
		||||
@@ -410,6 +410,12 @@ func RepoIDAssignment() func(ctx *Context) {
 | 
			
		||||
 | 
			
		||||
// RepoAssignment returns a middleware to handle repository assignment
 | 
			
		||||
func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
 | 
			
		||||
	if _, repoAssignmentOnce := ctx.Data["repoAssignmentExecuted"]; repoAssignmentOnce {
 | 
			
		||||
		log.Trace("RepoAssignment was exec already, skipping second call ...")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Data["repoAssignmentExecuted"] = true
 | 
			
		||||
 | 
			
		||||
	var (
 | 
			
		||||
		owner *user_model.User
 | 
			
		||||
		err   error
 | 
			
		||||
@@ -440,6 +446,26 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
 | 
			
		||||
	ctx.Repo.Owner = owner
 | 
			
		||||
	ctx.Data["Username"] = ctx.Repo.Owner.Name
 | 
			
		||||
 | 
			
		||||
	// redirect link to wiki
 | 
			
		||||
	if strings.HasSuffix(repoName, ".wiki") {
 | 
			
		||||
		// ctx.Req.URL.Path does not have the preceding appSubURL - any redirect must have this added
 | 
			
		||||
		// Now we happen to know that all of our paths are: /:username/:reponame/whatever_else
 | 
			
		||||
		originalRepoName := ctx.Params(":reponame")
 | 
			
		||||
		redirectRepoName := strings.TrimSuffix(repoName, ".wiki")
 | 
			
		||||
		redirectRepoName += originalRepoName[len(redirectRepoName)+5:]
 | 
			
		||||
		redirectPath := strings.Replace(
 | 
			
		||||
			ctx.Req.URL.EscapedPath(),
 | 
			
		||||
			url.PathEscape(userName)+"/"+url.PathEscape(originalRepoName),
 | 
			
		||||
			url.PathEscape(userName)+"/"+url.PathEscape(redirectRepoName)+"/wiki",
 | 
			
		||||
			1,
 | 
			
		||||
		)
 | 
			
		||||
		if ctx.Req.URL.RawQuery != "" {
 | 
			
		||||
			redirectPath += "?" + ctx.Req.URL.RawQuery
 | 
			
		||||
		}
 | 
			
		||||
		ctx.Redirect(path.Join(setting.AppSubURL, redirectPath))
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Get repository.
 | 
			
		||||
	repo, err := repo_model.GetRepositoryByName(owner.ID, repoName)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -572,6 +598,9 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
 | 
			
		||||
		ctx.ServerError("RepoAssignment Invalid repo "+repo_model.RepoPath(userName, repoName), err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if ctx.Repo.GitRepo != nil {
 | 
			
		||||
		ctx.Repo.GitRepo.Close()
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Repo.GitRepo = gitRepo
 | 
			
		||||
 | 
			
		||||
	// We opened it, we should close it
 | 
			
		||||
@@ -911,7 +940,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
 | 
			
		||||
 | 
			
		||||
			if refType == RepoRefLegacy {
 | 
			
		||||
				// redirect from old URL scheme to new URL scheme
 | 
			
		||||
				prefix := strings.TrimPrefix(setting.AppSubURL+strings.TrimSuffix(ctx.Req.URL.Path, ctx.Params("*")), ctx.Repo.RepoLink)
 | 
			
		||||
				prefix := strings.TrimPrefix(setting.AppSubURL+strings.ToLower(strings.TrimSuffix(ctx.Req.URL.Path, ctx.Params("*"))), strings.ToLower(ctx.Repo.RepoLink))
 | 
			
		||||
 | 
			
		||||
				ctx.Redirect(path.Join(
 | 
			
		||||
					ctx.Repo.RepoLink,
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user