mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-03 08:02:36 +09:00 
			
		
		
		
	Compare commits
	
		
			198 Commits
		
	
	
		
			v1.22.0-de
			...
			v1.14.6
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					bb054fdfa1 | ||
| 
						 | 
					7760a7f385 | ||
| 
						 | 
					3107c9dfc3 | ||
| 
						 | 
					a66ff8a210 | ||
| 
						 | 
					6a3c7856c8 | ||
| 
						 | 
					3299f044d3 | ||
| 
						 | 
					e6c222511d | ||
| 
						 | 
					62fa153f9f | ||
| 
						 | 
					be46f240d9 | ||
| 
						 | 
					ca55e49cc0 | ||
| 
						 | 
					58615be523 | ||
| 
						 | 
					6df82db0f7 | ||
| 
						 | 
					d98694e6ca | ||
| 
						 | 
					ac0f452b30 | ||
| 
						 | 
					6e5fd5c584 | ||
| 
						 | 
					d0b8e3c8e1 | ||
| 
						 | 
					7ff8e863a5 | ||
| 
						 | 
					c65e49d72f | ||
| 
						 | 
					50084daa4c | ||
| 
						 | 
					c7db7438b7 | ||
| 
						 | 
					e11f042a95 | ||
| 
						 | 
					87782636e6 | ||
| 
						 | 
					b935472cdf | ||
| 
						 | 
					8ac48584ec | ||
| 
						 | 
					e898590c81 | ||
| 
						 | 
					d407857d97 | ||
| 
						 | 
					8cfd6695da | ||
| 
						 | 
					f832e8eeea | ||
| 
						 | 
					544ef7d394 | ||
| 
						 | 
					5ff807acde | ||
| 
						 | 
					849d316d8d | ||
| 
						 | 
					946eb1321c | ||
| 
						 | 
					bc82bb9cda | ||
| 
						 | 
					f034804e5d | ||
| 
						 | 
					c1887bfc9b | ||
| 
						 | 
					41a4047e79 | ||
| 
						 | 
					ac84bb7183 | ||
| 
						 | 
					3be67e9a2b | ||
| 
						 | 
					ce2ade05e6 | ||
| 
						 | 
					1e76f7b5b7 | ||
| 
						 | 
					2265058c31 | ||
| 
						 | 
					ba74fdbda9 | ||
| 
						 | 
					0600f7972a | ||
| 
						 | 
					8007602b40 | ||
| 
						 | 
					3a79f1190f | ||
| 
						 | 
					d95489b7ed | ||
| 
						 | 
					a9e1a37b71 | ||
| 
						 | 
					5a589ef9ec | ||
| 
						 | 
					159bc8842a | ||
| 
						 | 
					4b771d393e | ||
| 
						 | 
					0c2cbfcb3b | ||
| 
						 | 
					8c4bf4c3b4 | ||
| 
						 | 
					3bcf2e5c18 | ||
| 
						 | 
					ad54f008ac | ||
| 
						 | 
					c21167e3a2 | ||
| 
						 | 
					aaa539dd2d | ||
| 
						 | 
					e38134f707 | ||
| 
						 | 
					fa96ddb327 | ||
| 
						 | 
					a3e8450fd5 | ||
| 
						 | 
					41422f0df0 | ||
| 
						 | 
					f773733252 | ||
| 
						 | 
					cbaf8e8785 | ||
| 
						 | 
					1bf46836da | ||
| 
						 | 
					387a1bc472 | ||
| 
						 | 
					62daf84596 | ||
| 
						 | 
					39d209dccc | ||
| 
						 | 
					c88392e772 | ||
| 
						 | 
					a83cde2f3f | ||
| 
						 | 
					332eb2f6d2 | ||
| 
						 | 
					3ae1d7a59f | ||
| 
						 | 
					d054c4e7f3 | ||
| 
						 | 
					5e562e9b30 | ||
| 
						 | 
					c57e908f36 | ||
| 
						 | 
					1112fef93d | ||
| 
						 | 
					af11549fb2 | ||
| 
						 | 
					76d6184cd0 | ||
| 
						 | 
					d644709b22 | ||
| 
						 | 
					30584a6df8 | ||
| 
						 | 
					78710946f2 | ||
| 
						 | 
					22d700edfd | ||
| 
						 | 
					6782a64a4a | ||
| 
						 | 
					1ec11ac87e | ||
| 
						 | 
					2c2a30d6bb | ||
| 
						 | 
					717b313c34 | ||
| 
						 | 
					0a32861b28 | ||
| 
						 | 
					52ca7b9b65 | ||
| 
						 | 
					e078d08ecd | ||
| 
						 | 
					a83fb3a83a | ||
| 
						 | 
					f9b1fac4ea | ||
| 
						 | 
					f1e8b8c0d7 | ||
| 
						 | 
					dbbb75712d | ||
| 
						 | 
					462c6fdee2 | ||
| 
						 | 
					cead819cb5 | ||
| 
						 | 
					4fa2804238 | ||
| 
						 | 
					3ce46a7fbd | ||
| 
						 | 
					15886ce048 | ||
| 
						 | 
					a725d31496 | ||
| 
						 | 
					8e27f6e814 | ||
| 
						 | 
					54263ff123 | ||
| 
						 | 
					3bde297121 | ||
| 
						 | 
					0dfde367c1 | ||
| 
						 | 
					875501584b | ||
| 
						 | 
					4190c134e6 | ||
| 
						 | 
					cae46216e4 | ||
| 
						 | 
					761111f9ed | ||
| 
						 | 
					57f1476093 | ||
| 
						 | 
					bdba89452d | ||
| 
						 | 
					6e2dacfef6 | ||
| 
						 | 
					c0869c295a | ||
| 
						 | 
					a719311f6d | ||
| 
						 | 
					248b67af6f | ||
| 
						 | 
					990c6089db | ||
| 
						 | 
					5da024a019 | ||
| 
						 | 
					eff2499be7 | ||
| 
						 | 
					4a3c6384ac | ||
| 
						 | 
					2b1989e59f | ||
| 
						 | 
					340c4fc7c7 | ||
| 
						 | 
					918d3d96ff | ||
| 
						 | 
					92c91d7d8b | ||
| 
						 | 
					9dc76b2036 | ||
| 
						 | 
					802a4314ef | ||
| 
						 | 
					edd4ab49c8 | ||
| 
						 | 
					55e6cde7c1 | ||
| 
						 | 
					729fa06468 | ||
| 
						 | 
					b228a0aa44 | ||
| 
						 | 
					9e7e11224f | ||
| 
						 | 
					85880b2a0b | ||
| 
						 | 
					211bb911e3 | ||
| 
						 | 
					0554d1dd01 | ||
| 
						 | 
					00e55dd223 | ||
| 
						 | 
					b28c3245cc | ||
| 
						 | 
					ddfb729168 | ||
| 
						 | 
					6ef62e3f8e | ||
| 
						 | 
					2c4f1ed13e | ||
| 
						 | 
					fa3fe1e28a | ||
| 
						 | 
					62f5cf4386 | ||
| 
						 | 
					779d1185e7 | ||
| 
						 | 
					f3d0c76afc | ||
| 
						 | 
					5a4729d5e2 | ||
| 
						 | 
					88a7349375 | ||
| 
						 | 
					c3398906a1 | ||
| 
						 | 
					330fa75945 | ||
| 
						 | 
					55e159ca5f | ||
| 
						 | 
					87074ec860 | ||
| 
						 | 
					1fe5fe419e | ||
| 
						 | 
					67a12b8fac | ||
| 
						 | 
					e861dcbbaf | ||
| 
						 | 
					53c2136a9a | ||
| 
						 | 
					24ebc7e517 | ||
| 
						 | 
					b072907987 | ||
| 
						 | 
					942b0360ad | ||
| 
						 | 
					1ec4913add | ||
| 
						 | 
					16e34025b4 | ||
| 
						 | 
					456d63b6cf | ||
| 
						 | 
					798ac3f85a | ||
| 
						 | 
					460093b952 | ||
| 
						 | 
					38d184d518 | ||
| 
						 | 
					80b55263d8 | ||
| 
						 | 
					32232db55f | ||
| 
						 | 
					cf9b6c281f | ||
| 
						 | 
					a8c6a4a70e | ||
| 
						 | 
					e6050e80f7 | ||
| 
						 | 
					3803b15d76 | ||
| 
						 | 
					af73e1ee35 | ||
| 
						 | 
					4bc8dfc6a3 | ||
| 
						 | 
					33c4e246fe | ||
| 
						 | 
					8ec7beb9f4 | ||
| 
						 | 
					c6eb9b30ae | ||
| 
						 | 
					f75a9b27b0 | ||
| 
						 | 
					2705696d4d | ||
| 
						 | 
					2b68f66e0e | ||
| 
						 | 
					5c7d30cf52 | ||
| 
						 | 
					e520dff4da | ||
| 
						 | 
					2bc759518e | ||
| 
						 | 
					92b2883058 | ||
| 
						 | 
					0ebfc1405c | ||
| 
						 | 
					fd5c67226e | ||
| 
						 | 
					61308825a6 | ||
| 
						 | 
					0cccad04f0 | ||
| 
						 | 
					a0e5c49ac3 | ||
| 
						 | 
					3558310c1f | ||
| 
						 | 
					e99534cfd2 | ||
| 
						 | 
					27acf6165e | ||
| 
						 | 
					f286a28568 | ||
| 
						 | 
					b5c4cb1bde | ||
| 
						 | 
					26b98417ad | ||
| 
						 | 
					8b0cf88c0c | ||
| 
						 | 
					23db3375df | ||
| 
						 | 
					14011d77c9 | ||
| 
						 | 
					5519e26c2f | ||
| 
						 | 
					6feb435867 | ||
| 
						 | 
					61444ed8ca | ||
| 
						 | 
					d770cc9886 | ||
| 
						 | 
					fbaa01998a | ||
| 
						 | 
					ac2ae66ae7 | ||
| 
						 | 
					ed60fe0986 | ||
| 
						 | 
					29e0d62790 | ||
| 
						 | 
					7b464fa67b | 
							
								
								
									
										16
									
								
								.drone.yml
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								.drone.yml
									
									
									
									
									
								
							@@ -522,7 +522,7 @@ steps:
 | 
			
		||||
    image: plugins/s3:1
 | 
			
		||||
    settings:
 | 
			
		||||
      acl: public-read
 | 
			
		||||
      bucket: releases
 | 
			
		||||
      bucket: gitea-artifacts
 | 
			
		||||
      endpoint: https://storage.gitea.io
 | 
			
		||||
      path_style: true
 | 
			
		||||
      source: "dist/release/*"
 | 
			
		||||
@@ -543,7 +543,7 @@ steps:
 | 
			
		||||
    image: plugins/s3:1
 | 
			
		||||
    settings:
 | 
			
		||||
      acl: public-read
 | 
			
		||||
      bucket: releases
 | 
			
		||||
      bucket: gitea-artifacts
 | 
			
		||||
      endpoint: https://storage.gitea.io
 | 
			
		||||
      path_style: true
 | 
			
		||||
      source: "dist/release/*"
 | 
			
		||||
@@ -618,7 +618,7 @@ steps:
 | 
			
		||||
    image: plugins/s3:1
 | 
			
		||||
    settings:
 | 
			
		||||
      acl: public-read
 | 
			
		||||
      bucket: releases
 | 
			
		||||
      bucket: gitea-artifacts
 | 
			
		||||
      endpoint: https://storage.gitea.io
 | 
			
		||||
      path_style: true
 | 
			
		||||
      source: "dist/release/*"
 | 
			
		||||
@@ -709,7 +709,7 @@ steps:
 | 
			
		||||
 | 
			
		||||
  - name: publish
 | 
			
		||||
    pull: always
 | 
			
		||||
    image: plugins/docker:linux-amd64
 | 
			
		||||
    image: techknowlogick/drone-docker:latest
 | 
			
		||||
    settings:
 | 
			
		||||
      auto_tag: true
 | 
			
		||||
      auto_tag_suffix: linux-amd64
 | 
			
		||||
@@ -726,7 +726,7 @@ steps:
 | 
			
		||||
        - pull_request
 | 
			
		||||
 | 
			
		||||
  - name: publish-rootless
 | 
			
		||||
    image: plugins/docker:linux-amd64
 | 
			
		||||
    image: techknowlogick/drone-docker:latest
 | 
			
		||||
    settings:
 | 
			
		||||
      dockerfile: Dockerfile.rootless
 | 
			
		||||
      auto_tag: true
 | 
			
		||||
@@ -764,7 +764,7 @@ trigger:
 | 
			
		||||
steps:
 | 
			
		||||
  - name: dryrun
 | 
			
		||||
    pull: always
 | 
			
		||||
    image: plugins/docker:linux-arm64
 | 
			
		||||
    image: techknowlogick/drone-docker:latest
 | 
			
		||||
    settings:
 | 
			
		||||
      dry_run: true
 | 
			
		||||
      repo: gitea/gitea
 | 
			
		||||
@@ -806,7 +806,7 @@ steps:
 | 
			
		||||
 | 
			
		||||
  - name: publish
 | 
			
		||||
    pull: always
 | 
			
		||||
    image: plugins/docker:linux-arm64
 | 
			
		||||
    image: techknowlogick/drone-docker:latest
 | 
			
		||||
    settings:
 | 
			
		||||
      auto_tag: true
 | 
			
		||||
      auto_tag_suffix: linux-arm64
 | 
			
		||||
@@ -826,7 +826,7 @@ steps:
 | 
			
		||||
        - pull_request
 | 
			
		||||
 | 
			
		||||
  - name: publish-rootless
 | 
			
		||||
    image: plugins/docker:linux-arm64
 | 
			
		||||
    image: techknowlogick/drone-docker:latest
 | 
			
		||||
    settings:
 | 
			
		||||
      dockerfile: Dockerfile.rootless
 | 
			
		||||
      auto_tag: true
 | 
			
		||||
 
 | 
			
		||||
@@ -110,3 +110,7 @@ issues:
 | 
			
		||||
    - text: "exitAfterDefer:"
 | 
			
		||||
      linters:
 | 
			
		||||
        - gocritic
 | 
			
		||||
    - path: modules/graceful/manager_windows.go
 | 
			
		||||
      linters:
 | 
			
		||||
        - staticcheck
 | 
			
		||||
      text: "svc.IsAnInteractiveSession is deprecated: Use IsWindowsService instead."
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										235
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										235
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -4,14 +4,181 @@ 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.14.0-RC1](https://github.com/go-gitea/gitea/releases/tag/v1.14.0) - 2021-03-19
 | 
			
		||||
## [1.14.6](https://github.com/go-gitea/gitea/releases/tag/v1.14.6) - 2021-08-04
 | 
			
		||||
 | 
			
		||||
* SECURITY
 | 
			
		||||
  * Bump github.com/markbates/goth from v1.67.1 to v1.68.0 (#16538) (#16540)
 | 
			
		||||
  * Switch to maintained JWT lib (#16532) (#16535)
 | 
			
		||||
  * Upgrade to latest version of golang-jwt (as forked for 1.14) (#16590) (#16607)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Add basic edit ldap auth test & actually fix #16252 (#16465) (#16495)
 | 
			
		||||
  * Make cancel from CatFileBatch and CatFileBatchCheck wait for the command to end (#16479) (#16481)
 | 
			
		||||
 | 
			
		||||
## [1.14.5](https://github.com/go-gitea/gitea/releases/tag/v1.14.5) - 2021-07-16
 | 
			
		||||
 | 
			
		||||
* SECURITY
 | 
			
		||||
  * Hide mirror passwords on repo settings page (#16022) (#16355)
 | 
			
		||||
  * Update bluemonday to v1.0.15 (#16379) (#16380)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Retry rename on lock induced failures (#16435) (#16439)
 | 
			
		||||
  * Validate issue index before querying DB (#16406) (#16410)
 | 
			
		||||
  * Fix crash following ldap authentication update (#16447) (#16449)
 | 
			
		||||
* ENHANCEMENTS
 | 
			
		||||
  * Redirect on bad CSRF instead of presenting bad page (#14937) (#16378)
 | 
			
		||||
 | 
			
		||||
## [1.14.4](https://github.com/go-gitea/gitea/releases/tag/v1.14.4) - 2021-07-06
 | 
			
		||||
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Fix relative links in postprocessed images (#16334) (#16340)
 | 
			
		||||
  * Fix list_options GetStartEnd (#16303) (#16305)
 | 
			
		||||
  * Fix API to use author for commits instead of committer (#16276) (#16277)
 | 
			
		||||
  * Handle misencoding of login_source cfg in mssql (#16268) (#16275)
 | 
			
		||||
  * Fixed issues not updated by commits (#16254) (#16261)
 | 
			
		||||
  * Improve efficiency in FindRenderizableReferenceNumeric and getReference (#16251) (#16255)
 | 
			
		||||
  * Use html.Parse rather than html.ParseFragment (#16223) (#16225)
 | 
			
		||||
  * Fix milestone counters on new issue (#16183) (#16224)
 | 
			
		||||
  * reqOrgMembership calls need to be preceded by reqToken (#16198) (#16219)
 | 
			
		||||
 | 
			
		||||
## [1.14.3](https://github.com/go-gitea/gitea/releases/tag/v1.14.3) - 2021-06-10
 | 
			
		||||
 | 
			
		||||
* SECURITY
 | 
			
		||||
  * Encrypt migration credentials at rest (#15895) (#16187)
 | 
			
		||||
  * Only check access tokens if they are likely to be tokens (#16164) (#16171)
 | 
			
		||||
  * Add missing SameSite settings for the i_like_gitea cookie (#16037) (#16039)
 | 
			
		||||
  * Fix setting of SameSite on cookies (#15989) (#15991)
 | 
			
		||||
* API
 | 
			
		||||
  * Repository object only count releases as releases (#16184) (#16190)
 | 
			
		||||
  * EditOrg respect RepoAdminChangeTeamAccess option (#16184) (#16190)
 | 
			
		||||
  * Fix overly strict edit pr permissions (#15900) (#16081)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Run processors on whole of text (#16155) (#16185)
 | 
			
		||||
  * Class `issue-keyword` is being incorrectly stripped off spans (#16163) (#16172)
 | 
			
		||||
  * Fix language switch for install page (#16043) (#16128)
 | 
			
		||||
  * Fix bug on getIssueIDsByRepoID (#16119) (#16124)
 | 
			
		||||
  * Set self-adjusting deadline for connection writing (#16068) (#16123)
 | 
			
		||||
  * Fix http path bug (#16117) (#16120)
 | 
			
		||||
  * Fix data URI scramble (#16098) (#16118)
 | 
			
		||||
  * Merge all deleteBranch as one function and also fix bug when delete branch don't close related PRs (#16067) (#16097)
 | 
			
		||||
  * git migration: don't prompt interactively for clone credentials (#15902) (#16082)
 | 
			
		||||
  * Fix case change in ownernames (#16045) (#16050)
 | 
			
		||||
  * Don't manipulate input params in email notification (#16011) (#16033)
 | 
			
		||||
  * Remove branch URL before IssueRefURL (#15968) (#15970)
 | 
			
		||||
  * Fix layout of milestone view (#15927) (#15940)
 | 
			
		||||
  * GitHub Migration, migrate draft releases too (#15884) (#15888)
 | 
			
		||||
  * Close the gitrepo when deleting the repository (#15876) (#15887)
 | 
			
		||||
  * Upgrade xorm to v1.1.0 (#15869) (#15885)
 | 
			
		||||
  * Fix blame row height alignment (#15863) (#15883)
 | 
			
		||||
  * Fix error message when saving generated LOCAL_ROOT_URL config (#15880) (#15882)
 | 
			
		||||
  * Backport Fix LFS commit finder not working (#15856) (#15874)
 | 
			
		||||
  * Stop calling WriteHeader in Write (#15862) (#15873)
 | 
			
		||||
  * Add timeout to writing to responses (#15831) (#15872)
 | 
			
		||||
  * Return go-get info on subdirs (#15642) (#15871)
 | 
			
		||||
  * Restore PAM user autocreation functionality (#15825) (#15867)
 | 
			
		||||
  * Fix truncate utf8 string (#15828) (#15854)
 | 
			
		||||
  * Fix bound address/port for caddy's certmagic library (#15758) (#15848)
 | 
			
		||||
  * Upgrade unrolled/render to v1.1.1 (#15845) (#15846)
 | 
			
		||||
  * Queue manager FlushAll can loop rapidly - add delay (#15733) (#15840)
 | 
			
		||||
  * Tagger can be empty, as can Commit and Author - tolerate this (#15835) (#15839)
 | 
			
		||||
  * Set autocomplete off on branches selector (#15809) (#15833)
 | 
			
		||||
  * Add missing error to Doctor log (#15813) (#15824)
 | 
			
		||||
  * Move restore repo to internal router and invoke from command to avoid open the same db file or queues files (#15790) (#15816)
 | 
			
		||||
* ENHANCEMENTS
 | 
			
		||||
  * Removable media support to snap package (#16136) (#16138)
 | 
			
		||||
  * Move sans-serif fallback font higher than emoji fonts (#15855) (#15892)
 | 
			
		||||
* DOCKER
 | 
			
		||||
  * Only write config in environment-to-ini if there are changes (#15861) (#15868)
 | 
			
		||||
  * Only offer hostcertificates if they exist (#15849) (#15853)
 | 
			
		||||
 | 
			
		||||
## [1.14.2](https://github.com/go-gitea/gitea/releases/tag/v1.14.2) - 2021-05-08
 | 
			
		||||
 | 
			
		||||
* API
 | 
			
		||||
  * Make change repo settings work on empty repos (#15778) (#15789)
 | 
			
		||||
  * Add pull "merged" notification subject status to API (#15344) (#15654)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Ensure that ctx.Written is checked after issues(...) calls (#15797) (#15798)
 | 
			
		||||
  * Use pulls in commit graph unless pulls are disabled (#15734 & #15740 & #15774) (#15775)
 | 
			
		||||
  * Set GIT_DIR correctly if it is not set (#15751) (#15769)
 | 
			
		||||
  * Fix bug where repositories appear unadopted (#15757) (#15767)
 | 
			
		||||
  * Not show `ref-in-new-issue` pop when issue was disabled (#15761) (#15765)
 | 
			
		||||
  * Drop back to use IsAnInteractiveSession for SVC (#15749) (#15762)
 | 
			
		||||
  * Fix setting version table in dump (#15753) (#15759)
 | 
			
		||||
  * Fix close button change on delete in simplemde area (#15737) (#15747)
 | 
			
		||||
  * Defer closing the gitrepo until the end of the wrapped context functions (#15653) (#15746)
 | 
			
		||||
  * Fix some ui bug about draft release (#15137) (#15745)
 | 
			
		||||
  * Only log Error on getLastCommitStatus error to let pull list still be visible (#15716) (#15715)
 | 
			
		||||
  * Move tooltip down to allow selection of Remove File on error (#15672) (#15714)
 | 
			
		||||
  * Fix setting redis db path (#15698) (#15708)
 | 
			
		||||
  * Fix DB session cleanup (#15697) (#15700)
 | 
			
		||||
  * Fixed several activation bugs (#15473) (#15685)
 | 
			
		||||
  * Delete references if repository gets deleted (#15681) (#15684)
 | 
			
		||||
  * Fix orphaned objects deletion bug (#15657) (#15683)
 | 
			
		||||
  * Delete protected branch if repository gets removed (#15658) (#15676)
 | 
			
		||||
  * Remove spurious set name from eventsource.sharedworker.js (#15643) (#15652)
 | 
			
		||||
  * Not update updated uinx for `git gc` (#15637) (#15641)
 | 
			
		||||
  * Fix commit graph author link (#15627) (#15630)
 | 
			
		||||
  * Fix webhook timeout bug (#15613) (#15621)
 | 
			
		||||
  * Resolve panic on failed interface conversion in migration v156 (#15604) (#15610)
 | 
			
		||||
  * Fix missing storage init (#15589) (#15598)
 | 
			
		||||
  * If the default branch is not present do not report error on stats indexing (#15546 & #15583) (#15594)
 | 
			
		||||
  * Fix lfs management find (#15537) (#15578)
 | 
			
		||||
  * Fix NPE on view commit with notes (#15561) (#15573)
 | 
			
		||||
  * Fix bug on commit graph (#15517) (#15530)
 | 
			
		||||
  * Send size to /avatars if requested (#15459) (#15528)
 | 
			
		||||
  * Prevent migration 156 failure if tag commit missing (#15519) (#15527)
 | 
			
		||||
* ENHANCEMENTS
 | 
			
		||||
  * Display conflict-free merge messages for pull requests (#15773) (#15796)
 | 
			
		||||
  * Exponential Backoff for ByteFIFO (#15724) (#15793)
 | 
			
		||||
  * Issue list alignment tweaks (#15483) (#15766)
 | 
			
		||||
  * Implement delete release attachments and update release attachments' name (#14130) (#15666)
 | 
			
		||||
  * Add placeholder text to deploy key textarea (#15575) (#15576)
 | 
			
		||||
  * Project board improvements (#15429) (#15560)
 | 
			
		||||
  * Repo branch page: label size, PR ref, new PR button alignment (#15363) (#15365)
 | 
			
		||||
* MISC
 | 
			
		||||
  * Fix webkit calendar icon color on arc-green (#15713) (#15728)
 | 
			
		||||
  * Performance improvement for last commit cache and show-ref (#15455) (#15701)
 | 
			
		||||
  * Bump unrolled/render to v1.1.0 (#15581) (#15608)
 | 
			
		||||
  * Add ETag header (#15370) (#15552)
 | 
			
		||||
 | 
			
		||||
## [1.14.1](https://github.com/go-gitea/gitea/releases/tag/v1.14.1) - 2021-04-15
 | 
			
		||||
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Fix bug clone wiki (#15499) (#15502)
 | 
			
		||||
  * Github Migration ignore rate limit, if not enabled (#15490) (#15495)
 | 
			
		||||
  * Use subdir for URL (#15446) (#15493)
 | 
			
		||||
  * Query the DB for the hash before inserting in to email_hash (#15457) (#15491)
 | 
			
		||||
  * Ensure review dismissal only dismisses the correct review (#15477) (#15489)
 | 
			
		||||
  * Use index of the supported tags to choose user lang (#15452) (#15488)
 | 
			
		||||
  * Fix wrong file link in code search page (#15466) (#15486)
 | 
			
		||||
  * Quick template fix for built-in SSH server in admin config (#15464) (#15481)
 | 
			
		||||
  * Prevent superfluous response.WriteHeader (#15456) (#15476)
 | 
			
		||||
  * Fix ambiguous argument error on tags (#15432) (#15474)
 | 
			
		||||
  * Add created_unix instead of expiry to migration (#15458) (#15463)
 | 
			
		||||
  * Fix repository search (#15428) (#15442)
 | 
			
		||||
  * Prevent NPE on avatar direct rendering if federated avatars disabled (#15434) (#15439)
 | 
			
		||||
  * Fix wiki clone urls (#15430) (#15431)
 | 
			
		||||
  * Fix dingtalk icon url at webhook (#15417) (#15426)
 | 
			
		||||
  * Standardise icon on projects PR page (#15387) (#15408)
 | 
			
		||||
* ENHANCEMENTS
 | 
			
		||||
  * Add option to skip LFS/attachment files for `dump` (#15407) (#15492)
 | 
			
		||||
  * Clone panel fixes (#15436)
 | 
			
		||||
  * Use semantic dropdown for code search query type (#15276) (#15364)
 | 
			
		||||
* BUILD
 | 
			
		||||
  * Build go-git variants for windows (#15482) (#15487)
 | 
			
		||||
  * Lock down build-images dependencies (Partial #15479) (#15480)
 | 
			
		||||
* MISC
 | 
			
		||||
  * Performance improvement for list pull requests (#15447) (#15500)
 | 
			
		||||
  * Fix potential copy lfs records failure when fork a repository (#15441) (#15485)
 | 
			
		||||
 | 
			
		||||
## [1.14.0](https://github.com/go-gitea/gitea/releases/tag/v1.14.0) - 2021-04-11
 | 
			
		||||
 | 
			
		||||
* SECURITY
 | 
			
		||||
  * Respect approved email domain list for externally validated user registration (#15014)
 | 
			
		||||
  * Add reverse proxy configuration support for remote IP address detection (#14959)
 | 
			
		||||
  * Ensure validation occurs on clone addresses too (#14994)
 | 
			
		||||
  * Fix several render issues highlighted during fuzzing (#14986)
 | 
			
		||||
* BREAKING
 | 
			
		||||
  * Fix double 'push tag' action feed (#15078) (#15083)
 | 
			
		||||
  * Remove possible resource leak (#15067) (#15082)
 | 
			
		||||
  * Handle unauthorized user events gracefully (#15071) (#15074)
 | 
			
		||||
  * Restore Access.log following migration to Chi framework (Stops access logging of /api/internal routes) (#14475)
 | 
			
		||||
  * Migrate from Macaron to Chi framework (#14293)
 | 
			
		||||
  * Deprecate building for mips (#14174)
 | 
			
		||||
@@ -42,6 +209,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
 | 
			
		||||
  * Dump github/gitlab/gitea repository data to a local directory and restore to gitea (#12244)
 | 
			
		||||
  * Create Rootless Docker image (#10154)
 | 
			
		||||
* API
 | 
			
		||||
  * Speedup issue search (#15179) (#15192)
 | 
			
		||||
  * Get pull, return head branch sha, even if deleted (#14931)
 | 
			
		||||
  * Export LFS & TimeTracking function status (#14753)
 | 
			
		||||
  * Show Gitea version in swagger (#14654)
 | 
			
		||||
@@ -66,6 +234,20 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
 | 
			
		||||
  * Add more filters to issues search (#13514)
 | 
			
		||||
  * Add review request api (#11355)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Fix delete nonexist oauth application 500 and prevent deadlock (#15384) (#15396)
 | 
			
		||||
  * Always set the merge base used to merge the commit (#15352) (#15385)
 | 
			
		||||
  * Upgrade to bluemonday 1.0.7 (#15379) (#15380)
 | 
			
		||||
  * Turn RepoRef and RepoAssignment back into func(*Context) (#15372) (#15377)
 | 
			
		||||
  * Move FCGI req.URL.Path fix-up to the FCGI listener (#15292) (#15361)
 | 
			
		||||
  * Show diff on rename with diff changes (#15338) (#15339)
 | 
			
		||||
  * Fix handling of logout event (#15323) (#15337)
 | 
			
		||||
  * Fix CanCreateRepo check (#15311) (#15321)
 | 
			
		||||
  * Fix xorm log stack level (#15285) (#15316)
 | 
			
		||||
  * Fix bug in Wrap (#15302) (#15309)
 | 
			
		||||
  * Drop the event source if we are unauthorized (#15275) (#15280)
 | 
			
		||||
  * Backport Fix graph pagination (#15225)  (#15249)
 | 
			
		||||
  * Prevent NPE in CommentMustAsDiff if no hunk header (#15199) (#15200)
 | 
			
		||||
  * should run RetrieveRepoMetas() for empty pr (#15187) (#15190)
 | 
			
		||||
  * Move setting to enable closing issue via commit in non default branch to repo settings (#14965)
 | 
			
		||||
  * Show correct issues for team dashboard (#14952)
 | 
			
		||||
  * Ensure that new pull request button works on forked forks owned by owner of the root and reduce ambiguity (#14932)
 | 
			
		||||
@@ -122,6 +304,9 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
 | 
			
		||||
  * Use GO variable in go-check target (#13146) (#13147)
 | 
			
		||||
* ENHANCEMENTS
 | 
			
		||||
  * UI style improvements
 | 
			
		||||
  * Dropzone styling improvements (#15291) (#15374)
 | 
			
		||||
  * Add size to Save function (#15264) (#15270)
 | 
			
		||||
  * Monaco improvements (#15333) (#15345)
 | 
			
		||||
  * Support .mailmap in code activity stats (#15009)
 | 
			
		||||
  * Sort release attachments by name (#15008)  
 | 
			
		||||
  * Add ui.explore settings to control view of explore pages (#14094)
 | 
			
		||||
@@ -267,6 +452,52 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
 | 
			
		||||
  * Reduce make verbosity (#13803)
 | 
			
		||||
  * Add git command error directory on log (#13194)
 | 
			
		||||
 | 
			
		||||
## [1.13.7](https://github.com/go-gitea/gitea/releases/tag/v1.13.7) - 2021-04-07
 | 
			
		||||
 | 
			
		||||
* SECURITY
 | 
			
		||||
  * Update to bluemonday-1.0.6 (#15294) (#15298)
 | 
			
		||||
  * Clusterfuzz found another way (#15160) (#15169)
 | 
			
		||||
* API
 | 
			
		||||
  * Fix wrong user returned in API (#15139) (#15150)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Add 'fonts' into 'KnownPublicEntries' (#15188) (#15317)
 | 
			
		||||
  * Speed up `enry.IsVendor` (#15213) (#15246)
 | 
			
		||||
  * Response 404 for diff/patch of a commit that not exist (#15221) (#15238)
 | 
			
		||||
  * Prevent NPE in CommentMustAsDiff if no hunk header (#15199) (#15201)
 | 
			
		||||
* MISC
 | 
			
		||||
  * Add size to Save function (#15264) (#15271)
 | 
			
		||||
 | 
			
		||||
## [1.13.6](https://github.com/go-gitea/gitea/releases/tag/v1.13.6) - 2021-03-23
 | 
			
		||||
 | 
			
		||||
* SECURITY
 | 
			
		||||
  * Fix bug on avatar middleware (#15124) (#15125)
 | 
			
		||||
  * Fix another clusterfuzz identified issue (#15096) (#15114)
 | 
			
		||||
* API
 | 
			
		||||
  * Fix nil pointer exception in get pull reviews API (#15106)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Fix markdown rendering in milestone content (#15056) (#15092)
 | 
			
		||||
 | 
			
		||||
## [1.13.5](https://github.com/go-gitea/gitea/releases/tag/v1.13.5) - 2021-03-21
 | 
			
		||||
 | 
			
		||||
* SECURITY
 | 
			
		||||
  * Update to goldmark 1.3.3 (#15059) (#15061)
 | 
			
		||||
  * Another clusterfuzz spotted issue (#15032) (#15034)
 | 
			
		||||
* API
 | 
			
		||||
  * Fix set milestone on PR creation (#14981) (#15001)
 | 
			
		||||
  * Prevent panic when editing forked repos by API (#14960) (#14963)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Fix bug when upload on web (#15042) (#15055)
 | 
			
		||||
  * Delete Labels & IssueLabels on Repo Delete too (#15039) (#15051)
 | 
			
		||||
  * Fix postgres ID sequences broken by recreate-table (#15015) (#15029)
 | 
			
		||||
  * Fix several render issues (#14986) (#15013)
 | 
			
		||||
  * Make sure sibling images get a link too (#14979) (#14995)
 | 
			
		||||
  * Fix Anchor jumping with escaped query components (#14969) (#14977)
 | 
			
		||||
  * Fix release mail html template (#14976)
 | 
			
		||||
  * Fix excluding more than two labels on issues list (#14962) (#14973)
 | 
			
		||||
  * Don't mark each comment poster as OP (#14971) (#14972)
 | 
			
		||||
  * Add "captcha" to list of reserved usernames (#14930)
 | 
			
		||||
  * Re-enable import local paths after reversion from #13610 (#14925) (#14927)
 | 
			
		||||
 | 
			
		||||
## [1.13.4](https://github.com/go-gitea/gitea/releases/tag/v1.13.4) - 2021-03-07
 | 
			
		||||
 | 
			
		||||
* SECURITY
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										7
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								Makefile
									
									
									
									
									
								
							@@ -577,6 +577,9 @@ release-windows: | $(DIST_DIRS)
 | 
			
		||||
		$(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) .
 | 
			
		||||
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 .
 | 
			
		||||
endif
 | 
			
		||||
ifeq ($(CI),drone)
 | 
			
		||||
	cp /build/* $(DIST)/binaries
 | 
			
		||||
endif
 | 
			
		||||
@@ -699,8 +702,8 @@ generate-gitignore:
 | 
			
		||||
	GO111MODULE=on $(GO) run build/generate-gitignores.go
 | 
			
		||||
 | 
			
		||||
.PHONY: generate-images
 | 
			
		||||
generate-images:
 | 
			
		||||
	npm install --no-save --no-package-lock fabric imagemin-zopfli
 | 
			
		||||
generate-images: | node_modules
 | 
			
		||||
	npm install --no-save --no-package-lock fabric@4 imagemin-zopfli@7
 | 
			
		||||
	node build/generate-images.js $(TAGS)
 | 
			
		||||
 | 
			
		||||
.PHONY: generate-manpage
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@ import (
 | 
			
		||||
	pwd "code.gitea.io/gitea/modules/password"
 | 
			
		||||
	repo_module "code.gitea.io/gitea/modules/repository"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/modules/storage"
 | 
			
		||||
 | 
			
		||||
	"github.com/urfave/cli"
 | 
			
		||||
)
 | 
			
		||||
@@ -489,6 +490,10 @@ func runDeleteUser(c *cli.Context) error {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := storage.Init(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var err error
 | 
			
		||||
	var user *models.User
 | 
			
		||||
	if c.IsSet("email") {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										16
									
								
								cmd/dump.go
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								cmd/dump.go
									
									
									
									
									
								
							@@ -129,6 +129,14 @@ It can be used for backup and capture Gitea server image to send to maintainer`,
 | 
			
		||||
			Name:  "skip-custom-dir",
 | 
			
		||||
			Usage: "Skip custom directory",
 | 
			
		||||
		},
 | 
			
		||||
		cli.BoolFlag{
 | 
			
		||||
			Name:  "skip-lfs-data",
 | 
			
		||||
			Usage: "Skip LFS data",
 | 
			
		||||
		},
 | 
			
		||||
		cli.BoolFlag{
 | 
			
		||||
			Name:  "skip-attachment-data",
 | 
			
		||||
			Usage: "Skip attachment data",
 | 
			
		||||
		},
 | 
			
		||||
		cli.GenericFlag{
 | 
			
		||||
			Name:  "type",
 | 
			
		||||
			Value: outputTypeEnum,
 | 
			
		||||
@@ -214,7 +222,9 @@ func runDump(ctx *cli.Context) error {
 | 
			
		||||
			fatal("Failed to include repositories: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := storage.LFS.IterateObjects(func(objPath string, object storage.Object) error {
 | 
			
		||||
		if ctx.IsSet("skip-lfs-data") && ctx.Bool("skip-lfs-data") {
 | 
			
		||||
			log.Info("Skip dumping LFS data")
 | 
			
		||||
		} else if err := storage.LFS.IterateObjects(func(objPath string, object storage.Object) error {
 | 
			
		||||
			info, err := object.Stat()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
@@ -313,7 +323,9 @@ func runDump(ctx *cli.Context) error {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := storage.Attachments.IterateObjects(func(objPath string, object storage.Object) error {
 | 
			
		||||
	if ctx.IsSet("skip-attachment-data") && ctx.Bool("skip-attachment-data") {
 | 
			
		||||
		log.Info("Skip dumping attachment data")
 | 
			
		||||
	} else if err := storage.Attachments.IterateObjects(func(objPath string, object storage.Object) error {
 | 
			
		||||
		info, err := object.Stat()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/modules/public"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/modules/templates"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
 | 
			
		||||
	"github.com/gobwas/glob"
 | 
			
		||||
	"github.com/urfave/cli"
 | 
			
		||||
@@ -271,7 +272,7 @@ func extractAsset(d string, a asset, overwrite, rename bool) error {
 | 
			
		||||
	} else if !fi.Mode().IsRegular() {
 | 
			
		||||
		return fmt.Errorf("%s already exists, but it's not a regular file", dest)
 | 
			
		||||
	} else if rename {
 | 
			
		||||
		if err := os.Rename(dest, dest+".bak"); err != nil {
 | 
			
		||||
		if err := util.Rename(dest, dest+".bak"); err != nil {
 | 
			
		||||
			return fmt.Errorf("Error creating backup for %s: %v", dest, err)
 | 
			
		||||
		}
 | 
			
		||||
		// Attempt to respect file permissions mask (even if user:group will be set anew)
 | 
			
		||||
 
 | 
			
		||||
@@ -5,15 +5,12 @@
 | 
			
		||||
package cmd
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/migrations"
 | 
			
		||||
	"code.gitea.io/gitea/modules/migrations/base"
 | 
			
		||||
	"code.gitea.io/gitea/modules/private"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/modules/storage"
 | 
			
		||||
	pull_service "code.gitea.io/gitea/services/pull"
 | 
			
		||||
 | 
			
		||||
	"github.com/urfave/cli"
 | 
			
		||||
)
 | 
			
		||||
@@ -50,70 +47,18 @@ wiki, issues, labels, releases, release_assets, milestones, pull_requests, comme
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func runRestoreRepository(ctx *cli.Context) error {
 | 
			
		||||
	if err := initDB(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	setting.NewContext()
 | 
			
		||||
 | 
			
		||||
	log.Trace("AppPath: %s", setting.AppPath)
 | 
			
		||||
	log.Trace("AppWorkPath: %s", setting.AppWorkPath)
 | 
			
		||||
	log.Trace("Custom path: %s", setting.CustomPath)
 | 
			
		||||
	log.Trace("Log path: %s", setting.LogRootPath)
 | 
			
		||||
	setting.InitDBConfig()
 | 
			
		||||
 | 
			
		||||
	if err := storage.Init(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := pull_service.Init(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var opts = base.MigrateOptions{
 | 
			
		||||
		RepoName: ctx.String("repo_name"),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(ctx.String("units")) == 0 {
 | 
			
		||||
		opts.Wiki = true
 | 
			
		||||
		opts.Issues = true
 | 
			
		||||
		opts.Milestones = true
 | 
			
		||||
		opts.Labels = true
 | 
			
		||||
		opts.Releases = true
 | 
			
		||||
		opts.Comments = true
 | 
			
		||||
		opts.PullRequests = true
 | 
			
		||||
		opts.ReleaseAssets = true
 | 
			
		||||
	} else {
 | 
			
		||||
		units := strings.Split(ctx.String("units"), ",")
 | 
			
		||||
		for _, unit := range units {
 | 
			
		||||
			switch strings.ToLower(unit) {
 | 
			
		||||
			case "wiki":
 | 
			
		||||
				opts.Wiki = true
 | 
			
		||||
			case "issues":
 | 
			
		||||
				opts.Issues = true
 | 
			
		||||
			case "milestones":
 | 
			
		||||
				opts.Milestones = true
 | 
			
		||||
			case "labels":
 | 
			
		||||
				opts.Labels = true
 | 
			
		||||
			case "releases":
 | 
			
		||||
				opts.Releases = true
 | 
			
		||||
			case "release_assets":
 | 
			
		||||
				opts.ReleaseAssets = true
 | 
			
		||||
			case "comments":
 | 
			
		||||
				opts.Comments = true
 | 
			
		||||
			case "pull_requests":
 | 
			
		||||
				opts.PullRequests = true
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := migrations.RestoreRepository(
 | 
			
		||||
		context.Background(),
 | 
			
		||||
	statusCode, errStr := private.RestoreRepo(
 | 
			
		||||
		ctx.String("repo_dir"),
 | 
			
		||||
		ctx.String("owner_name"),
 | 
			
		||||
		ctx.String("repo_name"),
 | 
			
		||||
	); err != nil {
 | 
			
		||||
		log.Fatal("Failed to restore repository: %v", err)
 | 
			
		||||
		return err
 | 
			
		||||
		ctx.StringSlice("units"),
 | 
			
		||||
	)
 | 
			
		||||
	if statusCode == http.StatusOK {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
	log.Fatal("Failed to restore repository: %v", errStr)
 | 
			
		||||
	return errors.New(errStr)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/modules/private"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
 | 
			
		||||
	"github.com/dgrijalva/jwt-go"
 | 
			
		||||
	"github.com/golang-jwt/jwt"
 | 
			
		||||
	jsoniter "github.com/json-iterator/go"
 | 
			
		||||
	"github.com/kballard/go-shellquote"
 | 
			
		||||
	"github.com/urfave/cli"
 | 
			
		||||
 
 | 
			
		||||
@@ -175,7 +175,7 @@ func setPort(port string) error {
 | 
			
		||||
 | 
			
		||||
		cfg.Section("server").Key("LOCAL_ROOT_URL").SetValue(defaultLocalURL)
 | 
			
		||||
		if err := cfg.SaveTo(setting.CustomConf); err != nil {
 | 
			
		||||
			return fmt.Errorf("Error saving generated JWT Secret to custom config: %v", err)
 | 
			
		||||
			return fmt.Errorf("Error saving generated LOCAL_ROOT_URL to custom config: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
 
 | 
			
		||||
@@ -9,9 +9,11 @@ import (
 | 
			
		||||
	"net"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/http/fcgi"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/graceful"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func runHTTP(network, listenAddr, name string, m http.Handler) error {
 | 
			
		||||
@@ -48,7 +50,12 @@ func runFCGI(network, listenAddr, name string, m http.Handler) error {
 | 
			
		||||
	fcgiServer := graceful.NewServer(network, listenAddr, name)
 | 
			
		||||
 | 
			
		||||
	err := fcgiServer.ListenAndServe(func(listener net.Listener) error {
 | 
			
		||||
		return fcgi.Serve(listener, m)
 | 
			
		||||
		return fcgi.Serve(listener, http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
 | 
			
		||||
			if setting.AppSubURL != "" {
 | 
			
		||||
				req.URL.Path = strings.TrimPrefix(req.URL.Path, setting.AppSubURL)
 | 
			
		||||
			}
 | 
			
		||||
			m.ServeHTTP(resp, req)
 | 
			
		||||
		}))
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal("Failed to start FCGI main server: %v", err)
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@ package cmd
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
@@ -22,6 +23,15 @@ func runLetsEncrypt(listenAddr, domain, directory, email string, m http.Handler)
 | 
			
		||||
	// TODO: these are placeholders until we add options for each in settings with appropriate warning
 | 
			
		||||
	enableHTTPChallenge := true
 | 
			
		||||
	enableTLSALPNChallenge := true
 | 
			
		||||
	altHTTPPort := 0
 | 
			
		||||
	altTLSALPNPort := 0
 | 
			
		||||
 | 
			
		||||
	if p, err := strconv.Atoi(setting.PortToRedirect); err == nil {
 | 
			
		||||
		altHTTPPort = p
 | 
			
		||||
	}
 | 
			
		||||
	if p, err := strconv.Atoi(setting.HTTPPort); err == nil {
 | 
			
		||||
		altTLSALPNPort = p
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	magic := certmagic.NewDefault()
 | 
			
		||||
	magic.Storage = &certmagic.FileStorage{Path: directory}
 | 
			
		||||
@@ -30,6 +40,9 @@ func runLetsEncrypt(listenAddr, domain, directory, email string, m http.Handler)
 | 
			
		||||
		Agreed:                  setting.LetsEncryptTOS,
 | 
			
		||||
		DisableHTTPChallenge:    !enableHTTPChallenge,
 | 
			
		||||
		DisableTLSALPNChallenge: !enableTLSALPNChallenge,
 | 
			
		||||
		ListenHost:              setting.HTTPAddr,
 | 
			
		||||
		AltTLSALPNPort:          altTLSALPNPort,
 | 
			
		||||
		AltHTTPPort:             altHTTPPort,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	magic.Issuer = myACME
 | 
			
		||||
 
 | 
			
		||||
@@ -110,6 +110,8 @@ func runEnvironmentToIni(c *cli.Context) error {
 | 
			
		||||
	}
 | 
			
		||||
	cfg.NameMapper = ini.SnackCase
 | 
			
		||||
 | 
			
		||||
	changed := false
 | 
			
		||||
 | 
			
		||||
	prefix := c.String("prefix") + "__"
 | 
			
		||||
 | 
			
		||||
	for _, kv := range os.Environ() {
 | 
			
		||||
@@ -143,15 +145,21 @@ func runEnvironmentToIni(c *cli.Context) error {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		oldValue := key.Value()
 | 
			
		||||
		if !changed && oldValue != value {
 | 
			
		||||
			changed = true
 | 
			
		||||
		}
 | 
			
		||||
		key.SetValue(value)
 | 
			
		||||
	}
 | 
			
		||||
	destination := c.String("out")
 | 
			
		||||
	if len(destination) == 0 {
 | 
			
		||||
		destination = setting.CustomConf
 | 
			
		||||
	}
 | 
			
		||||
	err = cfg.SaveTo(destination)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	if destination != setting.CustomConf || changed {
 | 
			
		||||
		err = cfg.SaveTo(destination)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if c.Bool("clear") {
 | 
			
		||||
		for _, kv := range os.Environ() {
 | 
			
		||||
 
 | 
			
		||||
@@ -281,6 +281,10 @@ HTTP_PORT = 3000
 | 
			
		||||
; PORT_TO_REDIRECT.
 | 
			
		||||
REDIRECT_OTHER_PORT = false
 | 
			
		||||
PORT_TO_REDIRECT = 80
 | 
			
		||||
; Timeout for any write to the connection. (Set to 0 to disable all timeouts.)
 | 
			
		||||
PER_WRITE_TIMEOUT = 30s
 | 
			
		||||
; Timeout per Kb written to connections.
 | 
			
		||||
PER_WRITE_PER_KB_TIMEOUT = 30s
 | 
			
		||||
; Permission for unix socket
 | 
			
		||||
UNIX_SOCKET_PERMISSION = 666
 | 
			
		||||
; Local (DMZ) URL for Gitea workers (such as SSH update) accessing web service.
 | 
			
		||||
 
 | 
			
		||||
@@ -24,9 +24,29 @@ if [ ! -f /data/ssh/ssh_host_ecdsa_key ]; then
 | 
			
		||||
    ssh-keygen -t ecdsa -b 256 -f /data/ssh/ssh_host_ecdsa_key -N "" > /dev/null
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
if [ -e /data/ssh/ssh_host_ed25519_cert ]; then
 | 
			
		||||
  SSH_ED25519_CERT=${SSH_ED25519_CERT:-"/data/ssh/ssh_host_ed25519_cert"}
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
if [ -e /data/ssh/ssh_host_rsa_cert ]; then
 | 
			
		||||
  SSH_RSA_CERT=${SSH_RSA_CERT:-"/data/ssh/ssh_host_rsa_cert"}
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
if [ -e /data/ssh/ssh_host_ecdsa_cert ]; then
 | 
			
		||||
  SSH_ECDSA_CERT=${SSH_ECDSA_CERT:-"/data/ssh/ssh_host_ecdsa_cert"}
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
if [ -e /data/ssh/ssh_host_dsa_cert ]; then
 | 
			
		||||
  SSH_DSA_CERT=${SSH_DSA_CERT:-"/data/ssh/ssh_host_dsa_cert"}
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
if [ -d /etc/ssh ]; then
 | 
			
		||||
    SSH_PORT=${SSH_PORT:-"22"} \
 | 
			
		||||
    SSH_LISTEN_PORT=${SSH_LISTEN_PORT:-"${SSH_PORT}"} \
 | 
			
		||||
    SSH_ED25519_CERT="${SSH_ED25519_CERT:+"HostCertificate "}${SSH_ED25519_CERT}" \
 | 
			
		||||
    SSH_RSA_CERT="${SSH_RSA_CERT:+"HostCertificate "}${SSH_RSA_CERT}" \
 | 
			
		||||
    SSH_ECDSA_CERT="${SSH_ECDSA_CERT:+"HostCertificate "}${SSH_ECDSA_CERT}" \
 | 
			
		||||
    SSH_DSA_CERT="${SSH_DSA_CERT:+"HostCertificate "}${SSH_DSA_CERT}" \
 | 
			
		||||
    envsubst < /etc/templates/sshd_config > /etc/ssh/sshd_config
 | 
			
		||||
 | 
			
		||||
    chmod 0644 /etc/ssh/sshd_config
 | 
			
		||||
 
 | 
			
		||||
@@ -8,13 +8,13 @@ ListenAddress ::
 | 
			
		||||
LogLevel INFO
 | 
			
		||||
 | 
			
		||||
HostKey /data/ssh/ssh_host_ed25519_key
 | 
			
		||||
HostCertificate /data/ssh/ssh_host_ed25519_cert
 | 
			
		||||
${SSH_ED25519_CERT}
 | 
			
		||||
HostKey /data/ssh/ssh_host_rsa_key
 | 
			
		||||
HostCertificate /data/ssh/ssh_host_rsa_cert
 | 
			
		||||
${SSH_RSA_CERT}
 | 
			
		||||
HostKey /data/ssh/ssh_host_ecdsa_key
 | 
			
		||||
HostCertificate /data/ssh/ssh_host_ecdsa_cert
 | 
			
		||||
${SSH_ECDSA_CERT}
 | 
			
		||||
HostKey /data/ssh/ssh_host_dsa_key
 | 
			
		||||
HostCertificate /data/ssh/ssh_host_dsa_cert
 | 
			
		||||
${SSH_DSA_CERT}
 | 
			
		||||
 | 
			
		||||
AuthorizedKeysFile .ssh/authorized_keys
 | 
			
		||||
AuthorizedPrincipalsFile .ssh/authorized_principals
 | 
			
		||||
 
 | 
			
		||||
@@ -31,4 +31,4 @@ update: $(THEME)
 | 
			
		||||
$(THEME): $(THEME)/theme.toml
 | 
			
		||||
$(THEME)/theme.toml:
 | 
			
		||||
	mkdir -p $$(dirname $@)
 | 
			
		||||
	curl -s $(ARCHIVE) | tar xz -C $$(dirname $@)
 | 
			
		||||
	curl -L -s $(ARCHIVE) | tar xz -C $$(dirname $@)
 | 
			
		||||
 
 | 
			
		||||
@@ -237,6 +237,9 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
 | 
			
		||||
   most cases you do not need to change the default value. Alter it only if
 | 
			
		||||
   your SSH server node is not the same as HTTP node. Do not set this variable
 | 
			
		||||
   if `PROTOCOL` is set to `unix`.
 | 
			
		||||
- `PER_WRITE_TIMEOUT`: **30s**: Timeout for any write to the connection. (Set to 0 to
 | 
			
		||||
   disable all timeouts.)
 | 
			
		||||
- `PER_WRITE_PER_KB_TIMEOUT`: **10s**: Timeout per Kb written to connections.
 | 
			
		||||
 | 
			
		||||
- `DISABLE_SSH`: **false**: Disable SSH feature when it's not available.
 | 
			
		||||
- `START_SSH_SERVER`: **false**: When enabled, use the built-in SSH server.
 | 
			
		||||
@@ -260,6 +263,9 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
 | 
			
		||||
- `SSH_KEY_TEST_PATH`: **/tmp**: Directory to create temporary files in when testing public keys using ssh-keygen, default is the system temporary directory.
 | 
			
		||||
- `SSH_KEYGEN_PATH`: **ssh-keygen**: Path to ssh-keygen, default is 'ssh-keygen' which means the shell is responsible for finding out which one to call.
 | 
			
		||||
- `SSH_EXPOSE_ANONYMOUS`: **false**: Enable exposure of SSH clone URL to anonymous visitors, default is false.
 | 
			
		||||
- `SSH_PER_WRITE_TIMEOUT`: **30s**: Timeout for any write to the SSH connections. (Set to
 | 
			
		||||
  0 to disable all timeouts.)
 | 
			
		||||
- `SSH_PER_WRITE_PER_KB_TIMEOUT`: **10s**: Timeout per Kb written to SSH connections.
 | 
			
		||||
- `MINIMUM_KEY_SIZE_CHECK`: **true**: Indicate whether to check minimum key size with corresponding type.
 | 
			
		||||
 | 
			
		||||
- `OFFLINE_MODE`: **false**: Disables use of CDN for static files and Gravatar for profile pictures.
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										22
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								go.mod
									
									
									
									
									
								
							@@ -5,7 +5,7 @@ go 1.14
 | 
			
		||||
require (
 | 
			
		||||
	cloud.google.com/go v0.78.0 // indirect
 | 
			
		||||
	code.gitea.io/gitea-vet v0.2.1
 | 
			
		||||
	code.gitea.io/sdk/gitea v0.13.2
 | 
			
		||||
	code.gitea.io/sdk/gitea v0.14.0
 | 
			
		||||
	gitea.com/go-chi/binding v0.0.0-20210301195521-1fe1c9a555e7
 | 
			
		||||
	gitea.com/go-chi/cache v0.0.0-20210110083709-82c4c9ce2d5e
 | 
			
		||||
	gitea.com/go-chi/captcha v0.0.0-20210110083842-e7696c336a1e
 | 
			
		||||
@@ -28,7 +28,6 @@ require (
 | 
			
		||||
	github.com/couchbase/goutils v0.0.0-20210118111533-e33d3ffb5401 // indirect
 | 
			
		||||
	github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
 | 
			
		||||
	github.com/denisenkom/go-mssqldb v0.9.0
 | 
			
		||||
	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 | 
			
		||||
	github.com/dlclark/regexp2 v1.4.0 // indirect
 | 
			
		||||
	github.com/dustin/go-humanize v1.0.0
 | 
			
		||||
	github.com/editorconfig/editorconfig-core-go/v2 v2.4.1
 | 
			
		||||
@@ -53,6 +52,7 @@ require (
 | 
			
		||||
	github.com/gogs/chardet v0.0.0-20191104214054-4b6791f73a28
 | 
			
		||||
	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 v3.2.1+incompatible
 | 
			
		||||
	github.com/golang/snappy v0.0.3 // indirect
 | 
			
		||||
	github.com/google/go-github/v32 v32.1.0
 | 
			
		||||
	github.com/google/uuid v1.2.0
 | 
			
		||||
@@ -78,7 +78,7 @@ require (
 | 
			
		||||
	github.com/libdns/libdns v0.2.0 // indirect
 | 
			
		||||
	github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96
 | 
			
		||||
	github.com/mailru/easyjson v0.7.7 // indirect
 | 
			
		||||
	github.com/markbates/goth v1.67.1
 | 
			
		||||
	github.com/markbates/goth v1.68.0
 | 
			
		||||
	github.com/mattn/go-isatty v0.0.12
 | 
			
		||||
	github.com/mattn/go-runewidth v0.0.10 // indirect
 | 
			
		||||
	github.com/mattn/go-sqlite3 v1.14.6
 | 
			
		||||
@@ -86,7 +86,7 @@ require (
 | 
			
		||||
	github.com/mgechev/revive v1.0.3
 | 
			
		||||
	github.com/mholt/acmez v0.1.3 // indirect
 | 
			
		||||
	github.com/mholt/archiver/v3 v3.5.0
 | 
			
		||||
	github.com/microcosm-cc/bluemonday v1.0.4
 | 
			
		||||
	github.com/microcosm-cc/bluemonday v1.0.15
 | 
			
		||||
	github.com/miekg/dns v1.1.40 // indirect
 | 
			
		||||
	github.com/minio/md5-simd v1.1.2 // indirect
 | 
			
		||||
	github.com/minio/minio-go/v7 v7.0.10
 | 
			
		||||
@@ -122,13 +122,13 @@ require (
 | 
			
		||||
	github.com/unknwon/com v1.0.1
 | 
			
		||||
	github.com/unknwon/i18n v0.0.0-20200823051745-09abd91c7f2c
 | 
			
		||||
	github.com/unknwon/paginater v0.0.0-20200328080006-042474bd0eae
 | 
			
		||||
	github.com/unrolled/render v1.0.3
 | 
			
		||||
	github.com/unrolled/render v1.1.1
 | 
			
		||||
	github.com/urfave/cli v1.22.5
 | 
			
		||||
	github.com/willf/bitset v1.1.11 // indirect
 | 
			
		||||
	github.com/xanzy/go-gitlab v0.44.0
 | 
			
		||||
	github.com/xanzy/ssh-agent v0.3.0 // indirect
 | 
			
		||||
	github.com/yohcop/openid-go v1.0.0
 | 
			
		||||
	github.com/yuin/goldmark v1.3.2
 | 
			
		||||
	github.com/yuin/goldmark v1.3.3
 | 
			
		||||
	github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691
 | 
			
		||||
	github.com/yuin/goldmark-meta v1.0.0
 | 
			
		||||
	go.jolheiser.com/hcaptcha v0.0.4
 | 
			
		||||
@@ -136,10 +136,10 @@ require (
 | 
			
		||||
	go.uber.org/multierr v1.6.0 // indirect
 | 
			
		||||
	go.uber.org/zap v1.16.0 // indirect
 | 
			
		||||
	golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83
 | 
			
		||||
	golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
 | 
			
		||||
	golang.org/x/net v0.0.0-20210614182718-04defd469f4e
 | 
			
		||||
	golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93
 | 
			
		||||
	golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46
 | 
			
		||||
	golang.org/x/text v0.3.5
 | 
			
		||||
	golang.org/x/sys v0.0.0-20210423082822-04245dca01da
 | 
			
		||||
	golang.org/x/text v0.3.6
 | 
			
		||||
	golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
 | 
			
		||||
	golang.org/x/tools v0.1.0
 | 
			
		||||
	gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
 | 
			
		||||
@@ -149,9 +149,9 @@ require (
 | 
			
		||||
	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/xorm v1.0.7
 | 
			
		||||
	xorm.io/xorm v1.1.0
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
replace github.com/hashicorp/go-version => github.com/6543/go-version v1.2.4
 | 
			
		||||
 | 
			
		||||
replace github.com/microcosm-cc/bluemonday => github.com/lunny/bluemonday v1.0.5-0.20201227154428-ca34796141e8
 | 
			
		||||
replace github.com/golang-jwt/jwt v3.2.1+incompatible => github.com/zeripath/jwt v3.2.2-go1.14+incompatible
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										74
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										74
									
								
								go.sum
									
									
									
									
									
								
							@@ -38,8 +38,8 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
 | 
			
		||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
 | 
			
		||||
code.gitea.io/gitea-vet v0.2.1 h1:b30by7+3SkmiftK0RjuXqFvZg2q4p68uoPGuxhzBN0s=
 | 
			
		||||
code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
 | 
			
		||||
code.gitea.io/sdk/gitea v0.13.2 h1:wAnT/J7Z62q3fJXbgnecoaOBh8CM1Qq0/DakWxiv4yA=
 | 
			
		||||
code.gitea.io/sdk/gitea v0.13.2/go.mod h1:lee2y8LeV3kQb2iK+hHlMqoadL4bp27QOkOV/hawLKg=
 | 
			
		||||
code.gitea.io/sdk/gitea v0.14.0 h1:m4J352I3p9+bmJUfS+g0odeQzBY/5OXP91Gv6D4fnJ0=
 | 
			
		||||
code.gitea.io/sdk/gitea v0.14.0/go.mod h1:89WiyOX1KEcvjP66sRHdu0RafojGo60bT9UqW17VbWs=
 | 
			
		||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 | 
			
		||||
gitea.com/go-chi/binding v0.0.0-20210301195521-1fe1c9a555e7 h1:xCVJPY823C8RWpgMabTw2kOglDrg0iS3GcQU6wdwHkU=
 | 
			
		||||
gitea.com/go-chi/binding v0.0.0-20210301195521-1fe1c9a555e7/go.mod h1:AyfTrwtfYN54R/HmVvMYPnSTenH5bVoyh8x6tBluxEA=
 | 
			
		||||
@@ -127,8 +127,9 @@ github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:l
 | 
			
		||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
 | 
			
		||||
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
 | 
			
		||||
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
 | 
			
		||||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef h1:46PFijGLmAjMPwCCCo7Jf0W6f9slllCkkv7vyc1yOSg=
 | 
			
		||||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
 | 
			
		||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ=
 | 
			
		||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
 | 
			
		||||
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
 | 
			
		||||
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
 | 
			
		||||
github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48=
 | 
			
		||||
@@ -196,8 +197,6 @@ github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+
 | 
			
		||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 | 
			
		||||
github.com/chi-middleware/proxy v1.1.1 h1:4HaXUp8o2+bhHr1OhVy+VjN0+L7/07JDcn6v7YrTjrQ=
 | 
			
		||||
github.com/chi-middleware/proxy v1.1.1/go.mod h1:jQwMEJct2tz9VmtCELxvnXoMfa+SOdikvbVJVHv/M+0=
 | 
			
		||||
github.com/chris-ramon/douceur v0.2.0 h1:IDMEdxlEUUBYBKE4z/mJnFyVXox+MjuEVDJNN27glkU=
 | 
			
		||||
github.com/chris-ramon/douceur v0.2.0/go.mod h1:wDW5xjJdeoMm1mRt4sD4c/LbF/mWdEpRXQKjTR8nIBE=
 | 
			
		||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 | 
			
		||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
 | 
			
		||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
 | 
			
		||||
@@ -252,7 +251,6 @@ github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xb
 | 
			
		||||
github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
 | 
			
		||||
github.com/denisenkom/go-mssqldb v0.9.0 h1:RSohk2RsiZqLZ0zCjtfn3S4Gp4exhpBWHyQ7D0yGjAk=
 | 
			
		||||
github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
 | 
			
		||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
 | 
			
		||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 | 
			
		||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
 | 
			
		||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
 | 
			
		||||
@@ -776,8 +774,6 @@ github.com/libdns/libdns v0.2.0 h1:ewg3ByWrdUrxrje8ChPVMBNcotg7H9LQYg+u5De2RzI=
 | 
			
		||||
github.com/libdns/libdns v0.2.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
 | 
			
		||||
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
 | 
			
		||||
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
 | 
			
		||||
github.com/lunny/bluemonday v1.0.5-0.20201227154428-ca34796141e8 h1:1omo92DLtxQu6VwVPSZAmduHaK5zssed6cvkHyl1XOg=
 | 
			
		||||
github.com/lunny/bluemonday v1.0.5-0.20201227154428-ca34796141e8/go.mod h1:8iwZnFn2CDDNZ0r6UXhF4xawGvzaqzCRa1n3/lO3W2w=
 | 
			
		||||
github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 h1:uNwtsDp7ci48vBTTxDuwcoTXz4lwtDTe7TjCQ0noaWY=
 | 
			
		||||
github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96/go.mod h1:mmIfjCSQlGYXmJ95jFN84AkQFnVABtKuJL8IrzwvUKQ=
 | 
			
		||||
github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de/go.mod h1:3q8WtuPQsoRbatJuy3nvq/hRSvuBJrHHr+ybPPiNvHQ=
 | 
			
		||||
@@ -796,8 +792,8 @@ github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ
 | 
			
		||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
 | 
			
		||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
 | 
			
		||||
github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA=
 | 
			
		||||
github.com/markbates/goth v1.67.1 h1:gU5B0pzHVyhnJPwGynfFnkfvaQ39C1Sy+ewdl+bhAOw=
 | 
			
		||||
github.com/markbates/goth v1.67.1/go.mod h1:EyLFHGU5ySr2GXRDyJH5nu2dA7parbC8QwIYW/rGcWg=
 | 
			
		||||
github.com/markbates/goth v1.68.0 h1:90sKvjRAKHcl9V2uC9x/PJXeD78cFPiBsyP1xVhoQfA=
 | 
			
		||||
github.com/markbates/goth v1.68.0/go.mod h1:V2VcDMzDiMHW+YmqYl7i0cMiAUeCkAe4QE6jRKBhXZw=
 | 
			
		||||
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
 | 
			
		||||
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
 | 
			
		||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 | 
			
		||||
@@ -834,6 +830,8 @@ github.com/mholt/acmez v0.1.3 h1:J7MmNIk4Qf9b8mAGqAh4XkNeowv3f1zW816yf4zt7Qk=
 | 
			
		||||
github.com/mholt/acmez v0.1.3/go.mod h1:8qnn8QA/Ewx8E3ZSsmscqsIjhhpxuy9vqdgbX2ceceM=
 | 
			
		||||
github.com/mholt/archiver/v3 v3.5.0 h1:nE8gZIrw66cu4osS/U7UW7YDuGMHssxKutU8IfWxwWE=
 | 
			
		||||
github.com/mholt/archiver/v3 v3.5.0/go.mod h1:qqTTPUK/HZPFgFQ/TJ3BzvTpF/dPtFVJXdQbCmeMxwc=
 | 
			
		||||
github.com/microcosm-cc/bluemonday v1.0.15 h1:J4uN+qPng9rvkBZBoBb8YGR+ijuklIMpSOZZLjYpbeY=
 | 
			
		||||
github.com/microcosm-cc/bluemonday v1.0.15/go.mod h1:ZLvAzeakRwrGnzQEvstVzVt3ZpqOF2+sdFr0Om+ce30=
 | 
			
		||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
 | 
			
		||||
github.com/miekg/dns v1.1.30/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
 | 
			
		||||
github.com/miekg/dns v1.1.40 h1:pyyPFfGMnciYUk/mXpKkVmeMQjfXqt3FAJ2hy7tPiLA=
 | 
			
		||||
@@ -998,6 +996,8 @@ github.com/quasoft/websspi v1.0.0 h1:5nDgdM5xSur9s+B5w2xQ5kxf5nUGqgFgU4W0aDLZ8Mw
 | 
			
		||||
github.com/quasoft/websspi v1.0.0/go.mod h1:HmVdl939dQ0WIXZhyik+ARdI03M6bQzaSEKcgpFmewk=
 | 
			
		||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
 | 
			
		||||
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
 | 
			
		||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
 | 
			
		||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
 | 
			
		||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 | 
			
		||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
 | 
			
		||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 | 
			
		||||
@@ -1115,8 +1115,8 @@ github.com/unknwon/i18n v0.0.0-20200823051745-09abd91c7f2c h1:679/gJXwrsHC3RATr0
 | 
			
		||||
github.com/unknwon/i18n v0.0.0-20200823051745-09abd91c7f2c/go.mod h1:+5rDk6sDGpl3azws3O+f+GpFSyN9GVr0K8cvQLQM2ZQ=
 | 
			
		||||
github.com/unknwon/paginater v0.0.0-20200328080006-042474bd0eae h1:ihaXiJkaca54IaCSnEXtE/uSZOmPxKZhDfVLrzZLFDs=
 | 
			
		||||
github.com/unknwon/paginater v0.0.0-20200328080006-042474bd0eae/go.mod h1:1fdkY6xxl6ExVs2QFv7R0F5IRZHKA8RahhB9fMC9RvM=
 | 
			
		||||
github.com/unrolled/render v1.0.3 h1:baO+NG1bZSF2WR4zwh+0bMWauWky7DVrTOfvE2w+aFo=
 | 
			
		||||
github.com/unrolled/render v1.0.3/go.mod h1:gN9T0NhL4Bfbwu8ann7Ry/TGHYfosul+J0obPf6NBdM=
 | 
			
		||||
github.com/unrolled/render v1.1.1 h1:FpzNzkvlJQIlVdVaqeVBGWiCS8gpbmjtrKpDmCn6p64=
 | 
			
		||||
github.com/unrolled/render v1.1.1/go.mod h1:gN9T0NhL4Bfbwu8ann7Ry/TGHYfosul+J0obPf6NBdM=
 | 
			
		||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
 | 
			
		||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
 | 
			
		||||
github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU=
 | 
			
		||||
@@ -1145,13 +1145,15 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
 | 
			
		||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 | 
			
		||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 | 
			
		||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 | 
			
		||||
github.com/yuin/goldmark v1.3.2 h1:YjHC5TgyMmHpicTgEqDN0Q96Xo8K6tLXPnmNOHXCgs0=
 | 
			
		||||
github.com/yuin/goldmark v1.3.2/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 | 
			
		||||
github.com/yuin/goldmark v1.3.3 h1:37BdQwPx8VOSic8eDSWee6QL9mRpZRm9VJp/QugNrW0=
 | 
			
		||||
github.com/yuin/goldmark v1.3.3/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 | 
			
		||||
github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691 h1:VWSxtAiQNh3zgHJpdpkpVYjTPqRE3P6UZCOPa1nRDio=
 | 
			
		||||
github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691/go.mod h1:YLF3kDffRfUH/bTxOxHhV6lxwIB3Vfj91rEwNMS9MXo=
 | 
			
		||||
github.com/yuin/goldmark-meta v1.0.0 h1:ScsatUIT2gFS6azqzLGUjgOnELsBOxMXerM3ogdJhAM=
 | 
			
		||||
github.com/yuin/goldmark-meta v1.0.0/go.mod h1:zsNNOrZ4nLuyHAJeLQEZcQat8dm70SmB2kHbls092Gc=
 | 
			
		||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
 | 
			
		||||
github.com/zeripath/jwt v3.2.2-go1.14+incompatible h1:jqxA3KuCQRLn0lHdt1G8t1EUJ92FmRUFnXHghVvJLJs=
 | 
			
		||||
github.com/zeripath/jwt v3.2.2-go1.14+incompatible/go.mod h1:pYPrRXN84mQC6u5c/08icdKllASIBEOurvsTPbDurLs=
 | 
			
		||||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
 | 
			
		||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 | 
			
		||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 | 
			
		||||
@@ -1321,8 +1323,8 @@ golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwY
 | 
			
		||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 | 
			
		||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 | 
			
		||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 | 
			
		||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
 | 
			
		||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 | 
			
		||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
 | 
			
		||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 | 
			
		||||
@@ -1418,8 +1420,8 @@ golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7w
 | 
			
		||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46 h1:V066+OYJ66oTjnhm4Yrn7SXIwSCiDQJxpBxmvqb1N1c=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 | 
			
		||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
 | 
			
		||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
			
		||||
@@ -1429,8 +1431,9 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
 | 
			
		||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 | 
			
		||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
			
		||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
			
		||||
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
 | 
			
		||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
			
		||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
 | 
			
		||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
			
		||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 | 
			
		||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 | 
			
		||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 | 
			
		||||
@@ -1501,6 +1504,7 @@ golang.org/x/tools v0.0.0-20200928182047-19e03678916f/go.mod h1:z6u4i615ZeAfBE4X
 | 
			
		||||
golang.org/x/tools v0.0.0-20200929161345-d7fc70abf50f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
 | 
			
		||||
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 | 
			
		||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 | 
			
		||||
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 | 
			
		||||
golang.org/x/tools v0.0.0-20201125231158-b5590deeca9b/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 | 
			
		||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 | 
			
		||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 | 
			
		||||
@@ -1667,6 +1671,33 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt
 | 
			
		||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 | 
			
		||||
honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8=
 | 
			
		||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 | 
			
		||||
modernc.org/cc/v3 v3.31.5-0.20210308123301-7a3e9dab9009 h1:u0oCo5b9wyLr++HF3AN9JicGhkUxJhMz51+8TIZH9N0=
 | 
			
		||||
modernc.org/cc/v3 v3.31.5-0.20210308123301-7a3e9dab9009/go.mod h1:0R6jl1aZlIl2avnYfbfHBS1QB6/f+16mihBObaBC878=
 | 
			
		||||
modernc.org/ccgo/v3 v3.9.0 h1:JbcEIqjw4Agf+0g3Tc85YvfYqkkFOv6xBwS4zkfqSoA=
 | 
			
		||||
modernc.org/ccgo/v3 v3.9.0/go.mod h1:nQbgkn8mwzPdp4mm6BT6+p85ugQ7FrGgIcYaE7nSrpY=
 | 
			
		||||
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
 | 
			
		||||
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
 | 
			
		||||
modernc.org/libc v1.7.13-0.20210308123627-12f642a52bb8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
 | 
			
		||||
modernc.org/libc v1.8.0 h1:Pp4uv9g0csgBMpGPABKtkieF6O5MGhfGo6ZiOdlYfR8=
 | 
			
		||||
modernc.org/libc v1.8.0/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
 | 
			
		||||
modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
 | 
			
		||||
modernc.org/mathutil v1.2.2 h1:+yFk8hBprV+4c0U9GjFtL+dV3N8hOJ8JCituQcMShFY=
 | 
			
		||||
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
 | 
			
		||||
modernc.org/memory v1.0.4 h1:utMBrFcpnQDdNsmM6asmyH/FM9TqLPS7XF7otpJmrwM=
 | 
			
		||||
modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc=
 | 
			
		||||
modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A=
 | 
			
		||||
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
 | 
			
		||||
modernc.org/sqlite v1.10.1-0.20210314190707-798bbeb9bb84 h1:rgEUzE849tFlHSoeCrKyS9cZAljC+DY7MdMHKq6R6sY=
 | 
			
		||||
modernc.org/sqlite v1.10.1-0.20210314190707-798bbeb9bb84/go.mod h1:PGzq6qlhyYjL6uVbSgS6WoF7ZopTW/sI7+7p+mb4ZVU=
 | 
			
		||||
modernc.org/strutil v1.1.0 h1:+1/yCzZxY2pZwwrsbH+4T7BQMoLQ9QiBshRC9eicYsc=
 | 
			
		||||
modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
 | 
			
		||||
modernc.org/tcl v1.5.0 h1:euZSUNfE0Fd4W8VqXI1Ly1v7fqDJoBuAV88Ea+SnaSs=
 | 
			
		||||
modernc.org/tcl v1.5.0/go.mod h1:gb57hj4pO8fRrK54zveIfFXBaMHK3SKJNWcmRw1cRzc=
 | 
			
		||||
modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
 | 
			
		||||
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
 | 
			
		||||
modernc.org/z v1.0.1-0.20210308123920-1f282aa71362/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA=
 | 
			
		||||
modernc.org/z v1.0.1 h1:WyIDpEpAIx4Hel6q/Pcgj/VhaQV5XPJ2I6ryIYbjnpc=
 | 
			
		||||
modernc.org/z v1.0.1/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA=
 | 
			
		||||
mvdan.cc/xurls/v2 v2.2.0 h1:NSZPykBXJFCetGZykLAxaL6SIpvbVy/UFEniIfHAa8A=
 | 
			
		||||
mvdan.cc/xurls/v2 v2.2.0/go.mod h1:EV1RMtya9D6G5DMYPGD8zTQzaHet6Jh8gFlRgGRJeO8=
 | 
			
		||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 | 
			
		||||
@@ -1677,8 +1708,9 @@ sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1
 | 
			
		||||
strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251 h1:mUcz5b3FJbP5Cvdq7Khzn6J9OCUQJaBwgBkCR+MOwSs=
 | 
			
		||||
strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251/go.mod h1:FJGmPh3vz9jSos1L/F91iAgnC/aejc0wIIrF2ZwJxdY=
 | 
			
		||||
xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
 | 
			
		||||
xorm.io/builder v0.3.8/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
 | 
			
		||||
xorm.io/builder v0.3.9 h1:Sd65/LdWyO7LR8+Cbd+e7mm3sK/7U9k0jS3999IDHMc=
 | 
			
		||||
xorm.io/builder v0.3.9/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
 | 
			
		||||
xorm.io/xorm v1.0.6/go.mod h1:uF9EtbhODq5kNWxMbnBEj8hRRZnlcNSz2t2N7HW/+A4=
 | 
			
		||||
xorm.io/xorm v1.0.7 h1:26yBTDVI+CfQpVz2Y88fISh+aiJXIPP4eNoTJlwzsC4=
 | 
			
		||||
xorm.io/xorm v1.0.7/go.mod h1:uF9EtbhODq5kNWxMbnBEj8hRRZnlcNSz2t2N7HW/+A4=
 | 
			
		||||
xorm.io/xorm v1.1.0 h1:mkEsQXLauZajiOld2cB2PkFcUZKePepPgs1bC1dw8RA=
 | 
			
		||||
xorm.io/xorm v1.1.0/go.mod h1:EDzNHMuCVZNszkIRSLL2nI0zX+nQE8RstAVranlSfqI=
 | 
			
		||||
 
 | 
			
		||||
@@ -239,6 +239,26 @@ func doAPICreatePullRequest(ctx APITestContext, owner, repo, baseBranch, headBra
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func doAPIGetPullRequest(ctx APITestContext, owner, repo string, index int64) func(*testing.T) (api.PullRequest, error) {
 | 
			
		||||
	return func(t *testing.T) (api.PullRequest, error) {
 | 
			
		||||
		urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d?token=%s",
 | 
			
		||||
			owner, repo, index, ctx.Token)
 | 
			
		||||
		req := NewRequest(t, http.MethodGet, urlStr)
 | 
			
		||||
 | 
			
		||||
		expected := 200
 | 
			
		||||
		if ctx.ExpectedCode != 0 {
 | 
			
		||||
			expected = ctx.ExpectedCode
 | 
			
		||||
		}
 | 
			
		||||
		resp := ctx.Session.MakeRequest(t, req, expected)
 | 
			
		||||
 | 
			
		||||
		json := jsoniter.ConfigCompatibleWithStandardLibrary
 | 
			
		||||
		decoder := json.NewDecoder(resp.Body)
 | 
			
		||||
		pr := api.PullRequest{}
 | 
			
		||||
		err := decoder.Decode(&pr)
 | 
			
		||||
		return pr, err
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func doAPIMergePullRequest(ctx APITestContext, owner, repo string, index int64) func(*testing.T) {
 | 
			
		||||
	return func(t *testing.T) {
 | 
			
		||||
		urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge?token=%s",
 | 
			
		||||
 
 | 
			
		||||
@@ -92,6 +92,10 @@ func testAPIDeleteOAuth2Application(t *testing.T) {
 | 
			
		||||
	session.MakeRequest(t, req, http.StatusNoContent)
 | 
			
		||||
 | 
			
		||||
	models.AssertNotExistsBean(t, &models.OAuth2Application{UID: oldApp.UID, Name: oldApp.Name})
 | 
			
		||||
 | 
			
		||||
	// Delete again will return not found
 | 
			
		||||
	req = NewRequest(t, "DELETE", urlStr)
 | 
			
		||||
	session.MakeRequest(t, req, http.StatusNotFound)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func testAPIGetOAuth2Application(t *testing.T) {
 | 
			
		||||
 
 | 
			
		||||
@@ -130,11 +130,14 @@ func getNewRepoEditOption(opts *api.EditRepoOption) *api.EditRepoOption {
 | 
			
		||||
 | 
			
		||||
func TestAPIRepoEdit(t *testing.T) {
 | 
			
		||||
	onGiteaRun(t, func(t *testing.T, u *url.URL) {
 | 
			
		||||
		bFalse, bTrue := false, true
 | 
			
		||||
 | 
			
		||||
		user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)               // owner of the repo1 & repo16
 | 
			
		||||
		user3 := models.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User)               // owner of the repo3, is an org
 | 
			
		||||
		user4 := models.AssertExistsAndLoadBean(t, &models.User{ID: 4}).(*models.User)               // owner of neither repos
 | 
			
		||||
		repo1 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)   // public repo
 | 
			
		||||
		repo3 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository)   // public repo
 | 
			
		||||
		repo15 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 15}).(*models.Repository) // empty repo
 | 
			
		||||
		repo16 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) // private repo
 | 
			
		||||
 | 
			
		||||
		// Get user2's token
 | 
			
		||||
@@ -286,9 +289,8 @@ func TestAPIRepoEdit(t *testing.T) {
 | 
			
		||||
		// Test making a repo public that is private
 | 
			
		||||
		repo16 = models.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository)
 | 
			
		||||
		assert.True(t, repo16.IsPrivate)
 | 
			
		||||
		private := false
 | 
			
		||||
		repoEditOption = &api.EditRepoOption{
 | 
			
		||||
			Private: &private,
 | 
			
		||||
			Private: &bFalse,
 | 
			
		||||
		}
 | 
			
		||||
		url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, repo16.Name, token2)
 | 
			
		||||
		req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption)
 | 
			
		||||
@@ -296,11 +298,24 @@ func TestAPIRepoEdit(t *testing.T) {
 | 
			
		||||
		repo16 = models.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository)
 | 
			
		||||
		assert.False(t, repo16.IsPrivate)
 | 
			
		||||
		// Make it private again
 | 
			
		||||
		private = true
 | 
			
		||||
		repoEditOption.Private = &private
 | 
			
		||||
		repoEditOption.Private = &bTrue
 | 
			
		||||
		req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption)
 | 
			
		||||
		_ = session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
 | 
			
		||||
		// Test to change empty repo
 | 
			
		||||
		assert.False(t, repo15.IsArchived)
 | 
			
		||||
		url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, repo15.Name, token2)
 | 
			
		||||
		req = NewRequestWithJSON(t, "PATCH", url, &api.EditRepoOption{
 | 
			
		||||
			Archived: &bTrue,
 | 
			
		||||
		})
 | 
			
		||||
		_ = session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
		repo15 = models.AssertExistsAndLoadBean(t, &models.Repository{ID: 15}).(*models.Repository)
 | 
			
		||||
		assert.True(t, repo15.IsArchived)
 | 
			
		||||
		req = NewRequestWithJSON(t, "PATCH", url, &api.EditRepoOption{
 | 
			
		||||
			Archived: &bFalse,
 | 
			
		||||
		})
 | 
			
		||||
		_ = session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
 | 
			
		||||
		// Test using org repo "user3/repo3" where user2 is a collaborator
 | 
			
		||||
		origRepoEditOption = getRepoEditOptionFromRepo(repo3)
 | 
			
		||||
		repoEditOption = getNewRepoEditOption(origRepoEditOption)
 | 
			
		||||
 
 | 
			
		||||
@@ -223,7 +223,7 @@ func TestAPIViewRepo(t *testing.T) {
 | 
			
		||||
	DecodeJSON(t, resp, &repo)
 | 
			
		||||
	assert.EqualValues(t, 1, repo.ID)
 | 
			
		||||
	assert.EqualValues(t, "repo1", repo.Name)
 | 
			
		||||
	assert.EqualValues(t, 2, repo.Releases)
 | 
			
		||||
	assert.EqualValues(t, 1, repo.Releases)
 | 
			
		||||
	assert.EqualValues(t, 1, repo.OpenIssues)
 | 
			
		||||
	assert.EqualValues(t, 3, repo.OpenPulls)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -144,7 +144,9 @@ func TestAPITeamSearch(t *testing.T) {
 | 
			
		||||
	var results TeamSearchResults
 | 
			
		||||
 | 
			
		||||
	session := loginUser(t, user.Name)
 | 
			
		||||
	csrf := GetCSRF(t, session, "/"+org.Name)
 | 
			
		||||
	req := NewRequestf(t, "GET", "/api/v1/orgs/%s/teams/search?q=%s", org.Name, "_team")
 | 
			
		||||
	req.Header.Add("X-Csrf-Token", csrf)
 | 
			
		||||
	resp := session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
	DecodeJSON(t, resp, &results)
 | 
			
		||||
	assert.NotEmpty(t, results.Data)
 | 
			
		||||
@@ -154,7 +156,9 @@ func TestAPITeamSearch(t *testing.T) {
 | 
			
		||||
	// no access if not organization member
 | 
			
		||||
	user5 := models.AssertExistsAndLoadBean(t, &models.User{ID: 5}).(*models.User)
 | 
			
		||||
	session = loginUser(t, user5.Name)
 | 
			
		||||
	csrf = GetCSRF(t, session, "/"+org.Name)
 | 
			
		||||
	req = NewRequestf(t, "GET", "/api/v1/orgs/%s/teams/search?q=%s", org.Name, "team")
 | 
			
		||||
	req.Header.Add("X-Csrf-Token", csrf)
 | 
			
		||||
	resp = session.MakeRequest(t, req, http.StatusForbidden)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -122,7 +122,7 @@ func TestGetAttachment(t *testing.T) {
 | 
			
		||||
		t.Run(tc.name, func(t *testing.T) {
 | 
			
		||||
			//Write empty file to be available for response
 | 
			
		||||
			if tc.createFile {
 | 
			
		||||
				_, err := storage.Attachments.Save(models.AttachmentRelativePath(tc.uuid), strings.NewReader("hello world"))
 | 
			
		||||
				_, err := storage.Attachments.Save(models.AttachmentRelativePath(tc.uuid), strings.NewReader("hello world"), -1)
 | 
			
		||||
				assert.NoError(t, err)
 | 
			
		||||
			}
 | 
			
		||||
			//Actual test
 | 
			
		||||
 
 | 
			
		||||
@@ -144,6 +144,60 @@ func TestLDAPUserSignin(t *testing.T) {
 | 
			
		||||
	assert.Equal(t, u.Email, htmlDoc.Find(`label[for="email"]`).Siblings().First().Text())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestLDAPAuthChange(t *testing.T) {
 | 
			
		||||
	defer prepareTestEnv(t)()
 | 
			
		||||
	addAuthSourceLDAP(t, "")
 | 
			
		||||
 | 
			
		||||
	session := loginUser(t, "user1")
 | 
			
		||||
	req := NewRequest(t, "GET", "/admin/auths")
 | 
			
		||||
	resp := session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
	doc := NewHTMLParser(t, resp.Body)
 | 
			
		||||
	href, exists := doc.Find("table.table td a").Attr("href")
 | 
			
		||||
	if !exists {
 | 
			
		||||
		assert.True(t, exists, "No authentication source found")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	req = NewRequest(t, "GET", href)
 | 
			
		||||
	resp = session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
	doc = NewHTMLParser(t, resp.Body)
 | 
			
		||||
	csrf := doc.GetCSRF()
 | 
			
		||||
	host, _ := doc.Find(`input[name="host"]`).Attr("value")
 | 
			
		||||
	assert.Equal(t, host, getLDAPServerHost())
 | 
			
		||||
	binddn, _ := doc.Find(`input[name="bind_dn"]`).Attr("value")
 | 
			
		||||
	assert.Equal(t, binddn, "uid=gitea,ou=service,dc=planetexpress,dc=com")
 | 
			
		||||
 | 
			
		||||
	req = NewRequestWithValues(t, "POST", href, map[string]string{
 | 
			
		||||
		"_csrf":                    csrf,
 | 
			
		||||
		"type":                     "2",
 | 
			
		||||
		"name":                     "ldap",
 | 
			
		||||
		"host":                     getLDAPServerHost(),
 | 
			
		||||
		"port":                     "389",
 | 
			
		||||
		"bind_dn":                  "uid=gitea,ou=service,dc=planetexpress,dc=com",
 | 
			
		||||
		"bind_password":            "password",
 | 
			
		||||
		"user_base":                "ou=people,dc=planetexpress,dc=com",
 | 
			
		||||
		"filter":                   "(&(objectClass=inetOrgPerson)(memberOf=cn=git,ou=people,dc=planetexpress,dc=com)(uid=%s))",
 | 
			
		||||
		"admin_filter":             "(memberOf=cn=admin_staff,ou=people,dc=planetexpress,dc=com)",
 | 
			
		||||
		"restricted_filter":        "(uid=leela)",
 | 
			
		||||
		"attribute_username":       "uid",
 | 
			
		||||
		"attribute_name":           "givenName",
 | 
			
		||||
		"attribute_surname":        "sn",
 | 
			
		||||
		"attribute_mail":           "mail",
 | 
			
		||||
		"attribute_ssh_public_key": "",
 | 
			
		||||
		"is_sync_enabled":          "on",
 | 
			
		||||
		"is_active":                "on",
 | 
			
		||||
	})
 | 
			
		||||
	session.MakeRequest(t, req, http.StatusFound)
 | 
			
		||||
 | 
			
		||||
	req = NewRequest(t, "GET", href)
 | 
			
		||||
	resp = session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
	doc = NewHTMLParser(t, resp.Body)
 | 
			
		||||
	host, _ = doc.Find(`input[name="host"]`).Attr("value")
 | 
			
		||||
	assert.Equal(t, host, getLDAPServerHost())
 | 
			
		||||
	binddn, _ = doc.Find(`input[name="bind_dn"]`).Attr("value")
 | 
			
		||||
	assert.Equal(t, binddn, "uid=gitea,ou=service,dc=planetexpress,dc=com")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestLDAPUserSync(t *testing.T) {
 | 
			
		||||
	if skipLDAPTests() {
 | 
			
		||||
		t.Skip()
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										69
									
								
								integrations/git_smart_http_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								integrations/git_smart_http_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,69 @@
 | 
			
		||||
// 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 integrations
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestGitSmartHTTP(t *testing.T) {
 | 
			
		||||
	onGiteaRun(t, testGitSmartHTTP)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func testGitSmartHTTP(t *testing.T, u *url.URL) {
 | 
			
		||||
	var kases = []struct {
 | 
			
		||||
		p    string
 | 
			
		||||
		code int
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			p:    "user2/repo1/info/refs",
 | 
			
		||||
			code: 200,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			p:    "user2/repo1/HEAD",
 | 
			
		||||
			code: 200,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			p:    "user2/repo1/objects/info/alternates",
 | 
			
		||||
			code: 404,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			p:    "user2/repo1/objects/info/http-alternates",
 | 
			
		||||
			code: 404,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			p:    "user2/repo1/../../custom/conf/app.ini",
 | 
			
		||||
			code: 404,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			p:    "user2/repo1/objects/info/../../../../custom/conf/app.ini",
 | 
			
		||||
			code: 404,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			p:    `user2/repo1/objects/info/..\..\..\..\custom\conf\app.ini`,
 | 
			
		||||
			code: 400,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, kase := range kases {
 | 
			
		||||
		t.Run(kase.p, func(t *testing.T) {
 | 
			
		||||
			p := u.String() + kase.p
 | 
			
		||||
			req, err := http.NewRequest("GET", p, nil)
 | 
			
		||||
			assert.NoError(t, err)
 | 
			
		||||
			req.SetBasicAuth("user2", userPassword)
 | 
			
		||||
			resp, err := http.DefaultClient.Do(req)
 | 
			
		||||
			assert.NoError(t, err)
 | 
			
		||||
			defer resp.Body.Close()
 | 
			
		||||
			assert.EqualValues(t, kase.code, resp.StatusCode)
 | 
			
		||||
			_, err = ioutil.ReadAll(resp.Body)
 | 
			
		||||
			assert.NoError(t, err)
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -5,6 +5,7 @@
 | 
			
		||||
package integrations
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/hex"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"math/rand"
 | 
			
		||||
@@ -208,13 +209,13 @@ func rawTest(t *testing.T, ctx *APITestContext, little, big, littleLFS, bigLFS s
 | 
			
		||||
 | 
			
		||||
		// Request raw paths
 | 
			
		||||
		req := NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", little))
 | 
			
		||||
		resp := session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
		assert.Equal(t, littleSize, resp.Body.Len())
 | 
			
		||||
		resp := session.MakeRequestNilResponseRecorder(t, req, http.StatusOK)
 | 
			
		||||
		assert.Equal(t, littleSize, resp.Length)
 | 
			
		||||
 | 
			
		||||
		setting.CheckLFSVersion()
 | 
			
		||||
		if setting.LFS.StartServer {
 | 
			
		||||
			req = NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", littleLFS))
 | 
			
		||||
			resp = session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
			resp := session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
			assert.NotEqual(t, littleSize, resp.Body.Len())
 | 
			
		||||
			assert.LessOrEqual(t, resp.Body.Len(), 1024)
 | 
			
		||||
			if resp.Body.Len() != littleSize && resp.Body.Len() <= 1024 {
 | 
			
		||||
@@ -224,12 +225,12 @@ func rawTest(t *testing.T, ctx *APITestContext, little, big, littleLFS, bigLFS s
 | 
			
		||||
 | 
			
		||||
		if !testing.Short() {
 | 
			
		||||
			req = NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", big))
 | 
			
		||||
			resp = session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
			assert.Equal(t, bigSize, resp.Body.Len())
 | 
			
		||||
			resp := session.MakeRequestNilResponseRecorder(t, req, http.StatusOK)
 | 
			
		||||
			assert.Equal(t, bigSize, resp.Length)
 | 
			
		||||
 | 
			
		||||
			if setting.LFS.StartServer {
 | 
			
		||||
				req = NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", bigLFS))
 | 
			
		||||
				resp = session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
				resp := session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
				assert.NotEqual(t, bigSize, resp.Body.Len())
 | 
			
		||||
				if resp.Body.Len() != bigSize && resp.Body.Len() <= 1024 {
 | 
			
		||||
					assert.Contains(t, resp.Body.String(), models.LFSMetaFileIdentifier)
 | 
			
		||||
@@ -450,27 +451,35 @@ func doMergeFork(ctx, baseCtx APITestContext, baseBranch, headBranch string) fun
 | 
			
		||||
		t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr))
 | 
			
		||||
 | 
			
		||||
		// Then get the diff string
 | 
			
		||||
		var diffStr string
 | 
			
		||||
		var diffHash string
 | 
			
		||||
		var diffLength int
 | 
			
		||||
		t.Run("GetDiff", func(t *testing.T) {
 | 
			
		||||
			req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d.diff", url.PathEscape(baseCtx.Username), url.PathEscape(baseCtx.Reponame), pr.Index))
 | 
			
		||||
			resp := ctx.Session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
			diffStr = resp.Body.String()
 | 
			
		||||
			resp := ctx.Session.MakeRequestNilResponseHashSumRecorder(t, req, http.StatusOK)
 | 
			
		||||
			diffHash = string(resp.Hash.Sum(nil))
 | 
			
		||||
			diffLength = resp.Length
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		// Now: Merge the PR & make sure that doesn't break the PR page or change its diff
 | 
			
		||||
		t.Run("MergePR", doAPIMergePullRequest(baseCtx, baseCtx.Username, baseCtx.Reponame, pr.Index))
 | 
			
		||||
		t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr))
 | 
			
		||||
		t.Run("EnsureDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffStr))
 | 
			
		||||
		t.Run("CheckPR", func(t *testing.T) {
 | 
			
		||||
			oldMergeBase := pr.MergeBase
 | 
			
		||||
			pr2, err := doAPIGetPullRequest(baseCtx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t)
 | 
			
		||||
			assert.NoError(t, err)
 | 
			
		||||
			assert.Equal(t, oldMergeBase, pr2.MergeBase)
 | 
			
		||||
		})
 | 
			
		||||
		t.Run("EnsurDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffHash, diffLength))
 | 
			
		||||
 | 
			
		||||
		// Then: Delete the head branch & make sure that doesn't break the PR page or change its diff
 | 
			
		||||
		t.Run("DeleteHeadBranch", doBranchDelete(baseCtx, baseCtx.Username, baseCtx.Reponame, headBranch))
 | 
			
		||||
		t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr))
 | 
			
		||||
		t.Run("EnsureDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffStr))
 | 
			
		||||
		t.Run("EnsureDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffHash, diffLength))
 | 
			
		||||
 | 
			
		||||
		// Delete the head repository & make sure that doesn't break the PR page or change its diff
 | 
			
		||||
		t.Run("DeleteHeadRepository", doAPIDeleteRepository(ctx))
 | 
			
		||||
		t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr))
 | 
			
		||||
		t.Run("EnsureDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffStr))
 | 
			
		||||
		t.Run("EnsureDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffHash, diffLength))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -514,20 +523,15 @@ func doEnsureCanSeePull(ctx APITestContext, pr api.PullRequest) func(t *testing.
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func doEnsureDiffNoChange(ctx APITestContext, pr api.PullRequest, diffStr string) func(t *testing.T) {
 | 
			
		||||
func doEnsureDiffNoChange(ctx APITestContext, pr api.PullRequest, diffHash string, diffLength int) func(t *testing.T) {
 | 
			
		||||
	return func(t *testing.T) {
 | 
			
		||||
		req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d.diff", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), pr.Index))
 | 
			
		||||
		resp := ctx.Session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
		expectedMaxLen := len(diffStr)
 | 
			
		||||
		if expectedMaxLen > 800 {
 | 
			
		||||
			expectedMaxLen = 800
 | 
			
		||||
		}
 | 
			
		||||
		actual := resp.Body.String()
 | 
			
		||||
		actualMaxLen := len(actual)
 | 
			
		||||
		if actualMaxLen > 800 {
 | 
			
		||||
			actualMaxLen = 800
 | 
			
		||||
		}
 | 
			
		||||
		assert.Equal(t, diffStr, actual, "Unexpected change in the diff string: expected: %s but was actually: %s", diffStr[:expectedMaxLen], actual[:actualMaxLen])
 | 
			
		||||
		resp := ctx.Session.MakeRequestNilResponseHashSumRecorder(t, req, http.StatusOK)
 | 
			
		||||
		actual := string(resp.Hash.Sum(nil))
 | 
			
		||||
		actualLength := resp.Length
 | 
			
		||||
 | 
			
		||||
		equal := diffHash == actual
 | 
			
		||||
		assert.True(t, equal, "Unexpected change in the diff string: expected hash: %s size: %d but was actually: %s size: %d", hex.EncodeToString([]byte(diffHash)), diffLength, hex.EncodeToString([]byte(actual)), actualLength)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										35
									
								
								integrations/goget_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								integrations/goget_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
// 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 integrations
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestGoGet(t *testing.T) {
 | 
			
		||||
	defer prepareTestEnv(t)()
 | 
			
		||||
 | 
			
		||||
	req := NewRequest(t, "GET", "/blah/glah/plah?go-get=1")
 | 
			
		||||
	resp := MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
 | 
			
		||||
	expected := fmt.Sprintf(`<!doctype html>
 | 
			
		||||
<html>
 | 
			
		||||
	<head>
 | 
			
		||||
		<meta name="go-import" content="%[1]s:%[2]s/blah/glah git %[3]sblah/glah.git">
 | 
			
		||||
		<meta name="go-source" content="%[1]s:%[2]s/blah/glah _ %[3]sblah/glah/src/branch/master{/dir} %[3]sblah/glah/src/branch/master{/dir}/{file}#L{line}">
 | 
			
		||||
	</head>
 | 
			
		||||
	<body>
 | 
			
		||||
		go get --insecure %[1]s:%[2]s/blah/glah
 | 
			
		||||
	</body>
 | 
			
		||||
</html>
 | 
			
		||||
`, setting.Domain, setting.HTTPPort, setting.AppURL)
 | 
			
		||||
 | 
			
		||||
	assert.Equal(t, expected, resp.Body.String())
 | 
			
		||||
}
 | 
			
		||||
@@ -9,6 +9,8 @@ import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"database/sql"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"hash"
 | 
			
		||||
	"hash/fnv"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/http/cookiejar"
 | 
			
		||||
@@ -58,6 +60,26 @@ func NewNilResponseRecorder() *NilResponseRecorder {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type NilResponseHashSumRecorder struct {
 | 
			
		||||
	httptest.ResponseRecorder
 | 
			
		||||
	Hash   hash.Hash
 | 
			
		||||
	Length int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (n *NilResponseHashSumRecorder) Write(b []byte) (int, error) {
 | 
			
		||||
	_, _ = n.Hash.Write(b)
 | 
			
		||||
	n.Length += len(b)
 | 
			
		||||
	return len(b), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewRecorder returns an initialized ResponseRecorder.
 | 
			
		||||
func NewNilResponseHashSumRecorder() *NilResponseHashSumRecorder {
 | 
			
		||||
	return &NilResponseHashSumRecorder{
 | 
			
		||||
		Hash:             fnv.New32(),
 | 
			
		||||
		ResponseRecorder: *httptest.NewRecorder(),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMain(m *testing.M) {
 | 
			
		||||
	defer log.Close()
 | 
			
		||||
 | 
			
		||||
@@ -284,6 +306,23 @@ func (s *TestSession) MakeRequestNilResponseRecorder(t testing.TB, req *http.Req
 | 
			
		||||
	return resp
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *TestSession) MakeRequestNilResponseHashSumRecorder(t testing.TB, req *http.Request, expectedStatus int) *NilResponseHashSumRecorder {
 | 
			
		||||
	t.Helper()
 | 
			
		||||
	baseURL, err := url.Parse(setting.AppURL)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	for _, c := range s.jar.Cookies(baseURL) {
 | 
			
		||||
		req.AddCookie(c)
 | 
			
		||||
	}
 | 
			
		||||
	resp := MakeRequestNilResponseHashSumRecorder(t, req, expectedStatus)
 | 
			
		||||
 | 
			
		||||
	ch := http.Header{}
 | 
			
		||||
	ch.Add("Cookie", strings.Join(resp.Header()["Set-Cookie"], ";"))
 | 
			
		||||
	cr := http.Request{Header: ch}
 | 
			
		||||
	s.jar.SetCookies(baseURL, cr.Cookies())
 | 
			
		||||
 | 
			
		||||
	return resp
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const userPassword = "password"
 | 
			
		||||
 | 
			
		||||
var loginSessionCache = make(map[string]*TestSession, 10)
 | 
			
		||||
@@ -429,6 +468,19 @@ func MakeRequestNilResponseRecorder(t testing.TB, req *http.Request, expectedSta
 | 
			
		||||
	return recorder
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func MakeRequestNilResponseHashSumRecorder(t testing.TB, req *http.Request, expectedStatus int) *NilResponseHashSumRecorder {
 | 
			
		||||
	t.Helper()
 | 
			
		||||
	recorder := NewNilResponseHashSumRecorder()
 | 
			
		||||
	c.ServeHTTP(recorder, req)
 | 
			
		||||
	if expectedStatus != NoExpectedStatus {
 | 
			
		||||
		if !assert.EqualValues(t, expectedStatus, recorder.Code,
 | 
			
		||||
			"Request: %s %s", req.Method, req.URL.String()) {
 | 
			
		||||
			logUnexpectedResponse(t, &recorder.ResponseRecorder)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return recorder
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// logUnexpectedResponse logs the contents of an unexpected response.
 | 
			
		||||
func logUnexpectedResponse(t testing.TB, recorder *httptest.ResponseRecorder) {
 | 
			
		||||
	t.Helper()
 | 
			
		||||
 
 | 
			
		||||
@@ -10,9 +10,11 @@ import (
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/modules/test"
 | 
			
		||||
 | 
			
		||||
	"github.com/PuerkitoBio/goquery"
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"github.com/unknwon/i18n"
 | 
			
		||||
)
 | 
			
		||||
@@ -83,7 +85,7 @@ func TestCreateRelease(t *testing.T) {
 | 
			
		||||
	session := loginUser(t, "user2")
 | 
			
		||||
	createNewRelease(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", false, false)
 | 
			
		||||
 | 
			
		||||
	checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", i18n.Tr("en", "repo.release.stable"), 2)
 | 
			
		||||
	checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", i18n.Tr("en", "repo.release.stable"), 3)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCreateReleasePreRelease(t *testing.T) {
 | 
			
		||||
@@ -92,7 +94,7 @@ func TestCreateReleasePreRelease(t *testing.T) {
 | 
			
		||||
	session := loginUser(t, "user2")
 | 
			
		||||
	createNewRelease(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", true, false)
 | 
			
		||||
 | 
			
		||||
	checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", i18n.Tr("en", "repo.release.prerelease"), 2)
 | 
			
		||||
	checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", i18n.Tr("en", "repo.release.prerelease"), 3)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCreateReleaseDraft(t *testing.T) {
 | 
			
		||||
@@ -101,7 +103,7 @@ func TestCreateReleaseDraft(t *testing.T) {
 | 
			
		||||
	session := loginUser(t, "user2")
 | 
			
		||||
	createNewRelease(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", false, true)
 | 
			
		||||
 | 
			
		||||
	checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", i18n.Tr("en", "repo.release.draft"), 2)
 | 
			
		||||
	checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", i18n.Tr("en", "repo.release.draft"), 3)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCreateReleasePaging(t *testing.T) {
 | 
			
		||||
@@ -127,3 +129,80 @@ func TestCreateReleasePaging(t *testing.T) {
 | 
			
		||||
	session2 := loginUser(t, "user4")
 | 
			
		||||
	checkLatestReleaseAndCount(t, session2, "/user2/repo1", "v0.0.11", i18n.Tr("en", "repo.release.stable"), 10)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestViewReleaseListNoLogin(t *testing.T) {
 | 
			
		||||
	defer prepareTestEnv(t)()
 | 
			
		||||
 | 
			
		||||
	repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
 | 
			
		||||
 | 
			
		||||
	link := repo.Link() + "/releases"
 | 
			
		||||
 | 
			
		||||
	req := NewRequest(t, "GET", link)
 | 
			
		||||
	rsp := MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
 | 
			
		||||
	htmlDoc := NewHTMLParser(t, rsp.Body)
 | 
			
		||||
	releases := htmlDoc.Find("#release-list li.ui.grid")
 | 
			
		||||
	assert.Equal(t, 1, releases.Length())
 | 
			
		||||
 | 
			
		||||
	links := make([]string, 0, 5)
 | 
			
		||||
	releases.Each(func(i int, s *goquery.Selection) {
 | 
			
		||||
		link, exist := s.Find(".release-list-title a").Attr("href")
 | 
			
		||||
		if !exist {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		links = append(links, link)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	assert.EqualValues(t, []string{"/user2/repo1/releases/tag/v1.1"}, links)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestViewReleaseListLogin(t *testing.T) {
 | 
			
		||||
	defer prepareTestEnv(t)()
 | 
			
		||||
 | 
			
		||||
	repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
 | 
			
		||||
 | 
			
		||||
	link := repo.Link() + "/releases"
 | 
			
		||||
 | 
			
		||||
	session := loginUser(t, "user1")
 | 
			
		||||
	req := NewRequest(t, "GET", link)
 | 
			
		||||
	rsp := session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
 | 
			
		||||
	htmlDoc := NewHTMLParser(t, rsp.Body)
 | 
			
		||||
	releases := htmlDoc.Find("#release-list li.ui.grid")
 | 
			
		||||
	assert.Equal(t, 2, releases.Length())
 | 
			
		||||
 | 
			
		||||
	links := make([]string, 0, 5)
 | 
			
		||||
	releases.Each(func(i int, s *goquery.Selection) {
 | 
			
		||||
		link, exist := s.Find(".release-list-title a").Attr("href")
 | 
			
		||||
		if !exist {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		links = append(links, link)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	assert.EqualValues(t, []string{"/user2/repo1/releases/tag/draft-release",
 | 
			
		||||
		"/user2/repo1/releases/tag/v1.1"}, links)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestViewTagsList(t *testing.T) {
 | 
			
		||||
	defer prepareTestEnv(t)()
 | 
			
		||||
 | 
			
		||||
	repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
 | 
			
		||||
 | 
			
		||||
	link := repo.Link() + "/tags"
 | 
			
		||||
 | 
			
		||||
	session := loginUser(t, "user1")
 | 
			
		||||
	req := NewRequest(t, "GET", link)
 | 
			
		||||
	rsp := session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
 | 
			
		||||
	htmlDoc := NewHTMLParser(t, rsp.Body)
 | 
			
		||||
	tags := htmlDoc.Find(".tag-list tr")
 | 
			
		||||
	assert.Equal(t, 2, tags.Length())
 | 
			
		||||
 | 
			
		||||
	tagNames := make([]string, 0, 5)
 | 
			
		||||
	tags.Each(func(i int, s *goquery.Selection) {
 | 
			
		||||
		tagNames = append(tagNames, s.Find(".tag a.df.ac").Text())
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	assert.EqualValues(t, []string{"delete-tag", "v1.1"}, tagNames)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ import (
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/modules/test"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
@@ -134,5 +135,13 @@ func TestCreateBranchInvalidCSRF(t *testing.T) {
 | 
			
		||||
		"_csrf":           "fake_csrf",
 | 
			
		||||
		"new_branch_name": "test",
 | 
			
		||||
	})
 | 
			
		||||
	session.MakeRequest(t, req, http.StatusBadRequest)
 | 
			
		||||
	resp := session.MakeRequest(t, req, http.StatusFound)
 | 
			
		||||
	loc := resp.Header().Get("Location")
 | 
			
		||||
	assert.Equal(t, setting.AppSubURL+"/", loc)
 | 
			
		||||
	resp = session.MakeRequest(t, NewRequest(t, "GET", loc), http.StatusOK)
 | 
			
		||||
	htmlDoc := NewHTMLParser(t, resp.Body)
 | 
			
		||||
	assert.Equal(t,
 | 
			
		||||
		"Bad Request: Invalid CSRF token",
 | 
			
		||||
		strings.TrimSpace(htmlDoc.doc.Find(".ui.message").Text()),
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -382,7 +382,7 @@ func activityQueryCondition(opts GetFeedsOptions) (builder.Cond, error) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if opts.Date != "" {
 | 
			
		||||
		dateLow, err := time.Parse("2006-01-02", opts.Date)
 | 
			
		||||
		dateLow, err := time.ParseInLocation("2006-01-02", opts.Date, setting.DefaultUILocation)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Warn("Unable to parse %s, filter not applied: %v", opts.Date, err)
 | 
			
		||||
		} else {
 | 
			
		||||
 
 | 
			
		||||
@@ -85,7 +85,7 @@ func (a *Attachment) LinkedRepository() (*Repository, UnitType, error) {
 | 
			
		||||
func NewAttachment(attach *Attachment, buf []byte, file io.Reader) (_ *Attachment, err error) {
 | 
			
		||||
	attach.UUID = gouuid.New().String()
 | 
			
		||||
 | 
			
		||||
	size, err := storage.Attachments.Save(attach.RelativePath(), io.MultiReader(bytes.NewReader(buf), file))
 | 
			
		||||
	size, err := storage.Attachments.Save(attach.RelativePath(), io.MultiReader(bytes.NewReader(buf), file), -1)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Create: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
@@ -125,8 +125,8 @@ func getAttachmentByUUID(e Engine, uuid string) (*Attachment, error) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetAttachmentsByUUIDs returns attachment by given UUID list.
 | 
			
		||||
func GetAttachmentsByUUIDs(uuids []string) ([]*Attachment, error) {
 | 
			
		||||
	return getAttachmentsByUUIDs(x, uuids)
 | 
			
		||||
func GetAttachmentsByUUIDs(ctx DBContext, uuids []string) ([]*Attachment, error) {
 | 
			
		||||
	return getAttachmentsByUUIDs(ctx.e, uuids)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getAttachmentsByUUIDs(e Engine, uuids []string) ([]*Attachment, error) {
 | 
			
		||||
@@ -183,12 +183,12 @@ func getAttachmentByReleaseIDFileName(e Engine, releaseID int64, fileName string
 | 
			
		||||
 | 
			
		||||
// DeleteAttachment deletes the given attachment and optionally the associated file.
 | 
			
		||||
func DeleteAttachment(a *Attachment, remove bool) error {
 | 
			
		||||
	_, err := DeleteAttachments([]*Attachment{a}, remove)
 | 
			
		||||
	_, err := DeleteAttachments(DefaultDBContext(), []*Attachment{a}, remove)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeleteAttachments deletes the given attachments and optionally the associated files.
 | 
			
		||||
func DeleteAttachments(attachments []*Attachment, remove bool) (int, error) {
 | 
			
		||||
func DeleteAttachments(ctx DBContext, attachments []*Attachment, remove bool) (int, error) {
 | 
			
		||||
	if len(attachments) == 0 {
 | 
			
		||||
		return 0, nil
 | 
			
		||||
	}
 | 
			
		||||
@@ -198,7 +198,7 @@ func DeleteAttachments(attachments []*Attachment, remove bool) (int, error) {
 | 
			
		||||
		ids = append(ids, a.ID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cnt, err := x.In("id", ids).NoAutoCondition().Delete(attachments[0])
 | 
			
		||||
	cnt, err := ctx.e.In("id", ids).NoAutoCondition().Delete(attachments[0])
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0, err
 | 
			
		||||
	}
 | 
			
		||||
@@ -220,7 +220,7 @@ func DeleteAttachmentsByIssue(issueID int64, remove bool) (int, error) {
 | 
			
		||||
		return 0, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return DeleteAttachments(attachments, remove)
 | 
			
		||||
	return DeleteAttachments(DefaultDBContext(), attachments, remove)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeleteAttachmentsByComment deletes all attachments associated with the given comment.
 | 
			
		||||
@@ -230,7 +230,7 @@ func DeleteAttachmentsByComment(commentID int64, remove bool) (int, error) {
 | 
			
		||||
		return 0, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return DeleteAttachments(attachments, remove)
 | 
			
		||||
	return DeleteAttachments(DefaultDBContext(), attachments, remove)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateAttachment updates the given attachment in database
 | 
			
		||||
@@ -238,6 +238,15 @@ func UpdateAttachment(atta *Attachment) error {
 | 
			
		||||
	return updateAttachment(x, atta)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateAttachmentByUUID Updates attachment via uuid
 | 
			
		||||
func UpdateAttachmentByUUID(ctx DBContext, attach *Attachment, cols ...string) error {
 | 
			
		||||
	if attach.UUID == "" {
 | 
			
		||||
		return fmt.Errorf("Attachement uuid should not blank")
 | 
			
		||||
	}
 | 
			
		||||
	_, err := ctx.e.Where("uuid=?", attach.UUID).Cols(cols...).Update(attach)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func updateAttachment(e Engine, atta *Attachment) error {
 | 
			
		||||
	var sess *xorm.Session
 | 
			
		||||
	if atta.ID != 0 && atta.UUID == "" {
 | 
			
		||||
 
 | 
			
		||||
@@ -120,7 +120,7 @@ func TestUpdateAttachment(t *testing.T) {
 | 
			
		||||
func TestGetAttachmentsByUUIDs(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, PrepareTestDatabase())
 | 
			
		||||
 | 
			
		||||
	attachList, err := GetAttachmentsByUUIDs([]string{"a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a17", "not-existing-uuid"})
 | 
			
		||||
	attachList, err := GetAttachmentsByUUIDs(DefaultDBContext(), []string{"a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a17", "not-existing-uuid"})
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, 2, len(attachList))
 | 
			
		||||
	assert.Equal(t, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", attachList[0].UUID)
 | 
			
		||||
 
 | 
			
		||||
@@ -81,7 +81,7 @@ func LibravatarURL(email string) (*url.URL, error) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// HashedAvatarLink returns an avatar link for a provided email
 | 
			
		||||
func HashedAvatarLink(email string) string {
 | 
			
		||||
func HashedAvatarLink(email string, size int) string {
 | 
			
		||||
	lowerEmail := strings.ToLower(strings.TrimSpace(email))
 | 
			
		||||
	sum := fmt.Sprintf("%x", md5.Sum([]byte(lowerEmail)))
 | 
			
		||||
	_, _ = cache.GetString("Avatar:"+sum, func() (string, error) {
 | 
			
		||||
@@ -96,6 +96,11 @@ func HashedAvatarLink(email string) string {
 | 
			
		||||
			// we don't care about any DB problem just return the lowerEmail
 | 
			
		||||
			return lowerEmail, nil
 | 
			
		||||
		}
 | 
			
		||||
		has, err := sess.Where("email = ? AND hash = ?", emailHash.Email, emailHash.Hash).Get(new(EmailHash))
 | 
			
		||||
		if has || err != nil {
 | 
			
		||||
			// Seriously we don't care about any DB problems just return the lowerEmail - we expect the transaction to fail most of the time
 | 
			
		||||
			return lowerEmail, nil
 | 
			
		||||
		}
 | 
			
		||||
		_, _ = sess.Insert(emailHash)
 | 
			
		||||
		if err := sess.Commit(); err != nil {
 | 
			
		||||
			// Seriously we don't care about any DB problems just return the lowerEmail - we expect the transaction to fail most of the time
 | 
			
		||||
@@ -103,6 +108,9 @@ func HashedAvatarLink(email string) string {
 | 
			
		||||
		}
 | 
			
		||||
		return lowerEmail, nil
 | 
			
		||||
	})
 | 
			
		||||
	if size > 0 {
 | 
			
		||||
		return setting.AppSubURL + "/avatar/" + url.PathEscape(sum) + "?size=" + strconv.Itoa(size)
 | 
			
		||||
	}
 | 
			
		||||
	return setting.AppSubURL + "/avatar/" + url.PathEscape(sum)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -124,7 +132,7 @@ func SizedAvatarLink(email string, size int) string {
 | 
			
		||||
		// This is the slow path that would need to call LibravatarURL() which
 | 
			
		||||
		// does DNS lookups. Avoid it by issuing a redirect so we don't block
 | 
			
		||||
		// the template render with network requests.
 | 
			
		||||
		return HashedAvatarLink(email)
 | 
			
		||||
		return HashedAvatarLink(email, size)
 | 
			
		||||
	} else if !setting.DisableGravatar {
 | 
			
		||||
		// copy GravatarSourceURL, because we will modify its Path.
 | 
			
		||||
		copyOfGravatarSourceURL := *setting.GravatarSourceURL
 | 
			
		||||
 
 | 
			
		||||
@@ -141,6 +141,12 @@ func (milestone *Milestone) checkForConsistency(t *testing.T) {
 | 
			
		||||
	actual := getCount(t, x.Where("is_closed=?", true), &Issue{MilestoneID: milestone.ID})
 | 
			
		||||
	assert.EqualValues(t, milestone.NumClosedIssues, actual,
 | 
			
		||||
		"Unexpected number of closed issues for milestone %+v", milestone)
 | 
			
		||||
 | 
			
		||||
	completeness := 0
 | 
			
		||||
	if milestone.NumIssues > 0 {
 | 
			
		||||
		completeness = milestone.NumClosedIssues * 100 / milestone.NumIssues
 | 
			
		||||
	}
 | 
			
		||||
	assert.Equal(t, completeness, milestone.Completeness)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (label *Label) checkForConsistency(t *testing.T) {
 | 
			
		||||
@@ -296,11 +302,15 @@ func CountOrphanedObjects(subject, refobject, joinCond string) (int64, error) {
 | 
			
		||||
 | 
			
		||||
// DeleteOrphanedObjects delete subjects with have no existing refobject anymore
 | 
			
		||||
func DeleteOrphanedObjects(subject, refobject, joinCond string) error {
 | 
			
		||||
	_, err := x.In("id", builder.Select("`"+subject+"`.id").
 | 
			
		||||
	subQuery := builder.Select("`"+subject+"`.id").
 | 
			
		||||
		From("`"+subject+"`").
 | 
			
		||||
		Join("LEFT", "`"+refobject+"`", joinCond).
 | 
			
		||||
		Where(builder.IsNull{"`" + refobject + "`.id"})).
 | 
			
		||||
		Delete("`" + subject + "`")
 | 
			
		||||
		Where(builder.IsNull{"`" + refobject + "`.id"})
 | 
			
		||||
	sql, args, err := builder.Delete(builder.In("id", subQuery)).From("`" + subject + "`").ToSQL()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	_, err = x.Exec(append([]interface{}{sql}, args...)...)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -338,7 +348,7 @@ func FixCommentTypeLabelWithEmptyLabel() (int64, error) {
 | 
			
		||||
 | 
			
		||||
// CountCommentTypeLabelWithOutsideLabels count label comments with outside label
 | 
			
		||||
func CountCommentTypeLabelWithOutsideLabels() (int64, error) {
 | 
			
		||||
	return x.Where("comment.type = ? AND (issue.repo_id != label.repo_id OR (label.repo_id = 0 AND repository.owner_id != label.org_id))", CommentTypeLabel).
 | 
			
		||||
	return x.Where("comment.type = ? AND ((label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id))", CommentTypeLabel).
 | 
			
		||||
		Table("comment").
 | 
			
		||||
		Join("inner", "label", "label.id = comment.label_id").
 | 
			
		||||
		Join("inner", "issue", "issue.id = comment.issue_id ").
 | 
			
		||||
@@ -354,8 +364,9 @@ func FixCommentTypeLabelWithOutsideLabels() (int64, error) {
 | 
			
		||||
				FROM comment AS com
 | 
			
		||||
					INNER JOIN label ON com.label_id = label.id
 | 
			
		||||
					INNER JOIN issue on issue.id = com.issue_id
 | 
			
		||||
					INNER JOIN repository ON issue.repo_id = repository.id
 | 
			
		||||
				WHERE
 | 
			
		||||
					com.type = ? AND (issue.repo_id != label.repo_id OR (label.repo_id = 0 AND label.org_id != repo.owner_id))
 | 
			
		||||
					com.type = ? AND ((label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id))
 | 
			
		||||
	) AS il_too)`, CommentTypeLabel)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0, err
 | 
			
		||||
@@ -366,9 +377,9 @@ func FixCommentTypeLabelWithOutsideLabels() (int64, error) {
 | 
			
		||||
 | 
			
		||||
// CountIssueLabelWithOutsideLabels count label comments with outside label
 | 
			
		||||
func CountIssueLabelWithOutsideLabels() (int64, error) {
 | 
			
		||||
	return x.Where(builder.Expr("issue.repo_id != label.repo_id OR (label.repo_id = 0 AND repository.owner_id != label.org_id)")).
 | 
			
		||||
	return x.Where(builder.Expr("(label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id)")).
 | 
			
		||||
		Table("issue_label").
 | 
			
		||||
		Join("inner", "label", "issue_label.id = label.id ").
 | 
			
		||||
		Join("inner", "label", "issue_label.label_id = label.id ").
 | 
			
		||||
		Join("inner", "issue", "issue.id = issue_label.issue_id ").
 | 
			
		||||
		Join("inner", "repository", "issue.repo_id = repository.id").
 | 
			
		||||
		Count(new(IssueLabel))
 | 
			
		||||
@@ -380,11 +391,11 @@ func FixIssueLabelWithOutsideLabels() (int64, error) {
 | 
			
		||||
		SELECT il_too.id FROM (
 | 
			
		||||
			SELECT il_too_too.id
 | 
			
		||||
				FROM issue_label AS il_too_too
 | 
			
		||||
					INNER JOIN label ON il_too_too.id = label.id
 | 
			
		||||
					INNER JOIN label ON il_too_too.label_id = label.id
 | 
			
		||||
					INNER JOIN issue on issue.id = il_too_too.issue_id
 | 
			
		||||
					INNER JOIN repository on repository.id = issue.repo_id
 | 
			
		||||
				WHERE
 | 
			
		||||
					issue.repo_id != label.repo_id OR (label.repo_id = 0 AND label.org_id != repository.owner_id)
 | 
			
		||||
					(label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id)
 | 
			
		||||
	) AS il_too )`)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										32
									
								
								models/consistency_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								models/consistency_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
// Copyright 2021 Gitea. All rights reserved.
 | 
			
		||||
// Use of this source code is governed by a MIT-style
 | 
			
		||||
// license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestDeleteOrphanedObjects(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, PrepareTestDatabase())
 | 
			
		||||
 | 
			
		||||
	countBefore, err := x.Count(&PullRequest{})
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	_, err = x.Insert(&PullRequest{IssueID: 1000}, &PullRequest{IssueID: 1001}, &PullRequest{IssueID: 1003})
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	orphaned, err := CountOrphanedObjects("pull_request", "issue", "pull_request.issue_id=issue.id")
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.EqualValues(t, 3, orphaned)
 | 
			
		||||
 | 
			
		||||
	err = DeleteOrphanedObjects("pull_request", "issue", "pull_request.issue_id=issue.id")
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	countAfter, err := x.Count(&PullRequest{})
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.EqualValues(t, countBefore, countAfter)
 | 
			
		||||
}
 | 
			
		||||
@@ -43,3 +43,15 @@
 | 
			
		||||
  is_tag: true
 | 
			
		||||
  created_unix: 946684800
 | 
			
		||||
 | 
			
		||||
-
 | 
			
		||||
  id: 4
 | 
			
		||||
  repo_id: 1
 | 
			
		||||
  publisher_id: 2
 | 
			
		||||
  tag_name: "draft-release"
 | 
			
		||||
  lower_tag_name: "draft-release"
 | 
			
		||||
  target: "master"
 | 
			
		||||
  title: "draft-release"
 | 
			
		||||
  is_draft: true
 | 
			
		||||
  is_prerelease: false
 | 
			
		||||
  is_tag: false
 | 
			
		||||
  created_unix: 1619524806
 | 
			
		||||
 
 | 
			
		||||
@@ -648,8 +648,10 @@ func (issue *Issue) doChangeStatus(e *xorm.Session, doer *User, isMergePull bool
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Update issue count of milestone
 | 
			
		||||
	if err := updateMilestoneClosedNum(e, issue.MilestoneID); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	if issue.MilestoneID > 0 {
 | 
			
		||||
		if err := updateMilestoneCounters(e, issue.MilestoneID); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := issue.updateClosedNum(e); err != nil {
 | 
			
		||||
@@ -912,7 +914,7 @@ func newIssue(e *xorm.Session, doer *User, opts NewIssueOptions) (err error) {
 | 
			
		||||
	opts.Issue.Index = inserted.Index
 | 
			
		||||
 | 
			
		||||
	if opts.Issue.MilestoneID > 0 {
 | 
			
		||||
		if _, err = e.Exec("UPDATE `milestone` SET num_issues=num_issues+1 WHERE id=?", opts.Issue.MilestoneID); err != nil {
 | 
			
		||||
		if err := updateMilestoneCounters(e, opts.Issue.MilestoneID); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -1032,6 +1034,9 @@ func newIssueAttempt(repo *Repository, issue *Issue, labelIDs []int64, uuids []s
 | 
			
		||||
 | 
			
		||||
// GetIssueByIndex returns raw issue without loading attributes by index in a repository.
 | 
			
		||||
func GetIssueByIndex(repoID, index int64) (*Issue, error) {
 | 
			
		||||
	if index < 1 {
 | 
			
		||||
		return nil, ErrIssueNotExist{}
 | 
			
		||||
	}
 | 
			
		||||
	issue := &Issue{
 | 
			
		||||
		RepoID: repoID,
 | 
			
		||||
		Index:  index,
 | 
			
		||||
@@ -1086,7 +1091,7 @@ func getIssuesByIDs(e Engine, issueIDs []int64) ([]*Issue, error) {
 | 
			
		||||
 | 
			
		||||
func getIssueIDsByRepoID(e Engine, repoID int64) ([]int64, error) {
 | 
			
		||||
	ids := make([]int64, 0, 10)
 | 
			
		||||
	err := e.Table("issue").Where("repo_id = ?", repoID).Find(&ids)
 | 
			
		||||
	err := e.Table("issue").Cols("id").Where("repo_id = ?", repoID).Find(&ids)
 | 
			
		||||
	return ids, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -53,6 +53,9 @@ func (issues IssueList) loadRepositories(e Engine) ([]*Repository, error) {
 | 
			
		||||
 | 
			
		||||
	for _, issue := range issues {
 | 
			
		||||
		issue.Repo = repoMaps[issue.RepoID]
 | 
			
		||||
		if issue.PullRequest != nil {
 | 
			
		||||
			issue.PullRequest.BaseRepo = issue.Repo
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return valuesRepository(repoMaps), nil
 | 
			
		||||
}
 | 
			
		||||
@@ -516,6 +519,11 @@ func (issues IssueList) LoadDiscussComments() error {
 | 
			
		||||
	return issues.loadComments(x, builder.Eq{"comment.type": CommentTypeComment})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LoadPullRequests loads pull requests
 | 
			
		||||
func (issues IssueList) LoadPullRequests() error {
 | 
			
		||||
	return issues.loadPullRequests(x)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetApprovalCounts returns a map of issue ID to slice of approval counts
 | 
			
		||||
// FIXME: only returns official counts due to double counting of non-official approvals
 | 
			
		||||
func (issues IssueList) GetApprovalCounts() (map[int64][]*ReviewCount, error) {
 | 
			
		||||
 
 | 
			
		||||
@@ -129,8 +129,12 @@ func GetMilestoneByRepoIDANDName(repoID int64, name string) (*Milestone, error)
 | 
			
		||||
 | 
			
		||||
// GetMilestoneByID returns the milestone via id .
 | 
			
		||||
func GetMilestoneByID(id int64) (*Milestone, error) {
 | 
			
		||||
	return getMilestoneByID(x, id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getMilestoneByID(e Engine, id int64) (*Milestone, error) {
 | 
			
		||||
	var m Milestone
 | 
			
		||||
	has, err := x.ID(id).Get(&m)
 | 
			
		||||
	has, err := e.ID(id).Get(&m)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	} else if !has {
 | 
			
		||||
@@ -155,10 +159,6 @@ func UpdateMilestone(m *Milestone, oldIsClosed bool) error {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := updateMilestoneCompleteness(sess, m.ID); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// if IsClosed changed, update milestone numbers of repository
 | 
			
		||||
	if oldIsClosed != m.IsClosed {
 | 
			
		||||
		if err := updateRepoMilestoneNum(sess, m.RepoID); err != nil {
 | 
			
		||||
@@ -171,23 +171,31 @@ func UpdateMilestone(m *Milestone, oldIsClosed bool) error {
 | 
			
		||||
 | 
			
		||||
func updateMilestone(e Engine, m *Milestone) error {
 | 
			
		||||
	m.Name = strings.TrimSpace(m.Name)
 | 
			
		||||
	_, err := e.ID(m.ID).AllCols().
 | 
			
		||||
	_, err := e.ID(m.ID).AllCols().Update(m)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return updateMilestoneCounters(e, m.ID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// updateMilestoneCounters calculates NumIssues, NumClosesIssues and Completeness
 | 
			
		||||
func updateMilestoneCounters(e Engine, id int64) error {
 | 
			
		||||
	_, err := e.ID(id).
 | 
			
		||||
		SetExpr("num_issues", builder.Select("count(*)").From("issue").Where(
 | 
			
		||||
			builder.Eq{"milestone_id": m.ID},
 | 
			
		||||
			builder.Eq{"milestone_id": id},
 | 
			
		||||
		)).
 | 
			
		||||
		SetExpr("num_closed_issues", builder.Select("count(*)").From("issue").Where(
 | 
			
		||||
			builder.Eq{
 | 
			
		||||
				"milestone_id": m.ID,
 | 
			
		||||
				"milestone_id": id,
 | 
			
		||||
				"is_closed":    true,
 | 
			
		||||
			},
 | 
			
		||||
		)).
 | 
			
		||||
		Update(m)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func updateMilestoneCompleteness(e Engine, milestoneID int64) error {
 | 
			
		||||
	_, err := e.Exec("UPDATE `milestone` SET completeness=100*num_closed_issues/(CASE WHEN num_issues > 0 THEN num_issues ELSE 1 END) WHERE id=?",
 | 
			
		||||
		milestoneID,
 | 
			
		||||
		Update(&Milestone{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	_, err = e.Exec("UPDATE `milestone` SET completeness=100*num_closed_issues/(CASE WHEN num_issues > 0 THEN num_issues ELSE 1 END) WHERE id=?",
 | 
			
		||||
		id,
 | 
			
		||||
	)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
@@ -256,25 +264,15 @@ func changeMilestoneAssign(e *xorm.Session, doer *User, issue *Issue, oldMilesto
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if oldMilestoneID > 0 {
 | 
			
		||||
		if err := updateMilestoneTotalNum(e, oldMilestoneID); err != nil {
 | 
			
		||||
		if err := updateMilestoneCounters(e, oldMilestoneID); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if issue.IsClosed {
 | 
			
		||||
			if err := updateMilestoneClosedNum(e, oldMilestoneID); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if issue.MilestoneID > 0 {
 | 
			
		||||
		if err := updateMilestoneTotalNum(e, issue.MilestoneID); err != nil {
 | 
			
		||||
		if err := updateMilestoneCounters(e, issue.MilestoneID); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if issue.IsClosed {
 | 
			
		||||
			if err := updateMilestoneClosedNum(e, issue.MilestoneID); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if oldMilestoneID > 0 || issue.MilestoneID > 0 {
 | 
			
		||||
@@ -558,29 +556,6 @@ func updateRepoMilestoneNum(e Engine, repoID int64) error {
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func updateMilestoneTotalNum(e Engine, milestoneID int64) (err error) {
 | 
			
		||||
	if _, err = e.Exec("UPDATE `milestone` SET num_issues=(SELECT count(*) FROM issue WHERE milestone_id=?) WHERE id=?",
 | 
			
		||||
		milestoneID,
 | 
			
		||||
		milestoneID,
 | 
			
		||||
	); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return updateMilestoneCompleteness(e, milestoneID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func updateMilestoneClosedNum(e Engine, milestoneID int64) (err error) {
 | 
			
		||||
	if _, err = e.Exec("UPDATE `milestone` SET num_closed_issues=(SELECT count(*) FROM issue WHERE milestone_id=? AND is_closed=?) WHERE id=?",
 | 
			
		||||
		milestoneID,
 | 
			
		||||
		true,
 | 
			
		||||
		milestoneID,
 | 
			
		||||
	); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return updateMilestoneCompleteness(e, milestoneID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//  _____               _            _ _____ _
 | 
			
		||||
// |_   _| __ __ _  ___| | _____  __| |_   _(_)_ __ ___   ___  ___
 | 
			
		||||
//   | || '__/ _` |/ __| |/ / _ \/ _` | | | | | '_ ` _ \ / _ \/ __|
 | 
			
		||||
 
 | 
			
		||||
@@ -215,7 +215,7 @@ func TestChangeMilestoneStatus(t *testing.T) {
 | 
			
		||||
	CheckConsistencyFor(t, &Repository{ID: milestone.RepoID}, &Milestone{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestUpdateMilestoneClosedNum(t *testing.T) {
 | 
			
		||||
func TestUpdateMilestoneCounters(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, PrepareTestDatabase())
 | 
			
		||||
	issue := AssertExistsAndLoadBean(t, &Issue{MilestoneID: 1},
 | 
			
		||||
		"is_closed=0").(*Issue)
 | 
			
		||||
@@ -224,14 +224,14 @@ func TestUpdateMilestoneClosedNum(t *testing.T) {
 | 
			
		||||
	issue.ClosedUnix = timeutil.TimeStampNow()
 | 
			
		||||
	_, err := x.ID(issue.ID).Cols("is_closed", "closed_unix").Update(issue)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.NoError(t, updateMilestoneClosedNum(x, issue.MilestoneID))
 | 
			
		||||
	assert.NoError(t, updateMilestoneCounters(x, issue.MilestoneID))
 | 
			
		||||
	CheckConsistencyFor(t, &Milestone{})
 | 
			
		||||
 | 
			
		||||
	issue.IsClosed = false
 | 
			
		||||
	issue.ClosedUnix = 0
 | 
			
		||||
	_, err = x.ID(issue.ID).Cols("is_closed", "closed_unix").Update(issue)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.NoError(t, updateMilestoneClosedNum(x, issue.MilestoneID))
 | 
			
		||||
	assert.NoError(t, updateMilestoneCounters(x, issue.MilestoneID))
 | 
			
		||||
	CheckConsistencyFor(t, &Milestone{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -36,6 +36,14 @@ func TestIssue_ReplaceLabels(t *testing.T) {
 | 
			
		||||
	testSuccess(1, []int64{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_GetIssueIDsByRepoID(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, PrepareTestDatabase())
 | 
			
		||||
 | 
			
		||||
	ids, err := GetIssueIDsByRepoID(1)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Len(t, ids, 5)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestIssueAPIURL(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, PrepareTestDatabase())
 | 
			
		||||
	issue := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue)
 | 
			
		||||
 
 | 
			
		||||
@@ -41,7 +41,7 @@ func (opts *ListOptions) setEnginePagination(e Engine) Engine {
 | 
			
		||||
func (opts *ListOptions) GetStartEnd() (start, end int) {
 | 
			
		||||
	opts.setDefaultValues()
 | 
			
		||||
	start = (opts.Page - 1) * opts.PageSize
 | 
			
		||||
	end = start + opts.Page
 | 
			
		||||
	end = start + opts.PageSize
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,8 @@ func NewXORMLogger(showSQL bool) xormlog.Logger {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const stackLevel = 8
 | 
			
		||||
 | 
			
		||||
// Log a message with defined skip and at logging level
 | 
			
		||||
func (l *XORMLogBridge) Log(skip int, level log.Level, format string, v ...interface{}) error {
 | 
			
		||||
	return l.logger.Log(skip+1, level, format, v...)
 | 
			
		||||
@@ -33,42 +35,42 @@ func (l *XORMLogBridge) Log(skip int, level log.Level, format string, v ...inter
 | 
			
		||||
 | 
			
		||||
// Debug show debug log
 | 
			
		||||
func (l *XORMLogBridge) Debug(v ...interface{}) {
 | 
			
		||||
	_ = l.Log(2, log.DEBUG, fmt.Sprint(v...))
 | 
			
		||||
	_ = l.Log(stackLevel, log.DEBUG, fmt.Sprint(v...))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Debugf show debug log
 | 
			
		||||
func (l *XORMLogBridge) Debugf(format string, v ...interface{}) {
 | 
			
		||||
	_ = l.Log(2, log.DEBUG, format, v...)
 | 
			
		||||
	_ = l.Log(stackLevel, log.DEBUG, format, v...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Error show error log
 | 
			
		||||
func (l *XORMLogBridge) Error(v ...interface{}) {
 | 
			
		||||
	_ = l.Log(2, log.ERROR, fmt.Sprint(v...))
 | 
			
		||||
	_ = l.Log(stackLevel, log.ERROR, fmt.Sprint(v...))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Errorf show error log
 | 
			
		||||
func (l *XORMLogBridge) Errorf(format string, v ...interface{}) {
 | 
			
		||||
	_ = l.Log(2, log.ERROR, format, v...)
 | 
			
		||||
	_ = l.Log(stackLevel, log.ERROR, format, v...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Info show information level log
 | 
			
		||||
func (l *XORMLogBridge) Info(v ...interface{}) {
 | 
			
		||||
	_ = l.Log(2, log.INFO, fmt.Sprint(v...))
 | 
			
		||||
	_ = l.Log(stackLevel, log.INFO, fmt.Sprint(v...))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Infof show information level log
 | 
			
		||||
func (l *XORMLogBridge) Infof(format string, v ...interface{}) {
 | 
			
		||||
	_ = l.Log(2, log.INFO, format, v...)
 | 
			
		||||
	_ = l.Log(stackLevel, log.INFO, format, v...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Warn show warning log
 | 
			
		||||
func (l *XORMLogBridge) Warn(v ...interface{}) {
 | 
			
		||||
	_ = l.Log(2, log.WARN, fmt.Sprint(v...))
 | 
			
		||||
	_ = l.Log(stackLevel, log.WARN, fmt.Sprint(v...))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Warnf show warnning log
 | 
			
		||||
func (l *XORMLogBridge) Warnf(format string, v ...interface{}) {
 | 
			
		||||
	_ = l.Log(2, log.WARN, format, v...)
 | 
			
		||||
	_ = l.Log(stackLevel, log.WARN, format, v...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Level get logger level
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@ package models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"encoding/binary"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/smtp"
 | 
			
		||||
@@ -21,6 +22,7 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/modules/timeutil"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
	gouuid "github.com/google/uuid"
 | 
			
		||||
	jsoniter "github.com/json-iterator/go"
 | 
			
		||||
 | 
			
		||||
	"xorm.io/xorm"
 | 
			
		||||
@@ -68,6 +70,36 @@ var (
 | 
			
		||||
	_ convert.Conversion = &SSPIConfig{}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// jsonUnmarshalHandleDoubleEncode - due to a bug in xorm (see https://gitea.com/xorm/xorm/pulls/1957) - it's
 | 
			
		||||
// possible that a Blob may be double encoded or gain an unwanted prefix of 0xff 0xfe.
 | 
			
		||||
func jsonUnmarshalHandleDoubleEncode(bs []byte, v interface{}) error {
 | 
			
		||||
	json := jsoniter.ConfigCompatibleWithStandardLibrary
 | 
			
		||||
	err := json.Unmarshal(bs, v)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ok := true
 | 
			
		||||
		rs := []byte{}
 | 
			
		||||
		temp := make([]byte, 2)
 | 
			
		||||
		for _, rn := range string(bs) {
 | 
			
		||||
			if rn > 0xffff {
 | 
			
		||||
				ok = false
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
			binary.LittleEndian.PutUint16(temp, uint16(rn))
 | 
			
		||||
			rs = append(rs, temp...)
 | 
			
		||||
		}
 | 
			
		||||
		if ok {
 | 
			
		||||
			if rs[0] == 0xff && rs[1] == 0xfe {
 | 
			
		||||
				rs = rs[2:]
 | 
			
		||||
			}
 | 
			
		||||
			err = json.Unmarshal(rs, v)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil && len(bs) > 2 && bs[0] == 0xff && bs[1] == 0xfe {
 | 
			
		||||
		err = json.Unmarshal(bs[2:], v)
 | 
			
		||||
	}
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LDAPConfig holds configuration for LDAP login source.
 | 
			
		||||
type LDAPConfig struct {
 | 
			
		||||
	*ldap.Source
 | 
			
		||||
@@ -75,8 +107,7 @@ type LDAPConfig struct {
 | 
			
		||||
 | 
			
		||||
// FromDB fills up a LDAPConfig from serialized format.
 | 
			
		||||
func (cfg *LDAPConfig) FromDB(bs []byte) error {
 | 
			
		||||
	json := jsoniter.ConfigCompatibleWithStandardLibrary
 | 
			
		||||
	return json.Unmarshal(bs, &cfg)
 | 
			
		||||
	return jsonUnmarshalHandleDoubleEncode(bs, &cfg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToDB exports a LDAPConfig to a serialized format.
 | 
			
		||||
@@ -103,8 +134,7 @@ type SMTPConfig struct {
 | 
			
		||||
 | 
			
		||||
// FromDB fills up an SMTPConfig from serialized format.
 | 
			
		||||
func (cfg *SMTPConfig) FromDB(bs []byte) error {
 | 
			
		||||
	json := jsoniter.ConfigCompatibleWithStandardLibrary
 | 
			
		||||
	return json.Unmarshal(bs, cfg)
 | 
			
		||||
	return jsonUnmarshalHandleDoubleEncode(bs, cfg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToDB exports an SMTPConfig to a serialized format.
 | 
			
		||||
@@ -116,12 +146,12 @@ func (cfg *SMTPConfig) ToDB() ([]byte, error) {
 | 
			
		||||
// PAMConfig holds configuration for the PAM login source.
 | 
			
		||||
type PAMConfig struct {
 | 
			
		||||
	ServiceName string // pam service (e.g. system-auth)
 | 
			
		||||
	EmailDomain string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FromDB fills up a PAMConfig from serialized format.
 | 
			
		||||
func (cfg *PAMConfig) FromDB(bs []byte) error {
 | 
			
		||||
	json := jsoniter.ConfigCompatibleWithStandardLibrary
 | 
			
		||||
	return json.Unmarshal(bs, &cfg)
 | 
			
		||||
	return jsonUnmarshalHandleDoubleEncode(bs, cfg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToDB exports a PAMConfig to a serialized format.
 | 
			
		||||
@@ -142,8 +172,7 @@ type OAuth2Config struct {
 | 
			
		||||
 | 
			
		||||
// FromDB fills up an OAuth2Config from serialized format.
 | 
			
		||||
func (cfg *OAuth2Config) FromDB(bs []byte) error {
 | 
			
		||||
	json := jsoniter.ConfigCompatibleWithStandardLibrary
 | 
			
		||||
	return json.Unmarshal(bs, cfg)
 | 
			
		||||
	return jsonUnmarshalHandleDoubleEncode(bs, cfg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToDB exports an SMTPConfig to a serialized format.
 | 
			
		||||
@@ -163,8 +192,7 @@ type SSPIConfig struct {
 | 
			
		||||
 | 
			
		||||
// FromDB fills up an SSPIConfig from serialized format.
 | 
			
		||||
func (cfg *SSPIConfig) FromDB(bs []byte) error {
 | 
			
		||||
	json := jsoniter.ConfigCompatibleWithStandardLibrary
 | 
			
		||||
	return json.Unmarshal(bs, cfg)
 | 
			
		||||
	return jsonUnmarshalHandleDoubleEncode(bs, cfg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToDB exports an SSPIConfig to a serialized format.
 | 
			
		||||
@@ -696,15 +724,26 @@ func LoginViaPAM(user *User, login, password string, sourceID int64, cfg *PAMCon
 | 
			
		||||
 | 
			
		||||
	// Allow PAM sources with `@` in their name, like from Active Directory
 | 
			
		||||
	username := pamLogin
 | 
			
		||||
	email := pamLogin
 | 
			
		||||
	idx := strings.Index(pamLogin, "@")
 | 
			
		||||
	if idx > -1 {
 | 
			
		||||
		username = pamLogin[:idx]
 | 
			
		||||
	}
 | 
			
		||||
	if ValidateEmail(email) != nil {
 | 
			
		||||
		if cfg.EmailDomain != "" {
 | 
			
		||||
			email = fmt.Sprintf("%s@%s", username, cfg.EmailDomain)
 | 
			
		||||
		} else {
 | 
			
		||||
			email = fmt.Sprintf("%s@%s", username, setting.Service.NoReplyAddress)
 | 
			
		||||
		}
 | 
			
		||||
		if ValidateEmail(email) != nil {
 | 
			
		||||
			email = gouuid.New().String() + "@localhost"
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	user = &User{
 | 
			
		||||
		LowerName:   strings.ToLower(username),
 | 
			
		||||
		Name:        username,
 | 
			
		||||
		Email:       pamLogin,
 | 
			
		||||
		Email:       email,
 | 
			
		||||
		Passwd:      password,
 | 
			
		||||
		LoginType:   LoginPAM,
 | 
			
		||||
		LoginSource: sourceID,
 | 
			
		||||
 
 | 
			
		||||
@@ -39,6 +39,7 @@ func InsertMilestones(ms ...*Milestone) (err error) {
 | 
			
		||||
// InsertIssues insert issues to database
 | 
			
		||||
func InsertIssues(issues ...*Issue) error {
 | 
			
		||||
	sess := x.NewSession()
 | 
			
		||||
	defer sess.Close()
 | 
			
		||||
	if err := sess.Begin(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
@@ -194,6 +195,7 @@ func InsertPullRequests(prs ...*PullRequest) error {
 | 
			
		||||
// InsertReleases migrates release
 | 
			
		||||
func InsertReleases(rels ...*Release) error {
 | 
			
		||||
	sess := x.NewSession()
 | 
			
		||||
	defer sess.Close()
 | 
			
		||||
	if err := sess.Begin(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -88,6 +88,7 @@ func fixPublisherIDforTagReleases(x *xorm.Engine) error {
 | 
			
		||||
				repo = new(Repository)
 | 
			
		||||
				has, err := sess.ID(release.RepoID).Get(repo)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					log.Error("Error whilst loading repository[%d] for release[%d] with tag name %s. Error: %v", release.RepoID, release.ID, release.TagName, err)
 | 
			
		||||
					return err
 | 
			
		||||
				} else if !has {
 | 
			
		||||
					log.Warn("Release[%d] is orphaned and refers to non-existing repository %d", release.ID, release.RepoID)
 | 
			
		||||
@@ -99,28 +100,55 @@ func fixPublisherIDforTagReleases(x *xorm.Engine) error {
 | 
			
		||||
					// v120.go migration may not have been run correctly - we'll just replicate it here
 | 
			
		||||
					// because this appears to be a common-ish problem.
 | 
			
		||||
					if _, err := sess.Exec("UPDATE repository SET owner_name = (SELECT name FROM `user` WHERE `user`.id = repository.owner_id)"); err != nil {
 | 
			
		||||
						log.Error("Error whilst updating repository[%d] owner name", repo.ID)
 | 
			
		||||
						return err
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					if _, err := sess.ID(release.RepoID).Get(repo); err != nil {
 | 
			
		||||
						log.Error("Error whilst loading repository[%d] for release[%d] with tag name %s. Error: %v", release.RepoID, release.ID, release.TagName, err)
 | 
			
		||||
						return err
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				gitRepo, err = git.OpenRepository(repoPath(repo.OwnerName, repo.Name))
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					log.Error("Error whilst opening git repo for [%d]%s/%s. Error: %v", repo.ID, repo.OwnerName, repo.Name, err)
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			commit, err := gitRepo.GetTagCommit(release.TagName)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				if git.IsErrNotExist(err) {
 | 
			
		||||
					log.Warn("Unable to find commit %s for Tag: %s in [%d]%s/%s. Cannot update publisher ID.", err.(git.ErrNotExist).ID, release.TagName, repo.ID, repo.OwnerName, repo.Name)
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				log.Error("Error whilst getting commit for Tag: %s in [%d]%s/%s. Error: %v", release.TagName, repo.ID, repo.OwnerName, repo.Name, err)
 | 
			
		||||
				return fmt.Errorf("GetTagCommit: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if commit.Author.Email == "" {
 | 
			
		||||
				log.Warn("Tag: %s in Repo[%d]%s/%s does not have a tagger.", release.TagName, repo.ID, repo.OwnerName, repo.Name)
 | 
			
		||||
				commit, err = gitRepo.GetCommit(commit.ID.String())
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					if git.IsErrNotExist(err) {
 | 
			
		||||
						log.Warn("Unable to find commit %s for Tag: %s in [%d]%s/%s. Cannot update publisher ID.", err.(git.ErrNotExist).ID, release.TagName, repo.ID, repo.OwnerName, repo.Name)
 | 
			
		||||
						continue
 | 
			
		||||
					}
 | 
			
		||||
					log.Error("Error whilst getting commit for Tag: %s in [%d]%s/%s. Error: %v", release.TagName, repo.ID, repo.OwnerName, repo.Name, err)
 | 
			
		||||
					return fmt.Errorf("GetCommit: %v", err)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if commit.Author.Email == "" {
 | 
			
		||||
				log.Warn("Tag: %s in Repo[%d]%s/%s does not have a Tagger and its underlying commit does not have an Author either!", release.TagName, repo.ID, repo.OwnerName, repo.Name)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if user == nil || !strings.EqualFold(user.Email, commit.Author.Email) {
 | 
			
		||||
				user = new(User)
 | 
			
		||||
				_, err = sess.Where("email=?", commit.Author.Email).Get(user)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					log.Error("Error whilst getting commit author by email: %s for Tag: %s in [%d]%s/%s. Error: %v", commit.Author.Email, release.TagName, repo.ID, repo.OwnerName, repo.Name, err)
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
@@ -133,6 +161,7 @@ func fixPublisherIDforTagReleases(x *xorm.Engine) error {
 | 
			
		||||
 | 
			
		||||
			release.PublisherID = user.ID
 | 
			
		||||
			if _, err := sess.ID(release.ID).Cols("publisher_id").Update(release); err != nil {
 | 
			
		||||
				log.Error("Error whilst updating publisher[%d] for release[%d] with tag name %s. Error: %v", release.PublisherID, release.ID, release.TagName, err)
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -12,9 +12,9 @@ import (
 | 
			
		||||
 | 
			
		||||
func addSessionTable(x *xorm.Engine) error {
 | 
			
		||||
	type Session struct {
 | 
			
		||||
		Key         string `xorm:"pk CHAR(16)"`
 | 
			
		||||
		Data        []byte `xorm:"BLOB"`
 | 
			
		||||
		CreatedUnix timeutil.TimeStamp
 | 
			
		||||
		Key    string `xorm:"pk CHAR(16)"`
 | 
			
		||||
		Data   []byte `xorm:"BLOB"`
 | 
			
		||||
		Expiry timeutil.TimeStamp
 | 
			
		||||
	}
 | 
			
		||||
	return x.Sync2(new(Session))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -48,11 +48,11 @@ func removeInvalidLabels(x *xorm.Engine) error {
 | 
			
		||||
		SELECT il_too.id FROM (
 | 
			
		||||
			SELECT il_too_too.id
 | 
			
		||||
				FROM issue_label AS il_too_too
 | 
			
		||||
					INNER JOIN label ON il_too_too.id = label.id
 | 
			
		||||
					INNER JOIN label ON il_too_too.label_id = label.id
 | 
			
		||||
					INNER JOIN issue on issue.id = il_too_too.issue_id
 | 
			
		||||
					INNER JOIN repository on repository.id = issue.repo_id
 | 
			
		||||
				WHERE
 | 
			
		||||
					issue.repo_id != label.repo_id OR (label.repo_id = 0 AND label.org_id != repository.owner_id)
 | 
			
		||||
					(label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id)
 | 
			
		||||
	) AS il_too )`); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
@@ -65,7 +65,7 @@ func removeInvalidLabels(x *xorm.Engine) error {
 | 
			
		||||
					INNER JOIN issue on issue.id = com.issue_id
 | 
			
		||||
					INNER JOIN repository on repository.id = issue.repo_id
 | 
			
		||||
				WHERE
 | 
			
		||||
					com.type = ? AND (issue.repo_id != label.repo_id OR (label.repo_id = 0 AND label.org_id != repository.owner_id))
 | 
			
		||||
					com.type = ? AND ((label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id))
 | 
			
		||||
	) AS il_too)`, 7); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -319,7 +319,7 @@ func DumpDatabase(filePath, dbType string) error {
 | 
			
		||||
		ID      int64 `xorm:"pk autoincr"`
 | 
			
		||||
		Version int64
 | 
			
		||||
	}
 | 
			
		||||
	t, err := x.TableInfo(Version{})
 | 
			
		||||
	t, err := x.TableInfo(&Version{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@ func TestDumpDatabase(t *testing.T) {
 | 
			
		||||
		ID      int64 `xorm:"pk autoincr"`
 | 
			
		||||
		Version int64
 | 
			
		||||
	}
 | 
			
		||||
	assert.NoError(t, x.Sync2(Version{}))
 | 
			
		||||
	assert.NoError(t, x.Sync2(new(Version)))
 | 
			
		||||
 | 
			
		||||
	for _, dbName := range setting.SupportedDatabases {
 | 
			
		||||
		dbType := setting.GetDBTypeByName(dbName)
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/modules/timeutil"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
 | 
			
		||||
	"github.com/dgrijalva/jwt-go"
 | 
			
		||||
	"github.com/golang-jwt/jwt"
 | 
			
		||||
	uuid "github.com/google/uuid"
 | 
			
		||||
	"golang.org/x/crypto/bcrypt"
 | 
			
		||||
	"xorm.io/xorm"
 | 
			
		||||
@@ -235,7 +235,7 @@ func deleteOAuth2Application(sess *xorm.Session, id, userid int64) error {
 | 
			
		||||
	if deleted, err := sess.Delete(&OAuth2Application{ID: id, UID: userid}); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	} else if deleted == 0 {
 | 
			
		||||
		return fmt.Errorf("cannot find oauth2 application")
 | 
			
		||||
		return ErrOAuthApplicationNotFound{ID: id}
 | 
			
		||||
	}
 | 
			
		||||
	codes := make([]*OAuth2AuthorizationCode, 0)
 | 
			
		||||
	// delete correlating auth codes
 | 
			
		||||
@@ -261,6 +261,7 @@ func deleteOAuth2Application(sess *xorm.Session, id, userid int64) error {
 | 
			
		||||
// DeleteOAuth2Application deletes the application with the given id and the grants and auth codes related to it. It checks if the userid was the creator of the app.
 | 
			
		||||
func DeleteOAuth2Application(id, userid int64) error {
 | 
			
		||||
	sess := x.NewSession()
 | 
			
		||||
	defer sess.Close()
 | 
			
		||||
	if err := sess.Begin(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -212,12 +212,21 @@ func (pr *PullRequest) GetDefaultMergeMessage() string {
 | 
			
		||||
		log.Error("Cannot load issue %d for PR id %d: Error: %v", pr.IssueID, pr.ID, err)
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if pr.BaseRepoID == pr.HeadRepoID {
 | 
			
		||||
		return fmt.Sprintf("Merge pull request '%s' (#%d) from %s into %s", pr.Issue.Title, pr.Issue.Index, pr.HeadBranch, pr.BaseBranch)
 | 
			
		||||
	if err := pr.LoadBaseRepo(); err != nil {
 | 
			
		||||
		log.Error("LoadBaseRepo: %v", err)
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return fmt.Sprintf("Merge pull request '%s' (#%d) from %s:%s into %s", pr.Issue.Title, pr.Issue.Index, pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseBranch)
 | 
			
		||||
	issueReference := "#"
 | 
			
		||||
	if pr.BaseRepo.UnitEnabled(UnitTypeExternalTracker) {
 | 
			
		||||
		issueReference = "!"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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:%s into %s", pr.Issue.Title, issueReference, pr.Issue.Index, pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseBranch)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReviewCount represents a count of Reviews
 | 
			
		||||
@@ -406,7 +415,8 @@ func (pr *PullRequest) SetMerged() (bool, error) {
 | 
			
		||||
		return false, fmt.Errorf("Issue.changeStatus: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := sess.Where("id = ?", pr.ID).Cols("has_merged, status, merged_commit_id, merger_id, merged_unix").Update(pr); err != nil {
 | 
			
		||||
	// We need to save all of the data used to compute this merge as it may have already been changed by TestPatch. FIXME: need to set some state to prevent TestPatch from running whilst we are merging.
 | 
			
		||||
	if _, err := sess.Where("id = ?", pr.ID).Cols("has_merged, status, merge_base, merged_commit_id, merger_id, merged_unix").Update(pr); err != nil {
 | 
			
		||||
		return false, fmt.Errorf("Failed to update pr[%d]: %v", pr.ID, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -234,3 +234,36 @@ func TestPullRequest_GetWorkInProgressPrefixWorkInProgress(t *testing.T) {
 | 
			
		||||
	pr.Issue.Title = "[wip] " + original
 | 
			
		||||
	assert.Equal(t, "[wip]", pr.GetWorkInProgressPrefix())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPullRequest_GetDefaultMergeMessage_InternalTracker(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, PrepareTestDatabase())
 | 
			
		||||
	pr := AssertExistsAndLoadBean(t, &PullRequest{ID: 2}).(*PullRequest)
 | 
			
		||||
 | 
			
		||||
	assert.Equal(t, "Merge pull request 'issue3' (#3) from branch2 into master", pr.GetDefaultMergeMessage())
 | 
			
		||||
 | 
			
		||||
	pr.BaseRepoID = 1
 | 
			
		||||
	pr.HeadRepoID = 2
 | 
			
		||||
	assert.Equal(t, "Merge pull request 'issue3' (#3) from user2/repo1:branch2 into master", pr.GetDefaultMergeMessage())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPullRequest_GetDefaultMergeMessage_ExternalTracker(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, PrepareTestDatabase())
 | 
			
		||||
 | 
			
		||||
	externalTracker := RepoUnit{
 | 
			
		||||
		Type: UnitTypeExternalTracker,
 | 
			
		||||
		Config: &ExternalTrackerConfig{
 | 
			
		||||
			ExternalTrackerFormat: "https://someurl.com/{user}/{repo}/{issue}",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	baseRepo := &Repository{Name: "testRepo", ID: 1}
 | 
			
		||||
	baseRepo.Owner = &User{Name: "testOwner"}
 | 
			
		||||
	baseRepo.Units = []*RepoUnit{&externalTracker}
 | 
			
		||||
 | 
			
		||||
	pr := AssertExistsAndLoadBean(t, &PullRequest{ID: 2, BaseRepo: baseRepo}).(*PullRequest)
 | 
			
		||||
 | 
			
		||||
	assert.Equal(t, "Merge pull request 'issue3' (!3) from branch2 into master", pr.GetDefaultMergeMessage())
 | 
			
		||||
 | 
			
		||||
	pr.BaseRepoID = 1
 | 
			
		||||
	pr.HeadRepoID = 2
 | 
			
		||||
	assert.Equal(t, "Merge pull request 'issue3' (!3) from user2/repo1:branch2 into master", pr.GetDefaultMergeMessage())
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
@@ -117,17 +118,20 @@ func UpdateRelease(ctx DBContext, rel *Release) error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AddReleaseAttachments adds a release attachments
 | 
			
		||||
func AddReleaseAttachments(releaseID int64, attachmentUUIDs []string) (err error) {
 | 
			
		||||
func AddReleaseAttachments(ctx DBContext, releaseID int64, attachmentUUIDs []string) (err error) {
 | 
			
		||||
	// Check attachments
 | 
			
		||||
	attachments, err := GetAttachmentsByUUIDs(attachmentUUIDs)
 | 
			
		||||
	attachments, err := getAttachmentsByUUIDs(ctx.e, attachmentUUIDs)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("GetAttachmentsByUUIDs [uuids: %v]: %v", attachmentUUIDs, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i := range attachments {
 | 
			
		||||
		if attachments[i].ReleaseID != 0 {
 | 
			
		||||
			return errors.New("release permission denied")
 | 
			
		||||
		}
 | 
			
		||||
		attachments[i].ReleaseID = releaseID
 | 
			
		||||
		// No assign value could be 0, so ignore AllCols().
 | 
			
		||||
		if _, err = x.ID(attachments[i].ID).Update(attachments[i]); err != nil {
 | 
			
		||||
		if _, err = ctx.e.ID(attachments[i].ID).Update(attachments[i]); err != nil {
 | 
			
		||||
			return fmt.Errorf("update attachment [%d]: %v", attachments[i].ID, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -749,7 +749,7 @@ func (repo *Repository) updateSize(e Engine) error {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	repo.Size = size
 | 
			
		||||
	_, err = e.ID(repo.ID).Cols("size").Update(repo)
 | 
			
		||||
	_, err = e.ID(repo.ID).Cols("size").NoAutoTime().Update(repo)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -1215,7 +1215,7 @@ func ChangeRepositoryName(doer *User, repo *Repository, newRepoName string) (err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	newRepoPath := RepoPath(repo.Owner.Name, newRepoName)
 | 
			
		||||
	if err = os.Rename(repo.RepoPath(), newRepoPath); err != nil {
 | 
			
		||||
	if err = util.Rename(repo.RepoPath(), newRepoPath); err != nil {
 | 
			
		||||
		return fmt.Errorf("rename repository directory: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -1226,7 +1226,7 @@ func ChangeRepositoryName(doer *User, repo *Repository, newRepoName string) (err
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if isExist {
 | 
			
		||||
		if err = os.Rename(wikiPath, WikiPath(repo.Owner.Name, newRepoName)); err != nil {
 | 
			
		||||
		if err = util.Rename(wikiPath, WikiPath(repo.Owner.Name, newRepoName)); err != nil {
 | 
			
		||||
			return fmt.Errorf("rename repository wiki: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -1349,6 +1349,26 @@ func UpdateRepository(repo *Repository, visibilityChanged bool) (err error) {
 | 
			
		||||
	return sess.Commit()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateRepositoryOwnerNames updates repository owner_names (this should only be used when the ownerName has changed case)
 | 
			
		||||
func UpdateRepositoryOwnerNames(ownerID int64, ownerName string) error {
 | 
			
		||||
	if ownerID == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	sess := x.NewSession()
 | 
			
		||||
	defer sess.Close()
 | 
			
		||||
	if err := sess.Begin(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := sess.Where("owner_id = ?", ownerID).Cols("owner_name").Update(&Repository{
 | 
			
		||||
		OwnerName: ownerName,
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return sess.Commit()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateRepositoryUpdatedTime updates a repository's updated time
 | 
			
		||||
func UpdateRepositoryUpdatedTime(repoID int64, updateTime time.Time) error {
 | 
			
		||||
	_, err := x.Exec("UPDATE repository SET updated_unix = ? WHERE id = ?", updateTime.Unix(), repoID)
 | 
			
		||||
@@ -1454,23 +1474,26 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
 | 
			
		||||
	if err := deleteBeans(sess,
 | 
			
		||||
		&Access{RepoID: repo.ID},
 | 
			
		||||
		&Action{RepoID: repo.ID},
 | 
			
		||||
		&Watch{RepoID: repoID},
 | 
			
		||||
		&Star{RepoID: repoID},
 | 
			
		||||
		&Mirror{RepoID: repoID},
 | 
			
		||||
		&Milestone{RepoID: repoID},
 | 
			
		||||
		&Release{RepoID: repoID},
 | 
			
		||||
		&Collaboration{RepoID: repoID},
 | 
			
		||||
		&PullRequest{BaseRepoID: repoID},
 | 
			
		||||
		&RepoUnit{RepoID: repoID},
 | 
			
		||||
		&RepoRedirect{RedirectRepoID: repoID},
 | 
			
		||||
		&Webhook{RepoID: repoID},
 | 
			
		||||
		&HookTask{RepoID: repoID},
 | 
			
		||||
		&Notification{RepoID: repoID},
 | 
			
		||||
		&CommitStatus{RepoID: repoID},
 | 
			
		||||
		&RepoIndexerStatus{RepoID: repoID},
 | 
			
		||||
		&LanguageStat{RepoID: repoID},
 | 
			
		||||
		&Comment{RefRepoID: repoID},
 | 
			
		||||
		&CommitStatus{RepoID: repoID},
 | 
			
		||||
		&DeletedBranch{RepoID: repoID},
 | 
			
		||||
		&HookTask{RepoID: repoID},
 | 
			
		||||
		&LFSLock{RepoID: repoID},
 | 
			
		||||
		&LanguageStat{RepoID: repoID},
 | 
			
		||||
		&Milestone{RepoID: repoID},
 | 
			
		||||
		&Mirror{RepoID: repoID},
 | 
			
		||||
		&Notification{RepoID: repoID},
 | 
			
		||||
		&ProtectedBranch{RepoID: repoID},
 | 
			
		||||
		&PullRequest{BaseRepoID: repoID},
 | 
			
		||||
		&Release{RepoID: repoID},
 | 
			
		||||
		&RepoIndexerStatus{RepoID: repoID},
 | 
			
		||||
		&RepoRedirect{RedirectRepoID: repoID},
 | 
			
		||||
		&RepoUnit{RepoID: repoID},
 | 
			
		||||
		&Star{RepoID: repoID},
 | 
			
		||||
		&Task{RepoID: repoID},
 | 
			
		||||
		&Watch{RepoID: repoID},
 | 
			
		||||
		&Webhook{RepoID: repoID},
 | 
			
		||||
	); err != nil {
 | 
			
		||||
		return fmt.Errorf("deleteBeans: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
@@ -1486,10 +1509,6 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := sess.Where("repo_id = ?", repoID).Delete(new(RepoUnit)); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if repo.IsFork {
 | 
			
		||||
		if _, err := sess.Exec("UPDATE `repository` SET num_forks=num_forks-1 WHERE id=?", repo.ForkID); err != nil {
 | 
			
		||||
			return fmt.Errorf("decrease fork count: %v", err)
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,7 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
 | 
			
		||||
	"xorm.io/builder"
 | 
			
		||||
	"xorm.io/xorm"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// RepositoryListDefaultPageSize is the default number of repositories
 | 
			
		||||
@@ -363,6 +364,35 @@ func SearchRepository(opts *SearchRepoOptions) (RepositoryList, int64, error) {
 | 
			
		||||
 | 
			
		||||
// SearchRepositoryByCondition search repositories by condition
 | 
			
		||||
func SearchRepositoryByCondition(opts *SearchRepoOptions, cond builder.Cond, loadAttributes bool) (RepositoryList, int64, error) {
 | 
			
		||||
	sess, count, err := searchRepositoryByCondition(opts, cond)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, 0, err
 | 
			
		||||
	}
 | 
			
		||||
	defer sess.Close()
 | 
			
		||||
 | 
			
		||||
	defaultSize := 50
 | 
			
		||||
	if opts.PageSize > 0 {
 | 
			
		||||
		defaultSize = opts.PageSize
 | 
			
		||||
	}
 | 
			
		||||
	repos := make(RepositoryList, 0, defaultSize)
 | 
			
		||||
	if err := sess.Find(&repos); err != nil {
 | 
			
		||||
		return nil, 0, fmt.Errorf("Repo: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if opts.PageSize <= 0 {
 | 
			
		||||
		count = int64(len(repos))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if loadAttributes {
 | 
			
		||||
		if err := repos.loadAttributes(sess); err != nil {
 | 
			
		||||
			return nil, 0, fmt.Errorf("LoadAttributes: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return repos, count, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func searchRepositoryByCondition(opts *SearchRepoOptions, cond builder.Cond) (*xorm.Session, int64, error) {
 | 
			
		||||
	if opts.Page <= 0 {
 | 
			
		||||
		opts.Page = 1
 | 
			
		||||
	}
 | 
			
		||||
@@ -376,31 +406,24 @@ func SearchRepositoryByCondition(opts *SearchRepoOptions, cond builder.Cond, loa
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sess := x.NewSession()
 | 
			
		||||
	defer sess.Close()
 | 
			
		||||
 | 
			
		||||
	count, err := sess.
 | 
			
		||||
		Where(cond).
 | 
			
		||||
		Count(new(Repository))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, 0, fmt.Errorf("Count: %v", err)
 | 
			
		||||
	var count int64
 | 
			
		||||
	if opts.PageSize > 0 {
 | 
			
		||||
		var err error
 | 
			
		||||
		count, err = sess.
 | 
			
		||||
			Where(cond).
 | 
			
		||||
			Count(new(Repository))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			_ = sess.Close()
 | 
			
		||||
			return nil, 0, fmt.Errorf("Count: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	repos := make(RepositoryList, 0, opts.PageSize)
 | 
			
		||||
	sess.Where(cond).OrderBy(opts.OrderBy.String())
 | 
			
		||||
	if opts.PageSize > 0 {
 | 
			
		||||
		sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize)
 | 
			
		||||
	}
 | 
			
		||||
	if err = sess.Find(&repos); err != nil {
 | 
			
		||||
		return nil, 0, fmt.Errorf("Repo: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if loadAttributes {
 | 
			
		||||
		if err = repos.loadAttributes(sess); err != nil {
 | 
			
		||||
			return nil, 0, fmt.Errorf("LoadAttributes: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return repos, count, nil
 | 
			
		||||
	return sess, count, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// accessibleRepositoryCondition takes a user a returns a condition for checking if a repository is accessible
 | 
			
		||||
@@ -456,6 +479,33 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err
 | 
			
		||||
	return SearchRepository(opts)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SearchRepositoryIDs takes keyword and part of repository name to search,
 | 
			
		||||
// it returns results in given range and number of total results.
 | 
			
		||||
func SearchRepositoryIDs(opts *SearchRepoOptions) ([]int64, int64, error) {
 | 
			
		||||
	opts.IncludeDescription = false
 | 
			
		||||
 | 
			
		||||
	cond := SearchRepositoryCondition(opts)
 | 
			
		||||
 | 
			
		||||
	sess, count, err := searchRepositoryByCondition(opts, cond)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, 0, err
 | 
			
		||||
	}
 | 
			
		||||
	defer sess.Close()
 | 
			
		||||
 | 
			
		||||
	defaultSize := 50
 | 
			
		||||
	if opts.PageSize > 0 {
 | 
			
		||||
		defaultSize = opts.PageSize
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ids := make([]int64, 0, defaultSize)
 | 
			
		||||
	err = sess.Select("id").Table("repository").Find(&ids)
 | 
			
		||||
	if opts.PageSize <= 0 {
 | 
			
		||||
		count = int64(len(ids))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ids, count, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AccessibleRepoIDsQuery queries accessible repository ids. Usable as a subquery wherever repo ids need to be filtered.
 | 
			
		||||
func AccessibleRepoIDsQuery(user *User) *builder.Builder {
 | 
			
		||||
	// NB: Please note this code needs to still work if user is nil
 | 
			
		||||
 
 | 
			
		||||
@@ -210,13 +210,13 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) (err e
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if repoRenamed {
 | 
			
		||||
			if err := os.Rename(RepoPath(newOwnerName, repo.Name), RepoPath(oldOwnerName, repo.Name)); err != nil {
 | 
			
		||||
			if err := util.Rename(RepoPath(newOwnerName, repo.Name), RepoPath(oldOwnerName, repo.Name)); err != nil {
 | 
			
		||||
				log.Critical("Unable to move repository %s/%s directory from %s back to correct place %s: %v", oldOwnerName, repo.Name, RepoPath(newOwnerName, repo.Name), RepoPath(oldOwnerName, repo.Name), err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if wikiRenamed {
 | 
			
		||||
			if err := os.Rename(WikiPath(newOwnerName, repo.Name), WikiPath(oldOwnerName, repo.Name)); err != nil {
 | 
			
		||||
			if err := util.Rename(WikiPath(newOwnerName, repo.Name), WikiPath(oldOwnerName, repo.Name)); err != nil {
 | 
			
		||||
				log.Critical("Unable to move wiki for repository %s/%s directory from %s back to correct place %s: %v", oldOwnerName, repo.Name, WikiPath(newOwnerName, repo.Name), WikiPath(oldOwnerName, repo.Name), err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
@@ -330,10 +330,10 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) (err e
 | 
			
		||||
			SELECT il_too.id FROM (
 | 
			
		||||
				SELECT il_too_too.id
 | 
			
		||||
					FROM issue_label AS il_too_too
 | 
			
		||||
						INNER JOIN label ON il_too_too.id = label.id
 | 
			
		||||
						INNER JOIN label ON il_too_too.label_id = label.id
 | 
			
		||||
						INNER JOIN issue on issue.id = il_too_too.issue_id
 | 
			
		||||
					WHERE
 | 
			
		||||
						issue.repo_id = ? AND (issue.repo_id != label.repo_id OR (label.repo_id = 0 AND label.org_id != ?))
 | 
			
		||||
						issue.repo_id = ? AND ((label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != ?))
 | 
			
		||||
		) AS il_too )`, repo.ID, newOwner.ID); err != nil {
 | 
			
		||||
			return fmt.Errorf("Unable to remove old org labels: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
@@ -343,9 +343,9 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) (err e
 | 
			
		||||
				SELECT com.id
 | 
			
		||||
					FROM comment AS com
 | 
			
		||||
						INNER JOIN label ON com.label_id = label.id
 | 
			
		||||
						INNER JOIN issue on issue.id = com.issue_id
 | 
			
		||||
						INNER JOIN issue ON issue.id = com.issue_id
 | 
			
		||||
					WHERE
 | 
			
		||||
						com.type = ? AND issue.repo_id = ? AND (issue.repo_id != label.repo_id OR (label.repo_id = 0 AND label.org_id != ?))
 | 
			
		||||
						com.type = ? AND issue.repo_id = ? AND ((label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != ?))
 | 
			
		||||
		) AS il_too)`, CommentTypeLabel, repo.ID, newOwner.ID); err != nil {
 | 
			
		||||
			return fmt.Errorf("Unable to remove old org label comments: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
@@ -358,7 +358,7 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) (err e
 | 
			
		||||
		return fmt.Errorf("Failed to create dir %s: %v", dir, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := os.Rename(RepoPath(oldOwner.Name, repo.Name), RepoPath(newOwner.Name, repo.Name)); err != nil {
 | 
			
		||||
	if err := util.Rename(RepoPath(oldOwner.Name, repo.Name), RepoPath(newOwner.Name, repo.Name)); err != nil {
 | 
			
		||||
		return fmt.Errorf("rename repository directory: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	repoRenamed = true
 | 
			
		||||
@@ -370,7 +370,7 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) (err e
 | 
			
		||||
		log.Error("Unable to check if %s exists. Error: %v", wikiPath, err)
 | 
			
		||||
		return err
 | 
			
		||||
	} else if isExist {
 | 
			
		||||
		if err := os.Rename(wikiPath, WikiPath(newOwner.Name, repo.Name)); err != nil {
 | 
			
		||||
		if err := util.Rename(wikiPath, WikiPath(newOwner.Name, repo.Name)); err != nil {
 | 
			
		||||
			return fmt.Errorf("rename repository wiki: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		wikiRenamed = true
 | 
			
		||||
 
 | 
			
		||||
@@ -28,8 +28,7 @@ type UnitConfig struct{}
 | 
			
		||||
 | 
			
		||||
// FromDB fills up a UnitConfig from serialized format.
 | 
			
		||||
func (cfg *UnitConfig) FromDB(bs []byte) error {
 | 
			
		||||
	json := jsoniter.ConfigCompatibleWithStandardLibrary
 | 
			
		||||
	return json.Unmarshal(bs, &cfg)
 | 
			
		||||
	return jsonUnmarshalHandleDoubleEncode(bs, &cfg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToDB exports a UnitConfig to a serialized format.
 | 
			
		||||
@@ -45,8 +44,7 @@ type ExternalWikiConfig struct {
 | 
			
		||||
 | 
			
		||||
// FromDB fills up a ExternalWikiConfig from serialized format.
 | 
			
		||||
func (cfg *ExternalWikiConfig) FromDB(bs []byte) error {
 | 
			
		||||
	json := jsoniter.ConfigCompatibleWithStandardLibrary
 | 
			
		||||
	return json.Unmarshal(bs, &cfg)
 | 
			
		||||
	return jsonUnmarshalHandleDoubleEncode(bs, &cfg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToDB exports a ExternalWikiConfig to a serialized format.
 | 
			
		||||
@@ -64,8 +62,7 @@ type ExternalTrackerConfig struct {
 | 
			
		||||
 | 
			
		||||
// FromDB fills up a ExternalTrackerConfig from serialized format.
 | 
			
		||||
func (cfg *ExternalTrackerConfig) FromDB(bs []byte) error {
 | 
			
		||||
	json := jsoniter.ConfigCompatibleWithStandardLibrary
 | 
			
		||||
	return json.Unmarshal(bs, &cfg)
 | 
			
		||||
	return jsonUnmarshalHandleDoubleEncode(bs, &cfg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToDB exports a ExternalTrackerConfig to a serialized format.
 | 
			
		||||
@@ -83,8 +80,7 @@ type IssuesConfig struct {
 | 
			
		||||
 | 
			
		||||
// FromDB fills up a IssuesConfig from serialized format.
 | 
			
		||||
func (cfg *IssuesConfig) FromDB(bs []byte) error {
 | 
			
		||||
	json := jsoniter.ConfigCompatibleWithStandardLibrary
 | 
			
		||||
	return json.Unmarshal(bs, &cfg)
 | 
			
		||||
	return jsonUnmarshalHandleDoubleEncode(bs, &cfg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToDB exports a IssuesConfig to a serialized format.
 | 
			
		||||
@@ -106,8 +102,7 @@ type PullRequestsConfig struct {
 | 
			
		||||
 | 
			
		||||
// FromDB fills up a PullRequestsConfig from serialized format.
 | 
			
		||||
func (cfg *PullRequestsConfig) FromDB(bs []byte) error {
 | 
			
		||||
	json := jsoniter.ConfigCompatibleWithStandardLibrary
 | 
			
		||||
	return json.Unmarshal(bs, &cfg)
 | 
			
		||||
	return jsonUnmarshalHandleDoubleEncode(bs, &cfg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToDB exports a PullRequestsConfig to a serialized format.
 | 
			
		||||
 
 | 
			
		||||
@@ -566,7 +566,11 @@ func DismissReview(review *Review, isDismiss bool) (err error) {
 | 
			
		||||
 | 
			
		||||
	review.Dismissed = isDismiss
 | 
			
		||||
 | 
			
		||||
	_, err = x.Cols("dismissed").Update(review)
 | 
			
		||||
	if review.ID == 0 {
 | 
			
		||||
		return ErrReviewNotExist{}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err = x.ID(review.ID).Cols("dismissed").Update(review)
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -143,11 +143,57 @@ func TestGetReviewersByIssueID(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDismissReview(t *testing.T) {
 | 
			
		||||
	review1 := AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review)
 | 
			
		||||
	review2 := AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review)
 | 
			
		||||
	assert.NoError(t, DismissReview(review1, true))
 | 
			
		||||
	assert.NoError(t, DismissReview(review2, true))
 | 
			
		||||
	assert.NoError(t, DismissReview(review2, true))
 | 
			
		||||
	assert.NoError(t, DismissReview(review2, false))
 | 
			
		||||
	assert.NoError(t, DismissReview(review2, false))
 | 
			
		||||
	assert.NoError(t, PrepareTestDatabase())
 | 
			
		||||
 | 
			
		||||
	rejectReviewExample := AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review)
 | 
			
		||||
	requestReviewExample := AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review)
 | 
			
		||||
	approveReviewExample := AssertExistsAndLoadBean(t, &Review{ID: 8}).(*Review)
 | 
			
		||||
	assert.False(t, rejectReviewExample.Dismissed)
 | 
			
		||||
	assert.False(t, requestReviewExample.Dismissed)
 | 
			
		||||
	assert.False(t, approveReviewExample.Dismissed)
 | 
			
		||||
 | 
			
		||||
	assert.NoError(t, DismissReview(rejectReviewExample, true))
 | 
			
		||||
	rejectReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review)
 | 
			
		||||
	requestReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review)
 | 
			
		||||
	assert.True(t, rejectReviewExample.Dismissed)
 | 
			
		||||
	assert.False(t, requestReviewExample.Dismissed)
 | 
			
		||||
 | 
			
		||||
	assert.NoError(t, DismissReview(requestReviewExample, true))
 | 
			
		||||
	rejectReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review)
 | 
			
		||||
	requestReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review)
 | 
			
		||||
	assert.True(t, rejectReviewExample.Dismissed)
 | 
			
		||||
	assert.False(t, requestReviewExample.Dismissed)
 | 
			
		||||
	assert.False(t, approveReviewExample.Dismissed)
 | 
			
		||||
 | 
			
		||||
	assert.NoError(t, DismissReview(requestReviewExample, true))
 | 
			
		||||
	rejectReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review)
 | 
			
		||||
	requestReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review)
 | 
			
		||||
	assert.True(t, rejectReviewExample.Dismissed)
 | 
			
		||||
	assert.False(t, requestReviewExample.Dismissed)
 | 
			
		||||
	assert.False(t, approveReviewExample.Dismissed)
 | 
			
		||||
 | 
			
		||||
	assert.NoError(t, DismissReview(requestReviewExample, false))
 | 
			
		||||
	rejectReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review)
 | 
			
		||||
	requestReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review)
 | 
			
		||||
	assert.True(t, rejectReviewExample.Dismissed)
 | 
			
		||||
	assert.False(t, requestReviewExample.Dismissed)
 | 
			
		||||
	assert.False(t, approveReviewExample.Dismissed)
 | 
			
		||||
 | 
			
		||||
	assert.NoError(t, DismissReview(requestReviewExample, false))
 | 
			
		||||
	rejectReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review)
 | 
			
		||||
	requestReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review)
 | 
			
		||||
	assert.True(t, rejectReviewExample.Dismissed)
 | 
			
		||||
	assert.False(t, requestReviewExample.Dismissed)
 | 
			
		||||
	assert.False(t, approveReviewExample.Dismissed)
 | 
			
		||||
 | 
			
		||||
	assert.NoError(t, DismissReview(rejectReviewExample, false))
 | 
			
		||||
	assert.False(t, rejectReviewExample.Dismissed)
 | 
			
		||||
	assert.False(t, requestReviewExample.Dismissed)
 | 
			
		||||
	assert.False(t, approveReviewExample.Dismissed)
 | 
			
		||||
 | 
			
		||||
	assert.NoError(t, DismissReview(approveReviewExample, true))
 | 
			
		||||
	assert.False(t, rejectReviewExample.Dismissed)
 | 
			
		||||
	assert.False(t, requestReviewExample.Dismissed)
 | 
			
		||||
	assert.True(t, approveReviewExample.Dismissed)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -117,6 +117,6 @@ func CountSessions() (int64, error) {
 | 
			
		||||
 | 
			
		||||
// CleanupSessions cleans up expired sessions
 | 
			
		||||
func CleanupSessions(maxLifetime int64) error {
 | 
			
		||||
	_, err := x.Where("created_unix <= ?", timeutil.TimeStampNow().Add(-maxLifetime)).Delete(&Session{})
 | 
			
		||||
	_, err := x.Where("expiry <= ?", timeutil.TimeStampNow().Add(-maxLifetime)).Delete(&Session{})
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -834,7 +834,7 @@ func rewriteAllPublicKeys(e Engine) error {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t.Close()
 | 
			
		||||
	return os.Rename(tmpPath, fPath)
 | 
			
		||||
	return util.Rename(tmpPath, fPath)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RegeneratePublicKeys regenerates the authorized_keys file
 | 
			
		||||
@@ -1316,7 +1316,7 @@ func rewriteAllPrincipalKeys(e Engine) error {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t.Close()
 | 
			
		||||
	return os.Rename(tmpPath, fPath)
 | 
			
		||||
	return util.Rename(tmpPath, fPath)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ListPrincipalKeys returns a list of principals belongs to given user.
 | 
			
		||||
 
 | 
			
		||||
@@ -8,8 +8,11 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	migration "code.gitea.io/gitea/modules/migrations/base"
 | 
			
		||||
	"code.gitea.io/gitea/modules/secret"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/modules/structs"
 | 
			
		||||
	"code.gitea.io/gitea/modules/timeutil"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
	jsoniter "github.com/json-iterator/go"
 | 
			
		||||
 | 
			
		||||
	"xorm.io/builder"
 | 
			
		||||
@@ -110,6 +113,24 @@ func (task *Task) MigrateConfig() (*migration.MigrateOptions, error) {
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// decrypt credentials
 | 
			
		||||
		if opts.CloneAddrEncrypted != "" {
 | 
			
		||||
			if opts.CloneAddr, err = secret.DecryptSecret(setting.SecretKey, opts.CloneAddrEncrypted); err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if opts.AuthPasswordEncrypted != "" {
 | 
			
		||||
			if opts.AuthPassword, err = secret.DecryptSecret(setting.SecretKey, opts.AuthPasswordEncrypted); err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if opts.AuthTokenEncrypted != "" {
 | 
			
		||||
			if opts.AuthToken, err = secret.DecryptSecret(setting.SecretKey, opts.AuthTokenEncrypted); err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return &opts, nil
 | 
			
		||||
	}
 | 
			
		||||
	return nil, fmt.Errorf("Task type is %s, not Migrate Repo", task.Type.Name())
 | 
			
		||||
@@ -205,12 +226,31 @@ func createTask(e Engine, task *Task) error {
 | 
			
		||||
func FinishMigrateTask(task *Task) error {
 | 
			
		||||
	task.Status = structs.TaskStatusFinished
 | 
			
		||||
	task.EndTime = timeutil.TimeStampNow()
 | 
			
		||||
 | 
			
		||||
	// delete credentials when we're done, they're a liability.
 | 
			
		||||
	conf, err := task.MigrateConfig()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	conf.AuthPassword = ""
 | 
			
		||||
	conf.AuthToken = ""
 | 
			
		||||
	conf.CloneAddr = util.SanitizeURLCredentials(conf.CloneAddr, true)
 | 
			
		||||
	conf.AuthPasswordEncrypted = ""
 | 
			
		||||
	conf.AuthTokenEncrypted = ""
 | 
			
		||||
	conf.CloneAddrEncrypted = ""
 | 
			
		||||
	json := jsoniter.ConfigCompatibleWithStandardLibrary
 | 
			
		||||
	confBytes, err := json.Marshal(conf)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	task.PayloadContent = string(confBytes)
 | 
			
		||||
 | 
			
		||||
	sess := x.NewSession()
 | 
			
		||||
	defer sess.Close()
 | 
			
		||||
	if err := sess.Begin(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := sess.ID(task.ID).Cols("status", "end_time").Update(task); err != nil {
 | 
			
		||||
	if _, err := sess.ID(task.ID).Cols("status", "end_time", "payload_content").Update(task); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -57,9 +57,15 @@ func GetAccessTokenBySHA(token string) (*AccessToken, error) {
 | 
			
		||||
	if token == "" {
 | 
			
		||||
		return nil, ErrAccessTokenEmpty{}
 | 
			
		||||
	}
 | 
			
		||||
	if len(token) < 8 {
 | 
			
		||||
	// A token is defined as being SHA1 sum these are 40 hexadecimal bytes long
 | 
			
		||||
	if len(token) != 40 {
 | 
			
		||||
		return nil, ErrAccessTokenNotExist{token}
 | 
			
		||||
	}
 | 
			
		||||
	for _, x := range []byte(token) {
 | 
			
		||||
		if x < '0' || (x > '9' && x < 'a') || x > 'f' {
 | 
			
		||||
			return nil, ErrAccessTokenNotExist{token}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	var tokens []AccessToken
 | 
			
		||||
	lastEight := token[len(token)-8:]
 | 
			
		||||
	err := x.Table(&AccessToken{}).Where("token_last_eight = ?", lastEight).Find(&tokens)
 | 
			
		||||
 
 | 
			
		||||
@@ -239,10 +239,10 @@ func (u *User) GetEmail() string {
 | 
			
		||||
	return u.Email
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetAllUsers returns a slice of all users found in DB.
 | 
			
		||||
// GetAllUsers returns a slice of all individual users found in DB.
 | 
			
		||||
func GetAllUsers() ([]*User, error) {
 | 
			
		||||
	users := make([]*User, 0)
 | 
			
		||||
	return users, x.OrderBy("id").Find(&users)
 | 
			
		||||
	return users, x.OrderBy("id").Where("type = ?", UserTypeIndividual).Find(&users)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsLocal returns true if user login type is LoginPlain.
 | 
			
		||||
@@ -1011,7 +1011,7 @@ func ChangeUserName(u *User, newUserName string) (err error) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Do not fail if directory does not exist
 | 
			
		||||
	if err = os.Rename(UserPath(oldUserName), UserPath(newUserName)); err != nil && !os.IsNotExist(err) {
 | 
			
		||||
	if err = util.Rename(UserPath(oldUserName), UserPath(newUserName)); err != nil && !os.IsNotExist(err) {
 | 
			
		||||
		return fmt.Errorf("Rename user directory: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -1020,7 +1020,7 @@ func ChangeUserName(u *User, newUserName string) (err error) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = sess.Commit(); err != nil {
 | 
			
		||||
		if err2 := os.Rename(UserPath(newUserName), UserPath(oldUserName)); err2 != nil && !os.IsNotExist(err2) {
 | 
			
		||||
		if err2 := util.Rename(UserPath(newUserName), UserPath(oldUserName)); err2 != nil && !os.IsNotExist(err2) {
 | 
			
		||||
			log.Critical("Unable to rollback directory change during failed username change from: %s to: %s. DB Error: %v. Filesystem Error: %v", oldUserName, newUserName, err, err2)
 | 
			
		||||
			return fmt.Errorf("failed to rollback directory change during failed username change from: %s to: %s. DB Error: %w. Filesystem Error: %v", oldUserName, newUserName, err, err2)
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -82,6 +82,9 @@ func (u *User) RealSizedAvatarLink(size int) string {
 | 
			
		||||
		if u.Avatar == "" {
 | 
			
		||||
			return DefaultAvatarLink()
 | 
			
		||||
		}
 | 
			
		||||
		if size > 0 {
 | 
			
		||||
			return setting.AppSubURL + "/avatars/" + u.Avatar + "?size=" + strconv.Itoa(size)
 | 
			
		||||
		}
 | 
			
		||||
		return setting.AppSubURL + "/avatars/" + u.Avatar
 | 
			
		||||
	case setting.DisableGravatar, setting.OfflineMode:
 | 
			
		||||
		if u.Avatar == "" {
 | 
			
		||||
@@ -89,7 +92,9 @@ func (u *User) RealSizedAvatarLink(size int) string {
 | 
			
		||||
				log.Error("GenerateRandomAvatar: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if size > 0 {
 | 
			
		||||
			return setting.AppSubURL + "/avatars/" + u.Avatar + "?size=" + strconv.Itoa(size)
 | 
			
		||||
		}
 | 
			
		||||
		return setting.AppSubURL + "/avatars/" + u.Avatar
 | 
			
		||||
	}
 | 
			
		||||
	return SizedAvatarLink(u.AvatarEmail, size)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										70
									
								
								modules/analyze/vendor.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								modules/analyze/vendor.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
			
		||||
// 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 analyze
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/go-enry/go-enry/v2/data"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var isVendorRegExp *regexp.Regexp
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	matchers := data.VendorMatchers
 | 
			
		||||
 | 
			
		||||
	caretStrings := make([]string, 0, 10)
 | 
			
		||||
	caretShareStrings := make([]string, 0, 10)
 | 
			
		||||
 | 
			
		||||
	matcherStrings := make([]string, 0, len(matchers))
 | 
			
		||||
	for _, matcher := range matchers {
 | 
			
		||||
		str := matcher.String()
 | 
			
		||||
		if str[0] == '^' {
 | 
			
		||||
			caretStrings = append(caretStrings, str[1:])
 | 
			
		||||
		} else if str[0:5] == "(^|/)" {
 | 
			
		||||
			caretShareStrings = append(caretShareStrings, str[5:])
 | 
			
		||||
		} else {
 | 
			
		||||
			matcherStrings = append(matcherStrings, str)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sort.Strings(caretShareStrings)
 | 
			
		||||
	sort.Strings(caretStrings)
 | 
			
		||||
	sort.Strings(matcherStrings)
 | 
			
		||||
 | 
			
		||||
	sb := &strings.Builder{}
 | 
			
		||||
	sb.WriteString("(?:^(?:")
 | 
			
		||||
	sb.WriteString(caretStrings[0])
 | 
			
		||||
	for _, matcher := range caretStrings[1:] {
 | 
			
		||||
		sb.WriteString(")|(?:")
 | 
			
		||||
		sb.WriteString(matcher)
 | 
			
		||||
	}
 | 
			
		||||
	sb.WriteString("))")
 | 
			
		||||
	sb.WriteString("|")
 | 
			
		||||
	sb.WriteString("(?:(?:^|/)(?:")
 | 
			
		||||
	sb.WriteString(caretShareStrings[0])
 | 
			
		||||
	for _, matcher := range caretShareStrings[1:] {
 | 
			
		||||
		sb.WriteString(")|(?:")
 | 
			
		||||
		sb.WriteString(matcher)
 | 
			
		||||
	}
 | 
			
		||||
	sb.WriteString("))")
 | 
			
		||||
	sb.WriteString("|")
 | 
			
		||||
	sb.WriteString("(?:")
 | 
			
		||||
	sb.WriteString(matcherStrings[0])
 | 
			
		||||
	for _, matcher := range matcherStrings[1:] {
 | 
			
		||||
		sb.WriteString(")|(?:")
 | 
			
		||||
		sb.WriteString(matcher)
 | 
			
		||||
	}
 | 
			
		||||
	sb.WriteString(")")
 | 
			
		||||
	combined := sb.String()
 | 
			
		||||
	isVendorRegExp = regexp.MustCompile(combined)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsVendor returns whether or not path is a vendor path.
 | 
			
		||||
func IsVendor(path string) bool {
 | 
			
		||||
	return isVendorRegExp.MatchString(path)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										42
									
								
								modules/analyze/vendor_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								modules/analyze/vendor_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
// 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 analyze
 | 
			
		||||
 | 
			
		||||
import "testing"
 | 
			
		||||
 | 
			
		||||
func TestIsVendor(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		path string
 | 
			
		||||
		want bool
 | 
			
		||||
	}{
 | 
			
		||||
		{"cache/", true},
 | 
			
		||||
		{"random/cache/", true},
 | 
			
		||||
		{"cache", false},
 | 
			
		||||
		{"dependencies/", true},
 | 
			
		||||
		{"Dependencies/", true},
 | 
			
		||||
		{"dependency/", false},
 | 
			
		||||
		{"dist/", true},
 | 
			
		||||
		{"dist", false},
 | 
			
		||||
		{"random/dist/", true},
 | 
			
		||||
		{"random/dist", false},
 | 
			
		||||
		{"deps/", true},
 | 
			
		||||
		{"configure", true},
 | 
			
		||||
		{"a/configure", true},
 | 
			
		||||
		{"config.guess", true},
 | 
			
		||||
		{"config.guess/", false},
 | 
			
		||||
		{".vscode/", true},
 | 
			
		||||
		{"doc/_build/", true},
 | 
			
		||||
		{"a/docs/_build/", true},
 | 
			
		||||
		{"a/dasdocs/_build-vsdoc.js", true},
 | 
			
		||||
		{"a/dasdocs/_build-vsdoc.j", false},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.path, func(t *testing.T) {
 | 
			
		||||
			if got := IsVendor(tt.path); got != tt.want {
 | 
			
		||||
				t.Errorf("IsVendor() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -21,6 +21,7 @@ import (
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
	"unicode"
 | 
			
		||||
	"unicode/utf8"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/git"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
@@ -213,19 +214,19 @@ func EllipsisString(str string, length int) string {
 | 
			
		||||
	if length <= 3 {
 | 
			
		||||
		return "..."
 | 
			
		||||
	}
 | 
			
		||||
	if len(str) <= length {
 | 
			
		||||
	if utf8.RuneCountInString(str) <= length {
 | 
			
		||||
		return str
 | 
			
		||||
	}
 | 
			
		||||
	return str[:length-3] + "..."
 | 
			
		||||
	return string([]rune(str)[:length-3]) + "..."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TruncateString returns a truncated string with given limit,
 | 
			
		||||
// it returns input string if length is not reached limit.
 | 
			
		||||
func TruncateString(str string, limit int) string {
 | 
			
		||||
	if len(str) < limit {
 | 
			
		||||
	if utf8.RuneCountInString(str) < limit {
 | 
			
		||||
		return str
 | 
			
		||||
	}
 | 
			
		||||
	return str[:limit]
 | 
			
		||||
	return string([]rune(str)[:limit])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// StringsToInt64s converts a slice of string to a slice of int64.
 | 
			
		||||
 
 | 
			
		||||
@@ -170,6 +170,10 @@ func TestEllipsisString(t *testing.T) {
 | 
			
		||||
	assert.Equal(t, "fo...", EllipsisString("foobar", 5))
 | 
			
		||||
	assert.Equal(t, "foobar", EllipsisString("foobar", 6))
 | 
			
		||||
	assert.Equal(t, "foobar", EllipsisString("foobar", 10))
 | 
			
		||||
	assert.Equal(t, "测...", EllipsisString("测试文本一二三四", 4))
 | 
			
		||||
	assert.Equal(t, "测试...", EllipsisString("测试文本一二三四", 5))
 | 
			
		||||
	assert.Equal(t, "测试文...", EllipsisString("测试文本一二三四", 6))
 | 
			
		||||
	assert.Equal(t, "测试文本一二三四", EllipsisString("测试文本一二三四", 10))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestTruncateString(t *testing.T) {
 | 
			
		||||
@@ -181,6 +185,10 @@ func TestTruncateString(t *testing.T) {
 | 
			
		||||
	assert.Equal(t, "fooba", TruncateString("foobar", 5))
 | 
			
		||||
	assert.Equal(t, "foobar", TruncateString("foobar", 6))
 | 
			
		||||
	assert.Equal(t, "foobar", TruncateString("foobar", 7))
 | 
			
		||||
	assert.Equal(t, "测试文本", TruncateString("测试文本一二三四", 4))
 | 
			
		||||
	assert.Equal(t, "测试文本一", TruncateString("测试文本一二三四", 5))
 | 
			
		||||
	assert.Equal(t, "测试文本一二", TruncateString("测试文本一二三四", 6))
 | 
			
		||||
	assert.Equal(t, "测试文本一二三", TruncateString("测试文本一二三四", 7))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStringsToInt64s(t *testing.T) {
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,7 @@ import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/modules/web/middleware"
 | 
			
		||||
 | 
			
		||||
	"github.com/unknwon/com"
 | 
			
		||||
@@ -266,7 +267,12 @@ func Validate(ctx *Context, x CSRF) {
 | 
			
		||||
				-1,
 | 
			
		||||
				x.GetCookiePath(),
 | 
			
		||||
				x.GetCookieDomain()) // FIXME: Do we need to set the Secure, httpOnly and SameSite values too?
 | 
			
		||||
			x.Error(ctx.Resp)
 | 
			
		||||
			if middleware.IsAPIPath(ctx.Req) {
 | 
			
		||||
				x.Error(ctx.Resp)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			ctx.Flash.Error(ctx.Tr("error.invalid_csrf"))
 | 
			
		||||
			ctx.Redirect(setting.AppSubURL + "/")
 | 
			
		||||
		}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
@@ -277,10 +283,19 @@ func Validate(ctx *Context, x CSRF) {
 | 
			
		||||
				-1,
 | 
			
		||||
				x.GetCookiePath(),
 | 
			
		||||
				x.GetCookieDomain()) // FIXME: Do we need to set the Secure, httpOnly and SameSite values too?
 | 
			
		||||
			x.Error(ctx.Resp)
 | 
			
		||||
			if middleware.IsAPIPath(ctx.Req) {
 | 
			
		||||
				x.Error(ctx.Resp)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			ctx.Flash.Error(ctx.Tr("error.invalid_csrf"))
 | 
			
		||||
			ctx.Redirect(setting.AppSubURL + "/")
 | 
			
		||||
		}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	http.Error(ctx.Resp, "Bad Request: no CSRF token present", http.StatusBadRequest)
 | 
			
		||||
	if middleware.IsAPIPath(ctx.Req) {
 | 
			
		||||
		http.Error(ctx.Resp, "Bad Request: no CSRF token present", http.StatusBadRequest)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Flash.Error(ctx.Tr("error.missing_csrf"))
 | 
			
		||||
	ctx.Redirect(setting.AppSubURL + "/")
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,9 +6,9 @@
 | 
			
		||||
package context
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"path"
 | 
			
		||||
	"strings"
 | 
			
		||||
@@ -394,239 +394,233 @@ func RepoIDAssignment() func(ctx *Context) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RepoAssignment returns a middleware to handle repository assignment
 | 
			
		||||
func RepoAssignment() func(http.Handler) http.Handler {
 | 
			
		||||
	return func(next http.Handler) http.Handler {
 | 
			
		||||
		return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
 | 
			
		||||
			var (
 | 
			
		||||
				owner *models.User
 | 
			
		||||
				err   error
 | 
			
		||||
				ctx   = GetContext(req)
 | 
			
		||||
			)
 | 
			
		||||
func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
 | 
			
		||||
	var (
 | 
			
		||||
		owner *models.User
 | 
			
		||||
		err   error
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
			userName := ctx.Params(":username")
 | 
			
		||||
			repoName := ctx.Params(":reponame")
 | 
			
		||||
			repoName = strings.TrimSuffix(repoName, ".git")
 | 
			
		||||
	userName := ctx.Params(":username")
 | 
			
		||||
	repoName := ctx.Params(":reponame")
 | 
			
		||||
	repoName = strings.TrimSuffix(repoName, ".git")
 | 
			
		||||
 | 
			
		||||
			// Check if the user is the same as the repository owner
 | 
			
		||||
			if ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName) {
 | 
			
		||||
				owner = ctx.User
 | 
			
		||||
	// Check if the user is the same as the repository owner
 | 
			
		||||
	if ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName) {
 | 
			
		||||
		owner = ctx.User
 | 
			
		||||
	} else {
 | 
			
		||||
		owner, err = models.GetUserByName(userName)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			if models.IsErrUserNotExist(err) {
 | 
			
		||||
				if ctx.Query("go-get") == "1" {
 | 
			
		||||
					EarlyResponseForGoGetMeta(ctx)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				ctx.NotFound("GetUserByName", nil)
 | 
			
		||||
			} else {
 | 
			
		||||
				owner, err = models.GetUserByName(userName)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					if models.IsErrUserNotExist(err) {
 | 
			
		||||
						if ctx.Query("go-get") == "1" {
 | 
			
		||||
							EarlyResponseForGoGetMeta(ctx)
 | 
			
		||||
							return
 | 
			
		||||
						}
 | 
			
		||||
						ctx.NotFound("GetUserByName", nil)
 | 
			
		||||
					} else {
 | 
			
		||||
						ctx.ServerError("GetUserByName", err)
 | 
			
		||||
					}
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				ctx.ServerError("GetUserByName", err)
 | 
			
		||||
			}
 | 
			
		||||
			ctx.Repo.Owner = owner
 | 
			
		||||
			ctx.Data["Username"] = ctx.Repo.Owner.Name
 | 
			
		||||
 | 
			
		||||
			// Get repository.
 | 
			
		||||
			repo, err := models.GetRepositoryByName(owner.ID, repoName)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				if models.IsErrRepoNotExist(err) {
 | 
			
		||||
					redirectRepoID, err := models.LookupRepoRedirect(owner.ID, repoName)
 | 
			
		||||
					if err == nil {
 | 
			
		||||
						RedirectToRepo(ctx, redirectRepoID)
 | 
			
		||||
					} else if models.IsErrRepoRedirectNotExist(err) {
 | 
			
		||||
						if ctx.Query("go-get") == "1" {
 | 
			
		||||
							EarlyResponseForGoGetMeta(ctx)
 | 
			
		||||
							return
 | 
			
		||||
						}
 | 
			
		||||
						ctx.NotFound("GetRepositoryByName", nil)
 | 
			
		||||
					} else {
 | 
			
		||||
						ctx.ServerError("LookupRepoRedirect", err)
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					ctx.ServerError("GetRepositoryByName", err)
 | 
			
		||||
				}
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			repo.Owner = owner
 | 
			
		||||
 | 
			
		||||
			repoAssignment(ctx, repo)
 | 
			
		||||
			if ctx.Written() {
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			ctx.Repo.RepoLink = repo.Link()
 | 
			
		||||
			ctx.Data["RepoLink"] = ctx.Repo.RepoLink
 | 
			
		||||
			ctx.Data["RepoRelPath"] = ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name
 | 
			
		||||
 | 
			
		||||
			unit, err := ctx.Repo.Repository.GetUnit(models.UnitTypeExternalTracker)
 | 
			
		||||
			if err == nil {
 | 
			
		||||
				ctx.Data["RepoExternalIssuesLink"] = unit.ExternalTrackerConfig().ExternalTrackerURL
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			ctx.Data["NumTags"], err = models.GetReleaseCountByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{
 | 
			
		||||
				IncludeTags: true,
 | 
			
		||||
			})
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				ctx.ServerError("GetReleaseCountByRepoID", err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			ctx.Data["NumReleases"], err = models.GetReleaseCountByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{})
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				ctx.ServerError("GetReleaseCountByRepoID", err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			ctx.Data["Title"] = owner.Name + "/" + repo.Name
 | 
			
		||||
			ctx.Data["Repository"] = repo
 | 
			
		||||
			ctx.Data["Owner"] = ctx.Repo.Repository.Owner
 | 
			
		||||
			ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner()
 | 
			
		||||
			ctx.Data["IsRepositoryAdmin"] = ctx.Repo.IsAdmin()
 | 
			
		||||
			ctx.Data["RepoOwnerIsOrganization"] = repo.Owner.IsOrganization()
 | 
			
		||||
			ctx.Data["CanWriteCode"] = ctx.Repo.CanWrite(models.UnitTypeCode)
 | 
			
		||||
			ctx.Data["CanWriteIssues"] = ctx.Repo.CanWrite(models.UnitTypeIssues)
 | 
			
		||||
			ctx.Data["CanWritePulls"] = ctx.Repo.CanWrite(models.UnitTypePullRequests)
 | 
			
		||||
 | 
			
		||||
			if ctx.Data["CanSignedUserFork"], err = ctx.Repo.Repository.CanUserFork(ctx.User); err != nil {
 | 
			
		||||
				ctx.ServerError("CanUserFork", err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			ctx.Data["DisableSSH"] = setting.SSH.Disabled
 | 
			
		||||
			ctx.Data["ExposeAnonSSH"] = setting.SSH.ExposeAnonymous
 | 
			
		||||
			ctx.Data["DisableHTTP"] = setting.Repository.DisableHTTPGit
 | 
			
		||||
			ctx.Data["RepoSearchEnabled"] = setting.Indexer.RepoIndexerEnabled
 | 
			
		||||
			ctx.Data["CloneLink"] = repo.CloneLink()
 | 
			
		||||
			ctx.Data["WikiCloneLink"] = repo.WikiCloneLink()
 | 
			
		||||
 | 
			
		||||
			if ctx.IsSigned {
 | 
			
		||||
				ctx.Data["IsWatchingRepo"] = models.IsWatching(ctx.User.ID, repo.ID)
 | 
			
		||||
				ctx.Data["IsStaringRepo"] = models.IsStaring(ctx.User.ID, repo.ID)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if repo.IsFork {
 | 
			
		||||
				RetrieveBaseRepo(ctx, repo)
 | 
			
		||||
				if ctx.Written() {
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if repo.IsGenerated() {
 | 
			
		||||
				RetrieveTemplateRepo(ctx, repo)
 | 
			
		||||
				if ctx.Written() {
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Disable everything when the repo is being created
 | 
			
		||||
			if ctx.Repo.Repository.IsBeingCreated() {
 | 
			
		||||
				ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			gitRepo, err := git.OpenRepository(models.RepoPath(userName, repoName))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				ctx.ServerError("RepoAssignment Invalid repo "+models.RepoPath(userName, repoName), err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			ctx.Repo.GitRepo = gitRepo
 | 
			
		||||
 | 
			
		||||
			// We opened it, we should close it
 | 
			
		||||
			defer func() {
 | 
			
		||||
				// If it's been set to nil then assume someone else has closed it.
 | 
			
		||||
				if ctx.Repo.GitRepo != nil {
 | 
			
		||||
					ctx.Repo.GitRepo.Close()
 | 
			
		||||
				}
 | 
			
		||||
			}()
 | 
			
		||||
 | 
			
		||||
			// Stop at this point when the repo is empty.
 | 
			
		||||
			if ctx.Repo.Repository.IsEmpty {
 | 
			
		||||
				ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
 | 
			
		||||
				next.ServeHTTP(w, req)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			tags, err := ctx.Repo.GitRepo.GetTags()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				ctx.ServerError("GetTags", err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			ctx.Data["Tags"] = tags
 | 
			
		||||
 | 
			
		||||
			brs, _, err := ctx.Repo.GitRepo.GetBranches(0, 0)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				ctx.ServerError("GetBranches", err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			ctx.Data["Branches"] = brs
 | 
			
		||||
			ctx.Data["BranchesCount"] = len(brs)
 | 
			
		||||
 | 
			
		||||
			ctx.Data["TagName"] = ctx.Repo.TagName
 | 
			
		||||
 | 
			
		||||
			// If not branch selected, try default one.
 | 
			
		||||
			// If default branch doesn't exists, fall back to some other branch.
 | 
			
		||||
			if len(ctx.Repo.BranchName) == 0 {
 | 
			
		||||
				if len(ctx.Repo.Repository.DefaultBranch) > 0 && gitRepo.IsBranchExist(ctx.Repo.Repository.DefaultBranch) {
 | 
			
		||||
					ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch
 | 
			
		||||
				} else if len(brs) > 0 {
 | 
			
		||||
					ctx.Repo.BranchName = brs[0]
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			ctx.Data["BranchName"] = ctx.Repo.BranchName
 | 
			
		||||
			ctx.Data["CommitID"] = ctx.Repo.CommitID
 | 
			
		||||
 | 
			
		||||
			// People who have push access or have forked repository can propose a new pull request.
 | 
			
		||||
			canPush := ctx.Repo.CanWrite(models.UnitTypeCode) || (ctx.IsSigned && ctx.User.HasForkedRepo(ctx.Repo.Repository.ID))
 | 
			
		||||
			canCompare := false
 | 
			
		||||
 | 
			
		||||
			// Pull request is allowed if this is a fork repository
 | 
			
		||||
			// and base repository accepts pull requests.
 | 
			
		||||
			if repo.BaseRepo != nil && repo.BaseRepo.AllowsPulls() {
 | 
			
		||||
				canCompare = true
 | 
			
		||||
				ctx.Data["BaseRepo"] = repo.BaseRepo
 | 
			
		||||
				ctx.Repo.PullRequest.BaseRepo = repo.BaseRepo
 | 
			
		||||
				ctx.Repo.PullRequest.Allowed = canPush
 | 
			
		||||
				ctx.Repo.PullRequest.HeadInfo = ctx.Repo.Owner.Name + ":" + ctx.Repo.BranchName
 | 
			
		||||
			} else if repo.AllowsPulls() {
 | 
			
		||||
				// Or, this is repository accepts pull requests between branches.
 | 
			
		||||
				canCompare = true
 | 
			
		||||
				ctx.Data["BaseRepo"] = repo
 | 
			
		||||
				ctx.Repo.PullRequest.BaseRepo = repo
 | 
			
		||||
				ctx.Repo.PullRequest.Allowed = canPush
 | 
			
		||||
				ctx.Repo.PullRequest.SameRepo = true
 | 
			
		||||
				ctx.Repo.PullRequest.HeadInfo = ctx.Repo.BranchName
 | 
			
		||||
			}
 | 
			
		||||
			ctx.Data["CanCompareOrPull"] = canCompare
 | 
			
		||||
			ctx.Data["PullRequestCtx"] = ctx.Repo.PullRequest
 | 
			
		||||
 | 
			
		||||
			if ctx.Repo.Repository.Status == models.RepositoryPendingTransfer {
 | 
			
		||||
				repoTransfer, err := models.GetPendingRepositoryTransfer(ctx.Repo.Repository)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					ctx.ServerError("GetPendingRepositoryTransfer", err)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if err := repoTransfer.LoadAttributes(); err != nil {
 | 
			
		||||
					ctx.ServerError("LoadRecipient", err)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				ctx.Data["RepoTransfer"] = repoTransfer
 | 
			
		||||
				if ctx.User != nil {
 | 
			
		||||
					ctx.Data["CanUserAcceptTransfer"] = repoTransfer.CanUserAcceptTransfer(ctx.User)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if ctx.Query("go-get") == "1" {
 | 
			
		||||
				ctx.Data["GoGetImport"] = ComposeGoGetImport(owner.Name, repo.Name)
 | 
			
		||||
				prefix := setting.AppURL + path.Join(owner.Name, repo.Name, "src", "branch", ctx.Repo.BranchName)
 | 
			
		||||
				ctx.Data["GoDocDirectory"] = prefix + "{/dir}"
 | 
			
		||||
				ctx.Data["GoDocFile"] = prefix + "{/dir}/{file}#L{line}"
 | 
			
		||||
			}
 | 
			
		||||
			next.ServeHTTP(w, req)
 | 
			
		||||
		})
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Repo.Owner = owner
 | 
			
		||||
	ctx.Data["Username"] = ctx.Repo.Owner.Name
 | 
			
		||||
 | 
			
		||||
	// Get repository.
 | 
			
		||||
	repo, err := models.GetRepositoryByName(owner.ID, repoName)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if models.IsErrRepoNotExist(err) {
 | 
			
		||||
			redirectRepoID, err := models.LookupRepoRedirect(owner.ID, repoName)
 | 
			
		||||
			if err == nil {
 | 
			
		||||
				RedirectToRepo(ctx, redirectRepoID)
 | 
			
		||||
			} else if models.IsErrRepoRedirectNotExist(err) {
 | 
			
		||||
				if ctx.Query("go-get") == "1" {
 | 
			
		||||
					EarlyResponseForGoGetMeta(ctx)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				ctx.NotFound("GetRepositoryByName", nil)
 | 
			
		||||
			} else {
 | 
			
		||||
				ctx.ServerError("LookupRepoRedirect", err)
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			ctx.ServerError("GetRepositoryByName", err)
 | 
			
		||||
		}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	repo.Owner = owner
 | 
			
		||||
 | 
			
		||||
	repoAssignment(ctx, repo)
 | 
			
		||||
	if ctx.Written() {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Repo.RepoLink = repo.Link()
 | 
			
		||||
	ctx.Data["RepoLink"] = ctx.Repo.RepoLink
 | 
			
		||||
	ctx.Data["RepoRelPath"] = ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name
 | 
			
		||||
 | 
			
		||||
	unit, err := ctx.Repo.Repository.GetUnit(models.UnitTypeExternalTracker)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		ctx.Data["RepoExternalIssuesLink"] = unit.ExternalTrackerConfig().ExternalTrackerURL
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Data["NumTags"], err = models.GetReleaseCountByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{
 | 
			
		||||
		IncludeTags: true,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("GetReleaseCountByRepoID", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Data["NumReleases"], err = models.GetReleaseCountByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("GetReleaseCountByRepoID", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Data["Title"] = owner.Name + "/" + repo.Name
 | 
			
		||||
	ctx.Data["Repository"] = repo
 | 
			
		||||
	ctx.Data["Owner"] = ctx.Repo.Repository.Owner
 | 
			
		||||
	ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner()
 | 
			
		||||
	ctx.Data["IsRepositoryAdmin"] = ctx.Repo.IsAdmin()
 | 
			
		||||
	ctx.Data["RepoOwnerIsOrganization"] = repo.Owner.IsOrganization()
 | 
			
		||||
	ctx.Data["CanWriteCode"] = ctx.Repo.CanWrite(models.UnitTypeCode)
 | 
			
		||||
	ctx.Data["CanWriteIssues"] = ctx.Repo.CanWrite(models.UnitTypeIssues)
 | 
			
		||||
	ctx.Data["CanWritePulls"] = ctx.Repo.CanWrite(models.UnitTypePullRequests)
 | 
			
		||||
 | 
			
		||||
	if ctx.Data["CanSignedUserFork"], err = ctx.Repo.Repository.CanUserFork(ctx.User); err != nil {
 | 
			
		||||
		ctx.ServerError("CanUserFork", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Data["DisableSSH"] = setting.SSH.Disabled
 | 
			
		||||
	ctx.Data["ExposeAnonSSH"] = setting.SSH.ExposeAnonymous
 | 
			
		||||
	ctx.Data["DisableHTTP"] = setting.Repository.DisableHTTPGit
 | 
			
		||||
	ctx.Data["RepoSearchEnabled"] = setting.Indexer.RepoIndexerEnabled
 | 
			
		||||
	ctx.Data["CloneLink"] = repo.CloneLink()
 | 
			
		||||
	ctx.Data["WikiCloneLink"] = repo.WikiCloneLink()
 | 
			
		||||
 | 
			
		||||
	if ctx.IsSigned {
 | 
			
		||||
		ctx.Data["IsWatchingRepo"] = models.IsWatching(ctx.User.ID, repo.ID)
 | 
			
		||||
		ctx.Data["IsStaringRepo"] = models.IsStaring(ctx.User.ID, repo.ID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if repo.IsFork {
 | 
			
		||||
		RetrieveBaseRepo(ctx, repo)
 | 
			
		||||
		if ctx.Written() {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if repo.IsGenerated() {
 | 
			
		||||
		RetrieveTemplateRepo(ctx, repo)
 | 
			
		||||
		if ctx.Written() {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Disable everything when the repo is being created
 | 
			
		||||
	if ctx.Repo.Repository.IsBeingCreated() {
 | 
			
		||||
		ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	gitRepo, err := git.OpenRepository(models.RepoPath(userName, repoName))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("RepoAssignment Invalid repo "+models.RepoPath(userName, repoName), err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Repo.GitRepo = gitRepo
 | 
			
		||||
 | 
			
		||||
	// We opened it, we should close it
 | 
			
		||||
	cancel = func() {
 | 
			
		||||
		// If it's been set to nil then assume someone else has closed it.
 | 
			
		||||
		if ctx.Repo.GitRepo != nil {
 | 
			
		||||
			ctx.Repo.GitRepo.Close()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Stop at this point when the repo is empty.
 | 
			
		||||
	if ctx.Repo.Repository.IsEmpty {
 | 
			
		||||
		ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tags, err := ctx.Repo.GitRepo.GetTags()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("GetTags", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Data["Tags"] = tags
 | 
			
		||||
 | 
			
		||||
	brs, _, err := ctx.Repo.GitRepo.GetBranches(0, 0)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("GetBranches", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Data["Branches"] = brs
 | 
			
		||||
	ctx.Data["BranchesCount"] = len(brs)
 | 
			
		||||
 | 
			
		||||
	ctx.Data["TagName"] = ctx.Repo.TagName
 | 
			
		||||
 | 
			
		||||
	// If not branch selected, try default one.
 | 
			
		||||
	// If default branch doesn't exists, fall back to some other branch.
 | 
			
		||||
	if len(ctx.Repo.BranchName) == 0 {
 | 
			
		||||
		if len(ctx.Repo.Repository.DefaultBranch) > 0 && gitRepo.IsBranchExist(ctx.Repo.Repository.DefaultBranch) {
 | 
			
		||||
			ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch
 | 
			
		||||
		} else if len(brs) > 0 {
 | 
			
		||||
			ctx.Repo.BranchName = brs[0]
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Data["BranchName"] = ctx.Repo.BranchName
 | 
			
		||||
	ctx.Data["CommitID"] = ctx.Repo.CommitID
 | 
			
		||||
 | 
			
		||||
	// People who have push access or have forked repository can propose a new pull request.
 | 
			
		||||
	canPush := ctx.Repo.CanWrite(models.UnitTypeCode) || (ctx.IsSigned && ctx.User.HasForkedRepo(ctx.Repo.Repository.ID))
 | 
			
		||||
	canCompare := false
 | 
			
		||||
 | 
			
		||||
	// Pull request is allowed if this is a fork repository
 | 
			
		||||
	// and base repository accepts pull requests.
 | 
			
		||||
	if repo.BaseRepo != nil && repo.BaseRepo.AllowsPulls() {
 | 
			
		||||
		canCompare = true
 | 
			
		||||
		ctx.Data["BaseRepo"] = repo.BaseRepo
 | 
			
		||||
		ctx.Repo.PullRequest.BaseRepo = repo.BaseRepo
 | 
			
		||||
		ctx.Repo.PullRequest.Allowed = canPush
 | 
			
		||||
		ctx.Repo.PullRequest.HeadInfo = ctx.Repo.Owner.Name + ":" + ctx.Repo.BranchName
 | 
			
		||||
	} else if repo.AllowsPulls() {
 | 
			
		||||
		// Or, this is repository accepts pull requests between branches.
 | 
			
		||||
		canCompare = true
 | 
			
		||||
		ctx.Data["BaseRepo"] = repo
 | 
			
		||||
		ctx.Repo.PullRequest.BaseRepo = repo
 | 
			
		||||
		ctx.Repo.PullRequest.Allowed = canPush
 | 
			
		||||
		ctx.Repo.PullRequest.SameRepo = true
 | 
			
		||||
		ctx.Repo.PullRequest.HeadInfo = ctx.Repo.BranchName
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Data["CanCompareOrPull"] = canCompare
 | 
			
		||||
	ctx.Data["PullRequestCtx"] = ctx.Repo.PullRequest
 | 
			
		||||
 | 
			
		||||
	if ctx.Repo.Repository.Status == models.RepositoryPendingTransfer {
 | 
			
		||||
		repoTransfer, err := models.GetPendingRepositoryTransfer(ctx.Repo.Repository)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.ServerError("GetPendingRepositoryTransfer", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := repoTransfer.LoadAttributes(); err != nil {
 | 
			
		||||
			ctx.ServerError("LoadRecipient", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ctx.Data["RepoTransfer"] = repoTransfer
 | 
			
		||||
		if ctx.User != nil {
 | 
			
		||||
			ctx.Data["CanUserAcceptTransfer"] = repoTransfer.CanUserAcceptTransfer(ctx.User)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ctx.Query("go-get") == "1" {
 | 
			
		||||
		ctx.Data["GoGetImport"] = ComposeGoGetImport(owner.Name, repo.Name)
 | 
			
		||||
		prefix := setting.AppURL + path.Join(owner.Name, repo.Name, "src", "branch", ctx.Repo.BranchName)
 | 
			
		||||
		ctx.Data["GoDocDirectory"] = prefix + "{/dir}"
 | 
			
		||||
		ctx.Data["GoDocFile"] = prefix + "{/dir}/{file}#L{line}"
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RepoRefType type of repo reference
 | 
			
		||||
@@ -651,7 +645,7 @@ const (
 | 
			
		||||
 | 
			
		||||
// RepoRef handles repository reference names when the ref name is not
 | 
			
		||||
// explicitly given
 | 
			
		||||
func RepoRef() func(http.Handler) http.Handler {
 | 
			
		||||
func RepoRef() func(*Context) context.CancelFunc {
 | 
			
		||||
	// since no ref name is explicitly specified, ok to just use branch
 | 
			
		||||
	return RepoRefByType(RepoRefBranch)
 | 
			
		||||
}
 | 
			
		||||
@@ -730,130 +724,129 @@ func getRefName(ctx *Context, pathType RepoRefType) string {
 | 
			
		||||
 | 
			
		||||
// RepoRefByType handles repository reference name for a specific type
 | 
			
		||||
// of repository reference
 | 
			
		||||
func RepoRefByType(refType RepoRefType) func(http.Handler) http.Handler {
 | 
			
		||||
	return func(next http.Handler) http.Handler {
 | 
			
		||||
		return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
 | 
			
		||||
			ctx := GetContext(req)
 | 
			
		||||
			// Empty repository does not have reference information.
 | 
			
		||||
			if ctx.Repo.Repository.IsEmpty {
 | 
			
		||||
func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context) context.CancelFunc {
 | 
			
		||||
	return func(ctx *Context) (cancel context.CancelFunc) {
 | 
			
		||||
		// Empty repository does not have reference information.
 | 
			
		||||
		if ctx.Repo.Repository.IsEmpty {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var (
 | 
			
		||||
			refName string
 | 
			
		||||
			err     error
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		if ctx.Repo.GitRepo == nil {
 | 
			
		||||
			repoPath := models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
 | 
			
		||||
			ctx.Repo.GitRepo, err = git.OpenRepository(repoPath)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				ctx.ServerError("RepoRef Invalid repo "+repoPath, err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			// We opened it, we should close it
 | 
			
		||||
			cancel = func() {
 | 
			
		||||
				// If it's been set to nil then assume someone else has closed it.
 | 
			
		||||
				if ctx.Repo.GitRepo != nil {
 | 
			
		||||
					ctx.Repo.GitRepo.Close()
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
			var (
 | 
			
		||||
				refName string
 | 
			
		||||
				err     error
 | 
			
		||||
			)
 | 
			
		||||
 | 
			
		||||
			if ctx.Repo.GitRepo == nil {
 | 
			
		||||
				repoPath := models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
 | 
			
		||||
				ctx.Repo.GitRepo, err = git.OpenRepository(repoPath)
 | 
			
		||||
		// Get default branch.
 | 
			
		||||
		if len(ctx.Params("*")) == 0 {
 | 
			
		||||
			refName = ctx.Repo.Repository.DefaultBranch
 | 
			
		||||
			ctx.Repo.BranchName = refName
 | 
			
		||||
			if !ctx.Repo.GitRepo.IsBranchExist(refName) {
 | 
			
		||||
				brs, _, err := ctx.Repo.GitRepo.GetBranches(0, 0)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					ctx.ServerError("RepoRef Invalid repo "+repoPath, err)
 | 
			
		||||
					ctx.ServerError("GetBranches", err)
 | 
			
		||||
					return
 | 
			
		||||
				} else if len(brs) == 0 {
 | 
			
		||||
					err = fmt.Errorf("No branches in non-empty repository %s",
 | 
			
		||||
						ctx.Repo.GitRepo.Path)
 | 
			
		||||
					ctx.ServerError("GetBranches", err)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				// We opened it, we should close it
 | 
			
		||||
				defer func() {
 | 
			
		||||
					// If it's been set to nil then assume someone else has closed it.
 | 
			
		||||
					if ctx.Repo.GitRepo != nil {
 | 
			
		||||
						ctx.Repo.GitRepo.Close()
 | 
			
		||||
					}
 | 
			
		||||
				}()
 | 
			
		||||
				refName = brs[0]
 | 
			
		||||
			}
 | 
			
		||||
			ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				ctx.ServerError("GetBranchCommit", err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
 | 
			
		||||
			ctx.Repo.IsViewBranch = true
 | 
			
		||||
 | 
			
		||||
		} else {
 | 
			
		||||
			refName = getRefName(ctx, refType)
 | 
			
		||||
			ctx.Repo.BranchName = refName
 | 
			
		||||
			if refType.RefTypeIncludesBranches() && ctx.Repo.GitRepo.IsBranchExist(refName) {
 | 
			
		||||
				ctx.Repo.IsViewBranch = true
 | 
			
		||||
 | 
			
		||||
			// Get default branch.
 | 
			
		||||
			if len(ctx.Params("*")) == 0 {
 | 
			
		||||
				refName = ctx.Repo.Repository.DefaultBranch
 | 
			
		||||
				ctx.Repo.BranchName = refName
 | 
			
		||||
				if !ctx.Repo.GitRepo.IsBranchExist(refName) {
 | 
			
		||||
					brs, _, err := ctx.Repo.GitRepo.GetBranches(0, 0)
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						ctx.ServerError("GetBranches", err)
 | 
			
		||||
						return
 | 
			
		||||
					} else if len(brs) == 0 {
 | 
			
		||||
						err = fmt.Errorf("No branches in non-empty repository %s",
 | 
			
		||||
							ctx.Repo.GitRepo.Path)
 | 
			
		||||
						ctx.ServerError("GetBranches", err)
 | 
			
		||||
						return
 | 
			
		||||
					}
 | 
			
		||||
					refName = brs[0]
 | 
			
		||||
				}
 | 
			
		||||
				ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					ctx.ServerError("GetBranchCommit", err)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
 | 
			
		||||
				ctx.Repo.IsViewBranch = true
 | 
			
		||||
 | 
			
		||||
			} else if refType.RefTypeIncludesTags() && ctx.Repo.GitRepo.IsTagExist(refName) {
 | 
			
		||||
				ctx.Repo.IsViewTag = true
 | 
			
		||||
				ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetTagCommit(refName)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					ctx.ServerError("GetTagCommit", err)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
 | 
			
		||||
			} else if len(refName) >= 7 && len(refName) <= 40 {
 | 
			
		||||
				ctx.Repo.IsViewCommit = true
 | 
			
		||||
				ctx.Repo.CommitID = refName
 | 
			
		||||
 | 
			
		||||
				ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					ctx.NotFound("GetCommit", err)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				// If short commit ID add canonical link header
 | 
			
		||||
				if len(refName) < 40 {
 | 
			
		||||
					ctx.Header().Set("Link", fmt.Sprintf("<%s>; rel=\"canonical\"",
 | 
			
		||||
						util.URLJoin(setting.AppURL, strings.Replace(ctx.Req.URL.RequestURI(), refName, ctx.Repo.Commit.ID.String(), 1))))
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				refName = getRefName(ctx, refType)
 | 
			
		||||
				ctx.Repo.BranchName = refName
 | 
			
		||||
				if refType.RefTypeIncludesBranches() && ctx.Repo.GitRepo.IsBranchExist(refName) {
 | 
			
		||||
					ctx.Repo.IsViewBranch = true
 | 
			
		||||
 | 
			
		||||
					ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						ctx.ServerError("GetBranchCommit", err)
 | 
			
		||||
						return
 | 
			
		||||
					}
 | 
			
		||||
					ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
 | 
			
		||||
 | 
			
		||||
				} else if refType.RefTypeIncludesTags() && ctx.Repo.GitRepo.IsTagExist(refName) {
 | 
			
		||||
					ctx.Repo.IsViewTag = true
 | 
			
		||||
					ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetTagCommit(refName)
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						ctx.ServerError("GetTagCommit", err)
 | 
			
		||||
						return
 | 
			
		||||
					}
 | 
			
		||||
					ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
 | 
			
		||||
				} else if len(refName) >= 7 && len(refName) <= 40 {
 | 
			
		||||
					ctx.Repo.IsViewCommit = true
 | 
			
		||||
					ctx.Repo.CommitID = refName
 | 
			
		||||
 | 
			
		||||
					ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName)
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						ctx.NotFound("GetCommit", err)
 | 
			
		||||
						return
 | 
			
		||||
					}
 | 
			
		||||
					// If short commit ID add canonical link header
 | 
			
		||||
					if len(refName) < 40 {
 | 
			
		||||
						ctx.Header().Set("Link", fmt.Sprintf("<%s>; rel=\"canonical\"",
 | 
			
		||||
							util.URLJoin(setting.AppURL, strings.Replace(ctx.Req.URL.RequestURI(), refName, ctx.Repo.Commit.ID.String(), 1))))
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					ctx.NotFound("RepoRef invalid repo", fmt.Errorf("branch or tag not exist: %s", refName))
 | 
			
		||||
				if len(ignoreNotExistErr) > 0 && ignoreNotExistErr[0] {
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if refType == RepoRefLegacy {
 | 
			
		||||
					// redirect from old URL scheme to new URL scheme
 | 
			
		||||
					ctx.Redirect(path.Join(
 | 
			
		||||
						setting.AppSubURL,
 | 
			
		||||
						strings.TrimSuffix(ctx.Req.URL.Path, ctx.Params("*")),
 | 
			
		||||
						ctx.Repo.BranchNameSubURL(),
 | 
			
		||||
						ctx.Repo.TreePath))
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			ctx.Data["BranchName"] = ctx.Repo.BranchName
 | 
			
		||||
			ctx.Data["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL()
 | 
			
		||||
			ctx.Data["CommitID"] = ctx.Repo.CommitID
 | 
			
		||||
			ctx.Data["TreePath"] = ctx.Repo.TreePath
 | 
			
		||||
			ctx.Data["IsViewBranch"] = ctx.Repo.IsViewBranch
 | 
			
		||||
			ctx.Data["IsViewTag"] = ctx.Repo.IsViewTag
 | 
			
		||||
			ctx.Data["IsViewCommit"] = ctx.Repo.IsViewCommit
 | 
			
		||||
			ctx.Data["CanCreateBranch"] = ctx.Repo.CanCreateBranch()
 | 
			
		||||
 | 
			
		||||
			ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				ctx.ServerError("GetCommitsCount", err)
 | 
			
		||||
				ctx.NotFound("RepoRef invalid repo", fmt.Errorf("branch or tag not exist: %s", refName))
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
 | 
			
		||||
 | 
			
		||||
			next.ServeHTTP(w, req)
 | 
			
		||||
		})
 | 
			
		||||
			if refType == RepoRefLegacy {
 | 
			
		||||
				// redirect from old URL scheme to new URL scheme
 | 
			
		||||
				ctx.Redirect(path.Join(
 | 
			
		||||
					setting.AppSubURL,
 | 
			
		||||
					strings.TrimSuffix(ctx.Req.URL.Path, ctx.Params("*")),
 | 
			
		||||
					ctx.Repo.BranchNameSubURL(),
 | 
			
		||||
					ctx.Repo.TreePath))
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ctx.Data["BranchName"] = ctx.Repo.BranchName
 | 
			
		||||
		ctx.Data["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL()
 | 
			
		||||
		ctx.Data["CommitID"] = ctx.Repo.CommitID
 | 
			
		||||
		ctx.Data["TreePath"] = ctx.Repo.TreePath
 | 
			
		||||
		ctx.Data["IsViewBranch"] = ctx.Repo.IsViewBranch
 | 
			
		||||
		ctx.Data["IsViewTag"] = ctx.Repo.IsViewTag
 | 
			
		||||
		ctx.Data["IsViewCommit"] = ctx.Repo.IsViewCommit
 | 
			
		||||
		ctx.Data["CanCreateBranch"] = ctx.Repo.CanCreateBranch()
 | 
			
		||||
 | 
			
		||||
		ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.ServerError("GetCommitsCount", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,9 @@
 | 
			
		||||
 | 
			
		||||
package context
 | 
			
		||||
 | 
			
		||||
import "net/http"
 | 
			
		||||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ResponseWriter represents a response writer for HTTP
 | 
			
		||||
type ResponseWriter interface {
 | 
			
		||||
@@ -47,7 +49,7 @@ func (r *Response) Write(bs []byte) (int, error) {
 | 
			
		||||
		return size, err
 | 
			
		||||
	}
 | 
			
		||||
	if r.status == 0 {
 | 
			
		||||
		r.WriteHeader(200)
 | 
			
		||||
		r.status = http.StatusOK
 | 
			
		||||
	}
 | 
			
		||||
	return size, nil
 | 
			
		||||
}
 | 
			
		||||
@@ -60,8 +62,10 @@ func (r *Response) WriteHeader(statusCode int) {
 | 
			
		||||
		}
 | 
			
		||||
		r.beforeExecuted = true
 | 
			
		||||
	}
 | 
			
		||||
	r.status = statusCode
 | 
			
		||||
	r.ResponseWriter.WriteHeader(statusCode)
 | 
			
		||||
	if r.status == 0 {
 | 
			
		||||
		r.status = statusCode
 | 
			
		||||
		r.ResponseWriter.WriteHeader(statusCode)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Flush flush cached data
 | 
			
		||||
 
 | 
			
		||||
@@ -155,8 +155,8 @@ func ToCommit(repo *models.Repository, commit *git.Commit, userCache map[string]
 | 
			
		||||
			URL: repo.APIURL() + "/git/commits/" + commit.ID.String(),
 | 
			
		||||
			Author: &api.CommitUser{
 | 
			
		||||
				Identity: api.Identity{
 | 
			
		||||
					Name:  commit.Committer.Name,
 | 
			
		||||
					Email: commit.Committer.Email,
 | 
			
		||||
					Name:  commit.Author.Name,
 | 
			
		||||
					Email: commit.Author.Email,
 | 
			
		||||
				},
 | 
			
		||||
				Date: commit.Author.When.Format(time.RFC3339),
 | 
			
		||||
			},
 | 
			
		||||
 
 | 
			
		||||
@@ -47,6 +47,11 @@ func ToNotificationThread(n *models.Notification) *api.NotificationThread {
 | 
			
		||||
			if err == nil && comment != nil {
 | 
			
		||||
				result.Subject.LatestCommentURL = comment.APIURL()
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			pr, _ := n.Issue.GetPullRequest()
 | 
			
		||||
			if pr != nil && pr.HasMerged {
 | 
			
		||||
				result.Subject.State = "merged"
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	case models.NotificationSourceCommit:
 | 
			
		||||
		result.Subject = &api.NotificationSubject{
 | 
			
		||||
 
 | 
			
		||||
@@ -85,18 +85,17 @@ func ToPullReviewCommentList(review *models.Review, doer *models.User) ([]*api.P
 | 
			
		||||
 | 
			
		||||
	apiComments := make([]*api.PullReviewComment, 0, len(review.CodeComments))
 | 
			
		||||
 | 
			
		||||
	auth := false
 | 
			
		||||
	if doer != nil {
 | 
			
		||||
		auth = doer.IsAdmin || doer.ID == review.ReviewerID
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, lines := range review.CodeComments {
 | 
			
		||||
		for _, comments := range lines {
 | 
			
		||||
			for _, comment := range comments {
 | 
			
		||||
				auth := false
 | 
			
		||||
				if doer != nil {
 | 
			
		||||
					auth = doer.IsAdmin || doer.ID == comment.Poster.ID
 | 
			
		||||
				}
 | 
			
		||||
				apiComment := &api.PullReviewComment{
 | 
			
		||||
					ID:           comment.ID,
 | 
			
		||||
					Body:         comment.Content,
 | 
			
		||||
					Reviewer:     ToUser(review.Reviewer, doer != nil, auth),
 | 
			
		||||
					Reviewer:     ToUser(comment.Poster, doer != nil, auth),
 | 
			
		||||
					ReviewID:     review.ID,
 | 
			
		||||
					Created:      comment.CreatedUnix.AsTime(),
 | 
			
		||||
					Updated:      comment.UpdatedUnix.AsTime(),
 | 
			
		||||
 
 | 
			
		||||
@@ -89,7 +89,7 @@ func innerToRepo(repo *models.Repository, mode models.AccessMode, isParent bool)
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	numReleases, _ := models.GetReleaseCountByRepoID(repo.ID, models.FindReleasesOptions{IncludeDrafts: false, IncludeTags: true})
 | 
			
		||||
	numReleases, _ := models.GetReleaseCountByRepoID(repo.ID, models.FindReleasesOptions{IncludeDrafts: false, IncludeTags: false})
 | 
			
		||||
 | 
			
		||||
	mirrorInterval := ""
 | 
			
		||||
	if repo.IsMirror {
 | 
			
		||||
 
 | 
			
		||||
@@ -23,13 +23,13 @@ func checkDBConsistency(logger log.Logger, autofix bool) error {
 | 
			
		||||
	// find labels without existing repo or org
 | 
			
		||||
	count, err := models.CountOrphanedLabels()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.Critical("Error: %v whilst counting orphaned labels")
 | 
			
		||||
		logger.Critical("Error: %v whilst counting orphaned labels", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if count > 0 {
 | 
			
		||||
		if autofix {
 | 
			
		||||
			if err = models.DeleteOrphanedLabels(); err != nil {
 | 
			
		||||
				logger.Critical("Error: %v whilst deleting orphaned labels")
 | 
			
		||||
				logger.Critical("Error: %v whilst deleting orphaned labels", err)
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			logger.Info("%d labels without existing repository/organisation deleted", count)
 | 
			
		||||
@@ -41,13 +41,13 @@ func checkDBConsistency(logger log.Logger, autofix bool) error {
 | 
			
		||||
	// find IssueLabels without existing label
 | 
			
		||||
	count, err = models.CountOrphanedIssueLabels()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.Critical("Error: %v whilst counting orphaned issue_labels")
 | 
			
		||||
		logger.Critical("Error: %v whilst counting orphaned issue_labels", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if count > 0 {
 | 
			
		||||
		if autofix {
 | 
			
		||||
			if err = models.DeleteOrphanedIssueLabels(); err != nil {
 | 
			
		||||
				logger.Critical("Error: %v whilst deleting orphaned issue_labels")
 | 
			
		||||
				logger.Critical("Error: %v whilst deleting orphaned issue_labels", err)
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			logger.Info("%d issue_labels without existing label deleted", count)
 | 
			
		||||
@@ -59,13 +59,13 @@ func checkDBConsistency(logger log.Logger, autofix bool) error {
 | 
			
		||||
	// find issues without existing repository
 | 
			
		||||
	count, err = models.CountOrphanedIssues()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.Critical("Error: %v whilst counting orphaned issues")
 | 
			
		||||
		logger.Critical("Error: %v whilst counting orphaned issues", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if count > 0 {
 | 
			
		||||
		if autofix {
 | 
			
		||||
			if err = models.DeleteOrphanedIssues(); err != nil {
 | 
			
		||||
				logger.Critical("Error: %v whilst deleting orphaned issues")
 | 
			
		||||
				logger.Critical("Error: %v whilst deleting orphaned issues", err)
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			logger.Info("%d issues without existing repository deleted", count)
 | 
			
		||||
@@ -77,13 +77,13 @@ func checkDBConsistency(logger log.Logger, autofix bool) error {
 | 
			
		||||
	// find pulls without existing issues
 | 
			
		||||
	count, err = models.CountOrphanedObjects("pull_request", "issue", "pull_request.issue_id=issue.id")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.Critical("Error: %v whilst counting orphaned objects")
 | 
			
		||||
		logger.Critical("Error: %v whilst counting orphaned objects", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if count > 0 {
 | 
			
		||||
		if autofix {
 | 
			
		||||
			if err = models.DeleteOrphanedObjects("pull_request", "issue", "pull_request.issue_id=issue.id"); err != nil {
 | 
			
		||||
				logger.Critical("Error: %v whilst deleting orphaned objects")
 | 
			
		||||
				logger.Critical("Error: %v whilst deleting orphaned objects", err)
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			logger.Info("%d pull requests without existing issue deleted", count)
 | 
			
		||||
@@ -95,13 +95,13 @@ func checkDBConsistency(logger log.Logger, autofix bool) error {
 | 
			
		||||
	// find tracked times without existing issues/pulls
 | 
			
		||||
	count, err = models.CountOrphanedObjects("tracked_time", "issue", "tracked_time.issue_id=issue.id")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.Critical("Error: %v whilst counting orphaned objects")
 | 
			
		||||
		logger.Critical("Error: %v whilst counting orphaned objects", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if count > 0 {
 | 
			
		||||
		if autofix {
 | 
			
		||||
			if err = models.DeleteOrphanedObjects("tracked_time", "issue", "tracked_time.issue_id=issue.id"); err != nil {
 | 
			
		||||
				logger.Critical("Error: %v whilst deleting orphaned objects")
 | 
			
		||||
				logger.Critical("Error: %v whilst deleting orphaned objects", err)
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			logger.Info("%d tracked times without existing issue deleted", count)
 | 
			
		||||
@@ -113,14 +113,14 @@ func checkDBConsistency(logger log.Logger, autofix bool) error {
 | 
			
		||||
	// find null archived repositories
 | 
			
		||||
	count, err = models.CountNullArchivedRepository()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.Critical("Error: %v whilst counting null archived repositories")
 | 
			
		||||
		logger.Critical("Error: %v whilst counting null archived repositories", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if count > 0 {
 | 
			
		||||
		if autofix {
 | 
			
		||||
			updatedCount, err := models.FixNullArchivedRepository()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logger.Critical("Error: %v whilst fixing null archived repositories")
 | 
			
		||||
				logger.Critical("Error: %v whilst fixing null archived repositories", err)
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			logger.Info("%d repositories with null is_archived updated", updatedCount)
 | 
			
		||||
@@ -132,14 +132,14 @@ func checkDBConsistency(logger log.Logger, autofix bool) error {
 | 
			
		||||
	// find label comments with empty labels
 | 
			
		||||
	count, err = models.CountCommentTypeLabelWithEmptyLabel()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.Critical("Error: %v whilst counting label comments with empty labels")
 | 
			
		||||
		logger.Critical("Error: %v whilst counting label comments with empty labels", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if count > 0 {
 | 
			
		||||
		if autofix {
 | 
			
		||||
			updatedCount, err := models.FixCommentTypeLabelWithEmptyLabel()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logger.Critical("Error: %v whilst removing label comments with empty labels")
 | 
			
		||||
				logger.Critical("Error: %v whilst removing label comments with empty labels", err)
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			logger.Info("%d label comments with empty labels removed", updatedCount)
 | 
			
		||||
@@ -191,13 +191,14 @@ func checkDBConsistency(logger log.Logger, autofix bool) error {
 | 
			
		||||
	if setting.Database.UsePostgreSQL {
 | 
			
		||||
		count, err = models.CountBadSequences()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logger.Critical("Error: %v whilst checking sequence values")
 | 
			
		||||
			logger.Critical("Error: %v whilst checking sequence values", err)
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if count > 0 {
 | 
			
		||||
			if autofix {
 | 
			
		||||
				err := models.FixBadSequences()
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					logger.Critical("Error: %v whilst attempting to fix sequences")
 | 
			
		||||
					logger.Critical("Error: %v whilst attempting to fix sequences", err)
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
				logger.Info("%d sequences updated", count)
 | 
			
		||||
@@ -207,6 +208,60 @@ func checkDBConsistency(logger log.Logger, autofix bool) error {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// find protected branches without existing repository
 | 
			
		||||
	count, err = models.CountOrphanedObjects("protected_branch", "repository", "protected_branch.repo_id=repository.id")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.Critical("Error: %v whilst counting orphaned objects", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if count > 0 {
 | 
			
		||||
		if autofix {
 | 
			
		||||
			if err = models.DeleteOrphanedObjects("protected_branch", "repository", "protected_branch.repo_id=repository.id"); err != nil {
 | 
			
		||||
				logger.Critical("Error: %v whilst deleting orphaned objects", err)
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			logger.Info("%d protected branches without existing repository deleted", count)
 | 
			
		||||
		} else {
 | 
			
		||||
			logger.Warn("%d protected branches without existing repository", count)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// find deleted branches without existing repository
 | 
			
		||||
	count, err = models.CountOrphanedObjects("deleted_branch", "repository", "deleted_branch.repo_id=repository.id")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.Critical("Error: %v whilst counting orphaned objects", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if count > 0 {
 | 
			
		||||
		if autofix {
 | 
			
		||||
			if err = models.DeleteOrphanedObjects("deleted_branch", "repository", "deleted_branch.repo_id=repository.id"); err != nil {
 | 
			
		||||
				logger.Critical("Error: %v whilst deleting orphaned objects", err)
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			logger.Info("%d deleted branches without existing repository deleted", count)
 | 
			
		||||
		} else {
 | 
			
		||||
			logger.Warn("%d deleted branches without existing repository", count)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// find LFS locks without existing repository
 | 
			
		||||
	count, err = models.CountOrphanedObjects("lfs_lock", "repository", "lfs_lock.repo_id=repository.id")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.Critical("Error: %v whilst counting orphaned objects", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if count > 0 {
 | 
			
		||||
		if autofix {
 | 
			
		||||
			if err = models.DeleteOrphanedObjects("lfs_lock", "repository", "lfs_lock.repo_id=repository.id"); err != nil {
 | 
			
		||||
				logger.Critical("Error: %v whilst deleting orphaned objects", err)
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			logger.Info("%d LFS locks without existing repository deleted", count)
 | 
			
		||||
		} else {
 | 
			
		||||
			logger.Warn("%d LFS locks without existing repository", count)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ func checkDBVersion(logger log.Logger, autofix bool) error {
 | 
			
		||||
 | 
			
		||||
		err = models.NewEngine(context.Background(), migrations.Migrate)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logger.Critical("Error: %v during migration")
 | 
			
		||||
			logger.Critical("Error: %v during migration", err)
 | 
			
		||||
		}
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@
 | 
			
		||||
package emoji
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"io"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
@@ -145,6 +146,8 @@ func (n *rememberSecondWriteWriter) Write(p []byte) (int, error) {
 | 
			
		||||
	if n.writecount == 2 {
 | 
			
		||||
		n.idx = n.pos
 | 
			
		||||
		n.end = n.pos + len(p)
 | 
			
		||||
		n.pos += len(p)
 | 
			
		||||
		return len(p), io.EOF
 | 
			
		||||
	}
 | 
			
		||||
	n.pos += len(p)
 | 
			
		||||
	return len(p), nil
 | 
			
		||||
@@ -155,6 +158,8 @@ func (n *rememberSecondWriteWriter) WriteString(s string) (int, error) {
 | 
			
		||||
	if n.writecount == 2 {
 | 
			
		||||
		n.idx = n.pos
 | 
			
		||||
		n.end = n.pos + len(s)
 | 
			
		||||
		n.pos += len(s)
 | 
			
		||||
		return len(s), io.EOF
 | 
			
		||||
	}
 | 
			
		||||
	n.pos += len(s)
 | 
			
		||||
	return len(s), nil
 | 
			
		||||
 
 | 
			
		||||
@@ -51,6 +51,7 @@ type AuthenticationForm struct {
 | 
			
		||||
	TLS                           bool
 | 
			
		||||
	SkipVerify                    bool
 | 
			
		||||
	PAMServiceName                string
 | 
			
		||||
	PAMEmailDomain                string
 | 
			
		||||
	Oauth2Provider                string
 | 
			
		||||
	Oauth2Key                     string
 | 
			
		||||
	Oauth2Secret                  string
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@ import (
 | 
			
		||||
	"math/big"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/dgrijalva/jwt-go"
 | 
			
		||||
	"github.com/golang-jwt/jwt"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// GetRandomString generate random string by specify chars.
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@ package git
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"context"
 | 
			
		||||
	"io"
 | 
			
		||||
	"math"
 | 
			
		||||
	"strconv"
 | 
			
		||||
@@ -15,20 +16,24 @@ import (
 | 
			
		||||
 | 
			
		||||
// CatFileBatch opens git cat-file --batch in the provided repo and returns a stdin pipe, a stdout reader and cancel function
 | 
			
		||||
func CatFileBatch(repoPath string) (*io.PipeWriter, *bufio.Reader, func()) {
 | 
			
		||||
	// Next feed the commits in order into cat-file --batch, followed by their trees and sub trees as necessary.
 | 
			
		||||
	// We often want to feed the commits in order into cat-file --batch, followed by their trees and sub trees as necessary.
 | 
			
		||||
	// so let's create a batch stdin and stdout
 | 
			
		||||
	batchStdinReader, batchStdinWriter := io.Pipe()
 | 
			
		||||
	batchStdoutReader, batchStdoutWriter := io.Pipe()
 | 
			
		||||
	ctx, ctxCancel := context.WithCancel(DefaultContext)
 | 
			
		||||
	closed := make(chan struct{})
 | 
			
		||||
	cancel := func() {
 | 
			
		||||
		_ = batchStdinReader.Close()
 | 
			
		||||
		_ = batchStdinWriter.Close()
 | 
			
		||||
		_ = batchStdoutReader.Close()
 | 
			
		||||
		_ = batchStdoutWriter.Close()
 | 
			
		||||
		ctxCancel()
 | 
			
		||||
		<-closed
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		stderr := strings.Builder{}
 | 
			
		||||
		err := NewCommand("cat-file", "--batch").RunInDirFullPipeline(repoPath, batchStdoutWriter, &stderr, batchStdinReader)
 | 
			
		||||
		err := NewCommandContext(ctx, "cat-file", "--batch").RunInDirFullPipeline(repoPath, batchStdoutWriter, &stderr, batchStdinReader)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			_ = batchStdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String()))
 | 
			
		||||
			_ = batchStdinReader.CloseWithError(ConcatenateError(err, (&stderr).String()))
 | 
			
		||||
@@ -36,10 +41,11 @@ func CatFileBatch(repoPath string) (*io.PipeWriter, *bufio.Reader, func()) {
 | 
			
		||||
			_ = batchStdoutWriter.Close()
 | 
			
		||||
			_ = batchStdinReader.Close()
 | 
			
		||||
		}
 | 
			
		||||
		close(closed)
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	// For simplicities sake we'll us a buffered reader to read from the cat-file --batch
 | 
			
		||||
	batchReader := bufio.NewReader(batchStdoutReader)
 | 
			
		||||
	batchReader := bufio.NewReaderSize(batchStdoutReader, 32*1024)
 | 
			
		||||
 | 
			
		||||
	return batchStdinWriter, batchReader, cancel
 | 
			
		||||
}
 | 
			
		||||
@@ -149,17 +155,18 @@ headerLoop:
 | 
			
		||||
// constant hextable to help quickly convert between 20byte and 40byte hashes
 | 
			
		||||
const hextable = "0123456789abcdef"
 | 
			
		||||
 | 
			
		||||
// to40ByteSHA converts a 20-byte SHA in a 40-byte slice into a 40-byte sha in place
 | 
			
		||||
// without allocations. This is at least 100x quicker that hex.EncodeToString
 | 
			
		||||
// NB This requires that sha is a 40-byte slice
 | 
			
		||||
func to40ByteSHA(sha []byte) []byte {
 | 
			
		||||
// To40ByteSHA converts a 20-byte SHA into a 40-byte sha. Input and output can be the
 | 
			
		||||
// same 40 byte slice to support in place conversion without allocations.
 | 
			
		||||
// This is at least 100x quicker that hex.EncodeToString
 | 
			
		||||
// NB This requires that out is a 40-byte slice
 | 
			
		||||
func To40ByteSHA(sha, out []byte) []byte {
 | 
			
		||||
	for i := 19; i >= 0; i-- {
 | 
			
		||||
		v := sha[i]
 | 
			
		||||
		vhi, vlo := v>>4, v&0x0f
 | 
			
		||||
		shi, slo := hextable[vhi], hextable[vlo]
 | 
			
		||||
		sha[i*2], sha[i*2+1] = shi, slo
 | 
			
		||||
		out[i*2], out[i*2+1] = shi, slo
 | 
			
		||||
	}
 | 
			
		||||
	return sha
 | 
			
		||||
	return out
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ParseTreeLineSkipMode reads an entry from a tree in a cat-file --batch stream
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@ package git
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
@@ -14,32 +15,15 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestBlob_Data(t *testing.T) {
 | 
			
		||||
	output := `Copyright (c) 2016 The Gitea Authors
 | 
			
		||||
Copyright (c) 2015 The Gogs Authors
 | 
			
		||||
 | 
			
		||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
in the Software without restriction, including without limitation the rights
 | 
			
		||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
furnished to do so, subject to the following conditions:
 | 
			
		||||
 | 
			
		||||
The above copyright notice and this permission notice shall be included in
 | 
			
		||||
all copies or substantial portions of the Software.
 | 
			
		||||
 | 
			
		||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | 
			
		||||
THE SOFTWARE.
 | 
			
		||||
`
 | 
			
		||||
	repo, err := OpenRepository("../../.git")
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	output := "file2\n"
 | 
			
		||||
	bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
 | 
			
		||||
	repo, err := OpenRepository(bareRepo1Path)
 | 
			
		||||
	if !assert.NoError(t, err) {
 | 
			
		||||
		t.Fatal()
 | 
			
		||||
	}
 | 
			
		||||
	defer repo.Close()
 | 
			
		||||
 | 
			
		||||
	testBlob, err := repo.GetBlob("a8d4b49dd073a4a38a7e58385eeff7cc52568697")
 | 
			
		||||
	testBlob, err := repo.GetBlob("6c493ff740f9380390d5c9ddef4af18697ac9375")
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	r, err := testBlob.DataAsync()
 | 
			
		||||
@@ -53,13 +37,14 @@ THE SOFTWARE.
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Benchmark_Blob_Data(b *testing.B) {
 | 
			
		||||
	repo, err := OpenRepository("../../.git")
 | 
			
		||||
	bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
 | 
			
		||||
	repo, err := OpenRepository(bareRepo1Path)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		b.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	defer repo.Close()
 | 
			
		||||
 | 
			
		||||
	testBlob, err := repo.GetBlob("a8d4b49dd073a4a38a7e58385eeff7cc52568697")
 | 
			
		||||
	testBlob, err := repo.GetBlob("6c493ff740f9380390d5c9ddef4af18697ac9375")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		b.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -124,12 +124,18 @@ func (c *Command) RunInDirTimeoutEnvFullPipelineFunc(env []string, timeout time.
 | 
			
		||||
 | 
			
		||||
	cmd := exec.CommandContext(ctx, c.name, c.args...)
 | 
			
		||||
	if env == nil {
 | 
			
		||||
		cmd.Env = append(os.Environ(), fmt.Sprintf("LC_ALL=%s", DefaultLocale))
 | 
			
		||||
		cmd.Env = os.Environ()
 | 
			
		||||
	} else {
 | 
			
		||||
		cmd.Env = env
 | 
			
		||||
		cmd.Env = append(cmd.Env, fmt.Sprintf("LC_ALL=%s", DefaultLocale))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd.Env = append(
 | 
			
		||||
		cmd.Env,
 | 
			
		||||
		fmt.Sprintf("LC_ALL=%s", DefaultLocale),
 | 
			
		||||
		// avoid prompting for credentials interactively, supported since git v2.3
 | 
			
		||||
		"GIT_TERMINAL_PROMPT=0",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	// TODO: verify if this is still needed in golang 1.15
 | 
			
		||||
	if goVersionLessThan115 {
 | 
			
		||||
		cmd.Env = append(cmd.Env, "GODEBUG=asyncpreemptoff=1")
 | 
			
		||||
 
 | 
			
		||||
@@ -102,10 +102,13 @@ func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache *LastCo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getLastCommitForPathsByCache(commitID, treePath string, paths []string, cache *LastCommitCache) (map[string]*Commit, []string, error) {
 | 
			
		||||
	wr, rd, cancel := CatFileBatch(cache.repo.Path)
 | 
			
		||||
	defer cancel()
 | 
			
		||||
 | 
			
		||||
	var unHitEntryPaths []string
 | 
			
		||||
	var results = make(map[string]*Commit)
 | 
			
		||||
	for _, p := range paths {
 | 
			
		||||
		lastCommit, err := cache.Get(commitID, path.Join(treePath, p))
 | 
			
		||||
		lastCommit, err := cache.Get(commitID, path.Join(treePath, p), wr, rd)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, nil, err
 | 
			
		||||
		}
 | 
			
		||||
@@ -300,7 +303,7 @@ revListLoop:
 | 
			
		||||
					commits[0] = string(commitID)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			treeID = to40ByteSHA(treeID)
 | 
			
		||||
			treeID = To40ByteSHA(treeID, treeID)
 | 
			
		||||
			_, err = batchStdinWriter.Write(treeID)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,9 @@ import (
 | 
			
		||||
// If used as part of a cat-file --batch stream you need to limit the reader to the correct size
 | 
			
		||||
func CommitFromReader(gitRepo *Repository, sha SHA1, reader io.Reader) (*Commit, error) {
 | 
			
		||||
	commit := &Commit{
 | 
			
		||||
		ID: sha,
 | 
			
		||||
		ID:        sha,
 | 
			
		||||
		Author:    &Signature{},
 | 
			
		||||
		Committer: &Signature{},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	payloadSB := new(strings.Builder)
 | 
			
		||||
 
 | 
			
		||||
@@ -47,7 +47,7 @@ func GetRawDiffForFile(repoPath, startCommit, endCommit string, diffType RawDiff
 | 
			
		||||
func GetRepoRawDiffForFile(repo *Repository, startCommit, endCommit string, diffType RawDiffType, file string, writer io.Writer) error {
 | 
			
		||||
	commit, err := repo.GetCommit(endCommit)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("GetCommit: %v", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	fileArgs := make([]string, 0)
 | 
			
		||||
	if len(file) > 0 {
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,8 @@
 | 
			
		||||
package git
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"io"
 | 
			
		||||
	"path"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -34,7 +36,7 @@ func NewLastCommitCache(repoPath string, gitRepo *Repository, ttl func() int64,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Get get the last commit information by commit id and entry path
 | 
			
		||||
func (c *LastCommitCache) Get(ref, entryPath string) (interface{}, error) {
 | 
			
		||||
func (c *LastCommitCache) Get(ref, entryPath string, wr *io.PipeWriter, rd *bufio.Reader) (interface{}, error) {
 | 
			
		||||
	v := c.cache.Get(c.getCacheKey(c.repoPath, ref, entryPath))
 | 
			
		||||
	if vs, ok := v.(string); ok {
 | 
			
		||||
		log("LastCommitCache hit level 1: [%s:%s:%s]", ref, entryPath, vs)
 | 
			
		||||
@@ -46,7 +48,10 @@ func (c *LastCommitCache) Get(ref, entryPath string) (interface{}, error) {
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		commit, err := c.repo.getCommit(id)
 | 
			
		||||
		if _, err := wr.Write([]byte(vs + "\n")); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		commit, err := c.repo.getCommitFromBatchReader(rd, id)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@ package git
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// GetNote retrieves the git-notes data for a given commit.
 | 
			
		||||
@@ -49,7 +50,13 @@ func GetNote(repo *Repository, commitID string, note *Note) error {
 | 
			
		||||
	}
 | 
			
		||||
	note.Message = d
 | 
			
		||||
 | 
			
		||||
	lastCommits, err := GetLastCommitForPaths(notes, "", []string{path})
 | 
			
		||||
	treePath := ""
 | 
			
		||||
	if idx := strings.LastIndex(path, "/"); idx > -1 {
 | 
			
		||||
		treePath = path[:idx]
 | 
			
		||||
		path = path[idx+1:]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	lastCommits, err := GetLastCommitForPaths(notes, treePath, []string{path})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -43,8 +43,6 @@ func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) {
 | 
			
		||||
 | 
			
		||||
	basePath := repo.Path
 | 
			
		||||
 | 
			
		||||
	hashStr := hash.String()
 | 
			
		||||
 | 
			
		||||
	// Use rev-list to provide us with all commits in order
 | 
			
		||||
	revListReader, revListWriter := io.Pipe()
 | 
			
		||||
	defer func() {
 | 
			
		||||
@@ -74,7 +72,7 @@ func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) {
 | 
			
		||||
 | 
			
		||||
	fnameBuf := make([]byte, 4096)
 | 
			
		||||
	modeBuf := make([]byte, 40)
 | 
			
		||||
	workingShaBuf := make([]byte, 40)
 | 
			
		||||
	workingShaBuf := make([]byte, 20)
 | 
			
		||||
 | 
			
		||||
	for scan.Scan() {
 | 
			
		||||
		// Get the next commit ID
 | 
			
		||||
@@ -127,12 +125,12 @@ func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) {
 | 
			
		||||
			case "tree":
 | 
			
		||||
				var n int64
 | 
			
		||||
				for n < size {
 | 
			
		||||
					mode, fname, sha, count, err := git.ParseTreeLine(batchReader, modeBuf, fnameBuf, workingShaBuf)
 | 
			
		||||
					mode, fname, sha20byte, count, err := git.ParseTreeLine(batchReader, modeBuf, fnameBuf, workingShaBuf)
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						return nil, err
 | 
			
		||||
					}
 | 
			
		||||
					n += int64(count)
 | 
			
		||||
					if bytes.Equal(sha, []byte(hashStr)) {
 | 
			
		||||
					if bytes.Equal(sha20byte, hash[:]) {
 | 
			
		||||
						result := LFSResult{
 | 
			
		||||
							Name:         curPath + string(fname),
 | 
			
		||||
							SHA:          curCommit.ID.String(),
 | 
			
		||||
@@ -142,7 +140,9 @@ func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) {
 | 
			
		||||
						}
 | 
			
		||||
						resultsMap[curCommit.ID.String()+":"+curPath+string(fname)] = &result
 | 
			
		||||
					} else if string(mode) == git.EntryModeTree.String() {
 | 
			
		||||
						trees = append(trees, sha)
 | 
			
		||||
						sha40Byte := make([]byte, 40)
 | 
			
		||||
						git.To40ByteSHA(sha20byte, sha40Byte)
 | 
			
		||||
						trees = append(trees, sha40Byte)
 | 
			
		||||
						paths = append(paths, curPath+string(fname)+"/")
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 
 | 
			
		||||
@@ -21,14 +21,7 @@ func (repo *Repository) GetBranchCommitID(name string) (string, error) {
 | 
			
		||||
 | 
			
		||||
// GetTagCommitID returns last commit ID string of given tag.
 | 
			
		||||
func (repo *Repository) GetTagCommitID(name string) (string, error) {
 | 
			
		||||
	stdout, err := NewCommand("rev-list", "-n", "1", TagPrefix+name).RunInDir(repo.Path)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if strings.Contains(err.Error(), "unknown revision or path") {
 | 
			
		||||
			return "", ErrNotExist{name, ""}
 | 
			
		||||
		}
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	return strings.TrimSpace(stdout), nil
 | 
			
		||||
	return repo.GetRefCommitID(TagPrefix + name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ConvertToSHA1 returns a Hash object from a potential ID string
 | 
			
		||||
 
 | 
			
		||||
@@ -9,9 +9,10 @@ package git
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -34,6 +35,18 @@ func (repo *Repository) ResolveReference(name string) (string, error) {
 | 
			
		||||
 | 
			
		||||
// GetRefCommitID returns the last commit ID string of given reference (branch or tag).
 | 
			
		||||
func (repo *Repository) GetRefCommitID(name string) (string, error) {
 | 
			
		||||
	if strings.HasPrefix(name, "refs/") {
 | 
			
		||||
		// We're gonna try just reading the ref file as this is likely to be quicker than other options
 | 
			
		||||
		fileInfo, err := os.Lstat(filepath.Join(repo.Path, name))
 | 
			
		||||
		if err == nil && fileInfo.Mode().IsRegular() && fileInfo.Size() == 41 {
 | 
			
		||||
			ref, err := ioutil.ReadFile(filepath.Join(repo.Path, name))
 | 
			
		||||
 | 
			
		||||
			if err == nil && SHAPattern.Match(ref[:40]) && ref[40] == '\n' {
 | 
			
		||||
				return string(ref[:40]), nil
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	stdout, err := NewCommand("show-ref", "--verify", "--hash", name).RunInDir(repo.Path)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if strings.Contains(err.Error(), "not a valid ref") {
 | 
			
		||||
@@ -69,6 +82,11 @@ func (repo *Repository) getCommit(id SHA1) (*Commit, error) {
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	bufReader := bufio.NewReader(stdoutReader)
 | 
			
		||||
 | 
			
		||||
	return repo.getCommitFromBatchReader(bufReader, id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (repo *Repository) getCommitFromBatchReader(bufReader *bufio.Reader, id SHA1) (*Commit, error) {
 | 
			
		||||
	_, typ, size, err := ReadBatchLine(bufReader)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if errors.Is(err, io.EOF) {
 | 
			
		||||
@@ -106,7 +124,6 @@ func (repo *Repository) getCommit(id SHA1) (*Commit, error) {
 | 
			
		||||
	case "commit":
 | 
			
		||||
		return CommitFromReader(repo, id, io.LimitReader(bufReader, size))
 | 
			
		||||
	default:
 | 
			
		||||
		_ = stdoutReader.CloseWithError(fmt.Errorf("unknown typ: %s", typ))
 | 
			
		||||
		log("Unknown typ: %s", typ)
 | 
			
		||||
		return nil, ErrNotExist{
 | 
			
		||||
			ID: id.String(),
 | 
			
		||||
 
 | 
			
		||||
@@ -43,7 +43,7 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
 | 
			
		||||
 | 
			
		||||
	sizes := make(map[string]int64)
 | 
			
		||||
	err = tree.Files().ForEach(func(f *object.File) error {
 | 
			
		||||
		if f.Size == 0 || enry.IsVendor(f.Name) || enry.IsDotFile(f.Name) ||
 | 
			
		||||
		if f.Size == 0 || analyze.IsVendor(f.Name) || enry.IsDotFile(f.Name) ||
 | 
			
		||||
			enry.IsDocumentation(f.Name) || enry.IsConfiguration(f.Name) {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -67,7 +67,7 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
 | 
			
		||||
	for _, f := range entries {
 | 
			
		||||
		contentBuf.Reset()
 | 
			
		||||
		content = contentBuf.Bytes()
 | 
			
		||||
		if f.Size() == 0 || enry.IsVendor(f.Name()) || enry.IsDotFile(f.Name()) ||
 | 
			
		||||
		if f.Size() == 0 || analyze.IsVendor(f.Name()) || enry.IsDotFile(f.Name()) ||
 | 
			
		||||
			enry.IsDocumentation(f.Name()) || enry.IsConfiguration(f.Name()) {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,23 +7,18 @@ package git
 | 
			
		||||
import (
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestGetLatestCommitTime(t *testing.T) {
 | 
			
		||||
	lct, err := GetLatestCommitTime(".")
 | 
			
		||||
	bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
 | 
			
		||||
	lct, err := GetLatestCommitTime(bareRepo1Path)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	// Time is in the past
 | 
			
		||||
	now := time.Now()
 | 
			
		||||
	assert.True(t, lct.Unix() < now.Unix(), "%d not smaller than %d", lct, now)
 | 
			
		||||
	// Time is after Mon Oct 23 03:52:09 2017 +0300
 | 
			
		||||
	// Time is Sun Jul 21 22:43:13 2019 +0200
 | 
			
		||||
	// which is the time of commit
 | 
			
		||||
	// d47b98c44c9a6472e44ab80efe65235e11c6da2a
 | 
			
		||||
	refTime, err := time.Parse("Mon Jan 02 15:04:05 2006 -0700", "Mon Oct 23 03:52:09 2017 +0300")
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.True(t, lct.Unix() > refTime.Unix(), "%d not greater than %d", lct, refTime)
 | 
			
		||||
	// feaf4ba6bc635fec442f46ddd4512416ec43c2c2 (refs/heads/master)
 | 
			
		||||
	assert.EqualValues(t, 1563741793, lct.Unix())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestRepoIsEmpty(t *testing.T) {
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user