mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-03 08:02:36 +09:00 
			
		
		
		
	Compare commits
	
		
			38 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					bb77e6c12d | ||
| 
						 | 
					fabc0ad157 | ||
| 
						 | 
					a13fb154ae | ||
| 
						 | 
					36c66303df | ||
| 
						 | 
					f65e29c077 | ||
| 
						 | 
					a97c8a8966 | ||
| 
						 | 
					69b7776af5 | ||
| 
						 | 
					18c1edf15c | ||
| 
						 | 
					70ffec4509 | ||
| 
						 | 
					bc196a35e1 | ||
| 
						 | 
					8d31cfbfff | ||
| 
						 | 
					e84a432f76 | ||
| 
						 | 
					1fc9f11253 | ||
| 
						 | 
					0dfe5fa2d6 | ||
| 
						 | 
					1d17313949 | ||
| 
						 | 
					9c318a17f5 | ||
| 
						 | 
					72fa108cbc | ||
| 
						 | 
					db134c5d71 | ||
| 
						 | 
					73b68015de | ||
| 
						 | 
					e4919e414f | ||
| 
						 | 
					f7606de13a | ||
| 
						 | 
					483bda4b2d | ||
| 
						 | 
					edd57028a1 | ||
| 
						 | 
					083b85c655 | ||
| 
						 | 
					d5027b6c09 | ||
| 
						 | 
					a044ec8b53 | ||
| 
						 | 
					f93d72c09b | ||
| 
						 | 
					2f22337125 | ||
| 
						 | 
					781ad8a79e | ||
| 
						 | 
					cada7202aa | ||
| 
						 | 
					0b331e2213 | ||
| 
						 | 
					0734ca0132 | ||
| 
						 | 
					0b83cc21be | ||
| 
						 | 
					b68e605d56 | ||
| 
						 | 
					42991dc89a | ||
| 
						 | 
					160de9fbda | ||
| 
						 | 
					d644289fcb | ||
| 
						 | 
					fd9ff7cd6f | 
							
								
								
									
										134
									
								
								.drone.yml
									
									
									
									
									
								
							
							
						
						
									
										134
									
								
								.drone.yml
									
									
									
									
									
								
							@@ -567,7 +567,7 @@ steps:
 | 
			
		||||
    settings:
 | 
			
		||||
      acl: public-read
 | 
			
		||||
      bucket: gitea-artifacts
 | 
			
		||||
      endpoint: https://storage.gitea.io
 | 
			
		||||
      endpoint: https://ams3.digitaloceanspaces.com
 | 
			
		||||
      path_style: true
 | 
			
		||||
      source: "dist/release/*"
 | 
			
		||||
      strip_prefix: dist/release/
 | 
			
		||||
@@ -588,7 +588,7 @@ steps:
 | 
			
		||||
    settings:
 | 
			
		||||
      acl: public-read
 | 
			
		||||
      bucket: gitea-artifacts
 | 
			
		||||
      endpoint: https://storage.gitea.io
 | 
			
		||||
      endpoint: https://ams3.digitaloceanspaces.com
 | 
			
		||||
      path_style: true
 | 
			
		||||
      source: "dist/release/*"
 | 
			
		||||
      strip_prefix: dist/release/
 | 
			
		||||
@@ -663,7 +663,7 @@ steps:
 | 
			
		||||
    settings:
 | 
			
		||||
      acl: public-read
 | 
			
		||||
      bucket: gitea-artifacts
 | 
			
		||||
      endpoint: https://storage.gitea.io
 | 
			
		||||
      endpoint: https://ams3.digitaloceanspaces.com
 | 
			
		||||
      path_style: true
 | 
			
		||||
      source: "dist/release/*"
 | 
			
		||||
      strip_prefix: dist/release/
 | 
			
		||||
@@ -850,6 +850,67 @@ steps:
 | 
			
		||||
        exclude:
 | 
			
		||||
        - pull_request
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
kind: pipeline
 | 
			
		||||
name: docker-linux-amd64-release-branch
 | 
			
		||||
 | 
			
		||||
platform:
 | 
			
		||||
  os: linux
 | 
			
		||||
  arch: amd64
 | 
			
		||||
 | 
			
		||||
depends_on:
 | 
			
		||||
  - testing-amd64
 | 
			
		||||
  - testing-arm64
 | 
			
		||||
 | 
			
		||||
trigger:
 | 
			
		||||
  ref:
 | 
			
		||||
  - "refs/heads/release/v*"
 | 
			
		||||
  event:
 | 
			
		||||
    exclude:
 | 
			
		||||
    - cron
 | 
			
		||||
 | 
			
		||||
steps:
 | 
			
		||||
  - name: fetch-tags
 | 
			
		||||
    image: docker:git
 | 
			
		||||
    commands:
 | 
			
		||||
      - git fetch --tags --force
 | 
			
		||||
 | 
			
		||||
  - name: publish
 | 
			
		||||
    pull: always
 | 
			
		||||
    image: techknowlogick/drone-docker:latest
 | 
			
		||||
    settings:
 | 
			
		||||
      auto_tag: false
 | 
			
		||||
      tags: ${DRONE_BRANCH##release/v}-dev-linux-amd64
 | 
			
		||||
      repo: gitea/gitea
 | 
			
		||||
      build_args:
 | 
			
		||||
        - GOPROXY=https://goproxy.cn
 | 
			
		||||
      password:
 | 
			
		||||
        from_secret: docker_password
 | 
			
		||||
      username:
 | 
			
		||||
        from_secret: docker_username
 | 
			
		||||
    when:
 | 
			
		||||
      event:
 | 
			
		||||
        exclude:
 | 
			
		||||
        - pull_request
 | 
			
		||||
 | 
			
		||||
  - name: publish-rootless
 | 
			
		||||
    image: techknowlogick/drone-docker:latest
 | 
			
		||||
    settings:
 | 
			
		||||
      dockerfile: Dockerfile.rootless
 | 
			
		||||
      auto_tag: false
 | 
			
		||||
      tags: ${DRONE_BRANCH##release/v}-dev-linux-amd64-rootless
 | 
			
		||||
      repo: gitea/gitea
 | 
			
		||||
      build_args:
 | 
			
		||||
        - GOPROXY=https://goproxy.cn
 | 
			
		||||
      password:
 | 
			
		||||
        from_secret: docker_password
 | 
			
		||||
      username:
 | 
			
		||||
        from_secret: docker_username
 | 
			
		||||
    when:
 | 
			
		||||
      event:
 | 
			
		||||
        exclude:
 | 
			
		||||
        - pull_request
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
kind: pipeline
 | 
			
		||||
type: docker
 | 
			
		||||
@@ -1006,6 +1067,68 @@ steps:
 | 
			
		||||
      event:
 | 
			
		||||
        exclude:
 | 
			
		||||
        - pull_request
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
kind: pipeline
 | 
			
		||||
name: docker-linux-arm64-release-branch
 | 
			
		||||
 | 
			
		||||
platform:
 | 
			
		||||
  os: linux
 | 
			
		||||
  arch: arm64
 | 
			
		||||
 | 
			
		||||
depends_on:
 | 
			
		||||
  - testing-amd64
 | 
			
		||||
  - testing-arm64
 | 
			
		||||
 | 
			
		||||
trigger:
 | 
			
		||||
  ref:
 | 
			
		||||
  - "refs/heads/release/v*"
 | 
			
		||||
  event:
 | 
			
		||||
    exclude:
 | 
			
		||||
    - cron
 | 
			
		||||
 | 
			
		||||
steps:
 | 
			
		||||
  - name: fetch-tags
 | 
			
		||||
    image: docker:git
 | 
			
		||||
    commands:
 | 
			
		||||
      - git fetch --tags --force
 | 
			
		||||
 | 
			
		||||
  - name: publish
 | 
			
		||||
    pull: always
 | 
			
		||||
    image: techknowlogick/drone-docker:latest
 | 
			
		||||
    settings:
 | 
			
		||||
      auto_tag: false
 | 
			
		||||
      tags: ${DRONE_BRANCH##release/v}-dev-linux-arm64
 | 
			
		||||
      repo: gitea/gitea
 | 
			
		||||
      build_args:
 | 
			
		||||
        - GOPROXY=https://goproxy.cn
 | 
			
		||||
      password:
 | 
			
		||||
        from_secret: docker_password
 | 
			
		||||
      username:
 | 
			
		||||
        from_secret: docker_username
 | 
			
		||||
    when:
 | 
			
		||||
      event:
 | 
			
		||||
        exclude:
 | 
			
		||||
        - pull_request
 | 
			
		||||
 | 
			
		||||
  - name: publish-rootless
 | 
			
		||||
    image: techknowlogick/drone-docker:latest
 | 
			
		||||
    settings:
 | 
			
		||||
      dockerfile: Dockerfile.rootless
 | 
			
		||||
      auto_tag: false
 | 
			
		||||
      tags: ${DRONE_BRANCH##release/v}-dev-linux-arm64-rootless
 | 
			
		||||
      repo: gitea/gitea
 | 
			
		||||
      build_args:
 | 
			
		||||
        - GOPROXY=https://goproxy.cn
 | 
			
		||||
      password:
 | 
			
		||||
        from_secret: docker_password
 | 
			
		||||
      username:
 | 
			
		||||
        from_secret: docker_username
 | 
			
		||||
    when:
 | 
			
		||||
      event:
 | 
			
		||||
        exclude:
 | 
			
		||||
        - pull_request
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
kind: pipeline
 | 
			
		||||
type: docker
 | 
			
		||||
@@ -1086,6 +1209,7 @@ steps:
 | 
			
		||||
trigger:
 | 
			
		||||
  ref:
 | 
			
		||||
  - refs/heads/main
 | 
			
		||||
  - "refs/heads/release/v*"
 | 
			
		||||
  event:
 | 
			
		||||
    exclude:
 | 
			
		||||
    - cron
 | 
			
		||||
@@ -1093,6 +1217,8 @@ trigger:
 | 
			
		||||
depends_on:
 | 
			
		||||
  - docker-linux-amd64-release
 | 
			
		||||
  - docker-linux-arm64-release
 | 
			
		||||
  - docker-linux-amd64-release-branch
 | 
			
		||||
  - docker-linux-arm64-release-branch
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
kind: pipeline
 | 
			
		||||
@@ -1126,6 +1252,8 @@ depends_on:
 | 
			
		||||
  - docker-linux-arm64-release
 | 
			
		||||
  - docker-linux-amd64-release-version
 | 
			
		||||
  - docker-linux-arm64-release-version
 | 
			
		||||
  - docker-linux-amd64-release-branch
 | 
			
		||||
  - docker-linux-arm64-release-branch
 | 
			
		||||
  - docker-manifest
 | 
			
		||||
  - docker-manifest-version
 | 
			
		||||
  - docs
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										58
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										58
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -4,13 +4,45 @@ This changelog goes through all the changes that have been made in each release
 | 
			
		||||
without substantial changes to our git log; to see the highlights of what has
 | 
			
		||||
been added to each release, please refer to the [blog](https://blog.gitea.io).
 | 
			
		||||
 | 
			
		||||
## [1.16.0-rc1](https://github.com/go-gitea/gitea/releases/tag/v1.16.0-rc1) - 2022-01-19
 | 
			
		||||
## [1.16.1](https://github.com/go-gitea/gitea/releases/tag/v1.16.1) - 2022-02-06
 | 
			
		||||
 | 
			
		||||
* SECURITY
 | 
			
		||||
  * Update JS dependencies, fix lint (#18389) (#18540)
 | 
			
		||||
* ENHANCEMENTS
 | 
			
		||||
  * Add dropdown icon to label set template dropdown (#18564) (#18571)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Comments on migrated issues/prs must link to the comment ID (#18630) (#18637)
 | 
			
		||||
  * Stop logging an error when notes are not found (#18626) (#18635)
 | 
			
		||||
  * Ensure that blob-excerpt links work for wiki (#18587) (#18624)
 | 
			
		||||
  * Only attempt to flush queue if the underlying worker pool is not finished (#18593) (#18620)
 | 
			
		||||
  * Ensure commit-statuses box is sized correctly in headers (#18538) (#18606)
 | 
			
		||||
  * Prevent merge messages from being sorted to the top of email chains (#18566) (#18588)
 | 
			
		||||
  * Prevent panic on prohibited user login with oauth2 (#18562) (#18563)
 | 
			
		||||
  * Collaborator trust model should trust collaborators (#18539) (#18557)
 | 
			
		||||
  * Detect conflicts with 3way merge (#18536) (#18537)
 | 
			
		||||
  * In docker rootless use $GITEA_APP_INI if provided (#18524) (#18535)
 | 
			
		||||
  * Add `GetUserTeams` (#18499) (#18531)
 | 
			
		||||
  * Fix review excerpt (#18502) (#18530)
 | 
			
		||||
  * Fix for AvatarURL database type (#18487) (#18529)
 | 
			
		||||
  * Use `ImagedProvider` for gplus oauth2 provider (#18504) (#18505)
 | 
			
		||||
  * Fix OAuth Source Edit Page (#18495) (#18503)
 | 
			
		||||
  * Use "read" value for General Access (#18496) (#18500)
 | 
			
		||||
  * Prevent NPE on partial match of compare URL and allow short SHA1 compare URLs (#18472) (#18473)
 | 
			
		||||
* BUILD
 | 
			
		||||
  * Make docker gitea/gitea:v1.16-dev etc refer to the latest build on that branch (#18551) (#18569)
 | 
			
		||||
* DOCS
 | 
			
		||||
  * Update 1.16.0 changelog to set #17846 as breaking (#18533) (#18534)
 | 
			
		||||
 | 
			
		||||
## [1.16.0](https://github.com/go-gitea/gitea/releases/tag/v1.16.0) - 2022-01-30
 | 
			
		||||
 | 
			
		||||
* BREAKING
 | 
			
		||||
  * Remove golang vendored directory (#18277)
 | 
			
		||||
  * Paginate releases page & set default page size to 10 (#16857)
 | 
			
		||||
  * Use shadowing script for docker (#17846)
 | 
			
		||||
  * Only allow webhook to send requests to allowed hosts (#17482)
 | 
			
		||||
* SECURITY
 | 
			
		||||
  * Disable content sniffing on `PlainTextBytes` (#18359) (#18365)
 | 
			
		||||
  * Only view milestones from current repo (#18414) (#18417)
 | 
			
		||||
  * Sanitize user-input on file name (#17666)
 | 
			
		||||
  * Use `hostmatcher` to replace `matchlist` to improve blocking of bad hosts in Webhooks (#17605)
 | 
			
		||||
* FEATURES
 | 
			
		||||
@@ -228,6 +260,16 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
 | 
			
		||||
  * Add left padding for chunk header of split diff view (#13397)
 | 
			
		||||
  * Allow U2F 2FA without TOTP (#11573)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * GitLab reviews may not have the updated_at field set (#18450) (#18461)
 | 
			
		||||
  * Fix detection of no commits when the default branch is not master (#18422) (#18423)
 | 
			
		||||
  * Fix broken oauth2 authentication source edit page (#18412) (#18419)
 | 
			
		||||
  * Place inline diff comment dialogs on split diff in 4th and 8th columns (#18403) (#18404)
 | 
			
		||||
  * Fix restore without topic failure (#18387) (#18400)
 | 
			
		||||
  * Fix commit's time (#18375) (#18392)
 | 
			
		||||
  * Fix partial cloning a repo (#18373) (#18377)
 | 
			
		||||
  * Stop trimming preceding and suffixing spaces from editor filenames (#18334)
 | 
			
		||||
  * Prevent showing webauthn error for every time visiting `/user/settings/security` (#18386)
 | 
			
		||||
  * Fix mime-type detection for HTTP server (#18370) (#18371)
 | 
			
		||||
  * Stop trimming preceding and suffixing spaces from editor filenames (#18334)
 | 
			
		||||
  * Restore propagation of ErrDependenciesLeft (#18325)
 | 
			
		||||
  * Fix PR comments UI (#18323)
 | 
			
		||||
@@ -295,10 +337,22 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
 | 
			
		||||
* BUILD
 | 
			
		||||
  * Add lockfile-check (#18285)
 | 
			
		||||
  * Don't store assets modified time into generated files (#18193)
 | 
			
		||||
  * Use shadowing script for docker (#17846)
 | 
			
		||||
* MISC
 | 
			
		||||
  * Update JS dependencies (#17611)
 | 
			
		||||
 | 
			
		||||
## [1.15.11](https://github.com/go-gitea/gitea/releases/tag/v1.15.11) - 2022-01-29
 | 
			
		||||
 | 
			
		||||
* SECURITY
 | 
			
		||||
  * Only view milestones from current repo (#18414) (#18418)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Fix broken when no commits and default branch is not master (#18422) (#18424)
 | 
			
		||||
  * Fix commit's time (#18375) (#18409)
 | 
			
		||||
  * Fix restore without topic failure (#18387) (#18401)
 | 
			
		||||
  * Fix mermaid import in 1.15 (it uses ESModule now) (#18382)
 | 
			
		||||
  * Update to go/text 0.3.7 (#18336)
 | 
			
		||||
* MISC
 | 
			
		||||
  * Upgrade EasyMDE to 2.16.1 (#18278) (#18279)
 | 
			
		||||
 | 
			
		||||
## [1.15.10](https://github.com/go-gitea/gitea/releases/tag/v1.15.10) - 2022-01-14
 | 
			
		||||
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}dev{{/if}}-rootless
 | 
			
		||||
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}dev{{/if}}-rootless
 | 
			
		||||
{{#if build.tags}}
 | 
			
		||||
tags:
 | 
			
		||||
{{#each build.tags}}
 | 
			
		||||
@@ -8,12 +8,12 @@ tags:
 | 
			
		||||
{{/if}}
 | 
			
		||||
manifests:
 | 
			
		||||
  -
 | 
			
		||||
    image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}dev{{/if}}-linux-amd64-rootless
 | 
			
		||||
    image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}dev{{/if}}-linux-amd64-rootless
 | 
			
		||||
    platform:
 | 
			
		||||
      architecture: amd64
 | 
			
		||||
      os: linux
 | 
			
		||||
  -
 | 
			
		||||
    image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}dev{{/if}}-linux-arm64-rootless
 | 
			
		||||
    image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}dev{{/if}}-linux-arm64-rootless
 | 
			
		||||
    platform:
 | 
			
		||||
      architecture: arm64
 | 
			
		||||
      os: linux
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}dev{{/if}}
 | 
			
		||||
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}dev{{/if}}
 | 
			
		||||
{{#if build.tags}}
 | 
			
		||||
tags:
 | 
			
		||||
{{#each build.tags}}
 | 
			
		||||
@@ -8,12 +8,12 @@ tags:
 | 
			
		||||
{{/if}}
 | 
			
		||||
manifests:
 | 
			
		||||
  -
 | 
			
		||||
    image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{else}}dev-{{/if}}linux-amd64
 | 
			
		||||
    image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}dev{{/if}}-linux-amd64
 | 
			
		||||
    platform:
 | 
			
		||||
      architecture: amd64
 | 
			
		||||
      os: linux
 | 
			
		||||
  -
 | 
			
		||||
    image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{else}}dev-{{/if}}linux-arm64
 | 
			
		||||
    image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}dev{{/if}}-linux-arm64
 | 
			
		||||
    platform:
 | 
			
		||||
      architecture: arm64
 | 
			
		||||
      os: linux
 | 
			
		||||
 
 | 
			
		||||
@@ -32,7 +32,7 @@ for i in "$@"; do
 | 
			
		||||
done
 | 
			
		||||
 | 
			
		||||
if [ -z "$APP_INI_SET" ]; then
 | 
			
		||||
	CONF_ARG="-c \"$APP_INI\""
 | 
			
		||||
	CONF_ARG="-c \"${GITEA_APP_INI:-$APP_INI}\""
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,7 @@ params:
 | 
			
		||||
  description: Git with a cup of tea
 | 
			
		||||
  author: The Gitea Authors
 | 
			
		||||
  website: https://docs.gitea.io
 | 
			
		||||
  version: 1.15.10
 | 
			
		||||
  version: 1.16.0
 | 
			
		||||
  minGoVersion: 1.16
 | 
			
		||||
  goVersion: 1.17
 | 
			
		||||
  minNodeVersion: 12.17
 | 
			
		||||
 
 | 
			
		||||
@@ -32,7 +32,7 @@ image as a service. Since there is no database available, one can be initialized
 | 
			
		||||
Create a directory for `data` and `config` then paste the following content into a file named `docker-compose.yml`.
 | 
			
		||||
Note that the volume should be owned by the user/group with the UID/GID specified in the config file. By default Gitea in docker will use uid:1000 gid:1000. If needed you can set ownership on those folders with the command: `sudo chown 1000:1000 config/ data/`
 | 
			
		||||
If you don't give the volume correct permissions, the container may not start.
 | 
			
		||||
For a stable release you could use `:latest-rootless`, `:1-rootless` or specify a certain release like `:{{< version >}}-rootless`, but if you'd like to use the latest development version then `:dev-rootless` would be an appropriate tag.
 | 
			
		||||
For a stable release you could use `:latest-rootless`, `:1-rootless` or specify a certain release like `:{{< version >}}-rootless`, but if you'd like to use the latest development version then `:dev-rootless` would be an appropriate tag. If you'd like to run the latest commit from a release branch you can use the `:1.x-dev-rootless` tag, where x is the minor version of Gitea. (e.g. `:1.16-dev-rootless`)
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
version: "2"
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,7 @@ image as a service. Since there is no database available, one can be initialized
 | 
			
		||||
Create a directory like `gitea` and paste the following content into a file named `docker-compose.yml`.
 | 
			
		||||
Note that the volume should be owned by the user/group with the UID/GID specified in the config file.
 | 
			
		||||
If you don't give the volume correct permissions, the container may not start.
 | 
			
		||||
For a stable release you can use `:latest`, `:1` or specify a certain release like `:{{< version >}}`, but if you'd like to use the latest development version of Gitea then you could use the `:dev` tag.
 | 
			
		||||
For a stable release you can use `:latest`, `:1` or specify a certain release like `:{{< version >}}`, but if you'd like to use the latest development version of Gitea then you could use the `:dev` tag. If you'd like to run the latest commit from a release branch you can use the `:1.x-dev` tag, where x is the minor version of Gitea. (e.g. `:1.16-dev`)
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
version: "3"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								go.mod
									
									
									
									
									
								
							@@ -30,7 +30,7 @@ require (
 | 
			
		||||
	github.com/denisenkom/go-mssqldb v0.10.0
 | 
			
		||||
	github.com/djherbis/buffer v1.2.0
 | 
			
		||||
	github.com/djherbis/nio/v3 v3.0.1
 | 
			
		||||
	github.com/duo-labs/webauthn v0.0.0-20211221191814-a22482edaa3b
 | 
			
		||||
	github.com/duo-labs/webauthn v0.0.0-20220122034320-81aea484c951
 | 
			
		||||
	github.com/dustin/go-humanize v1.0.0
 | 
			
		||||
	github.com/editorconfig/editorconfig-core-go/v2 v2.4.2
 | 
			
		||||
	github.com/emirpasic/gods v1.12.0
 | 
			
		||||
@@ -54,7 +54,7 @@ require (
 | 
			
		||||
	github.com/golang-jwt/jwt/v4 v4.2.0
 | 
			
		||||
	github.com/golang/snappy v0.0.4 // indirect
 | 
			
		||||
	github.com/google/go-github/v39 v39.2.0
 | 
			
		||||
	github.com/google/uuid v1.2.0
 | 
			
		||||
	github.com/google/uuid v1.3.0
 | 
			
		||||
	github.com/gorilla/feeds v1.1.1
 | 
			
		||||
	github.com/gorilla/mux v1.8.0 // indirect
 | 
			
		||||
	github.com/gorilla/sessions v1.2.1
 | 
			
		||||
@@ -145,8 +145,6 @@ replace github.com/markbates/goth v1.68.0 => github.com/zeripath/goth v1.68.1-0.
 | 
			
		||||
 | 
			
		||||
replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0
 | 
			
		||||
 | 
			
		||||
replace github.com/duo-labs/webauthn => github.com/authelia/webauthn v0.0.0-20211225121951-80d1f2a572e4
 | 
			
		||||
 | 
			
		||||
replace github.com/satori/go.uuid v1.2.0 => github.com/gofrs/uuid v4.2.0+incompatible
 | 
			
		||||
 | 
			
		||||
exclude github.com/gofrs/uuid v3.2.0+incompatible
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										9
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										9
									
								
								go.sum
									
									
									
									
									
								
							@@ -131,8 +131,6 @@ github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:o
 | 
			
		||||
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/authelia/webauthn v0.0.0-20211225121951-80d1f2a572e4 h1:u3eFvgr4A8IjlAokbFt6XY6VdurX7DEYnQMQ4K2yobc=
 | 
			
		||||
github.com/authelia/webauthn v0.0.0-20211225121951-80d1f2a572e4/go.mod h1:EYSpSkwoEcryMmQGfhol2IiB3IMN9IIIaNd/wcAQMGQ=
 | 
			
		||||
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=
 | 
			
		||||
@@ -276,6 +274,8 @@ github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD
 | 
			
		||||
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
 | 
			
		||||
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
 | 
			
		||||
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
 | 
			
		||||
github.com/duo-labs/webauthn v0.0.0-20220122034320-81aea484c951 h1:17esZ09oW+29rklBtCVphIguql2u3NxYH2OasFPPZoo=
 | 
			
		||||
github.com/duo-labs/webauthn v0.0.0-20220122034320-81aea484c951/go.mod h1:nHy3JdztZWcsjenDeBuE8gn171OAwg12LBN027UP5AE=
 | 
			
		||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 | 
			
		||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
 | 
			
		||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 | 
			
		||||
@@ -491,7 +491,6 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
 | 
			
		||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
 | 
			
		||||
github.com/goccy/go-json v0.7.4 h1:B44qRUFwz/vxPKPISQ1KhvzRi9kZ28RAf6YtjriBZ5k=
 | 
			
		||||
github.com/goccy/go-json v0.7.4/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 | 
			
		||||
github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0=
 | 
			
		||||
github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
 | 
			
		||||
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
 | 
			
		||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 | 
			
		||||
@@ -585,8 +584,8 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4
 | 
			
		||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
			
		||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
			
		||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
			
		||||
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
 | 
			
		||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
			
		||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
 | 
			
		||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
			
		||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 | 
			
		||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 | 
			
		||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@ import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/http/httptest"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"os"
 | 
			
		||||
	"testing"
 | 
			
		||||
@@ -262,23 +263,26 @@ func doAPIMergePullRequest(ctx APITestContext, owner, repo string, index int64)
 | 
			
		||||
	return func(t *testing.T) {
 | 
			
		||||
		urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge?token=%s",
 | 
			
		||||
			owner, repo, index, ctx.Token)
 | 
			
		||||
		req := NewRequestWithJSON(t, http.MethodPost, urlStr, &forms.MergePullRequestForm{
 | 
			
		||||
			MergeMessageField: "doAPIMergePullRequest Merge",
 | 
			
		||||
			Do:                string(repo_model.MergeStyleMerge),
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		resp := ctx.Session.MakeRequest(t, req, NoExpectedStatus)
 | 
			
		||||
		var req *http.Request
 | 
			
		||||
		var resp *httptest.ResponseRecorder
 | 
			
		||||
 | 
			
		||||
		if resp.Code == http.StatusMethodNotAllowed {
 | 
			
		||||
			err := api.APIError{}
 | 
			
		||||
			DecodeJSON(t, resp, &err)
 | 
			
		||||
			assert.EqualValues(t, "Please try again later", err.Message)
 | 
			
		||||
			queue.GetManager().FlushAll(context.Background(), 5*time.Second)
 | 
			
		||||
		for i := 0; i < 6; i++ {
 | 
			
		||||
			req = NewRequestWithJSON(t, http.MethodPost, urlStr, &forms.MergePullRequestForm{
 | 
			
		||||
				MergeMessageField: "doAPIMergePullRequest Merge",
 | 
			
		||||
				Do:                string(repo_model.MergeStyleMerge),
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			resp = ctx.Session.MakeRequest(t, req, NoExpectedStatus)
 | 
			
		||||
 | 
			
		||||
			if resp.Code != http.StatusMethodNotAllowed {
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
			err := api.APIError{}
 | 
			
		||||
			DecodeJSON(t, resp, &err)
 | 
			
		||||
			assert.EqualValues(t, "Please try again later", err.Message)
 | 
			
		||||
			queue.GetManager().FlushAll(context.Background(), 5*time.Second)
 | 
			
		||||
			<-time.After(1 * time.Second)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		expected := ctx.ExpectedCode
 | 
			
		||||
 
 | 
			
		||||
@@ -123,6 +123,17 @@ func doGitClone(dstLocalPath string, u *url.URL) func(*testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func doPartialGitClone(dstLocalPath string, u *url.URL) func(*testing.T) {
 | 
			
		||||
	return func(t *testing.T) {
 | 
			
		||||
		assert.NoError(t, git.CloneWithArgs(context.Background(), u.String(), dstLocalPath, allowLFSFilters(), git.CloneRepoOptions{
 | 
			
		||||
			Filter: "blob:none",
 | 
			
		||||
		}))
 | 
			
		||||
		exist, err := util.IsExist(filepath.Join(dstLocalPath, "README.md"))
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		assert.True(t, exist)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func doGitCloneFail(u *url.URL) func(*testing.T) {
 | 
			
		||||
	return func(t *testing.T) {
 | 
			
		||||
		tmpDir, err := os.MkdirTemp("", "doGitCloneFail")
 | 
			
		||||
 
 | 
			
		||||
@@ -69,6 +69,12 @@ func testGit(t *testing.T, u *url.URL) {
 | 
			
		||||
 | 
			
		||||
		t.Run("Clone", doGitClone(dstPath, u))
 | 
			
		||||
 | 
			
		||||
		dstPath2, err := os.MkdirTemp("", httpContext.Reponame)
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		defer util.RemoveAll(dstPath2)
 | 
			
		||||
 | 
			
		||||
		t.Run("Partial Clone", doPartialGitClone(dstPath2, u))
 | 
			
		||||
 | 
			
		||||
		little, big := standardCommitAndPushTest(t, dstPath)
 | 
			
		||||
		littleLFS, bigLFS := lfsCommitAndPushTest(t, dstPath)
 | 
			
		||||
		rawTest(t, &httpContext, little, big, littleLFS, bigLFS)
 | 
			
		||||
 
 | 
			
		||||
@@ -71,7 +71,7 @@ const (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ParseCommitsWithSignature checks if signaute of commits are corresponding to users gpg keys.
 | 
			
		||||
func ParseCommitsWithSignature(oldCommits []*user_model.UserCommit, repoTrustModel repo_model.TrustModelType, isCodeReader func(*user_model.User) (bool, error)) []*SignCommit {
 | 
			
		||||
func ParseCommitsWithSignature(oldCommits []*user_model.UserCommit, repoTrustModel repo_model.TrustModelType, isOwnerMemberCollaborator func(*user_model.User) (bool, error)) []*SignCommit {
 | 
			
		||||
	newCommits := make([]*SignCommit, 0, len(oldCommits))
 | 
			
		||||
	keyMap := map[string]bool{}
 | 
			
		||||
 | 
			
		||||
@@ -81,7 +81,7 @@ func ParseCommitsWithSignature(oldCommits []*user_model.UserCommit, repoTrustMod
 | 
			
		||||
			Verification: ParseCommitWithSignature(c.Commit),
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		_ = CalculateTrustStatus(signCommit.Verification, repoTrustModel, isCodeReader, &keyMap)
 | 
			
		||||
		_ = CalculateTrustStatus(signCommit.Verification, repoTrustModel, isOwnerMemberCollaborator, &keyMap)
 | 
			
		||||
 | 
			
		||||
		newCommits = append(newCommits, signCommit)
 | 
			
		||||
	}
 | 
			
		||||
@@ -455,7 +455,7 @@ func hashAndVerifyForKeyID(sig *packet.Signature, payload string, committer *use
 | 
			
		||||
 | 
			
		||||
// CalculateTrustStatus will calculate the TrustStatus for a commit verification within a repository
 | 
			
		||||
// There are several trust models in Gitea
 | 
			
		||||
func CalculateTrustStatus(verification *CommitVerification, repoTrustModel repo_model.TrustModelType, isCodeReader func(*user_model.User) (bool, error), keyMap *map[string]bool) (err error) {
 | 
			
		||||
func CalculateTrustStatus(verification *CommitVerification, repoTrustModel repo_model.TrustModelType, isOwnerMemberCollaborator func(*user_model.User) (bool, error), keyMap *map[string]bool) (err error) {
 | 
			
		||||
	if !verification.Verified {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
@@ -500,11 +500,11 @@ func CalculateTrustStatus(verification *CommitVerification, repoTrustModel repo_
 | 
			
		||||
			var has bool
 | 
			
		||||
			isMember, has = (*keyMap)[verification.SigningKey.KeyID]
 | 
			
		||||
			if !has {
 | 
			
		||||
				isMember, err = isCodeReader(verification.SigningUser)
 | 
			
		||||
				isMember, err = isOwnerMemberCollaborator(verification.SigningUser)
 | 
			
		||||
				(*keyMap)[verification.SigningKey.KeyID] = isMember
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			isMember, err = isCodeReader(verification.SigningUser)
 | 
			
		||||
			isMember, err = isOwnerMemberCollaborator(verification.SigningUser)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !isMember {
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,7 @@ func ConvertFromGitCommit(commits []*git.Commit, repo *repo_model.Repository) []
 | 
			
		||||
			user_model.ValidateCommitsWithEmails(commits),
 | 
			
		||||
			repo.GetTrustModel(),
 | 
			
		||||
			func(user *user_model.User) (bool, error) {
 | 
			
		||||
				return IsUserRepoAdmin(repo, user)
 | 
			
		||||
				return IsOwnerMemberCollaborator(repo, user.ID)
 | 
			
		||||
			},
 | 
			
		||||
		),
 | 
			
		||||
		repo,
 | 
			
		||||
 
 | 
			
		||||
@@ -134,22 +134,6 @@ func GetMilestoneByRepoIDANDName(repoID int64, name string) (*Milestone, error)
 | 
			
		||||
	return &mile, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetMilestoneByID returns the milestone via id .
 | 
			
		||||
func GetMilestoneByID(id int64) (*Milestone, error) {
 | 
			
		||||
	return getMilestoneByID(db.GetEngine(db.DefaultContext), id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getMilestoneByID(e db.Engine, id int64) (*Milestone, error) {
 | 
			
		||||
	var m Milestone
 | 
			
		||||
	has, err := e.ID(id).Get(&m)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	} else if !has {
 | 
			
		||||
		return nil, ErrMilestoneNotExist{ID: id, RepoID: 0}
 | 
			
		||||
	}
 | 
			
		||||
	return &m, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateMilestone updates information of given milestone.
 | 
			
		||||
func UpdateMilestone(m *Milestone, oldIsClosed bool) error {
 | 
			
		||||
	ctx, committer, err := db.TxContext()
 | 
			
		||||
 
 | 
			
		||||
@@ -49,22 +49,67 @@ func init() {
 | 
			
		||||
	db.RegisterModel(new(TeamUnit))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SearchTeamOptions holds the search options
 | 
			
		||||
type SearchTeamOptions struct {
 | 
			
		||||
// SearchOrgTeamOptions holds the search options
 | 
			
		||||
type SearchOrgTeamOptions struct {
 | 
			
		||||
	db.ListOptions
 | 
			
		||||
	UserID      int64
 | 
			
		||||
	Keyword     string
 | 
			
		||||
	OrgID       int64
 | 
			
		||||
	IncludeDesc bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetUserTeamOptions holds the search options.
 | 
			
		||||
type GetUserTeamOptions struct {
 | 
			
		||||
	db.ListOptions
 | 
			
		||||
	UserID int64
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SearchMembersOptions holds the search options
 | 
			
		||||
type SearchMembersOptions struct {
 | 
			
		||||
	db.ListOptions
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SearchTeam search for teams. Caller is responsible to check permissions.
 | 
			
		||||
func SearchTeam(opts *SearchTeamOptions) ([]*Team, int64, error) {
 | 
			
		||||
// GetUserTeams search for org teams. Caller is responsible to check permissions.
 | 
			
		||||
func GetUserTeams(opts *GetUserTeamOptions) ([]*Team, int64, error) {
 | 
			
		||||
	if opts.Page <= 0 {
 | 
			
		||||
		opts.Page = 1
 | 
			
		||||
	}
 | 
			
		||||
	if opts.PageSize == 0 {
 | 
			
		||||
		// Default limit
 | 
			
		||||
		opts.PageSize = 10
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sess := db.GetEngine(db.DefaultContext)
 | 
			
		||||
 | 
			
		||||
	sess = sess.Join("INNER", "team_user", "team_user.team_id = team.id").
 | 
			
		||||
		And("team_user.uid=?", opts.UserID)
 | 
			
		||||
 | 
			
		||||
	count, err := sess.
 | 
			
		||||
		Count(new(Team))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, 0, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if opts.PageSize == -1 {
 | 
			
		||||
		opts.PageSize = int(count)
 | 
			
		||||
	} else {
 | 
			
		||||
		sess = sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sess = sess.Join("INNER", "team_user", "team_user.team_id = team.id").
 | 
			
		||||
		And("team_user.uid=?", opts.UserID)
 | 
			
		||||
 | 
			
		||||
	teams := make([]*Team, 0, opts.PageSize)
 | 
			
		||||
	if err = sess.
 | 
			
		||||
		OrderBy("lower_name").
 | 
			
		||||
		Find(&teams); err != nil {
 | 
			
		||||
		return nil, 0, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return teams, count, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SearchOrgTeams search for org teams. Caller is responsible to check permissions.
 | 
			
		||||
func SearchOrgTeams(opts *SearchOrgTeamOptions) ([]*Team, int64, error) {
 | 
			
		||||
	if opts.Page <= 0 {
 | 
			
		||||
		opts.Page = 1
 | 
			
		||||
	}
 | 
			
		||||
@@ -196,7 +241,7 @@ func (t *Team) getRepositories(e db.Engine) error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetRepositories returns paginated repositories in team of organization.
 | 
			
		||||
func (t *Team) GetRepositories(opts *SearchTeamOptions) error {
 | 
			
		||||
func (t *Team) GetRepositories(opts *SearchOrgTeamOptions) error {
 | 
			
		||||
	if opts.Page == 0 {
 | 
			
		||||
		return t.getRepositories(db.GetEngine(db.DefaultContext))
 | 
			
		||||
	}
 | 
			
		||||
@@ -716,7 +761,7 @@ func UpdateTeam(t *Team, authChanged, includeAllChanged bool) (err error) {
 | 
			
		||||
// DeleteTeam deletes given team.
 | 
			
		||||
// It's caller's responsibility to assign organization ID.
 | 
			
		||||
func DeleteTeam(t *Team) error {
 | 
			
		||||
	if err := t.GetRepositories(&SearchTeamOptions{}); err != nil {
 | 
			
		||||
	if err := t.GetRepositories(&SearchOrgTeamOptions{}); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -858,7 +903,7 @@ func AddTeamMember(team *Team, userID int64) error {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Get team and its repositories.
 | 
			
		||||
	if err := team.GetRepositories(&SearchTeamOptions{}); err != nil {
 | 
			
		||||
	if err := team.GetRepositories(&SearchOrgTeamOptions{}); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -46,7 +46,7 @@ func TestTeam_GetRepositories(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	test := func(teamID int64) {
 | 
			
		||||
		team := unittest.AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team)
 | 
			
		||||
		assert.NoError(t, team.GetRepositories(&SearchTeamOptions{}))
 | 
			
		||||
		assert.NoError(t, team.GetRepositories(&SearchOrgTeamOptions{}))
 | 
			
		||||
		assert.Len(t, team.Repos, team.NumRepos)
 | 
			
		||||
		for _, repo := range team.Repos {
 | 
			
		||||
			unittest.AssertExistsAndLoadBean(t, &TeamRepo{TeamID: teamID, RepoID: repo.ID})
 | 
			
		||||
@@ -292,7 +292,7 @@ func TestGetTeamMembers(t *testing.T) {
 | 
			
		||||
func TestGetUserTeams(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
			
		||||
	test := func(userID int64) {
 | 
			
		||||
		teams, _, err := SearchTeam(&SearchTeamOptions{UserID: userID})
 | 
			
		||||
		teams, _, err := GetUserTeams(&GetUserTeamOptions{UserID: userID})
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		for _, team := range teams {
 | 
			
		||||
			unittest.AssertExistsAndLoadBean(t, &TeamUser{TeamID: team.ID, UID: userID})
 | 
			
		||||
 
 | 
			
		||||
@@ -60,7 +60,7 @@ type ExternalLoginUser struct {
 | 
			
		||||
	LastName          string
 | 
			
		||||
	NickName          string
 | 
			
		||||
	Description       string
 | 
			
		||||
	AvatarURL         string
 | 
			
		||||
	AvatarURL         string `xorm:"TEXT"`
 | 
			
		||||
	Location          string
 | 
			
		||||
	AccessToken       string `xorm:"TEXT"`
 | 
			
		||||
	AccessTokenSecret string `xorm:"TEXT"`
 | 
			
		||||
 
 | 
			
		||||
@@ -291,6 +291,7 @@ func (ctx *Context) PlainTextBytes(status int, bs []byte) {
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Resp.WriteHeader(status)
 | 
			
		||||
	ctx.Resp.Header().Set("Content-Type", "text/plain;charset=utf-8")
 | 
			
		||||
	ctx.Resp.Header().Set("X-Content-Type-Options", "nosniff")
 | 
			
		||||
	if _, err := ctx.Resp.Write(bs); err != nil {
 | 
			
		||||
		log.Error("Write bytes failed: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -59,27 +59,28 @@ func GetRepoRawDiffForFile(repo *Repository, startCommit, endCommit string, diff
 | 
			
		||||
	ctx, _, finished := process.GetManager().AddContext(repo.Ctx, fmt.Sprintf("GetRawDiffForFile: [repo_path: %s]", repo.Path))
 | 
			
		||||
	defer finished()
 | 
			
		||||
 | 
			
		||||
	var cmd *exec.Cmd
 | 
			
		||||
	cmd := exec.CommandContext(ctx, GitExecutable, GlobalCommandArgs...)
 | 
			
		||||
 | 
			
		||||
	switch diffType {
 | 
			
		||||
	case RawDiffNormal:
 | 
			
		||||
		if len(startCommit) != 0 {
 | 
			
		||||
			cmd = exec.CommandContext(ctx, GitExecutable, append([]string{"diff", "-M", startCommit, endCommit}, fileArgs...)...)
 | 
			
		||||
			cmd.Args = append(cmd.Args, append([]string{"diff", "-M", startCommit, endCommit}, fileArgs...)...)
 | 
			
		||||
		} else if commit.ParentCount() == 0 {
 | 
			
		||||
			cmd = exec.CommandContext(ctx, GitExecutable, append([]string{"show", endCommit}, fileArgs...)...)
 | 
			
		||||
			cmd.Args = append(cmd.Args, append([]string{"show", endCommit}, fileArgs...)...)
 | 
			
		||||
		} else {
 | 
			
		||||
			c, _ := commit.Parent(0)
 | 
			
		||||
			cmd = exec.CommandContext(ctx, GitExecutable, append([]string{"diff", "-M", c.ID.String(), endCommit}, fileArgs...)...)
 | 
			
		||||
			cmd.Args = append(cmd.Args, append([]string{"diff", "-M", c.ID.String(), endCommit}, fileArgs...)...)
 | 
			
		||||
		}
 | 
			
		||||
	case RawDiffPatch:
 | 
			
		||||
		if len(startCommit) != 0 {
 | 
			
		||||
			query := fmt.Sprintf("%s...%s", endCommit, startCommit)
 | 
			
		||||
			cmd = exec.CommandContext(ctx, GitExecutable, append([]string{"format-patch", "--no-signature", "--stdout", "--root", query}, fileArgs...)...)
 | 
			
		||||
			cmd.Args = append(cmd.Args, append([]string{"format-patch", "--no-signature", "--stdout", "--root", query}, fileArgs...)...)
 | 
			
		||||
		} else if commit.ParentCount() == 0 {
 | 
			
		||||
			cmd = exec.CommandContext(ctx, GitExecutable, append([]string{"format-patch", "--no-signature", "--stdout", "--root", endCommit}, fileArgs...)...)
 | 
			
		||||
			cmd.Args = append(cmd.Args, append([]string{"format-patch", "--no-signature", "--stdout", "--root", endCommit}, fileArgs...)...)
 | 
			
		||||
		} else {
 | 
			
		||||
			c, _ := commit.Parent(0)
 | 
			
		||||
			query := fmt.Sprintf("%s...%s", endCommit, c.ID.String())
 | 
			
		||||
			cmd = exec.CommandContext(ctx, GitExecutable, append([]string{"format-patch", "--no-signature", "--stdout", query}, fileArgs...)...)
 | 
			
		||||
			cmd.Args = append(cmd.Args, append([]string{"format-patch", "--no-signature", "--stdout", query}, fileArgs...)...)
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		return fmt.Errorf("invalid diffType: %s", diffType)
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,9 @@ func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note)
 | 
			
		||||
	log.Trace("Searching for git note corresponding to the commit %q in the repository %q", commitID, repo.Path)
 | 
			
		||||
	notes, err := repo.GetCommit(NotesRef)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if IsErrNotExist(err) {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		log.Error("Unable to get commit from ref %q. Error: %v", NotesRef, err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,9 @@ func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note)
 | 
			
		||||
	log.Trace("Searching for git note corresponding to the commit %q in the repository %q", commitID, repo.Path)
 | 
			
		||||
	notes, err := repo.GetCommit(NotesRef)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if IsErrNotExist(err) {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		log.Error("Unable to get commit from ref %q. Error: %v", NotesRef, err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -79,16 +79,21 @@ func InitRepository(repoPath string, bare bool) error {
 | 
			
		||||
 | 
			
		||||
// IsEmpty Check if repository is empty.
 | 
			
		||||
func (repo *Repository) IsEmpty() (bool, error) {
 | 
			
		||||
	var errbuf strings.Builder
 | 
			
		||||
	if err := NewCommand("log", "-1").RunInDirPipeline(repo.Path, nil, &errbuf); err != nil {
 | 
			
		||||
		if strings.Contains(errbuf.String(), "fatal: bad default revision 'HEAD'") ||
 | 
			
		||||
			strings.Contains(errbuf.String(), "fatal: your current branch 'master' does not have any commits yet") {
 | 
			
		||||
			return true, nil
 | 
			
		||||
		}
 | 
			
		||||
	var errbuf, output strings.Builder
 | 
			
		||||
	if err := NewCommandContext(repo.Ctx, "rev-list", "--all", "--count", "--max-count=1").RunWithContext(&RunContext{
 | 
			
		||||
		Timeout: -1,
 | 
			
		||||
		Dir:     repo.Path,
 | 
			
		||||
		Stdout:  &output,
 | 
			
		||||
		Stderr:  &errbuf,
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return true, fmt.Errorf("check empty: %v - %s", err, errbuf.String())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false, nil
 | 
			
		||||
	c, err := strconv.Atoi(strings.TrimSpace(output.String()))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return true, fmt.Errorf("check empty: convert %s to count failed: %v", output.String(), err)
 | 
			
		||||
	}
 | 
			
		||||
	return c == 0, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CloneRepoOptions options when clone a repository
 | 
			
		||||
@@ -101,6 +106,7 @@ type CloneRepoOptions struct {
 | 
			
		||||
	Shared     bool
 | 
			
		||||
	NoCheckout bool
 | 
			
		||||
	Depth      int
 | 
			
		||||
	Filter     string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Clone clones original repository to target path.
 | 
			
		||||
@@ -141,7 +147,9 @@ func CloneWithArgs(ctx context.Context, from, to string, args []string, opts Clo
 | 
			
		||||
	if opts.Depth > 0 {
 | 
			
		||||
		cmd.AddArguments("--depth", strconv.Itoa(opts.Depth))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if opts.Filter != "" {
 | 
			
		||||
		cmd.AddArguments("--filter", opts.Filter)
 | 
			
		||||
	}
 | 
			
		||||
	if len(opts.Branch) > 0 {
 | 
			
		||||
		cmd.AddArguments("-b", opts.Branch)
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -117,7 +117,7 @@ func (graph *Graph) LoadAndProcessCommits(repository *repo_model.Repository, git
 | 
			
		||||
		c.Verification = asymkey_model.ParseCommitWithSignature(c.Commit)
 | 
			
		||||
 | 
			
		||||
		_ = asymkey_model.CalculateTrustStatus(c.Verification, repository.GetTrustModel(), func(user *user_model.User) (bool, error) {
 | 
			
		||||
			return models.IsUserRepoAdmin(repository, user)
 | 
			
		||||
			return models.IsOwnerMemberCollaborator(repository, user.ID)
 | 
			
		||||
		}, &keyMap)
 | 
			
		||||
 | 
			
		||||
		statuses, _, err := models.GetLatestCommitStatus(repository.ID, c.Commit.ID.String(), db.ListOptions{})
 | 
			
		||||
 
 | 
			
		||||
@@ -55,7 +55,7 @@ var (
 | 
			
		||||
	anySHA1Pattern = regexp.MustCompile(`https?://(?:\S+/){4,5}([0-9a-f]{40})(/[-+~_%.a-zA-Z0-9/]+)?(#[-+~_%.a-zA-Z0-9]+)?`)
 | 
			
		||||
 | 
			
		||||
	// comparePattern matches "http://domain/org/repo/compare/COMMIT1...COMMIT2#hash"
 | 
			
		||||
	comparePattern = regexp.MustCompile(`https?://(?:\S+/){4,5}([0-9a-f]{40})(\.\.\.?)([0-9a-f]{40})?(#[-+~_%.a-zA-Z0-9]+)?`)
 | 
			
		||||
	comparePattern = regexp.MustCompile(`https?://(?:\S+/){4,5}([0-9a-f]{7,40})(\.\.\.?)([0-9a-f]{7,40})?(#[-+~_%.a-zA-Z0-9]+)?`)
 | 
			
		||||
 | 
			
		||||
	validLinksPattern = regexp.MustCompile(`^[a-z][\w-]+://`)
 | 
			
		||||
 | 
			
		||||
@@ -944,6 +944,13 @@ func comparePatternProcessor(ctx *RenderContext, node *html.Node) {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Ensure that every group (m[0]...m[7]) has a match
 | 
			
		||||
		for i := 0; i < 8; i++ {
 | 
			
		||||
			if m[i] == -1 {
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		urlFull := node.Data[m[0]:m[1]]
 | 
			
		||||
		text1 := base.ShortSha(node.Data[m[2]:m[3]])
 | 
			
		||||
		textDots := base.ShortSha(node.Data[m[4]:m[5]])
 | 
			
		||||
 
 | 
			
		||||
@@ -546,3 +546,16 @@ func TestFuzz(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestIssue18471(t *testing.T) {
 | 
			
		||||
	data := `http://domain/org/repo/compare/783b039...da951ce`
 | 
			
		||||
 | 
			
		||||
	var res strings.Builder
 | 
			
		||||
	err := PostProcess(&RenderContext{
 | 
			
		||||
		URLPrefix: "https://example.com",
 | 
			
		||||
		Metas:     localMetas,
 | 
			
		||||
	}, strings.NewReader(data), &res)
 | 
			
		||||
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, res.String(), "<a href=\"http://domain/org/repo/compare/783b039...da951ce\" class=\"compare\"><code class=\"nohighlight\">783b039...da951ce</code></a>")
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										41
									
								
								modules/public/mime_types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								modules/public/mime_types.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
			
		||||
// Copyright 2022 The Gitea Authors. All rights reserved.
 | 
			
		||||
// Use of this source code is governed by a MIT-style
 | 
			
		||||
// license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
package public
 | 
			
		||||
 | 
			
		||||
import "strings"
 | 
			
		||||
 | 
			
		||||
// wellKnownMimeTypesLower comes from Golang's builtin mime package: `builtinTypesLower`, see the comment of detectWellKnownMimeType
 | 
			
		||||
var wellKnownMimeTypesLower = map[string]string{
 | 
			
		||||
	".avif": "image/avif",
 | 
			
		||||
	".css":  "text/css; charset=utf-8",
 | 
			
		||||
	".gif":  "image/gif",
 | 
			
		||||
	".htm":  "text/html; charset=utf-8",
 | 
			
		||||
	".html": "text/html; charset=utf-8",
 | 
			
		||||
	".jpeg": "image/jpeg",
 | 
			
		||||
	".jpg":  "image/jpeg",
 | 
			
		||||
	".js":   "text/javascript; charset=utf-8",
 | 
			
		||||
	".json": "application/json",
 | 
			
		||||
	".mjs":  "text/javascript; charset=utf-8",
 | 
			
		||||
	".pdf":  "application/pdf",
 | 
			
		||||
	".png":  "image/png",
 | 
			
		||||
	".svg":  "image/svg+xml",
 | 
			
		||||
	".wasm": "application/wasm",
 | 
			
		||||
	".webp": "image/webp",
 | 
			
		||||
	".xml":  "text/xml; charset=utf-8",
 | 
			
		||||
 | 
			
		||||
	// well, there are some types missing from the builtin list
 | 
			
		||||
	".txt": "text/plain; charset=utf-8",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// detectWellKnownMimeType will return the mime-type for a well-known file ext name
 | 
			
		||||
// The purpose of this function is to bypass the unstable behavior of Golang's mime.TypeByExtension
 | 
			
		||||
// mime.TypeByExtension would use OS's mime-type config to overwrite the well-known types (see its document).
 | 
			
		||||
// If the user's OS has incorrect mime-type config, it would make Gitea can not respond a correct Content-Type to browsers.
 | 
			
		||||
// For example, if Gitea returns `text/plain` for a `.js` file, the browser couldn't run the JS due to security reasons.
 | 
			
		||||
// detectWellKnownMimeType makes the Content-Type for well-known files stable.
 | 
			
		||||
func detectWellKnownMimeType(ext string) string {
 | 
			
		||||
	ext = strings.ToLower(ext)
 | 
			
		||||
	return wellKnownMimeTypesLower[ext]
 | 
			
		||||
}
 | 
			
		||||
@@ -95,6 +95,15 @@ func parseAcceptEncoding(val string) map[string]bool {
 | 
			
		||||
	return types
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// setWellKnownContentType will set the Content-Type if the file is a well-known type.
 | 
			
		||||
// See the comments of detectWellKnownMimeType
 | 
			
		||||
func setWellKnownContentType(w http.ResponseWriter, file string) {
 | 
			
		||||
	mimeType := detectWellKnownMimeType(filepath.Ext(file))
 | 
			
		||||
	if mimeType != "" {
 | 
			
		||||
		w.Header().Set("Content-Type", mimeType)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (opts *Options) handle(w http.ResponseWriter, req *http.Request, fs http.FileSystem, file string) bool {
 | 
			
		||||
	// use clean to keep the file is a valid path with no . or ..
 | 
			
		||||
	f, err := fs.Open(path.Clean(file))
 | 
			
		||||
@@ -125,6 +134,8 @@ func (opts *Options) handle(w http.ResponseWriter, req *http.Request, fs http.Fi
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	setWellKnownContentType(w, file)
 | 
			
		||||
 | 
			
		||||
	serveContent(w, req, fi, fi.ModTime(), f)
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,15 +9,12 @@ package public
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"compress/gzip"
 | 
			
		||||
	"io"
 | 
			
		||||
	"mime"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/timeutil"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -66,24 +63,16 @@ func serveContent(w http.ResponseWriter, req *http.Request, fi os.FileInfo, modt
 | 
			
		||||
	encodings := parseAcceptEncoding(req.Header.Get("Accept-Encoding"))
 | 
			
		||||
	if encodings["gzip"] {
 | 
			
		||||
		if cf, ok := fi.(*vfsgen۰CompressedFileInfo); ok {
 | 
			
		||||
			rd := bytes.NewReader(cf.GzipBytes())
 | 
			
		||||
			rdGzip := bytes.NewReader(cf.GzipBytes())
 | 
			
		||||
			// all static files are managed by Gitea, so we can make sure every file has the correct ext name
 | 
			
		||||
			// then we can get the correct Content-Type, we do not need to do http.DetectContentType on the decompressed data
 | 
			
		||||
			mimeType := detectWellKnownMimeType(filepath.Ext(fi.Name()))
 | 
			
		||||
			if mimeType == "" {
 | 
			
		||||
				mimeType = "application/octet-stream"
 | 
			
		||||
			}
 | 
			
		||||
			w.Header().Set("Content-Type", mimeType)
 | 
			
		||||
			w.Header().Set("Content-Encoding", "gzip")
 | 
			
		||||
			ctype := mime.TypeByExtension(filepath.Ext(fi.Name()))
 | 
			
		||||
			if ctype == "" {
 | 
			
		||||
				// read a chunk to decide between utf-8 text and binary
 | 
			
		||||
				var buf [512]byte
 | 
			
		||||
				grd, _ := gzip.NewReader(rd)
 | 
			
		||||
				n, _ := io.ReadFull(grd, buf[:])
 | 
			
		||||
				ctype = http.DetectContentType(buf[:n])
 | 
			
		||||
				_, err := rd.Seek(0, io.SeekStart) // rewind to output whole file
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					log.Error("rd.Seek error: %v", err)
 | 
			
		||||
					http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			w.Header().Set("Content-Type", ctype)
 | 
			
		||||
			http.ServeContent(w, req, fi.Name(), modtime, rd)
 | 
			
		||||
			http.ServeContent(w, req, fi.Name(), modtime, rdGzip)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -72,6 +72,8 @@ type ManagedPool interface {
 | 
			
		||||
	BoostWorkers() int
 | 
			
		||||
	// SetPoolSettings sets the user updatable settings for the pool
 | 
			
		||||
	SetPoolSettings(maxNumberOfWorkers, boostWorkers int, timeout time.Duration)
 | 
			
		||||
	// Done returns a channel that will be closed when the Pool's baseCtx is closed
 | 
			
		||||
	Done() <-chan struct{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ManagedQueueList implements the sort.Interface
 | 
			
		||||
@@ -141,7 +143,6 @@ func (m *Manager) Remove(qid int64) {
 | 
			
		||||
	delete(m.Queues, qid)
 | 
			
		||||
	m.mutex.Unlock()
 | 
			
		||||
	log.Trace("Queue Manager removed: QID: %d", qid)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetManagedQueue by qid
 | 
			
		||||
@@ -193,6 +194,17 @@ func (m *Manager) FlushAll(baseCtx context.Context, timeout time.Duration) error
 | 
			
		||||
				wg.Done()
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if pool, ok := mq.Managed.(ManagedPool); ok {
 | 
			
		||||
				// No point into flushing pools when their base's ctx is already done.
 | 
			
		||||
				select {
 | 
			
		||||
				case <-pool.Done():
 | 
			
		||||
					wg.Done()
 | 
			
		||||
					continue
 | 
			
		||||
				default:
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			allEmpty = false
 | 
			
		||||
			if flushable, ok := mq.Managed.(Flushable); ok {
 | 
			
		||||
				log.Debug("Flushing (flushable) queue: %s", mq.Name)
 | 
			
		||||
@@ -225,7 +237,6 @@ func (m *Manager) FlushAll(baseCtx context.Context, timeout time.Duration) error
 | 
			
		||||
		wg.Wait()
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ManagedQueues returns the managed queues
 | 
			
		||||
 
 | 
			
		||||
@@ -173,7 +173,6 @@ func (q *PersistableChannelQueue) Run(atShutdown, atTerminate func(func())) {
 | 
			
		||||
		q.internal.(*LevelQueue).Shutdown()
 | 
			
		||||
		GetManager().Remove(q.internal.(*LevelQueue).qid)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Flush flushes the queue and blocks till the queue is empty
 | 
			
		||||
@@ -252,14 +251,13 @@ func (q *PersistableChannelQueue) Shutdown() {
 | 
			
		||||
	q.channelQueue.Wait()
 | 
			
		||||
	q.internal.(*LevelQueue).Wait()
 | 
			
		||||
	// Redirect all remaining data in the chan to the internal channel
 | 
			
		||||
	go func() {
 | 
			
		||||
	close(q.channelQueue.dataChan)
 | 
			
		||||
	log.Trace("PersistableChannelQueue: %s Redirecting remaining data", q.delayedStarter.name)
 | 
			
		||||
	for data := range q.channelQueue.dataChan {
 | 
			
		||||
		_ = q.internal.Push(data)
 | 
			
		||||
		atomic.AddInt64(&q.channelQueue.numInQueue, -1)
 | 
			
		||||
	}
 | 
			
		||||
	log.Trace("PersistableChannelQueue: %s Done Redirecting remaining data", q.delayedStarter.name)
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	log.Debug("PersistableChannelQueue: %s Shutdown", q.delayedStarter.name)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -65,6 +65,11 @@ func NewWorkerPool(handle HandlerFunc, config WorkerPoolConfiguration) *WorkerPo
 | 
			
		||||
	return pool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Done returns when this worker pool's base context has been cancelled
 | 
			
		||||
func (p *WorkerPool) Done() <-chan struct{} {
 | 
			
		||||
	return p.baseCtx.Done()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Push pushes the data to the internal channel
 | 
			
		||||
func (p *WorkerPool) Push(data Data) {
 | 
			
		||||
	atomic.AddInt64(&p.numInQueue, 1)
 | 
			
		||||
@@ -90,7 +95,7 @@ func (p *WorkerPool) zeroBoost() {
 | 
			
		||||
		boost = p.maxNumberOfWorkers - p.numberOfWorkers
 | 
			
		||||
	}
 | 
			
		||||
	if mq != nil {
 | 
			
		||||
		log.Warn("WorkerPool: %d (for %s) has zero workers - adding %d temporary workers for %s", p.qid, mq.Name, boost, p.boostTimeout)
 | 
			
		||||
		log.Debug("WorkerPool: %d (for %s) has zero workers - adding %d temporary workers for %s", p.qid, mq.Name, boost, p.boostTimeout)
 | 
			
		||||
 | 
			
		||||
		start := time.Now()
 | 
			
		||||
		pid := mq.RegisterWorkers(boost, start, true, start.Add(p.boostTimeout), cancel, false)
 | 
			
		||||
@@ -98,7 +103,7 @@ func (p *WorkerPool) zeroBoost() {
 | 
			
		||||
			mq.RemoveWorkers(pid)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		log.Warn("WorkerPool: %d has zero workers - adding %d temporary workers for %s", p.qid, p.boostWorkers, p.boostTimeout)
 | 
			
		||||
		log.Debug("WorkerPool: %d has zero workers - adding %d temporary workers for %s", p.qid, p.boostWorkers, p.boostTimeout)
 | 
			
		||||
	}
 | 
			
		||||
	p.lock.Unlock()
 | 
			
		||||
	p.addWorkers(ctx, cancel, boost)
 | 
			
		||||
@@ -326,7 +331,10 @@ func (p *WorkerPool) FlushWithContext(ctx context.Context) error {
 | 
			
		||||
	log.Trace("WorkerPool: %d Flush", p.qid)
 | 
			
		||||
	for {
 | 
			
		||||
		select {
 | 
			
		||||
		case data := <-p.dataChan:
 | 
			
		||||
		case data, ok := <-p.dataChan:
 | 
			
		||||
			if !ok {
 | 
			
		||||
				return nil
 | 
			
		||||
			}
 | 
			
		||||
			p.handle(data)
 | 
			
		||||
			atomic.AddInt64(&p.numInQueue, -1)
 | 
			
		||||
		case <-p.baseCtx.Done():
 | 
			
		||||
@@ -341,7 +349,7 @@ func (p *WorkerPool) FlushWithContext(ctx context.Context) error {
 | 
			
		||||
 | 
			
		||||
func (p *WorkerPool) doWork(ctx context.Context) {
 | 
			
		||||
	delay := time.Millisecond * 300
 | 
			
		||||
	var data = make([]Data, 0, p.batchLength)
 | 
			
		||||
	data := make([]Data, 0, p.batchLength)
 | 
			
		||||
	for {
 | 
			
		||||
		select {
 | 
			
		||||
		case <-ctx.Done():
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ func TestIncludesAllRepositoriesTeams(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	testTeamRepositories := func(teamID int64, repoIds []int64) {
 | 
			
		||||
		team := unittest.AssertExistsAndLoadBean(t, &models.Team{ID: teamID}).(*models.Team)
 | 
			
		||||
		assert.NoError(t, team.GetRepositories(&models.SearchTeamOptions{}), "%s: GetRepositories", team.Name)
 | 
			
		||||
		assert.NoError(t, team.GetRepositories(&models.SearchOrgTeamOptions{}), "%s: GetRepositories", team.Name)
 | 
			
		||||
		assert.Len(t, team.Repos, team.NumRepos, "%s: len repo", team.Name)
 | 
			
		||||
		assert.Len(t, team.Repos, len(repoIds), "%s: repo count", team.Name)
 | 
			
		||||
		for i, rid := range repoIds {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										732
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										732
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										12
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								package.json
									
									
									
									
									
								
							@@ -10,7 +10,7 @@
 | 
			
		||||
    "@claviska/jquery-minicolors": "2.3.6",
 | 
			
		||||
    "@primer/octicons": "16.2.0",
 | 
			
		||||
    "add-asset-webpack-plugin": "2.0.1",
 | 
			
		||||
    "codemirror": "5.65.0",
 | 
			
		||||
    "codemirror": "5.65.1",
 | 
			
		||||
    "css-loader": "6.5.1",
 | 
			
		||||
    "dropzone": "6.0.0-beta.2",
 | 
			
		||||
    "easymde": "2.16.1",
 | 
			
		||||
@@ -23,13 +23,13 @@
 | 
			
		||||
    "less": "4.1.2",
 | 
			
		||||
    "less-loader": "10.2.0",
 | 
			
		||||
    "license-checker-webpack-plugin": "0.2.1",
 | 
			
		||||
    "mermaid": "8.13.9",
 | 
			
		||||
    "mermaid": "8.13.10",
 | 
			
		||||
    "mini-css-extract-plugin": "2.5.2",
 | 
			
		||||
    "monaco-editor": "0.31.1",
 | 
			
		||||
    "monaco-editor-webpack-plugin": "7.0.1",
 | 
			
		||||
    "pretty-ms": "7.0.1",
 | 
			
		||||
    "sortablejs": "1.14.0",
 | 
			
		||||
    "swagger-ui-dist": "4.1.3",
 | 
			
		||||
    "swagger-ui-dist": "4.2.1",
 | 
			
		||||
    "tributejs": "5.1.3",
 | 
			
		||||
    "uint8-to-base64": "0.2.0",
 | 
			
		||||
    "vue": "2.6.14",
 | 
			
		||||
@@ -37,8 +37,8 @@
 | 
			
		||||
    "vue-calendar-heatmap": "0.8.4",
 | 
			
		||||
    "vue-loader": "15.9.8",
 | 
			
		||||
    "vue-template-compiler": "2.6.14",
 | 
			
		||||
    "webpack": "5.66.0",
 | 
			
		||||
    "webpack-cli": "4.9.1",
 | 
			
		||||
    "webpack": "5.67.0",
 | 
			
		||||
    "webpack-cli": "4.9.2",
 | 
			
		||||
    "workbox-routing": "6.4.2",
 | 
			
		||||
    "workbox-strategies": "6.4.2",
 | 
			
		||||
    "worker-loader": "3.0.8",
 | 
			
		||||
@@ -55,7 +55,7 @@
 | 
			
		||||
    "jest-extended": "1.2.0",
 | 
			
		||||
    "jest-raw-loader": "1.0.1",
 | 
			
		||||
    "postcss-less": "6.0.0",
 | 
			
		||||
    "stylelint": "14.2.0",
 | 
			
		||||
    "stylelint": "14.3.0",
 | 
			
		||||
    "stylelint-config-standard": "24.0.0",
 | 
			
		||||
    "svgo": "2.8.0",
 | 
			
		||||
    "updates": "13.0.0"
 | 
			
		||||
 
 | 
			
		||||
@@ -47,7 +47,7 @@ func ListTeams(ctx *context.APIContext) {
 | 
			
		||||
	//   "200":
 | 
			
		||||
	//     "$ref": "#/responses/TeamList"
 | 
			
		||||
 | 
			
		||||
	teams, count, err := models.SearchTeam(&models.SearchTeamOptions{
 | 
			
		||||
	teams, count, err := models.SearchOrgTeams(&models.SearchOrgTeamOptions{
 | 
			
		||||
		ListOptions: utils.GetListOptions(ctx),
 | 
			
		||||
		OrgID:       ctx.Org.Organization.ID,
 | 
			
		||||
	})
 | 
			
		||||
@@ -90,7 +90,7 @@ func ListUserTeams(ctx *context.APIContext) {
 | 
			
		||||
	//   "200":
 | 
			
		||||
	//     "$ref": "#/responses/TeamList"
 | 
			
		||||
 | 
			
		||||
	teams, count, err := models.SearchTeam(&models.SearchTeamOptions{
 | 
			
		||||
	teams, count, err := models.GetUserTeams(&models.GetUserTeamOptions{
 | 
			
		||||
		ListOptions: utils.GetListOptions(ctx),
 | 
			
		||||
		UserID:      ctx.User.ID,
 | 
			
		||||
	})
 | 
			
		||||
@@ -533,7 +533,7 @@ func GetTeamRepos(ctx *context.APIContext) {
 | 
			
		||||
	//     "$ref": "#/responses/RepositoryList"
 | 
			
		||||
 | 
			
		||||
	team := ctx.Org.Team
 | 
			
		||||
	if err := team.GetRepositories(&models.SearchTeamOptions{
 | 
			
		||||
	if err := team.GetRepositories(&models.SearchOrgTeamOptions{
 | 
			
		||||
		ListOptions: utils.GetListOptions(ctx),
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		ctx.Error(http.StatusInternalServerError, "GetTeamRepos", err)
 | 
			
		||||
@@ -707,15 +707,14 @@ func SearchTeam(ctx *context.APIContext) {
 | 
			
		||||
 | 
			
		||||
	listOptions := utils.GetListOptions(ctx)
 | 
			
		||||
 | 
			
		||||
	opts := &models.SearchTeamOptions{
 | 
			
		||||
		UserID:      ctx.User.ID,
 | 
			
		||||
	opts := &models.SearchOrgTeamOptions{
 | 
			
		||||
		Keyword:     ctx.FormTrim("q"),
 | 
			
		||||
		OrgID:       ctx.Org.Organization.ID,
 | 
			
		||||
		IncludeDesc: ctx.FormString("include_desc") == "" || ctx.FormBool("include_desc"),
 | 
			
		||||
		ListOptions: listOptions,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	teams, maxResults, err := models.SearchTeam(opts)
 | 
			
		||||
	teams, maxResults, err := models.SearchOrgTeams(opts)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error("SearchTeam failed: %v", err)
 | 
			
		||||
		ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
 | 
			
		||||
 
 | 
			
		||||
@@ -192,6 +192,9 @@ func parseOAuth2Config(form forms.AuthenticationForm) *oauth2.Source {
 | 
			
		||||
		RequiredClaimName:             form.Oauth2RequiredClaimName,
 | 
			
		||||
		RequiredClaimValue:            form.Oauth2RequiredClaimValue,
 | 
			
		||||
		SkipLocalTwoFA:                form.SkipLocalTwoFA,
 | 
			
		||||
		GroupClaimName:                form.Oauth2GroupClaimName,
 | 
			
		||||
		RestrictedGroup:               form.Oauth2RestrictedGroup,
 | 
			
		||||
		AdminGroup:                    form.Oauth2AdminGroup,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -826,7 +826,7 @@ func SignInOAuthCallback(ctx *context.Context) {
 | 
			
		||||
	u, gothUser, err := oAuth2UserLoginCallback(authSource, ctx.Req, ctx.Resp)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if user_model.IsErrUserProhibitLogin(err) {
 | 
			
		||||
			uplerr := err.(*user_model.ErrUserProhibitLogin)
 | 
			
		||||
			uplerr := err.(user_model.ErrUserProhibitLogin)
 | 
			
		||||
			log.Info("Failed authentication attempt for %s from %s: %v", uplerr.Name, ctx.RemoteAddr(), err)
 | 
			
		||||
			ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
 | 
			
		||||
			ctx.HTML(http.StatusOK, "user/auth/prohibit_login")
 | 
			
		||||
@@ -904,6 +904,10 @@ func claimValueToStringSlice(claimValue interface{}) []string {
 | 
			
		||||
	switch rawGroup := claimValue.(type) {
 | 
			
		||||
	case []string:
 | 
			
		||||
		groups = rawGroup
 | 
			
		||||
	case []interface{}:
 | 
			
		||||
		for _, group := range rawGroup {
 | 
			
		||||
			groups = append(groups, fmt.Sprintf("%s", group))
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		str := fmt.Sprintf("%s", rawGroup)
 | 
			
		||||
		groups = strings.Split(str, ",")
 | 
			
		||||
 
 | 
			
		||||
@@ -319,7 +319,7 @@ func TeamRepositories(ctx *context.Context) {
 | 
			
		||||
	ctx.Data["Title"] = ctx.Org.Team.Name
 | 
			
		||||
	ctx.Data["PageIsOrgTeams"] = true
 | 
			
		||||
	ctx.Data["PageIsOrgTeamRepos"] = true
 | 
			
		||||
	if err := ctx.Org.Team.GetRepositories(&models.SearchTeamOptions{}); err != nil {
 | 
			
		||||
	if err := ctx.Org.Team.GetRepositories(&models.SearchOrgTeamOptions{}); err != nil {
 | 
			
		||||
		ctx.ServerError("GetRepositories", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -351,7 +351,7 @@ func Diff(ctx *context.Context) {
 | 
			
		||||
	ctx.Data["DiffNotAvailable"] = diff.NumFiles == 0
 | 
			
		||||
 | 
			
		||||
	if err := asymkey_model.CalculateTrustStatus(verification, ctx.Repo.Repository.GetTrustModel(), func(user *user_model.User) (bool, error) {
 | 
			
		||||
		return models.IsUserRepoAdmin(ctx.Repo.Repository, user)
 | 
			
		||||
		return models.IsOwnerMemberCollaborator(ctx.Repo.Repository, user.ID)
 | 
			
		||||
	}, nil); err != nil {
 | 
			
		||||
		ctx.ServerError("CalculateTrustStatus", err)
 | 
			
		||||
		return
 | 
			
		||||
 
 | 
			
		||||
@@ -786,6 +786,15 @@ func ExcerptBlob(ctx *context.Context) {
 | 
			
		||||
	direction := ctx.FormString("direction")
 | 
			
		||||
	filePath := ctx.FormString("path")
 | 
			
		||||
	gitRepo := ctx.Repo.GitRepo
 | 
			
		||||
	if ctx.FormBool("wiki") {
 | 
			
		||||
		var err error
 | 
			
		||||
		gitRepo, err = git.OpenRepositoryCtx(ctx, ctx.Repo.Repository.WikiPath())
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.ServerError("OpenRepository", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		defer gitRepo.Close()
 | 
			
		||||
	}
 | 
			
		||||
	chunkSize := gitdiff.BlobExcerptChunkSize
 | 
			
		||||
	commit, err := gitRepo.GetCommit(commitID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
 
 | 
			
		||||
@@ -491,7 +491,10 @@ func serviceRPC(h serviceHandler, service string) {
 | 
			
		||||
	defer finished()
 | 
			
		||||
 | 
			
		||||
	var stderr bytes.Buffer
 | 
			
		||||
	cmd := exec.CommandContext(ctx, git.GitExecutable, service, "--stateless-rpc", h.dir)
 | 
			
		||||
	args := make([]string, len(git.GlobalCommandArgs))
 | 
			
		||||
	copy(args, git.GlobalCommandArgs)
 | 
			
		||||
	args = append(args, []string{service, "--stateless-rpc", h.dir}...)
 | 
			
		||||
	cmd := exec.CommandContext(ctx, git.GitExecutable, args...)
 | 
			
		||||
	cmd.Dir = h.dir
 | 
			
		||||
	cmd.Env = append(os.Environ(), h.environ...)
 | 
			
		||||
	cmd.Stdout = h.w
 | 
			
		||||
 
 | 
			
		||||
@@ -802,7 +802,7 @@ func NewIssue(ctx *context.Context) {
 | 
			
		||||
 | 
			
		||||
	milestoneID := ctx.FormInt64("milestone")
 | 
			
		||||
	if milestoneID > 0 {
 | 
			
		||||
		milestone, err := models.GetMilestoneByID(milestoneID)
 | 
			
		||||
		milestone, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, milestoneID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Error("GetMilestoneByID: %d: %v", milestoneID, err)
 | 
			
		||||
		} else {
 | 
			
		||||
@@ -889,7 +889,7 @@ func ValidateRepoMetas(ctx *context.Context, form forms.CreateIssueForm, isPull
 | 
			
		||||
	// Check milestone.
 | 
			
		||||
	milestoneID := form.MilestoneID
 | 
			
		||||
	if milestoneID > 0 {
 | 
			
		||||
		milestone, err := models.GetMilestoneByID(milestoneID)
 | 
			
		||||
		milestone, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, milestoneID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.ServerError("GetMilestoneByID", err)
 | 
			
		||||
			return nil, nil, 0, 0
 | 
			
		||||
 
 | 
			
		||||
@@ -264,7 +264,7 @@ func DeleteMilestone(ctx *context.Context) {
 | 
			
		||||
// MilestoneIssuesAndPulls lists all the issues and pull requests of the milestone
 | 
			
		||||
func MilestoneIssuesAndPulls(ctx *context.Context) {
 | 
			
		||||
	milestoneID := ctx.ParamsInt64(":id")
 | 
			
		||||
	milestone, err := models.GetMilestoneByID(milestoneID)
 | 
			
		||||
	milestone, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, milestoneID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if models.IsErrMilestoneNotExist(err) {
 | 
			
		||||
			ctx.NotFound("GetMilestoneByID", err)
 | 
			
		||||
 
 | 
			
		||||
@@ -800,7 +800,7 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri
 | 
			
		||||
		verification := asymkey_model.ParseCommitWithSignature(latestCommit)
 | 
			
		||||
 | 
			
		||||
		if err := asymkey_model.CalculateTrustStatus(verification, ctx.Repo.Repository.GetTrustModel(), func(user *user_model.User) (bool, error) {
 | 
			
		||||
			return models.IsUserRepoAdmin(ctx.Repo.Repository, user)
 | 
			
		||||
			return models.IsOwnerMemberCollaborator(ctx.Repo.Repository, user.ID)
 | 
			
		||||
		}, nil); err != nil {
 | 
			
		||||
			ctx.ServerError("CalculateTrustStatus", err)
 | 
			
		||||
			return nil
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@
 | 
			
		||||
package web
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	gocontext "context"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path"
 | 
			
		||||
@@ -952,7 +953,25 @@ func RegisterRoutes(m *web.Route) {
 | 
			
		||||
 | 
			
		||||
		m.Group("/blob_excerpt", func() {
 | 
			
		||||
			m.Get("/{sha}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.ExcerptBlob)
 | 
			
		||||
		}, repo.MustBeNotEmpty, context.RepoRef(), reqRepoCodeReader)
 | 
			
		||||
		}, func(ctx *context.Context) (cancel gocontext.CancelFunc) {
 | 
			
		||||
			if ctx.FormBool("wiki") {
 | 
			
		||||
				ctx.Data["PageIsWiki"] = true
 | 
			
		||||
				repo.MustEnableWiki(ctx)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			reqRepoCodeReader(ctx)
 | 
			
		||||
			if ctx.Written() {
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			cancel = context.RepoRef()(ctx)
 | 
			
		||||
			if ctx.Written() {
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			repo.MustBeNotEmpty(ctx)
 | 
			
		||||
			return
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		m.Group("/pulls/{index}", func() {
 | 
			
		||||
			m.Get(".diff", repo.DownloadPullDiff)
 | 
			
		||||
 
 | 
			
		||||
@@ -70,13 +70,13 @@ func init() {
 | 
			
		||||
		}))
 | 
			
		||||
 | 
			
		||||
	// named gplus due to legacy gplus -> google migration (Google killed Google+). This ensures old connections still work
 | 
			
		||||
	RegisterGothProvider(NewSimpleProvider("gplus", "Google", []string{"email"},
 | 
			
		||||
	RegisterGothProvider(NewImagedProvider("/assets/img/auth/google.png", NewSimpleProvider("gplus", "Google", []string{"email"},
 | 
			
		||||
		func(clientKey, secret, callbackURL string, scopes ...string) goth.Provider {
 | 
			
		||||
			if setting.OAuth2Client.UpdateAvatar || setting.OAuth2Client.EnableAutoRegistration {
 | 
			
		||||
				scopes = append(scopes, "profile")
 | 
			
		||||
			}
 | 
			
		||||
			return google.New(clientKey, secret, callbackURL, scopes...)
 | 
			
		||||
		}))
 | 
			
		||||
		})))
 | 
			
		||||
 | 
			
		||||
	RegisterGothProvider(NewSimpleProvider("twitter", "Twitter", nil,
 | 
			
		||||
		func(clientKey, secret, callbackURL string, scopes ...string) goth.Provider {
 | 
			
		||||
@@ -107,5 +107,4 @@ func init() {
 | 
			
		||||
			return microsoftonline.New(clientID, secret, callbackURL, scopes...)
 | 
			
		||||
		},
 | 
			
		||||
	))
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@ import (
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	texttmpl "text/template"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models"
 | 
			
		||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
			
		||||
@@ -297,13 +298,15 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Make sure to compose independent messages to avoid leaking user emails
 | 
			
		||||
	msgID := createReference(ctx.Issue, ctx.Comment, ctx.ActionType)
 | 
			
		||||
	reference := createReference(ctx.Issue, nil, models.ActionType(0))
 | 
			
		||||
 | 
			
		||||
	msgs := make([]*Message, 0, len(recipients))
 | 
			
		||||
	for _, recipient := range recipients {
 | 
			
		||||
		msg := NewMessageFrom([]string{recipient.Email}, ctx.Doer.DisplayName(), setting.MailService.FromEmail, subject, mailBody.String())
 | 
			
		||||
		msg.Info = fmt.Sprintf("Subject: %s, %s", subject, info)
 | 
			
		||||
 | 
			
		||||
		msg.SetHeader("Message-ID", "<"+createReference(ctx.Issue, ctx.Comment)+">")
 | 
			
		||||
		reference := createReference(ctx.Issue, nil)
 | 
			
		||||
		msg.SetHeader("Message-ID", "<"+msgID+">")
 | 
			
		||||
		msg.SetHeader("In-Reply-To", "<"+reference+">")
 | 
			
		||||
		msg.SetHeader("References", "<"+reference+">")
 | 
			
		||||
 | 
			
		||||
@@ -317,7 +320,7 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient
 | 
			
		||||
	return msgs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createReference(issue *models.Issue, comment *models.Comment) string {
 | 
			
		||||
func createReference(issue *models.Issue, comment *models.Comment, actionType models.ActionType) string {
 | 
			
		||||
	var path string
 | 
			
		||||
	if issue.IsPull {
 | 
			
		||||
		path = "pulls"
 | 
			
		||||
@@ -328,6 +331,17 @@ func createReference(issue *models.Issue, comment *models.Comment) string {
 | 
			
		||||
	var extra string
 | 
			
		||||
	if comment != nil {
 | 
			
		||||
		extra = fmt.Sprintf("/comment/%d", comment.ID)
 | 
			
		||||
	} else {
 | 
			
		||||
		switch actionType {
 | 
			
		||||
		case models.ActionCloseIssue, models.ActionClosePullRequest:
 | 
			
		||||
			extra = fmt.Sprintf("/close/%d", time.Now().UnixNano()/1e6)
 | 
			
		||||
		case models.ActionReopenIssue, models.ActionReopenPullRequest:
 | 
			
		||||
			extra = fmt.Sprintf("/reopen/%d", time.Now().UnixNano()/1e6)
 | 
			
		||||
		case models.ActionMergePullRequest:
 | 
			
		||||
			extra = fmt.Sprintf("/merge/%d", time.Now().UnixNano()/1e6)
 | 
			
		||||
		case models.ActionPullRequestReadyForReview:
 | 
			
		||||
			extra = fmt.Sprintf("/ready/%d", time.Now().UnixNano()/1e6)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return fmt.Sprintf("%s/%s/%d%s@%s", issue.Repo.FullName(), path, issue.Index, extra, setting.Domain)
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,9 @@ package mailer
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"html/template"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
	texttmpl "text/template"
 | 
			
		||||
 | 
			
		||||
@@ -15,7 +17,6 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/models/unittest"
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -236,3 +237,115 @@ func TestGenerateAdditionalHeaders(t *testing.T) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_createReference(t *testing.T) {
 | 
			
		||||
	_, _, issue, comment := prepareMailerTest(t)
 | 
			
		||||
	_, _, pullIssue, _ := prepareMailerTest(t)
 | 
			
		||||
	pullIssue.IsPull = true
 | 
			
		||||
 | 
			
		||||
	type args struct {
 | 
			
		||||
		issue      *models.Issue
 | 
			
		||||
		comment    *models.Comment
 | 
			
		||||
		actionType models.ActionType
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name   string
 | 
			
		||||
		args   args
 | 
			
		||||
		prefix string
 | 
			
		||||
		suffix string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "Open Issue",
 | 
			
		||||
			args: args{
 | 
			
		||||
				issue:      issue,
 | 
			
		||||
				actionType: models.ActionCreateIssue,
 | 
			
		||||
			},
 | 
			
		||||
			prefix: fmt.Sprintf("%s/issues/%d@%s", issue.Repo.FullName(), issue.Index, setting.Domain),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "Open Pull",
 | 
			
		||||
			args: args{
 | 
			
		||||
				issue:      pullIssue,
 | 
			
		||||
				actionType: models.ActionCreatePullRequest,
 | 
			
		||||
			},
 | 
			
		||||
			prefix: fmt.Sprintf("%s/pulls/%d@%s", issue.Repo.FullName(), issue.Index, setting.Domain),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "Comment Issue",
 | 
			
		||||
			args: args{
 | 
			
		||||
				issue:      issue,
 | 
			
		||||
				comment:    comment,
 | 
			
		||||
				actionType: models.ActionCommentIssue,
 | 
			
		||||
			},
 | 
			
		||||
			prefix: fmt.Sprintf("%s/issues/%d/comment/%d@%s", issue.Repo.FullName(), issue.Index, comment.ID, setting.Domain),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "Comment Pull",
 | 
			
		||||
			args: args{
 | 
			
		||||
				issue:      pullIssue,
 | 
			
		||||
				comment:    comment,
 | 
			
		||||
				actionType: models.ActionCommentPull,
 | 
			
		||||
			},
 | 
			
		||||
			prefix: fmt.Sprintf("%s/pulls/%d/comment/%d@%s", issue.Repo.FullName(), issue.Index, comment.ID, setting.Domain),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "Close Issue",
 | 
			
		||||
			args: args{
 | 
			
		||||
				issue:      issue,
 | 
			
		||||
				actionType: models.ActionCloseIssue,
 | 
			
		||||
			},
 | 
			
		||||
			prefix: fmt.Sprintf("%s/issues/%d/close/", issue.Repo.FullName(), issue.Index),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "Close Pull",
 | 
			
		||||
			args: args{
 | 
			
		||||
				issue:      pullIssue,
 | 
			
		||||
				actionType: models.ActionClosePullRequest,
 | 
			
		||||
			},
 | 
			
		||||
			prefix: fmt.Sprintf("%s/pulls/%d/close/", issue.Repo.FullName(), issue.Index),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "Reopen Issue",
 | 
			
		||||
			args: args{
 | 
			
		||||
				issue:      issue,
 | 
			
		||||
				actionType: models.ActionReopenIssue,
 | 
			
		||||
			},
 | 
			
		||||
			prefix: fmt.Sprintf("%s/issues/%d/reopen/", issue.Repo.FullName(), issue.Index),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "Reopen Pull",
 | 
			
		||||
			args: args{
 | 
			
		||||
				issue:      pullIssue,
 | 
			
		||||
				actionType: models.ActionReopenPullRequest,
 | 
			
		||||
			},
 | 
			
		||||
			prefix: fmt.Sprintf("%s/pulls/%d/reopen/", issue.Repo.FullName(), issue.Index),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "Merge Pull",
 | 
			
		||||
			args: args{
 | 
			
		||||
				issue:      pullIssue,
 | 
			
		||||
				actionType: models.ActionMergePullRequest,
 | 
			
		||||
			},
 | 
			
		||||
			prefix: fmt.Sprintf("%s/pulls/%d/merge/", issue.Repo.FullName(), issue.Index),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "Ready Pull",
 | 
			
		||||
			args: args{
 | 
			
		||||
				issue:      pullIssue,
 | 
			
		||||
				actionType: models.ActionPullRequestReadyForReview,
 | 
			
		||||
			},
 | 
			
		||||
			prefix: fmt.Sprintf("%s/pulls/%d/ready/", issue.Repo.FullName(), issue.Index),
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			got := createReference(tt.args.issue, tt.args.comment, tt.args.actionType)
 | 
			
		||||
			if !strings.HasPrefix(got, tt.prefix) {
 | 
			
		||||
				t.Errorf("createReference() = %v, want %v", got, tt.prefix)
 | 
			
		||||
			}
 | 
			
		||||
			if !strings.HasSuffix(got, tt.suffix) {
 | 
			
		||||
				t.Errorf("createReference() = %v, want %v", got, tt.prefix)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -32,8 +32,7 @@ func init() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GitlabDownloaderFactory defines a gitlab downloader factory
 | 
			
		||||
type GitlabDownloaderFactory struct {
 | 
			
		||||
}
 | 
			
		||||
type GitlabDownloaderFactory struct{}
 | 
			
		||||
 | 
			
		||||
// New returns a Downloader related to this factory according MigrateOptions
 | 
			
		||||
func (f *GitlabDownloaderFactory) New(ctx context.Context, opts base.MigrateOptions) (base.Downloader, error) {
 | 
			
		||||
@@ -184,16 +183,17 @@ func (g *GitlabDownloader) GetTopics() ([]string, error) {
 | 
			
		||||
 | 
			
		||||
// GetMilestones returns milestones
 | 
			
		||||
func (g *GitlabDownloader) GetMilestones() ([]*base.Milestone, error) {
 | 
			
		||||
	var perPage = g.maxPerPage
 | 
			
		||||
	var state = "all"
 | 
			
		||||
	var milestones = make([]*base.Milestone, 0, perPage)
 | 
			
		||||
	perPage := g.maxPerPage
 | 
			
		||||
	state := "all"
 | 
			
		||||
	milestones := make([]*base.Milestone, 0, perPage)
 | 
			
		||||
	for i := 1; ; i++ {
 | 
			
		||||
		ms, _, err := g.client.Milestones.ListMilestones(g.repoID, &gitlab.ListMilestonesOptions{
 | 
			
		||||
			State: &state,
 | 
			
		||||
			ListOptions: gitlab.ListOptions{
 | 
			
		||||
				Page:    i,
 | 
			
		||||
				PerPage: perPage,
 | 
			
		||||
			}}, nil, gitlab.WithContext(g.ctx))
 | 
			
		||||
			},
 | 
			
		||||
		}, nil, gitlab.WithContext(g.ctx))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
@@ -203,7 +203,7 @@ func (g *GitlabDownloader) GetMilestones() ([]*base.Milestone, error) {
 | 
			
		||||
			if m.Description != "" {
 | 
			
		||||
				desc = m.Description
 | 
			
		||||
			}
 | 
			
		||||
			var state = "open"
 | 
			
		||||
			state := "open"
 | 
			
		||||
			var closedAt *time.Time
 | 
			
		||||
			if m.State != "" {
 | 
			
		||||
				state = m.State
 | 
			
		||||
@@ -255,8 +255,8 @@ func (g *GitlabDownloader) normalizeColor(val string) string {
 | 
			
		||||
 | 
			
		||||
// GetLabels returns labels
 | 
			
		||||
func (g *GitlabDownloader) GetLabels() ([]*base.Label, error) {
 | 
			
		||||
	var perPage = g.maxPerPage
 | 
			
		||||
	var labels = make([]*base.Label, 0, perPage)
 | 
			
		||||
	perPage := g.maxPerPage
 | 
			
		||||
	labels := make([]*base.Label, 0, perPage)
 | 
			
		||||
	for i := 1; ; i++ {
 | 
			
		||||
		ls, _, err := g.client.Labels.ListLabels(g.repoID, &gitlab.ListLabelsOptions{ListOptions: gitlab.ListOptions{
 | 
			
		||||
			Page:    i,
 | 
			
		||||
@@ -327,8 +327,8 @@ func (g *GitlabDownloader) convertGitlabRelease(rel *gitlab.Release) *base.Relea
 | 
			
		||||
 | 
			
		||||
// GetReleases returns releases
 | 
			
		||||
func (g *GitlabDownloader) GetReleases() ([]*base.Release, error) {
 | 
			
		||||
	var perPage = g.maxPerPage
 | 
			
		||||
	var releases = make([]*base.Release, 0, perPage)
 | 
			
		||||
	perPage := g.maxPerPage
 | 
			
		||||
	releases := make([]*base.Release, 0, perPage)
 | 
			
		||||
	for i := 1; ; i++ {
 | 
			
		||||
		ls, _, err := g.client.Releases.ListReleases(g.repoID, &gitlab.ListReleasesOptions{
 | 
			
		||||
			Page:    i,
 | 
			
		||||
@@ -381,7 +381,7 @@ func (g *GitlabDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, er
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var allIssues = make([]*base.Issue, 0, perPage)
 | 
			
		||||
	allIssues := make([]*base.Issue, 0, perPage)
 | 
			
		||||
 | 
			
		||||
	issues, _, err := g.client.Issues.ListProjectIssues(g.repoID, opt, nil, gitlab.WithContext(g.ctx))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -389,7 +389,7 @@ func (g *GitlabDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, er
 | 
			
		||||
	}
 | 
			
		||||
	for _, issue := range issues {
 | 
			
		||||
 | 
			
		||||
		var labels = make([]*base.Label, 0, len(issue.Labels))
 | 
			
		||||
		labels := make([]*base.Label, 0, len(issue.Labels))
 | 
			
		||||
		for _, l := range issue.Labels {
 | 
			
		||||
			labels = append(labels, &base.Label{
 | 
			
		||||
				Name: l,
 | 
			
		||||
@@ -402,7 +402,7 @@ func (g *GitlabDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, er
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var reactions []*base.Reaction
 | 
			
		||||
		var awardPage = 1
 | 
			
		||||
		awardPage := 1
 | 
			
		||||
		for {
 | 
			
		||||
			awards, _, err := g.client.AwardEmoji.ListIssueAwardEmoji(g.repoID, issue.IID, &gitlab.ListAwardEmojiOptions{Page: awardPage, PerPage: perPage}, gitlab.WithContext(g.ctx))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
@@ -456,9 +456,9 @@ func (g *GitlabDownloader) GetComments(opts base.GetCommentOptions) ([]*base.Com
 | 
			
		||||
		return nil, false, fmt.Errorf("unexpected context: %+v", opts.Context)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var allComments = make([]*base.Comment, 0, g.maxPerPage)
 | 
			
		||||
	allComments := make([]*base.Comment, 0, g.maxPerPage)
 | 
			
		||||
 | 
			
		||||
	var page = 1
 | 
			
		||||
	page := 1
 | 
			
		||||
 | 
			
		||||
	for {
 | 
			
		||||
		var comments []*gitlab.Discussion
 | 
			
		||||
@@ -503,7 +503,6 @@ func (g *GitlabDownloader) GetComments(opts base.GetCommentOptions) ([]*base.Com
 | 
			
		||||
					Created:     *c.CreatedAt,
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
		if resp.NextPage == 0 {
 | 
			
		||||
			break
 | 
			
		||||
@@ -526,7 +525,7 @@ func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullReque
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var allPRs = make([]*base.PullRequest, 0, perPage)
 | 
			
		||||
	allPRs := make([]*base.PullRequest, 0, perPage)
 | 
			
		||||
 | 
			
		||||
	prs, _, err := g.client.MergeRequests.ListProjectMergeRequests(g.repoID, opt, nil, gitlab.WithContext(g.ctx))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -534,7 +533,7 @@ func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullReque
 | 
			
		||||
	}
 | 
			
		||||
	for _, pr := range prs {
 | 
			
		||||
 | 
			
		||||
		var labels = make([]*base.Label, 0, len(pr.Labels))
 | 
			
		||||
		labels := make([]*base.Label, 0, len(pr.Labels))
 | 
			
		||||
		for _, l := range pr.Labels {
 | 
			
		||||
			labels = append(labels, &base.Label{
 | 
			
		||||
				Name: l,
 | 
			
		||||
@@ -547,12 +546,12 @@ func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullReque
 | 
			
		||||
			pr.State = "closed"
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var mergeTime = pr.MergedAt
 | 
			
		||||
		mergeTime := pr.MergedAt
 | 
			
		||||
		if merged && pr.MergedAt == nil {
 | 
			
		||||
			mergeTime = pr.UpdatedAt
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var closeTime = pr.ClosedAt
 | 
			
		||||
		closeTime := pr.ClosedAt
 | 
			
		||||
		if merged && pr.ClosedAt == nil {
 | 
			
		||||
			closeTime = pr.UpdatedAt
 | 
			
		||||
		}
 | 
			
		||||
@@ -568,7 +567,7 @@ func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullReque
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var reactions []*base.Reaction
 | 
			
		||||
		var awardPage = 1
 | 
			
		||||
		awardPage := 1
 | 
			
		||||
		for {
 | 
			
		||||
			awards, _, err := g.client.AwardEmoji.ListMergeRequestAwardEmoji(g.repoID, pr.IID, &gitlab.ListAwardEmojiOptions{Page: awardPage, PerPage: perPage}, gitlab.WithContext(g.ctx))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
@@ -641,13 +640,22 @@ func (g *GitlabDownloader) GetReviews(context base.IssueContext) ([]*base.Review
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var reviews = make([]*base.Review, 0, len(approvals.ApprovedBy))
 | 
			
		||||
	var createdAt time.Time
 | 
			
		||||
	if approvals.CreatedAt != nil {
 | 
			
		||||
		createdAt = *approvals.CreatedAt
 | 
			
		||||
	} else if approvals.UpdatedAt != nil {
 | 
			
		||||
		createdAt = *approvals.UpdatedAt
 | 
			
		||||
	} else {
 | 
			
		||||
		createdAt = time.Now()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	reviews := make([]*base.Review, 0, len(approvals.ApprovedBy))
 | 
			
		||||
	for _, user := range approvals.ApprovedBy {
 | 
			
		||||
		reviews = append(reviews, &base.Review{
 | 
			
		||||
			IssueIndex:   context.LocalID(),
 | 
			
		||||
			ReviewerID:   int64(user.User.ID),
 | 
			
		||||
			ReviewerName: user.User.Username,
 | 
			
		||||
			CreatedAt:    *approvals.UpdatedAt,
 | 
			
		||||
			CreatedAt:    createdAt,
 | 
			
		||||
			// All we get are approvals
 | 
			
		||||
			State: base.ReviewStateApproved,
 | 
			
		||||
		})
 | 
			
		||||
 
 | 
			
		||||
@@ -8,13 +8,16 @@ import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/http/httptest"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	base "code.gitea.io/gitea/modules/migration"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"github.com/xanzy/go-gitlab"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestGitlabDownloadRepo(t *testing.T) {
 | 
			
		||||
@@ -310,12 +313,14 @@ func TestGitlabDownloadRepo(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assertReviewsEqual(t, []*base.Review{
 | 
			
		||||
		{
 | 
			
		||||
			IssueIndex:   1,
 | 
			
		||||
			ReviewerID:   4102996,
 | 
			
		||||
			ReviewerName: "zeripath",
 | 
			
		||||
			CreatedAt:    time.Date(2019, 11, 28, 16, 2, 8, 377000000, time.UTC),
 | 
			
		||||
			State:        "APPROVED",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			IssueIndex:   1,
 | 
			
		||||
			ReviewerID:   527793,
 | 
			
		||||
			ReviewerName: "axifive",
 | 
			
		||||
			CreatedAt:    time.Date(2019, 11, 28, 16, 2, 8, 377000000, time.UTC),
 | 
			
		||||
@@ -327,6 +332,7 @@ func TestGitlabDownloadRepo(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assertReviewsEqual(t, []*base.Review{
 | 
			
		||||
		{
 | 
			
		||||
			IssueIndex:   2,
 | 
			
		||||
			ReviewerID:   4575606,
 | 
			
		||||
			ReviewerName: "real6543",
 | 
			
		||||
			CreatedAt:    time.Date(2020, 4, 19, 19, 24, 21, 108000000, time.UTC),
 | 
			
		||||
@@ -334,3 +340,137 @@ func TestGitlabDownloadRepo(t *testing.T) {
 | 
			
		||||
		},
 | 
			
		||||
	}, rvs)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func gitlabClientMockSetup(t *testing.T) (*http.ServeMux, *httptest.Server, *gitlab.Client) {
 | 
			
		||||
	// mux is the HTTP request multiplexer used with the test server.
 | 
			
		||||
	mux := http.NewServeMux()
 | 
			
		||||
 | 
			
		||||
	// server is a test HTTP server used to provide mock API responses.
 | 
			
		||||
	server := httptest.NewServer(mux)
 | 
			
		||||
 | 
			
		||||
	// client is the Gitlab client being tested.
 | 
			
		||||
	client, err := gitlab.NewClient("", gitlab.WithBaseURL(server.URL))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		server.Close()
 | 
			
		||||
		t.Fatalf("Failed to create client: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return mux, server, client
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func gitlabClientMockTeardown(server *httptest.Server) {
 | 
			
		||||
	server.Close()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type reviewTestCase struct {
 | 
			
		||||
	repoID, prID, reviewerID int
 | 
			
		||||
	reviewerName             string
 | 
			
		||||
	createdAt, updatedAt     *time.Time
 | 
			
		||||
	expectedCreatedAt        time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func convertTestCase(t reviewTestCase) (func(w http.ResponseWriter, r *http.Request), base.Review) {
 | 
			
		||||
	var updatedAtField string
 | 
			
		||||
	if t.updatedAt == nil {
 | 
			
		||||
		updatedAtField = ""
 | 
			
		||||
	} else {
 | 
			
		||||
		updatedAtField = `"updated_at": "` + t.updatedAt.Format(time.RFC3339) + `",`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var createdAtField string
 | 
			
		||||
	if t.createdAt == nil {
 | 
			
		||||
		createdAtField = ""
 | 
			
		||||
	} else {
 | 
			
		||||
		createdAtField = `"created_at": "` + t.createdAt.Format(time.RFC3339) + `",`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	handler := func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		fmt.Fprint(w, `
 | 
			
		||||
{
 | 
			
		||||
  "id": 5,
 | 
			
		||||
  "iid": `+strconv.Itoa(t.prID)+`,
 | 
			
		||||
  "project_id": `+strconv.Itoa(t.repoID)+`,
 | 
			
		||||
  "title": "Approvals API",
 | 
			
		||||
  "description": "Test",
 | 
			
		||||
  "state": "opened",
 | 
			
		||||
  `+createdAtField+`
 | 
			
		||||
  `+updatedAtField+`
 | 
			
		||||
  "merge_status": "cannot_be_merged",
 | 
			
		||||
  "approvals_required": 2,
 | 
			
		||||
  "approvals_left": 1,
 | 
			
		||||
  "approved_by": [
 | 
			
		||||
    {
 | 
			
		||||
      "user": {
 | 
			
		||||
        "name": "Administrator",
 | 
			
		||||
        "username": "`+t.reviewerName+`",
 | 
			
		||||
        "id": `+strconv.Itoa(t.reviewerID)+`,
 | 
			
		||||
        "state": "active",
 | 
			
		||||
        "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon",
 | 
			
		||||
        "web_url": "http://localhost:3000/root"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
}`)
 | 
			
		||||
	}
 | 
			
		||||
	review := base.Review{
 | 
			
		||||
		IssueIndex:   int64(t.prID),
 | 
			
		||||
		ReviewerID:   int64(t.reviewerID),
 | 
			
		||||
		ReviewerName: t.reviewerName,
 | 
			
		||||
		CreatedAt:    t.expectedCreatedAt,
 | 
			
		||||
		State:        "APPROVED",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return handler, review
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGitlabGetReviews(t *testing.T) {
 | 
			
		||||
	mux, server, client := gitlabClientMockSetup(t)
 | 
			
		||||
	defer gitlabClientMockTeardown(server)
 | 
			
		||||
 | 
			
		||||
	repoID := 1324
 | 
			
		||||
 | 
			
		||||
	downloader := &GitlabDownloader{
 | 
			
		||||
		ctx:    context.Background(),
 | 
			
		||||
		client: client,
 | 
			
		||||
		repoID: repoID,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	createdAt := time.Date(2020, 4, 19, 19, 24, 21, 0, time.UTC)
 | 
			
		||||
 | 
			
		||||
	for _, testCase := range []reviewTestCase{
 | 
			
		||||
		{
 | 
			
		||||
			repoID:            repoID,
 | 
			
		||||
			prID:              1,
 | 
			
		||||
			reviewerID:        801,
 | 
			
		||||
			reviewerName:      "someone1",
 | 
			
		||||
			createdAt:         nil,
 | 
			
		||||
			updatedAt:         &createdAt,
 | 
			
		||||
			expectedCreatedAt: createdAt,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			repoID:            repoID,
 | 
			
		||||
			prID:              2,
 | 
			
		||||
			reviewerID:        802,
 | 
			
		||||
			reviewerName:      "someone2",
 | 
			
		||||
			createdAt:         &createdAt,
 | 
			
		||||
			updatedAt:         nil,
 | 
			
		||||
			expectedCreatedAt: createdAt,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			repoID:            repoID,
 | 
			
		||||
			prID:              3,
 | 
			
		||||
			reviewerID:        803,
 | 
			
		||||
			reviewerName:      "someone3",
 | 
			
		||||
			createdAt:         nil,
 | 
			
		||||
			updatedAt:         nil,
 | 
			
		||||
			expectedCreatedAt: time.Now(),
 | 
			
		||||
		},
 | 
			
		||||
	} {
 | 
			
		||||
		mock, review := convertTestCase(testCase)
 | 
			
		||||
		mux.HandleFunc(fmt.Sprintf("/api/v4/projects/%d/merge_requests/%d/approvals", testCase.repoID, testCase.prID), mock)
 | 
			
		||||
 | 
			
		||||
		rvs, err := downloader.GetReviews(base.BasicIssueContext(testCase.prID))
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		assertReviewsEqual(t, []*base.Review{&review}, rvs)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -223,15 +223,15 @@ func assertRepositoryEqual(t *testing.T, expected, actual *base.Repository) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func assertReviewEqual(t *testing.T, expected, actual *base.Review) {
 | 
			
		||||
	assert.Equal(t, expected.ID, actual.ID)
 | 
			
		||||
	assert.Equal(t, expected.IssueIndex, actual.IssueIndex)
 | 
			
		||||
	assert.Equal(t, expected.ReviewerID, actual.ReviewerID)
 | 
			
		||||
	assert.Equal(t, expected.ReviewerName, actual.ReviewerName)
 | 
			
		||||
	assert.Equal(t, expected.Official, actual.Official)
 | 
			
		||||
	assert.Equal(t, expected.CommitID, actual.CommitID)
 | 
			
		||||
	assert.Equal(t, expected.Content, actual.Content)
 | 
			
		||||
	assertTimeEqual(t, expected.CreatedAt, actual.CreatedAt)
 | 
			
		||||
	assert.Equal(t, expected.State, actual.State)
 | 
			
		||||
	assert.Equal(t, expected.ID, actual.ID, "ID")
 | 
			
		||||
	assert.Equal(t, expected.IssueIndex, actual.IssueIndex, "IsssueIndex")
 | 
			
		||||
	assert.Equal(t, expected.ReviewerID, actual.ReviewerID, "ReviewerID")
 | 
			
		||||
	assert.Equal(t, expected.ReviewerName, actual.ReviewerName, "ReviewerName")
 | 
			
		||||
	assert.Equal(t, expected.Official, actual.Official, "Official")
 | 
			
		||||
	assert.Equal(t, expected.CommitID, actual.CommitID, "CommitID")
 | 
			
		||||
	assert.Equal(t, expected.Content, actual.Content, "Content")
 | 
			
		||||
	assert.WithinDuration(t, expected.CreatedAt, actual.CreatedAt, 10*time.Second)
 | 
			
		||||
	assert.Equal(t, expected.State, actual.State, "State")
 | 
			
		||||
	assertReviewCommentsEqual(t, expected.Comments, actual.Comments)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -97,6 +97,9 @@ func (r *RepositoryRestorer) GetTopics() ([]string, error) {
 | 
			
		||||
 | 
			
		||||
	bs, err := os.ReadFile(p)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if os.IsNotExist(err) {
 | 
			
		||||
			return nil, nil
 | 
			
		||||
		}
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -339,8 +339,10 @@ func checkConflicts(pr *models.PullRequest, gitRepo *git.Repository, tmpBasePath
 | 
			
		||||
	if prConfig.IgnoreWhitespaceConflicts {
 | 
			
		||||
		args = append(args, "--ignore-whitespace")
 | 
			
		||||
	}
 | 
			
		||||
	is3way := false
 | 
			
		||||
	if git.CheckGitVersionAtLeast("2.32.0") == nil {
 | 
			
		||||
		args = append(args, "--3way")
 | 
			
		||||
		is3way = true
 | 
			
		||||
	}
 | 
			
		||||
	args = append(args, patchPath)
 | 
			
		||||
	pr.ConflictedFiles = make([]string, 0, 5)
 | 
			
		||||
@@ -379,6 +381,9 @@ func checkConflicts(pr *models.PullRequest, gitRepo *git.Repository, tmpBasePath
 | 
			
		||||
 | 
			
		||||
				const prefix = "error: patch failed:"
 | 
			
		||||
				const errorPrefix = "error: "
 | 
			
		||||
				const threewayFailed = "Failed to perform three-way merge..."
 | 
			
		||||
				const appliedPatchPrefix = "Applied patch to '"
 | 
			
		||||
				const withConflicts = "' with conflicts."
 | 
			
		||||
 | 
			
		||||
				conflictMap := map[string]bool{}
 | 
			
		||||
 | 
			
		||||
@@ -390,6 +395,8 @@ func checkConflicts(pr *models.PullRequest, gitRepo *git.Repository, tmpBasePath
 | 
			
		||||
						conflict = true
 | 
			
		||||
						filepath := strings.TrimSpace(strings.Split(line[len(prefix):], ":")[0])
 | 
			
		||||
						conflictMap[filepath] = true
 | 
			
		||||
					} else if is3way && line == threewayFailed {
 | 
			
		||||
						conflict = true
 | 
			
		||||
					} else if strings.HasPrefix(line, errorPrefix) {
 | 
			
		||||
						conflict = true
 | 
			
		||||
						for _, suffix := range patchErrorSuffices {
 | 
			
		||||
@@ -401,6 +408,12 @@ func checkConflicts(pr *models.PullRequest, gitRepo *git.Repository, tmpBasePath
 | 
			
		||||
								break
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					} else if is3way && strings.HasPrefix(line, appliedPatchPrefix) && strings.HasSuffix(line, withConflicts) {
 | 
			
		||||
						conflict = true
 | 
			
		||||
						filepath := strings.TrimPrefix(strings.TrimSuffix(line, withConflicts), appliedPatchPrefix)
 | 
			
		||||
						if filepath != "" {
 | 
			
		||||
							conflictMap[filepath] = true
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
					// only list 10 conflicted files
 | 
			
		||||
					if len(conflictMap) >= 10 {
 | 
			
		||||
 
 | 
			
		||||
@@ -286,6 +286,10 @@
 | 
			
		||||
							<input id="skip_local_two_fa" name="skip_local_two_fa" type="checkbox" {{if $cfg.SkipLocalTwoFA}}checked{{end}}>
 | 
			
		||||
							<p class="help">{{.i18n.Tr "admin.auths.skip_local_two_fa_helper"}}</p>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class="oauth2_use_custom_url inline field">
 | 
			
		||||
						<div class="ui checkbox">
 | 
			
		||||
							<label><strong>{{.i18n.Tr "admin.auths.oauth2_use_custom_url"}}</strong></label>
 | 
			
		||||
							<input id="oauth2_use_custom_url" name="oauth2_use_custom_url" type="checkbox" {{if $cfg.CustomURLMapping}}checked{{end}}>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -56,7 +56,7 @@
 | 
			
		||||
								<br>
 | 
			
		||||
								<div class="field">
 | 
			
		||||
									<div class="ui radio checkbox">
 | 
			
		||||
										<input type="radio" name="permission" value="{{if .PageIsOrgTeamsNew}}read{{else}}{{.Team.AccessMode}}{{end}}" {{if or .PageIsOrgTeamsNew (eq .Team.AccessMode 1) (eq .Team.AccessMode 2)}}checked{{end}}>
 | 
			
		||||
										<input type="radio" name="permission" value="read" {{if or .PageIsOrgTeamsNew (eq .Team.AccessMode 1) (eq .Team.AccessMode 2)}}checked{{end}}>
 | 
			
		||||
										<label>{{.i18n.Tr "org.teams.general_access"}}</label>
 | 
			
		||||
										<span class="help">{{.i18n.Tr "org.teams.general_access_helper"}}</span>
 | 
			
		||||
									</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -75,7 +75,11 @@
 | 
			
		||||
							<pre class="commit-body" style="display: none;">{{RenderCommitBody .Message $commitRepoLink $.Repository.ComposeMetas}}</pre>
 | 
			
		||||
							{{end}}
 | 
			
		||||
						</td>
 | 
			
		||||
						{{if .Committer}}
 | 
			
		||||
							<td class="text right aligned">{{TimeSince .Committer.When $.Lang}}</td>
 | 
			
		||||
						{{else}}
 | 
			
		||||
							<td class="text right aligned">{{TimeSince .Author.When $.Lang}}</td>
 | 
			
		||||
						{{end}}
 | 
			
		||||
					</tr>
 | 
			
		||||
				{{end}}
 | 
			
		||||
			</tbody>
 | 
			
		||||
 
 | 
			
		||||
@@ -4,40 +4,35 @@
 | 
			
		||||
		{{if eq .GetType 4}}
 | 
			
		||||
			<td class="lines-num lines-num-old" data-line-num="{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}">
 | 
			
		||||
				{{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 5) }}
 | 
			
		||||
					<a role="button" class="blob-excerpt" data-url="{{$.RepoLink}}/blob_excerpt/{{PathEscape $.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=split&direction=down" data-anchor="{{$.Anchor}}">
 | 
			
		||||
					<a role="button" class="blob-excerpt" data-url="{{$.RepoLink}}/blob_excerpt/{{PathEscape $.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=split&direction=down&wiki={{$.PageIsWiki}}" data-anchor="{{$.Anchor}}">
 | 
			
		||||
						{{svg "octicon-fold-down"}}
 | 
			
		||||
					</a>
 | 
			
		||||
				{{end}}
 | 
			
		||||
				{{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 4) }}
 | 
			
		||||
					<a role="button" class="blob-excerpt" data-url="{{$.RepoLink}}/blob_excerpt/{{PathEscape $.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=split&direction=up" data-anchor="{{$.Anchor}}">
 | 
			
		||||
					<a role="button" class="blob-excerpt" data-url="{{$.RepoLink}}/blob_excerpt/{{PathEscape $.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=split&direction=up&wiki={{$.PageIsWiki}}" data-anchor="{{$.Anchor}}">
 | 
			
		||||
						{{svg "octicon-fold-up"}}
 | 
			
		||||
					</a>
 | 
			
		||||
				{{end}}
 | 
			
		||||
				{{if eq $line.GetExpandDirection 2}}
 | 
			
		||||
					<a role="button" class="blob-excerpt" data-url="{{$.RepoLink}}/blob_excerpt/{{PathEscape $.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=split&direction=" data-anchor="{{$.Anchor}}">
 | 
			
		||||
					<a role="button" class="blob-excerpt" data-url="{{$.RepoLink}}/blob_excerpt/{{PathEscape $.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=split&direction=&wiki={{$.PageIsWiki}}" data-anchor="{{$.Anchor}}">
 | 
			
		||||
						{{svg "octicon-fold"}}
 | 
			
		||||
					</a>
 | 
			
		||||
				{{end}}
 | 
			
		||||
			</td>
 | 
			
		||||
			<td colspan="5" class="lines-code lines-code-old ">{{$inlineDiff := $.section.GetComputedInlineDiffFor $line}}<code {{if $inlineDiff.EscapeStatus.Escaped}}class="code-inner has-escaped" title="{{$.i18n.Tr "repo.line_unicode"}}"{{else}}class="code-inner"{{end}}>{{$inlineDiff.Content}}</code></td>
 | 
			
		||||
		{{else}}
 | 
			
		||||
			{{$inlineDiff := $.section.GetComputedInlineDiffFor $line}}
 | 
			
		||||
			<td class="lines-num lines-num-old" data-line-num="{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}"><span rel="{{if $line.LeftIdx}}diff-{{Sha1 $.fileName}}L{{$line.LeftIdx}}{{end}}"></span></td>
 | 
			
		||||
			<td class="blob-excerpt lines-escape lines-escape-old">{{if and $line.LeftIdx $inlineDiff.EscapeStatus.Escaped}}<a href="" class="toggle-escape-button" title="{{$.i18n.Tr "repo.line_unicode"}}"></a>{{end}}</td>
 | 
			
		||||
			<td class="blob-excerpt lines-type-marker lines-type-marker-old">{{if $line.LeftIdx}}<span class="mono" data-type-marker=""></span>{{end}}</td>
 | 
			
		||||
			<td class="blob-excerpt lines-code lines-code-old halfwidth">{{/*
 | 
			
		||||
				*/}}{{if $line.LeftIdx}}{{/*
 | 
			
		||||
						*/}}{{$inlineDiff := $.section.GetComputedInlineDiffFor $line}}<code {{if $inlineDiff.EscapeStatus.Escaped}}class="code-inner has-escaped" title="{{$.i18n.Tr "repo.line_unicode"}}"{{else}}class="code-inner"{{end}}>{{$inlineDiff.Content}}</code>{{/*
 | 
			
		||||
					*/}}{{else}}{{/*
 | 
			
		||||
						*/}}<code class="code-inner"></code>{{/*
 | 
			
		||||
					*/}}{{end}}{{/*
 | 
			
		||||
				*/}}<code {{if and $line.LeftIdx $inlineDiff.EscapeStatus.Escaped}}class="code-inner has-escaped" title="{{$.i18n.Tr "repo.line_unicode"}}"{{else}}class="code-inner"{{end}}>{{if $line.LeftIdx}}{{$inlineDiff.Content}}{{end}}</code>{{/*
 | 
			
		||||
			*/}}</td>
 | 
			
		||||
			<td class="lines-num lines-num-new" data-line-num="{{if $line.RightIdx}}{{$line.RightIdx}}{{end}}"><span rel="{{if $line.RightIdx}}diff-{{Sha1 $.fileName}}R{{$line.RightIdx}}{{end}}"></span></td>
 | 
			
		||||
			<td class="blob-excerpt lines-escape lines-escape-new">{{if and $line.RightIdx $inlineDiff.EscapeStatus.Escaped}}<a href="" class="toggle-escape-button" title="{{$.i18n.Tr "repo.line_unicode"}}"></a>{{end}}</td>
 | 
			
		||||
			<td class="blob-excerpt lines-type-marker lines-type-marker-new">{{if $line.RightIdx}}<span class="mono" data-type-marker=""></span>{{end}}</td>
 | 
			
		||||
			<td class="blob-excerpt lines-code lines-code-new halfwidth">{{/*
 | 
			
		||||
				*/}}{{if $line.RightIdx}}{{/*
 | 
			
		||||
					*/}}{{$inlineDiff := $.section.GetComputedInlineDiffFor $line}}<code {{if $inlineDiff.EscapeStatus.Escaped}}class="code-inner has-escaped" title="{{$.i18n.Tr "repo.line_unicode"}}"{{else}}class="code-inner"{{end}}>{{$inlineDiff.Content}}</code>{{/*
 | 
			
		||||
				*/}}{{else}}{{/*
 | 
			
		||||
					*/}}<code class="code-inner"></code>{{/*
 | 
			
		||||
				*/}}{{end}}{{/*
 | 
			
		||||
				*/}}<code {{if and $line.RightIdx $inlineDiff.EscapeStatus.Escaped}}class="code-inner has-escaped" title="{{$.i18n.Tr "repo.line_unicode"}}"{{else}}class="code-inner"{{end}}>{{if $line.RightIdx}}{{$inlineDiff.Content}}{{end}}</code>{{/*
 | 
			
		||||
			*/}}</td>
 | 
			
		||||
		{{end}}
 | 
			
		||||
	</tr>
 | 
			
		||||
@@ -48,17 +43,17 @@
 | 
			
		||||
		{{if eq .GetType 4}}
 | 
			
		||||
			<td colspan="2" class="lines-num">
 | 
			
		||||
				{{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 5) }}
 | 
			
		||||
					<a role="button" class="blob-excerpt" data-url="{{$.RepoLink}}/blob_excerpt/{{PathEscape $.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=unified&direction=down" data-anchor="{{$.Anchor}}">
 | 
			
		||||
					<a role="button" class="blob-excerpt" data-url="{{$.RepoLink}}/blob_excerpt/{{PathEscape $.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=unified&direction=down&wiki={{$.PageIsWiki}}" data-anchor="{{$.Anchor}}">
 | 
			
		||||
						{{svg "octicon-fold-down"}}
 | 
			
		||||
					</a>
 | 
			
		||||
				{{end}}
 | 
			
		||||
				{{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 4) }}
 | 
			
		||||
					<a role="button" class="blob-excerpt" data-url="{{$.RepoLink}}/blob_excerpt/{{PathEscape $.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=unified&direction=up" data-anchor="{{$.Anchor}}">
 | 
			
		||||
					<a role="button" class="blob-excerpt" data-url="{{$.RepoLink}}/blob_excerpt/{{PathEscape $.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=unified&direction=up&wiki={{$.PageIsWiki}}" data-anchor="{{$.Anchor}}">
 | 
			
		||||
						{{svg "octicon-fold-up"}}
 | 
			
		||||
					</a>
 | 
			
		||||
				{{end}}
 | 
			
		||||
				{{if eq $line.GetExpandDirection 2}}
 | 
			
		||||
					<a role="button" class="blob-excerpt" data-url="{{$.RepoLink}}/blob_excerpt/{{PathEscape $.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=unified&direction=" data-anchor="{{$.Anchor}}">
 | 
			
		||||
					<a role="button" class="blob-excerpt" data-url="{{$.RepoLink}}/blob_excerpt/{{PathEscape $.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=unified&direction=&wiki={{$.PageIsWiki}}" data-anchor="{{$.Anchor}}">
 | 
			
		||||
						{{svg "octicon-fold"}}
 | 
			
		||||
					</a>
 | 
			
		||||
				{{end}}
 | 
			
		||||
@@ -67,8 +62,10 @@
 | 
			
		||||
			<td class="lines-num lines-num-old" data-line-num="{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}"><span rel="{{if $line.LeftIdx}}diff-{{Sha1 $.fileName}}L{{$line.LeftIdx}}{{end}}"></span></td>
 | 
			
		||||
			<td class="lines-num lines-num-new" data-line-num="{{if $line.RightIdx}}{{$line.RightIdx}}{{end}}"><span rel="{{if $line.RightIdx}}diff-{{Sha1 $.fileName}}R{{$line.RightIdx}}{{end}}"></span></td>
 | 
			
		||||
		{{end}}
 | 
			
		||||
		{{$inlineDiff := $.section.GetComputedInlineDiffFor $line}}
 | 
			
		||||
		<td class="blob-excerpt lines-escape">{{if $inlineDiff.EscapeStatus.Escaped}}<a href="" class="toggle-escape-button" title="{{$.i18n.Tr "repo.line_unicode"}}"></a>{{end}}</td>
 | 
			
		||||
		<td class="blob-excerpt lines-type-marker"><span class="mono" data-type-marker="{{$line.GetLineTypeMarker}}"></span></td>
 | 
			
		||||
		<td class="blob-excerpt lines-code{{if (not $line.RightIdx)}} lines-code-old{{end}}">{{$inlineDiff := $.section.GetComputedInlineDiffFor $line}}<code {{if $inlineDiff.EscapeStatus.Escaped}}class="code-inner has-escaped" title="{{$.i18n.Tr "repo.line_unicode"}}"{{else}}class="code-inner"{{end}}>{{$inlineDiff.Content}}</code></td>
 | 
			
		||||
		<td class="blob-excerpt lines-code{{if (not $line.RightIdx)}} lines-code-old{{end}}"><code {{if $inlineDiff.EscapeStatus.Escaped}}class="code-inner has-escaped" title="{{$.i18n.Tr "repo.line_unicode"}}"{{else}}class="code-inner"{{end}}>{{$inlineDiff.Content}}</code></td>
 | 
			
		||||
	</tr>
 | 
			
		||||
	{{end}}
 | 
			
		||||
{{end}}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,17 +7,17 @@
 | 
			
		||||
				{{if eq .GetType 4}}
 | 
			
		||||
					<td class="lines-num lines-num-old">
 | 
			
		||||
						{{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 5) }}
 | 
			
		||||
							<a role="button" class="blob-excerpt" data-url="{{$.root.RepoLink}}/blob_excerpt/{{PathEscape $.root.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=split&direction=down" data-anchor="diff-{{Sha1 $file.Name}}K{{$line.SectionInfo.RightIdx}}">
 | 
			
		||||
							<a role="button" class="blob-excerpt" data-url="{{$.root.RepoLink}}/blob_excerpt/{{PathEscape $.root.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=split&direction=down&wiki={{$.root.PageIsWiki}}" data-anchor="diff-{{Sha1 $file.Name}}K{{$line.SectionInfo.RightIdx}}">
 | 
			
		||||
								{{svg "octicon-fold-down"}}
 | 
			
		||||
							</a>
 | 
			
		||||
						{{end}}
 | 
			
		||||
						{{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 4) }}
 | 
			
		||||
							<a role="button" class="blob-excerpt" data-url="{{$.root.RepoLink}}/blob_excerpt/{{PathEscape $.root.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=split&direction=up" data-anchor="diff-{{Sha1 $file.Name}}K{{$line.SectionInfo.RightIdx}}">
 | 
			
		||||
							<a role="button" class="blob-excerpt" data-url="{{$.root.RepoLink}}/blob_excerpt/{{PathEscape $.root.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=split&direction=up&wiki={{$.root.PageIsWiki}}" data-anchor="diff-{{Sha1 $file.Name}}K{{$line.SectionInfo.RightIdx}}">
 | 
			
		||||
								{{svg "octicon-fold-up"}}
 | 
			
		||||
							</a>
 | 
			
		||||
						{{end}}
 | 
			
		||||
						{{if eq $line.GetExpandDirection 2}}
 | 
			
		||||
							<a role="button" class="blob-excerpt" data-url="{{$.root.RepoLink}}/blob_excerpt/{{PathEscape $.root.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=split&direction=" data-anchor="diff-{{Sha1 $file.Name}}K{{$line.SectionInfo.RightIdx}}">
 | 
			
		||||
							<a role="button" class="blob-excerpt" data-url="{{$.root.RepoLink}}/blob_excerpt/{{PathEscape $.root.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=split&direction=&wiki={{$.root.PageIsWiki}}" data-anchor="diff-{{Sha1 $file.Name}}K{{$line.SectionInfo.RightIdx}}">
 | 
			
		||||
								{{svg "octicon-fold"}}
 | 
			
		||||
							</a>
 | 
			
		||||
						{{end}}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,17 +6,17 @@
 | 
			
		||||
				{{if eq .GetType 4}}
 | 
			
		||||
					<td colspan="2" class="lines-num">
 | 
			
		||||
						{{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 5) }}
 | 
			
		||||
							<a role="button" class="blob-excerpt" data-url="{{$.root.RepoLink}}/blob_excerpt/{{PathEscape $.root.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=unified&direction=down" data-anchor="diff-{{Sha1 $file.Name}}K{{$line.SectionInfo.RightIdx}}">
 | 
			
		||||
							<a role="button" class="blob-excerpt" data-url="{{$.root.RepoLink}}/blob_excerpt/{{PathEscape $.root.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=unified&direction=down&wiki={{$.root.PageIsWiki}}" data-anchor="diff-{{Sha1 $file.Name}}K{{$line.SectionInfo.RightIdx}}">
 | 
			
		||||
								{{svg "octicon-fold-down"}}
 | 
			
		||||
							</a>
 | 
			
		||||
						{{end}}
 | 
			
		||||
						{{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 4) }}
 | 
			
		||||
							<a role="button" class="blob-excerpt" data-url="{{$.root.RepoLink}}/blob_excerpt/{{PathEscape $.root.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=unified&direction=up" data-anchor="diff-{{Sha1 $file.Name}}K{{$line.SectionInfo.RightIdx}}">
 | 
			
		||||
							<a role="button" class="blob-excerpt" data-url="{{$.root.RepoLink}}/blob_excerpt/{{PathEscape $.root.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=unified&direction=up&wiki={{$.root.PageIsWiki}}" data-anchor="diff-{{Sha1 $file.Name}}K{{$line.SectionInfo.RightIdx}}">
 | 
			
		||||
								{{svg "octicon-fold-up"}}
 | 
			
		||||
							</a>
 | 
			
		||||
						{{end}}
 | 
			
		||||
						{{if eq $line.GetExpandDirection 2}}
 | 
			
		||||
							<a role="button" class="blob-excerpt" data-url="{{$.root.RepoLink}}/blob_excerpt/{{PathEscape $.root.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=unified&direction=" data-anchor="diff-{{Sha1 $file.Name}}K{{$line.SectionInfo.RightIdx}}">
 | 
			
		||||
							<a role="button" class="blob-excerpt" data-url="{{$.root.RepoLink}}/blob_excerpt/{{PathEscape $.root.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=unified&direction=&wiki={{$.root.PageIsWiki}}" data-anchor="diff-{{Sha1 $file.Name}}K{{$line.SectionInfo.RightIdx}}">
 | 
			
		||||
								{{svg "octicon-fold"}}
 | 
			
		||||
							</a>
 | 
			
		||||
						{{end}}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,6 @@
 | 
			
		||||
<div class="ui centered grid">
 | 
			
		||||
	<div class="twelve wide computer column">
 | 
			
		||||
		<div class="ui attached left aligned segment">
 | 
			
		||||
			<!-- <h4 class="ui header">
 | 
			
		||||
				{{.i18n.Tr "repo.issues.label_templates.title"}}
 | 
			
		||||
				<a target="_blank" rel="noopener noreferrer" href="https://discuss.gogs.io/t/how-to-use-predefined-label-templates/599">
 | 
			
		||||
					<span class="octicon octicon-question"></span>
 | 
			
		||||
				</a>
 | 
			
		||||
			</h4> -->
 | 
			
		||||
			<p>{{.i18n.Tr "repo.issues.label_templates.info"}}</p>
 | 
			
		||||
			<br/>
 | 
			
		||||
			<form class="ui form center" action="{{.Link}}/initialize" method="post">
 | 
			
		||||
@@ -20,6 +14,7 @@
 | 
			
		||||
								<div class="item" data-value="{{$template}}">{{$template}}<br/><i>({{$labels}})</i></div>
 | 
			
		||||
							{{end}}
 | 
			
		||||
						</div>
 | 
			
		||||
						{{svg "octicon-triangle-down" 18 "dropdown icon"}}
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
				<button type="submit" class="ui blue button">{{.i18n.Tr "repo.issues.label_templates.use"}}</button>
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,7 @@
 | 
			
		||||
								{{ .OriginalAuthor }}
 | 
			
		||||
							</span>
 | 
			
		||||
							<span class="text grey">
 | 
			
		||||
								{{$.i18n.Tr "repo.issues.commented_at" (.Issue.HashTag|Escape) $createdStr | Safe}} {{if $.Repository.OriginalURL}}
 | 
			
		||||
								{{$.i18n.Tr "repo.issues.commented_at" (.HashTag|Escape) $createdStr | Safe}} {{if $.Repository.OriginalURL}}
 | 
			
		||||
							</span>
 | 
			
		||||
							<span class="text migrate">
 | 
			
		||||
								({{$.i18n.Tr "repo.migrated_from" ($.Repository.OriginalURL|Escape) ($.Repository.GetOriginalURLHostname|Escape) | Safe }}){{end}}
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,7 @@
 | 
			
		||||
					</span>
 | 
			
		||||
				{{end}}
 | 
			
		||||
			</th>
 | 
			
		||||
			<th class="text grey right age">{{if .LatestCommit}}{{if .LatestCommit.Author}}{{TimeSince .LatestCommit.Author.When $.Lang}}{{end}}{{end}}</th>
 | 
			
		||||
			<th class="text grey right age">{{if .LatestCommit}}{{if .LatestCommit.Committer}}{{TimeSince .LatestCommit.Committer.When $.Lang}}{{end}}{{end}}</th>
 | 
			
		||||
		</tr>
 | 
			
		||||
	</thead>
 | 
			
		||||
	<tbody>
 | 
			
		||||
 
 | 
			
		||||
@@ -496,13 +496,17 @@ export function initRepoPullRequestReview() {
 | 
			
		||||
        <tr class="add-comment" data-line-type="${lineType}">
 | 
			
		||||
          ${isSplit ? `
 | 
			
		||||
            <td class="lines-num"></td>
 | 
			
		||||
            <td class="lines-escape"></td>
 | 
			
		||||
            <td class="lines-type-marker"></td>
 | 
			
		||||
            <td class="add-comment-left"></td>
 | 
			
		||||
            <td class="lines-num"></td>
 | 
			
		||||
            <td class="lines-escape"></td>
 | 
			
		||||
            <td class="lines-type-marker"></td>
 | 
			
		||||
            <td class="add-comment-right"></td>
 | 
			
		||||
          ` : `
 | 
			
		||||
            <td colspan="3" class="lines-num"></td>
 | 
			
		||||
            <td class="lines-num"></td>
 | 
			
		||||
            <td class="lines-num"></td>
 | 
			
		||||
            <td class="lines-escape"></td>
 | 
			
		||||
            <td class="add-comment-left add-comment-right" colspan="2"></td>
 | 
			
		||||
          `}
 | 
			
		||||
        </tr>`);
 | 
			
		||||
 
 | 
			
		||||
@@ -150,13 +150,12 @@ export function initUserAuthWebAuthnRegister() {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!detectWebAuthnSupport()) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  $('#webauthn-error').modal({allowMultiple: false});
 | 
			
		||||
  $('#register-webauthn').on('click', (e) => {
 | 
			
		||||
    e.preventDefault();
 | 
			
		||||
    if (!detectWebAuthnSupport()) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    webAuthnRegisterRequest();
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,13 @@
 | 
			
		||||
    // otherwise some part of the popup will be hidden by viewport boundary
 | 
			
		||||
    max-height: 45vh;
 | 
			
		||||
    max-width: 60vw;
 | 
			
		||||
 | 
			
		||||
    &.ui.right {
 | 
			
		||||
      // Override `.ui.attached.header .right:not(.dropdown) height: 30px;` which would otherwise lead to
 | 
			
		||||
      // the status popup box having its height fixed at 30px. See https://github.com/go-gitea/gitea/issues/18498
 | 
			
		||||
      height: auto;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    overflow: auto;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
 | 
			
		||||
@@ -3039,7 +3046,7 @@ td.blob-excerpt {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.file-info-entry + .file-info-entry {
 | 
			
		||||
  border-left: 1px solid currentColor;
 | 
			
		||||
  border-left: 1px solid currentcolor;
 | 
			
		||||
  margin-left: 8px;
 | 
			
		||||
  padding-left: 8px;
 | 
			
		||||
}
 | 
			
		||||
@@ -3186,7 +3193,7 @@ td.blob-excerpt {
 | 
			
		||||
  background: var(--color-diff-inactive);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.code-diff-split tbody tr td:nth-child(4) {
 | 
			
		||||
.code-diff-split tbody tr td:nth-child(5) {
 | 
			
		||||
  border-left: 1px solid var(--color-secondary);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
.svg {
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  vertical-align: text-top;
 | 
			
		||||
  fill: currentColor;
 | 
			
		||||
  fill: currentcolor;
 | 
			
		||||
 | 
			
		||||
  .middle & {
 | 
			
		||||
    vertical-align: middle;
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  text {
 | 
			
		||||
    fill: currentColor !important;
 | 
			
		||||
    fill: currentcolor !important;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .total-contributions {
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,6 @@ body {
 | 
			
		||||
 | 
			
		||||
.swagger-back-link svg {
 | 
			
		||||
  color: inherit;
 | 
			
		||||
  fill: currentColor;
 | 
			
		||||
  fill: currentcolor;
 | 
			
		||||
  margin-right: .5rem;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user