mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-03 08:02:36 +09:00 
			
		
		
		
	Compare commits
	
		
			104 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					ff1c5815bb | ||
| 
						 | 
					87f8d37be5 | ||
| 
						 | 
					f4b96c1041 | ||
| 
						 | 
					a3f72303d1 | ||
| 
						 | 
					4317806ade | ||
| 
						 | 
					578f19a682 | ||
| 
						 | 
					f9b6404950 | ||
| 
						 | 
					52517e3e23 | ||
| 
						 | 
					36e96e3481 | ||
| 
						 | 
					a765410d0f | ||
| 
						 | 
					43fc2e528c | ||
| 
						 | 
					cb90eda213 | ||
| 
						 | 
					5f9c18b2b3 | ||
| 
						 | 
					4384b85046 | ||
| 
						 | 
					e0973a84a0 | ||
| 
						 | 
					054bc55a1c | ||
| 
						 | 
					4fb718d405 | ||
| 
						 | 
					df35049196 | ||
| 
						 | 
					ce75461380 | ||
| 
						 | 
					cea85c30a4 | ||
| 
						 | 
					6039138323 | ||
| 
						 | 
					eb43e73785 | ||
| 
						 | 
					c077a0361a | ||
| 
						 | 
					6f21a94d18 | ||
| 
						 | 
					8ebf0e68ec | ||
| 
						 | 
					3685cc7660 | ||
| 
						 | 
					9d9ccdbe43 | ||
| 
						 | 
					81b29d6263 | ||
| 
						 | 
					6591f87b28 | ||
| 
						 | 
					efc78c18c1 | ||
| 
						 | 
					f5a3c0dd6c | ||
| 
						 | 
					382101ecc7 | ||
| 
						 | 
					86c3481eff | ||
| 
						 | 
					039eb66c8c | ||
| 
						 | 
					36148ed083 | ||
| 
						 | 
					db4c7dcf15 | ||
| 
						 | 
					bec566282e | ||
| 
						 | 
					fa9be55018 | ||
| 
						 | 
					458239b46d | ||
| 
						 | 
					ae85ee1c6f | ||
| 
						 | 
					08d5a836ef | ||
| 
						 | 
					ad789542b8 | ||
| 
						 | 
					1f7802db97 | ||
| 
						 | 
					c876124efe | ||
| 
						 | 
					3a78ac4b32 | ||
| 
						 | 
					7ebc3da7cb | ||
| 
						 | 
					2e36ba0a00 | ||
| 
						 | 
					69a158dcc2 | ||
| 
						 | 
					913d6f3ff3 | ||
| 
						 | 
					044cb09ae8 | ||
| 
						 | 
					9da8e478dd | ||
| 
						 | 
					c8f3672a88 | ||
| 
						 | 
					edf85b820d | ||
| 
						 | 
					c04a4afac1 | ||
| 
						 | 
					65ad6362d7 | ||
| 
						 | 
					f9a0ae1dd4 | ||
| 
						 | 
					fb26b01688 | ||
| 
						 | 
					63628fdf1c | ||
| 
						 | 
					2e317d3f6e | ||
| 
						 | 
					ce69882180 | ||
| 
						 | 
					649abeda40 | ||
| 
						 | 
					4cfd62cddf | ||
| 
						 | 
					38fc6c75f3 | ||
| 
						 | 
					8671602ba9 | ||
| 
						 | 
					3d08e3a08c | ||
| 
						 | 
					d4a075d738 | ||
| 
						 | 
					bb77e6c12d | ||
| 
						 | 
					fabc0ad157 | ||
| 
						 | 
					a13fb154ae | ||
| 
						 | 
					36c66303df | ||
| 
						 | 
					f65e29c077 | ||
| 
						 | 
					a97c8a8966 | ||
| 
						 | 
					69b7776af5 | ||
| 
						 | 
					18c1edf15c | ||
| 
						 | 
					70ffec4509 | ||
| 
						 | 
					bc196a35e1 | ||
| 
						 | 
					8d31cfbfff | ||
| 
						 | 
					e84a432f76 | ||
| 
						 | 
					1fc9f11253 | ||
| 
						 | 
					0dfe5fa2d6 | ||
| 
						 | 
					1d17313949 | ||
| 
						 | 
					9c318a17f5 | ||
| 
						 | 
					72fa108cbc | ||
| 
						 | 
					db134c5d71 | ||
| 
						 | 
					73b68015de | ||
| 
						 | 
					e4919e414f | ||
| 
						 | 
					f7606de13a | ||
| 
						 | 
					483bda4b2d | ||
| 
						 | 
					edd57028a1 | ||
| 
						 | 
					083b85c655 | ||
| 
						 | 
					d5027b6c09 | ||
| 
						 | 
					a044ec8b53 | ||
| 
						 | 
					f93d72c09b | ||
| 
						 | 
					2f22337125 | ||
| 
						 | 
					781ad8a79e | ||
| 
						 | 
					cada7202aa | ||
| 
						 | 
					0b331e2213 | ||
| 
						 | 
					0734ca0132 | ||
| 
						 | 
					0b83cc21be | ||
| 
						 | 
					b68e605d56 | ||
| 
						 | 
					42991dc89a | ||
| 
						 | 
					160de9fbda | ||
| 
						 | 
					d644289fcb | ||
| 
						 | 
					fd9ff7cd6f | 
							
								
								
									
										389
									
								
								.drone.yml
									
									
									
									
									
								
							
							
						
						
									
										389
									
								
								.drone.yml
									
									
									
									
									
								
							@@ -13,12 +13,25 @@ trigger:
 | 
				
			|||||||
    - tag
 | 
					    - tag
 | 
				
			||||||
    - pull_request
 | 
					    - pull_request
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					volumes:
 | 
				
			||||||
 | 
					  - name: deps
 | 
				
			||||||
 | 
					    temp: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
steps:
 | 
					steps:
 | 
				
			||||||
  - name: deps-frontend
 | 
					  - name: deps-frontend
 | 
				
			||||||
    pull: always
 | 
					 | 
				
			||||||
    image: node:16
 | 
					    image: node:16
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    commands:
 | 
					    commands:
 | 
				
			||||||
      - make node_modules
 | 
					      - make deps-frontend
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - name: deps-backend
 | 
				
			||||||
 | 
					    image: golang:1.17
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
 | 
					    commands:
 | 
				
			||||||
 | 
					      - make deps-backend
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - name: deps
 | 
				
			||||||
 | 
					        path: /go
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: lint-frontend
 | 
					  - name: lint-frontend
 | 
				
			||||||
    image: node:16
 | 
					    image: node:16
 | 
				
			||||||
@@ -27,17 +40,17 @@ steps:
 | 
				
			|||||||
    depends_on: [deps-frontend]
 | 
					    depends_on: [deps-frontend]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: lint-backend
 | 
					  - name: lint-backend
 | 
				
			||||||
    pull: always
 | 
					 | 
				
			||||||
    image: gitea/test_env:linux-amd64  # https://gitea.com/gitea/test-env
 | 
					    image: gitea/test_env:linux-amd64  # https://gitea.com/gitea/test-env
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    commands:
 | 
					    commands:
 | 
				
			||||||
      - make lint-backend
 | 
					      - make lint-backend
 | 
				
			||||||
    environment:
 | 
					    environment:
 | 
				
			||||||
      GOPROXY: https://goproxy.cn # proxy.golang.org is blocked in China, this proxy is not
 | 
					      GOPROXY: https://goproxy.cn # proxy.golang.org is blocked in China, this proxy is not
 | 
				
			||||||
      GOSUMDB: sum.golang.org
 | 
					      GOSUMDB: sum.golang.org
 | 
				
			||||||
      TAGS: bindata sqlite sqlite_unlock_notify
 | 
					      TAGS: bindata sqlite sqlite_unlock_notify
 | 
				
			||||||
 | 
					    depends_on: [deps-backend]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: lint-backend-windows
 | 
					  - name: lint-backend-windows
 | 
				
			||||||
    pull: always
 | 
					 | 
				
			||||||
    image: gitea/test_env:linux-amd64  # https://gitea.com/gitea/test-env
 | 
					    image: gitea/test_env:linux-amd64  # https://gitea.com/gitea/test-env
 | 
				
			||||||
    commands:
 | 
					    commands:
 | 
				
			||||||
      - make golangci-lint vet
 | 
					      - make golangci-lint vet
 | 
				
			||||||
@@ -47,9 +60,9 @@ steps:
 | 
				
			|||||||
      TAGS: bindata sqlite sqlite_unlock_notify
 | 
					      TAGS: bindata sqlite sqlite_unlock_notify
 | 
				
			||||||
      GOOS: windows
 | 
					      GOOS: windows
 | 
				
			||||||
      GOARCH: amd64
 | 
					      GOARCH: amd64
 | 
				
			||||||
 | 
					    depends_on: [deps-backend]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: lint-backend-gogit
 | 
					  - name: lint-backend-gogit
 | 
				
			||||||
    pull: always
 | 
					 | 
				
			||||||
    image: gitea/test_env:linux-amd64  # https://gitea.com/gitea/test-env
 | 
					    image: gitea/test_env:linux-amd64  # https://gitea.com/gitea/test-env
 | 
				
			||||||
    commands:
 | 
					    commands:
 | 
				
			||||||
      - make lint-backend
 | 
					      - make lint-backend
 | 
				
			||||||
@@ -57,6 +70,7 @@ steps:
 | 
				
			|||||||
      GOPROXY: https://goproxy.cn # proxy.golang.org is blocked in China, this proxy is not
 | 
					      GOPROXY: https://goproxy.cn # proxy.golang.org is blocked in China, this proxy is not
 | 
				
			||||||
      GOSUMDB: sum.golang.org
 | 
					      GOSUMDB: sum.golang.org
 | 
				
			||||||
      TAGS: bindata gogit sqlite sqlite_unlock_notify
 | 
					      TAGS: bindata gogit sqlite sqlite_unlock_notify
 | 
				
			||||||
 | 
					    depends_on: [deps-backend]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: checks-frontend
 | 
					  - name: checks-frontend
 | 
				
			||||||
    image: node:16
 | 
					    image: node:16
 | 
				
			||||||
@@ -65,11 +79,13 @@ steps:
 | 
				
			|||||||
    depends_on: [deps-frontend]
 | 
					    depends_on: [deps-frontend]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: checks-backend
 | 
					  - name: checks-backend
 | 
				
			||||||
    pull: always
 | 
					 | 
				
			||||||
    image: golang:1.17
 | 
					    image: golang:1.17
 | 
				
			||||||
    commands:
 | 
					    commands:
 | 
				
			||||||
      - make checks-backend
 | 
					      - make checks-backend
 | 
				
			||||||
    depends_on: [lint-backend]
 | 
					    depends_on: [deps-backend]
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - name: deps
 | 
				
			||||||
 | 
					        path: /go
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: test-frontend
 | 
					  - name: test-frontend
 | 
				
			||||||
    image: node:16
 | 
					    image: node:16
 | 
				
			||||||
@@ -84,14 +100,17 @@ steps:
 | 
				
			|||||||
    depends_on: [test-frontend]
 | 
					    depends_on: [test-frontend]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: build-backend-no-gcc
 | 
					  - name: build-backend-no-gcc
 | 
				
			||||||
    pull: always
 | 
					 | 
				
			||||||
    image: golang:1.16 # this step is kept as the lowest version of golang that we support
 | 
					    image: golang:1.16 # this step is kept as the lowest version of golang that we support
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    environment:
 | 
					    environment:
 | 
				
			||||||
      GO111MODULE: on
 | 
					      GO111MODULE: on
 | 
				
			||||||
      GOPROXY: https://goproxy.cn
 | 
					      GOPROXY: https://goproxy.cn
 | 
				
			||||||
    commands:
 | 
					    commands:
 | 
				
			||||||
      - go build -o gitea_no_gcc # test if build succeeds without the sqlite tag
 | 
					      - go build -o gitea_no_gcc # test if build succeeds without the sqlite tag
 | 
				
			||||||
    depends_on: [checks-backend]
 | 
					    depends_on: [deps-backend, checks-backend]
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - name: deps
 | 
				
			||||||
 | 
					        path: /go
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: build-backend-arm64
 | 
					  - name: build-backend-arm64
 | 
				
			||||||
    image: golang:1.17
 | 
					    image: golang:1.17
 | 
				
			||||||
@@ -104,7 +123,10 @@ steps:
 | 
				
			|||||||
    commands:
 | 
					    commands:
 | 
				
			||||||
      - make backend # test cross compile
 | 
					      - make backend # test cross compile
 | 
				
			||||||
      - rm ./gitea # clean
 | 
					      - rm ./gitea # clean
 | 
				
			||||||
    depends_on: [checks-backend]
 | 
					    depends_on: [deps-backend, checks-backend]
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - name: deps
 | 
				
			||||||
 | 
					        path: /go
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: build-backend-windows
 | 
					  - name: build-backend-windows
 | 
				
			||||||
    image: golang:1.17
 | 
					    image: golang:1.17
 | 
				
			||||||
@@ -116,7 +138,10 @@ steps:
 | 
				
			|||||||
      TAGS: bindata gogit
 | 
					      TAGS: bindata gogit
 | 
				
			||||||
    commands:
 | 
					    commands:
 | 
				
			||||||
      - go build -o gitea_windows
 | 
					      - go build -o gitea_windows
 | 
				
			||||||
    depends_on: [checks-backend]
 | 
					    depends_on: [deps-backend, checks-backend]
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - name: deps
 | 
				
			||||||
 | 
					        path: /go
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: build-backend-386
 | 
					  - name: build-backend-386
 | 
				
			||||||
    image: golang:1.17
 | 
					    image: golang:1.17
 | 
				
			||||||
@@ -127,7 +152,10 @@ steps:
 | 
				
			|||||||
      GOARCH: 386
 | 
					      GOARCH: 386
 | 
				
			||||||
    commands:
 | 
					    commands:
 | 
				
			||||||
      - go build -o gitea_linux_386 # test if compatible with 32 bit
 | 
					      - go build -o gitea_linux_386 # test if compatible with 32 bit
 | 
				
			||||||
    depends_on: [checks-backend]
 | 
					    depends_on: [deps-backend, checks-backend]
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - name: deps
 | 
				
			||||||
 | 
					        path: /go
 | 
				
			||||||
 | 
					
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
kind: pipeline
 | 
					kind: pipeline
 | 
				
			||||||
@@ -147,21 +175,28 @@ trigger:
 | 
				
			|||||||
    - tag
 | 
					    - tag
 | 
				
			||||||
    - pull_request
 | 
					    - pull_request
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					volumes:
 | 
				
			||||||
 | 
					  - name: deps
 | 
				
			||||||
 | 
					    temp: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
services:
 | 
					services:
 | 
				
			||||||
  - name: mysql
 | 
					  - name: mysql
 | 
				
			||||||
    image: mysql:5.7
 | 
					    image: mysql:5.7
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    environment:
 | 
					    environment:
 | 
				
			||||||
      MYSQL_ALLOW_EMPTY_PASSWORD: yes
 | 
					      MYSQL_ALLOW_EMPTY_PASSWORD: yes
 | 
				
			||||||
      MYSQL_DATABASE: test
 | 
					      MYSQL_DATABASE: test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: mysql8
 | 
					  - name: mysql8
 | 
				
			||||||
    image: mysql:8
 | 
					    image: mysql:8
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    environment:
 | 
					    environment:
 | 
				
			||||||
      MYSQL_ALLOW_EMPTY_PASSWORD: yes
 | 
					      MYSQL_ALLOW_EMPTY_PASSWORD: yes
 | 
				
			||||||
      MYSQL_DATABASE: testgitea
 | 
					      MYSQL_DATABASE: testgitea
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: mssql
 | 
					  - name: mssql
 | 
				
			||||||
    image: mcr.microsoft.com/mssql/server:latest
 | 
					    image: mcr.microsoft.com/mssql/server:latest
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    environment:
 | 
					    environment:
 | 
				
			||||||
      ACCEPT_EULA: Y
 | 
					      ACCEPT_EULA: Y
 | 
				
			||||||
      MSSQL_PID: Standard
 | 
					      MSSQL_PID: Standard
 | 
				
			||||||
@@ -169,14 +204,17 @@ services:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  - name: ldap
 | 
					  - name: ldap
 | 
				
			||||||
    image: gitea/test-openldap:latest
 | 
					    image: gitea/test-openldap:latest
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: elasticsearch
 | 
					  - name: elasticsearch
 | 
				
			||||||
 | 
					    image: elasticsearch:7.5.0
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    environment:
 | 
					    environment:
 | 
				
			||||||
      discovery.type: single-node
 | 
					      discovery.type: single-node
 | 
				
			||||||
    image: elasticsearch:7.5.0
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: minio
 | 
					  - name: minio
 | 
				
			||||||
    image: minio/minio:RELEASE.2021-03-12T00-00-47Z
 | 
					    image: minio/minio:RELEASE.2021-03-12T00-00-47Z
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    commands:
 | 
					    commands:
 | 
				
			||||||
    - minio server /data
 | 
					    - minio server /data
 | 
				
			||||||
    environment:
 | 
					    environment:
 | 
				
			||||||
@@ -186,6 +224,7 @@ services:
 | 
				
			|||||||
steps:
 | 
					steps:
 | 
				
			||||||
  - name: fetch-tags
 | 
					  - name: fetch-tags
 | 
				
			||||||
    image: docker:git
 | 
					    image: docker:git
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    commands:
 | 
					    commands:
 | 
				
			||||||
      - git fetch --tags --force
 | 
					      - git fetch --tags --force
 | 
				
			||||||
    when:
 | 
					    when:
 | 
				
			||||||
@@ -193,19 +232,28 @@ steps:
 | 
				
			|||||||
        exclude:
 | 
					        exclude:
 | 
				
			||||||
          - pull_request
 | 
					          - pull_request
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: tag-pre-condition
 | 
					  - name: deps-backend
 | 
				
			||||||
 | 
					    image: golang:1.17
 | 
				
			||||||
    pull: always
 | 
					    pull: always
 | 
				
			||||||
 | 
					    commands:
 | 
				
			||||||
 | 
					      - make deps-backend
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - name: deps
 | 
				
			||||||
 | 
					        path: /go
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - name: tag-pre-condition
 | 
				
			||||||
    image: drone/git
 | 
					    image: drone/git
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    commands:
 | 
					    commands:
 | 
				
			||||||
      - git update-ref refs/heads/tag_test ${DRONE_COMMIT_SHA}
 | 
					      - git update-ref refs/heads/tag_test ${DRONE_COMMIT_SHA}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: prepare-test-env
 | 
					  - name: prepare-test-env
 | 
				
			||||||
    image: gitea/test_env:linux-amd64  # https://gitea.com/gitea/test-env
 | 
					    image: gitea/test_env:linux-amd64  # https://gitea.com/gitea/test-env
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    commands:
 | 
					    commands:
 | 
				
			||||||
      - ./build/test-env-prepare.sh
 | 
					      - ./build/test-env-prepare.sh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: build
 | 
					  - name: build
 | 
				
			||||||
    pull: always
 | 
					 | 
				
			||||||
    image: gitea/test_env:linux-amd64  # https://gitea.com/gitea/test-env
 | 
					    image: gitea/test_env:linux-amd64  # https://gitea.com/gitea/test-env
 | 
				
			||||||
    user: gitea
 | 
					    user: gitea
 | 
				
			||||||
    commands:
 | 
					    commands:
 | 
				
			||||||
@@ -215,8 +263,10 @@ steps:
 | 
				
			|||||||
      GOPROXY: https://goproxy.cn # proxy.golang.org is blocked in China, this proxy is not
 | 
					      GOPROXY: https://goproxy.cn # proxy.golang.org is blocked in China, this proxy is not
 | 
				
			||||||
      GOSUMDB: sum.golang.org
 | 
					      GOSUMDB: sum.golang.org
 | 
				
			||||||
      TAGS: bindata sqlite sqlite_unlock_notify
 | 
					      TAGS: bindata sqlite sqlite_unlock_notify
 | 
				
			||||||
    depends_on:
 | 
					    depends_on: [deps-backend, prepare-test-env]
 | 
				
			||||||
      - prepare-test-env
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - name: deps
 | 
				
			||||||
 | 
					        path: /go
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: unit-test
 | 
					  - name: unit-test
 | 
				
			||||||
    image: gitea/test_env:linux-amd64  # https://gitea.com/gitea/test-env
 | 
					    image: gitea/test_env:linux-amd64  # https://gitea.com/gitea/test-env
 | 
				
			||||||
@@ -229,9 +279,12 @@ steps:
 | 
				
			|||||||
      RACE_ENABLED: true
 | 
					      RACE_ENABLED: true
 | 
				
			||||||
      GITHUB_READ_TOKEN:
 | 
					      GITHUB_READ_TOKEN:
 | 
				
			||||||
        from_secret: github_read_token
 | 
					        from_secret: github_read_token
 | 
				
			||||||
 | 
					    depends_on: [deps-backend, prepare-test-env]
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - name: deps
 | 
				
			||||||
 | 
					        path: /go
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: unit-test-gogit
 | 
					  - name: unit-test-gogit
 | 
				
			||||||
    pull: always
 | 
					 | 
				
			||||||
    image: gitea/test_env:linux-amd64  # https://gitea.com/gitea/test-env
 | 
					    image: gitea/test_env:linux-amd64  # https://gitea.com/gitea/test-env
 | 
				
			||||||
    user: gitea
 | 
					    user: gitea
 | 
				
			||||||
    commands:
 | 
					    commands:
 | 
				
			||||||
@@ -242,6 +295,10 @@ steps:
 | 
				
			|||||||
      RACE_ENABLED: true
 | 
					      RACE_ENABLED: true
 | 
				
			||||||
      GITHUB_READ_TOKEN:
 | 
					      GITHUB_READ_TOKEN:
 | 
				
			||||||
        from_secret: github_read_token
 | 
					        from_secret: github_read_token
 | 
				
			||||||
 | 
					    depends_on: [deps-backend, prepare-test-env]
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - name: deps
 | 
				
			||||||
 | 
					        path: /go
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: test-mysql
 | 
					  - name: test-mysql
 | 
				
			||||||
    image: gitea/test_env:linux-amd64  # https://gitea.com/gitea/test-env
 | 
					    image: gitea/test_env:linux-amd64  # https://gitea.com/gitea/test-env
 | 
				
			||||||
@@ -255,8 +312,10 @@ steps:
 | 
				
			|||||||
      TEST_LDAP: 1
 | 
					      TEST_LDAP: 1
 | 
				
			||||||
      USE_REPO_TEST_DIR: 1
 | 
					      USE_REPO_TEST_DIR: 1
 | 
				
			||||||
      TEST_INDEXER_CODE_ES_URL: "http://elastic:changeme@elasticsearch:9200"
 | 
					      TEST_INDEXER_CODE_ES_URL: "http://elastic:changeme@elasticsearch:9200"
 | 
				
			||||||
    depends_on:
 | 
					    depends_on: [build]
 | 
				
			||||||
      - build
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - name: deps
 | 
				
			||||||
 | 
					        path: /go
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: test-mysql8
 | 
					  - name: test-mysql8
 | 
				
			||||||
    image: gitea/test_env:linux-amd64  # https://gitea.com/gitea/test-env
 | 
					    image: gitea/test_env:linux-amd64  # https://gitea.com/gitea/test-env
 | 
				
			||||||
@@ -269,8 +328,10 @@ steps:
 | 
				
			|||||||
      RACE_ENABLED: true
 | 
					      RACE_ENABLED: true
 | 
				
			||||||
      TEST_LDAP: 1
 | 
					      TEST_LDAP: 1
 | 
				
			||||||
      USE_REPO_TEST_DIR: 1
 | 
					      USE_REPO_TEST_DIR: 1
 | 
				
			||||||
    depends_on:
 | 
					    depends_on: [build]
 | 
				
			||||||
      - build
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - name: deps
 | 
				
			||||||
 | 
					        path: /go
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: test-mssql
 | 
					  - name: test-mssql
 | 
				
			||||||
    image: gitea/test_env:linux-amd64  # https://gitea.com/gitea/test-env
 | 
					    image: gitea/test_env:linux-amd64  # https://gitea.com/gitea/test-env
 | 
				
			||||||
@@ -283,8 +344,10 @@ steps:
 | 
				
			|||||||
      RACE_ENABLED: true
 | 
					      RACE_ENABLED: true
 | 
				
			||||||
      TEST_LDAP: 1
 | 
					      TEST_LDAP: 1
 | 
				
			||||||
      USE_REPO_TEST_DIR: 1
 | 
					      USE_REPO_TEST_DIR: 1
 | 
				
			||||||
    depends_on:
 | 
					    depends_on: [build]
 | 
				
			||||||
      - build
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - name: deps
 | 
				
			||||||
 | 
					        path: /go
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: generate-coverage
 | 
					  - name: generate-coverage
 | 
				
			||||||
    image: golang:1.17
 | 
					    image: golang:1.17
 | 
				
			||||||
@@ -293,9 +356,7 @@ steps:
 | 
				
			|||||||
    environment:
 | 
					    environment:
 | 
				
			||||||
      GOPROXY: https://goproxy.cn
 | 
					      GOPROXY: https://goproxy.cn
 | 
				
			||||||
      TAGS: bindata
 | 
					      TAGS: bindata
 | 
				
			||||||
    depends_on:
 | 
					    depends_on: [unit-test, test-mysql]
 | 
				
			||||||
      - unit-test
 | 
					 | 
				
			||||||
      - test-mysql
 | 
					 | 
				
			||||||
    when:
 | 
					    when:
 | 
				
			||||||
      branch:
 | 
					      branch:
 | 
				
			||||||
        - main
 | 
					        - main
 | 
				
			||||||
@@ -304,15 +365,14 @@ steps:
 | 
				
			|||||||
        - pull_request
 | 
					        - pull_request
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: coverage-codecov
 | 
					  - name: coverage-codecov
 | 
				
			||||||
 | 
					    image: woodpeckerci/plugin-codecov:next-alpine
 | 
				
			||||||
    pull: always
 | 
					    pull: always
 | 
				
			||||||
    image: plugins/codecov
 | 
					 | 
				
			||||||
    settings:
 | 
					    settings:
 | 
				
			||||||
      files:
 | 
					      files:
 | 
				
			||||||
        - coverage.all
 | 
					        - coverage.all
 | 
				
			||||||
      token:
 | 
					      token:
 | 
				
			||||||
        from_secret: codecov_token
 | 
					        from_secret: codecov_token
 | 
				
			||||||
    depends_on:
 | 
					    depends_on: [generate-coverage]
 | 
				
			||||||
      - generate-coverage
 | 
					 | 
				
			||||||
    when:
 | 
					    when:
 | 
				
			||||||
      branch:
 | 
					      branch:
 | 
				
			||||||
        - main
 | 
					        - main
 | 
				
			||||||
@@ -337,6 +397,10 @@ trigger:
 | 
				
			|||||||
    - tag
 | 
					    - tag
 | 
				
			||||||
    - pull_request
 | 
					    - pull_request
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					volumes:
 | 
				
			||||||
 | 
					  - name: deps
 | 
				
			||||||
 | 
					    temp: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
services:
 | 
					services:
 | 
				
			||||||
  - name: pgsql
 | 
					  - name: pgsql
 | 
				
			||||||
    pull: default
 | 
					    pull: default
 | 
				
			||||||
@@ -352,6 +416,7 @@ services:
 | 
				
			|||||||
steps:
 | 
					steps:
 | 
				
			||||||
  - name: fetch-tags
 | 
					  - name: fetch-tags
 | 
				
			||||||
    image: docker:git
 | 
					    image: docker:git
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    commands:
 | 
					    commands:
 | 
				
			||||||
      - git fetch --tags --force
 | 
					      - git fetch --tags --force
 | 
				
			||||||
    when:
 | 
					    when:
 | 
				
			||||||
@@ -359,13 +424,22 @@ steps:
 | 
				
			|||||||
        exclude:
 | 
					        exclude:
 | 
				
			||||||
          - pull_request
 | 
					          - pull_request
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - name: deps-backend
 | 
				
			||||||
 | 
					    image: golang:1.17
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
 | 
					    commands:
 | 
				
			||||||
 | 
					      - make deps-backend
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - name: deps
 | 
				
			||||||
 | 
					        path: /go
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: prepare-test-env
 | 
					  - name: prepare-test-env
 | 
				
			||||||
    image: gitea/test_env:linux-arm64  # https://gitea.com/gitea/test-env
 | 
					    image: gitea/test_env:linux-arm64  # https://gitea.com/gitea/test-env
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    commands:
 | 
					    commands:
 | 
				
			||||||
      - ./build/test-env-prepare.sh
 | 
					      - ./build/test-env-prepare.sh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: build
 | 
					  - name: build
 | 
				
			||||||
    pull: always
 | 
					 | 
				
			||||||
    image: gitea/test_env:linux-arm64  # https://gitea.com/gitea/test-env
 | 
					    image: gitea/test_env:linux-arm64  # https://gitea.com/gitea/test-env
 | 
				
			||||||
    user: gitea
 | 
					    user: gitea
 | 
				
			||||||
    commands:
 | 
					    commands:
 | 
				
			||||||
@@ -375,8 +449,10 @@ steps:
 | 
				
			|||||||
      GOPROXY: https://goproxy.cn # proxy.golang.org is blocked in China, this proxy is not
 | 
					      GOPROXY: https://goproxy.cn # proxy.golang.org is blocked in China, this proxy is not
 | 
				
			||||||
      GOSUMDB: sum.golang.org
 | 
					      GOSUMDB: sum.golang.org
 | 
				
			||||||
      TAGS: bindata gogit sqlite sqlite_unlock_notify
 | 
					      TAGS: bindata gogit sqlite sqlite_unlock_notify
 | 
				
			||||||
    depends_on:
 | 
					    depends_on: [deps-backend, prepare-test-env]
 | 
				
			||||||
      - prepare-test-env
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - name: deps
 | 
				
			||||||
 | 
					        path: /go
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: test-sqlite
 | 
					  - name: test-sqlite
 | 
				
			||||||
    image: gitea/test_env:linux-arm64  # https://gitea.com/gitea/test-env
 | 
					    image: gitea/test_env:linux-arm64  # https://gitea.com/gitea/test-env
 | 
				
			||||||
@@ -389,8 +465,10 @@ steps:
 | 
				
			|||||||
      RACE_ENABLED: true
 | 
					      RACE_ENABLED: true
 | 
				
			||||||
      TEST_TAGS: gogit sqlite sqlite_unlock_notify
 | 
					      TEST_TAGS: gogit sqlite sqlite_unlock_notify
 | 
				
			||||||
      USE_REPO_TEST_DIR: 1
 | 
					      USE_REPO_TEST_DIR: 1
 | 
				
			||||||
    depends_on:
 | 
					    depends_on: [build]
 | 
				
			||||||
      - build
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - name: deps
 | 
				
			||||||
 | 
					        path: /go
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: test-pgsql
 | 
					  - name: test-pgsql
 | 
				
			||||||
    image: gitea/test_env:linux-arm64  # https://gitea.com/gitea/test-env
 | 
					    image: gitea/test_env:linux-arm64  # https://gitea.com/gitea/test-env
 | 
				
			||||||
@@ -404,8 +482,10 @@ steps:
 | 
				
			|||||||
      TEST_TAGS: gogit
 | 
					      TEST_TAGS: gogit
 | 
				
			||||||
      TEST_LDAP: 1
 | 
					      TEST_LDAP: 1
 | 
				
			||||||
      USE_REPO_TEST_DIR: 1
 | 
					      USE_REPO_TEST_DIR: 1
 | 
				
			||||||
    depends_on:
 | 
					    depends_on: [build]
 | 
				
			||||||
      - build
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - name: deps
 | 
				
			||||||
 | 
					        path: /go
 | 
				
			||||||
 | 
					
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
kind: pipeline
 | 
					kind: pipeline
 | 
				
			||||||
@@ -425,8 +505,8 @@ trigger:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
steps:
 | 
					steps:
 | 
				
			||||||
  - name: download
 | 
					  - name: download
 | 
				
			||||||
    pull: always
 | 
					 | 
				
			||||||
    image: jonasfranz/crowdin
 | 
					    image: jonasfranz/crowdin
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    settings:
 | 
					    settings:
 | 
				
			||||||
      download: true
 | 
					      download: true
 | 
				
			||||||
      export_dir: options/locale/
 | 
					      export_dir: options/locale/
 | 
				
			||||||
@@ -437,14 +517,14 @@ steps:
 | 
				
			|||||||
        from_secret: crowdin_key
 | 
					        from_secret: crowdin_key
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: update
 | 
					  - name: update
 | 
				
			||||||
    pull: default
 | 
					 | 
				
			||||||
    image: alpine:3.13
 | 
					    image: alpine:3.13
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    commands:
 | 
					    commands:
 | 
				
			||||||
      - ./build/update-locales.sh
 | 
					      - ./build/update-locales.sh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: push
 | 
					  - name: push
 | 
				
			||||||
    pull: always
 | 
					 | 
				
			||||||
    image: appleboy/drone-git-push
 | 
					    image: appleboy/drone-git-push
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    settings:
 | 
					    settings:
 | 
				
			||||||
      author_email: "teabot@gitea.io"
 | 
					      author_email: "teabot@gitea.io"
 | 
				
			||||||
      author_name: GiteaBot
 | 
					      author_name: GiteaBot
 | 
				
			||||||
@@ -457,8 +537,8 @@ steps:
 | 
				
			|||||||
        from_secret: git_push_ssh_key
 | 
					        from_secret: git_push_ssh_key
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: upload_translations
 | 
					  - name: upload_translations
 | 
				
			||||||
    pull: always
 | 
					 | 
				
			||||||
    image: jonasfranz/crowdin
 | 
					    image: jonasfranz/crowdin
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    settings:
 | 
					    settings:
 | 
				
			||||||
      files:
 | 
					      files:
 | 
				
			||||||
        locale_en-US.ini: options/locale/locale_en-US.ini
 | 
					        locale_en-US.ini: options/locale/locale_en-US.ini
 | 
				
			||||||
@@ -488,12 +568,13 @@ trigger:
 | 
				
			|||||||
steps:
 | 
					steps:
 | 
				
			||||||
  - name: download
 | 
					  - name: download
 | 
				
			||||||
    image: golang:1.17
 | 
					    image: golang:1.17
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    commands:
 | 
					    commands:
 | 
				
			||||||
      - timeout -s ABRT 40m make generate-license generate-gitignore
 | 
					      - timeout -s ABRT 40m make generate-license generate-gitignore
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: push
 | 
					  - name: push
 | 
				
			||||||
    pull: always
 | 
					 | 
				
			||||||
    image: appleboy/drone-git-push
 | 
					    image: appleboy/drone-git-push
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    settings:
 | 
					    settings:
 | 
				
			||||||
      author_email: "teabot@gitea.io"
 | 
					      author_email: "teabot@gitea.io"
 | 
				
			||||||
      author_name: GiteaBot
 | 
					      author_name: GiteaBot
 | 
				
			||||||
@@ -529,15 +610,35 @@ depends_on:
 | 
				
			|||||||
  - testing-amd64
 | 
					  - testing-amd64
 | 
				
			||||||
  - testing-arm64
 | 
					  - testing-arm64
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					volumes:
 | 
				
			||||||
 | 
					  - name: deps
 | 
				
			||||||
 | 
					    temp: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
steps:
 | 
					steps:
 | 
				
			||||||
  - name: fetch-tags
 | 
					  - name: fetch-tags
 | 
				
			||||||
    image: docker:git
 | 
					    image: docker:git
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    commands:
 | 
					    commands:
 | 
				
			||||||
      - git fetch --tags --force
 | 
					      - git fetch --tags --force
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: static
 | 
					  - name: deps-frontend
 | 
				
			||||||
 | 
					    image: node:16
 | 
				
			||||||
    pull: always
 | 
					    pull: always
 | 
				
			||||||
 | 
					    commands:
 | 
				
			||||||
 | 
					      - make deps-frontend
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - name: deps-backend
 | 
				
			||||||
 | 
					    image: golang:1.17
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
 | 
					    commands:
 | 
				
			||||||
 | 
					      - make deps-backend
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - name: deps
 | 
				
			||||||
 | 
					        path: /go
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - name: static
 | 
				
			||||||
    image: techknowlogick/xgo:go-1.17.x
 | 
					    image: techknowlogick/xgo:go-1.17.x
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    commands:
 | 
					    commands:
 | 
				
			||||||
      - curl -sL https://deb.nodesource.com/setup_16.x | bash - && apt-get install -y nodejs
 | 
					      - curl -sL https://deb.nodesource.com/setup_16.x | bash - && apt-get install -y nodejs
 | 
				
			||||||
      - export PATH=$PATH:$GOPATH/bin
 | 
					      - export PATH=$PATH:$GOPATH/bin
 | 
				
			||||||
@@ -545,10 +646,13 @@ steps:
 | 
				
			|||||||
    environment:
 | 
					    environment:
 | 
				
			||||||
      GOPROXY: https://goproxy.cn # proxy.golang.org is blocked in China, this proxy is not
 | 
					      GOPROXY: https://goproxy.cn # proxy.golang.org is blocked in China, this proxy is not
 | 
				
			||||||
      TAGS: bindata sqlite sqlite_unlock_notify
 | 
					      TAGS: bindata sqlite sqlite_unlock_notify
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - name: deps
 | 
				
			||||||
 | 
					        path: /go
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: gpg-sign
 | 
					  - name: gpg-sign
 | 
				
			||||||
    pull: always
 | 
					 | 
				
			||||||
    image: plugins/gpgsign:1
 | 
					    image: plugins/gpgsign:1
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    settings:
 | 
					    settings:
 | 
				
			||||||
      detach_sign: true
 | 
					      detach_sign: true
 | 
				
			||||||
      excludes:
 | 
					      excludes:
 | 
				
			||||||
@@ -562,12 +666,12 @@ steps:
 | 
				
			|||||||
        from_secret: gpgsign_passphrase
 | 
					        from_secret: gpgsign_passphrase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: release-branch
 | 
					  - name: release-branch
 | 
				
			||||||
    pull: always
 | 
					 | 
				
			||||||
    image: woodpeckerci/plugin-s3:latest
 | 
					    image: woodpeckerci/plugin-s3:latest
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    settings:
 | 
					    settings:
 | 
				
			||||||
      acl: public-read
 | 
					      acl: public-read
 | 
				
			||||||
      bucket: gitea-artifacts
 | 
					      bucket: gitea-artifacts
 | 
				
			||||||
      endpoint: https://storage.gitea.io
 | 
					      endpoint: https://ams3.digitaloceanspaces.com
 | 
				
			||||||
      path_style: true
 | 
					      path_style: true
 | 
				
			||||||
      source: "dist/release/*"
 | 
					      source: "dist/release/*"
 | 
				
			||||||
      strip_prefix: dist/release/
 | 
					      strip_prefix: dist/release/
 | 
				
			||||||
@@ -588,7 +692,7 @@ steps:
 | 
				
			|||||||
    settings:
 | 
					    settings:
 | 
				
			||||||
      acl: public-read
 | 
					      acl: public-read
 | 
				
			||||||
      bucket: gitea-artifacts
 | 
					      bucket: gitea-artifacts
 | 
				
			||||||
      endpoint: https://storage.gitea.io
 | 
					      endpoint: https://ams3.digitaloceanspaces.com
 | 
				
			||||||
      path_style: true
 | 
					      path_style: true
 | 
				
			||||||
      source: "dist/release/*"
 | 
					      source: "dist/release/*"
 | 
				
			||||||
      strip_prefix: dist/release/
 | 
					      strip_prefix: dist/release/
 | 
				
			||||||
@@ -624,16 +728,35 @@ depends_on:
 | 
				
			|||||||
  - testing-arm64
 | 
					  - testing-arm64
 | 
				
			||||||
  - testing-amd64
 | 
					  - testing-amd64
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					volumes:
 | 
				
			||||||
 | 
					  - name: deps
 | 
				
			||||||
 | 
					    temp: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
steps:
 | 
					steps:
 | 
				
			||||||
  - name: fetch-tags
 | 
					  - name: fetch-tags
 | 
				
			||||||
    pull: default
 | 
					 | 
				
			||||||
    image: docker:git
 | 
					    image: docker:git
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    commands:
 | 
					    commands:
 | 
				
			||||||
      - git fetch --tags --force
 | 
					      - git fetch --tags --force
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: static
 | 
					  - name: deps-frontend
 | 
				
			||||||
 | 
					    image: node:16
 | 
				
			||||||
    pull: always
 | 
					    pull: always
 | 
				
			||||||
 | 
					    commands:
 | 
				
			||||||
 | 
					      - make deps-frontend
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - name: deps-backend
 | 
				
			||||||
 | 
					    image: golang:1.17
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
 | 
					    commands:
 | 
				
			||||||
 | 
					      - make deps-backend
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - name: deps
 | 
				
			||||||
 | 
					        path: /go
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - name: static
 | 
				
			||||||
    image: techknowlogick/xgo:go-1.17.x
 | 
					    image: techknowlogick/xgo:go-1.17.x
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    commands:
 | 
					    commands:
 | 
				
			||||||
      - curl -sL https://deb.nodesource.com/setup_16.x | bash - && apt-get install -y nodejs
 | 
					      - curl -sL https://deb.nodesource.com/setup_16.x | bash - && apt-get install -y nodejs
 | 
				
			||||||
      - export PATH=$PATH:$GOPATH/bin
 | 
					      - export PATH=$PATH:$GOPATH/bin
 | 
				
			||||||
@@ -641,10 +764,14 @@ steps:
 | 
				
			|||||||
    environment:
 | 
					    environment:
 | 
				
			||||||
      GOPROXY: https://goproxy.cn # proxy.golang.org is blocked in China, this proxy is not
 | 
					      GOPROXY: https://goproxy.cn # proxy.golang.org is blocked in China, this proxy is not
 | 
				
			||||||
      TAGS: bindata sqlite sqlite_unlock_notify
 | 
					      TAGS: bindata sqlite sqlite_unlock_notify
 | 
				
			||||||
 | 
					    depends_on: [fetch-tags]
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - name: deps
 | 
				
			||||||
 | 
					        path: /go
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: gpg-sign
 | 
					  - name: gpg-sign
 | 
				
			||||||
    pull: always
 | 
					 | 
				
			||||||
    image: plugins/gpgsign:1
 | 
					    image: plugins/gpgsign:1
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    settings:
 | 
					    settings:
 | 
				
			||||||
      detach_sign: true
 | 
					      detach_sign: true
 | 
				
			||||||
      excludes:
 | 
					      excludes:
 | 
				
			||||||
@@ -656,14 +783,15 @@ steps:
 | 
				
			|||||||
        from_secret: gpgsign_key
 | 
					        from_secret: gpgsign_key
 | 
				
			||||||
      GPGSIGN_PASSPHRASE:
 | 
					      GPGSIGN_PASSPHRASE:
 | 
				
			||||||
        from_secret: gpgsign_passphrase
 | 
					        from_secret: gpgsign_passphrase
 | 
				
			||||||
 | 
					    depends_on: [static]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: release-tag
 | 
					  - name: release-tag
 | 
				
			||||||
    pull: always
 | 
					 | 
				
			||||||
    image: woodpeckerci/plugin-s3:latest
 | 
					    image: woodpeckerci/plugin-s3:latest
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    settings:
 | 
					    settings:
 | 
				
			||||||
      acl: public-read
 | 
					      acl: public-read
 | 
				
			||||||
      bucket: gitea-artifacts
 | 
					      bucket: gitea-artifacts
 | 
				
			||||||
      endpoint: https://storage.gitea.io
 | 
					      endpoint: https://ams3.digitaloceanspaces.com
 | 
				
			||||||
      path_style: true
 | 
					      path_style: true
 | 
				
			||||||
      source: "dist/release/*"
 | 
					      source: "dist/release/*"
 | 
				
			||||||
      strip_prefix: dist/release/
 | 
					      strip_prefix: dist/release/
 | 
				
			||||||
@@ -673,16 +801,18 @@ steps:
 | 
				
			|||||||
        from_secret: aws_access_key_id
 | 
					        from_secret: aws_access_key_id
 | 
				
			||||||
      AWS_SECRET_ACCESS_KEY:
 | 
					      AWS_SECRET_ACCESS_KEY:
 | 
				
			||||||
        from_secret: aws_secret_access_key
 | 
					        from_secret: aws_secret_access_key
 | 
				
			||||||
 | 
					    depends_on: [gpg-sign]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: github
 | 
					  - name: github
 | 
				
			||||||
    pull: always
 | 
					 | 
				
			||||||
    image: plugins/github-release:1
 | 
					    image: plugins/github-release:1
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    settings:
 | 
					    settings:
 | 
				
			||||||
      files:
 | 
					      files:
 | 
				
			||||||
        - "dist/release/*"
 | 
					        - "dist/release/*"
 | 
				
			||||||
    environment:
 | 
					    environment:
 | 
				
			||||||
      GITHUB_TOKEN:
 | 
					      GITHUB_TOKEN:
 | 
				
			||||||
        from_secret: github_token
 | 
					        from_secret: github_token
 | 
				
			||||||
 | 
					    depends_on: [gpg-sign]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
kind: pipeline
 | 
					kind: pipeline
 | 
				
			||||||
@@ -704,16 +834,16 @@ trigger:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
steps:
 | 
					steps:
 | 
				
			||||||
  - name: build-docs
 | 
					  - name: build-docs
 | 
				
			||||||
    pull: always
 | 
					 | 
				
			||||||
    image: plugins/hugo:latest
 | 
					    image: plugins/hugo:latest
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    commands:
 | 
					    commands:
 | 
				
			||||||
      - apk add --no-cache make bash curl
 | 
					      - apk add --no-cache make bash curl
 | 
				
			||||||
      - cd docs
 | 
					      - cd docs
 | 
				
			||||||
      - make trans-copy clean build
 | 
					      - make trans-copy clean build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: publish-docs
 | 
					  - name: publish-docs
 | 
				
			||||||
    pull: always
 | 
					 | 
				
			||||||
    image: techknowlogick/drone-netlify:latest
 | 
					    image: techknowlogick/drone-netlify:latest
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    settings:
 | 
					    settings:
 | 
				
			||||||
      path: docs/public/
 | 
					      path: docs/public/
 | 
				
			||||||
      site_id: d2260bae-7861-4c02-8646-8f6440b12672
 | 
					      site_id: d2260bae-7861-4c02-8646-8f6440b12672
 | 
				
			||||||
@@ -749,12 +879,13 @@ trigger:
 | 
				
			|||||||
steps:
 | 
					steps:
 | 
				
			||||||
  - name: fetch-tags
 | 
					  - name: fetch-tags
 | 
				
			||||||
    image: docker:git
 | 
					    image: docker:git
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    commands:
 | 
					    commands:
 | 
				
			||||||
      - git fetch --tags --force
 | 
					      - git fetch --tags --force
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: publish
 | 
					  - name: publish
 | 
				
			||||||
    pull: always
 | 
					 | 
				
			||||||
    image: techknowlogick/drone-docker:latest
 | 
					    image: techknowlogick/drone-docker:latest
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    settings:
 | 
					    settings:
 | 
				
			||||||
      auto_tag: true
 | 
					      auto_tag: true
 | 
				
			||||||
      auto_tag_suffix: linux-amd64
 | 
					      auto_tag_suffix: linux-amd64
 | 
				
			||||||
@@ -811,12 +942,13 @@ trigger:
 | 
				
			|||||||
steps:
 | 
					steps:
 | 
				
			||||||
  - name: fetch-tags
 | 
					  - name: fetch-tags
 | 
				
			||||||
    image: docker:git
 | 
					    image: docker:git
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    commands:
 | 
					    commands:
 | 
				
			||||||
      - git fetch --tags --force
 | 
					      - git fetch --tags --force
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: publish
 | 
					  - name: publish
 | 
				
			||||||
    pull: always
 | 
					 | 
				
			||||||
    image: techknowlogick/drone-docker:latest
 | 
					    image: techknowlogick/drone-docker:latest
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    settings:
 | 
					    settings:
 | 
				
			||||||
      auto_tag: false
 | 
					      auto_tag: false
 | 
				
			||||||
      tags: dev-linux-amd64
 | 
					      tags: dev-linux-amd64
 | 
				
			||||||
@@ -850,6 +982,68 @@ steps:
 | 
				
			|||||||
        exclude:
 | 
					        exclude:
 | 
				
			||||||
        - pull_request
 | 
					        - 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
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
 | 
					    commands:
 | 
				
			||||||
 | 
					      - git fetch --tags --force
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - name: publish
 | 
				
			||||||
 | 
					    image: techknowlogick/drone-docker:latest
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
 | 
					    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
 | 
					kind: pipeline
 | 
				
			||||||
type: docker
 | 
					type: docker
 | 
				
			||||||
@@ -868,8 +1062,8 @@ trigger:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
steps:
 | 
					steps:
 | 
				
			||||||
  - name: dryrun
 | 
					  - name: dryrun
 | 
				
			||||||
    pull: always
 | 
					 | 
				
			||||||
    image: techknowlogick/drone-docker:latest
 | 
					    image: techknowlogick/drone-docker:latest
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    settings:
 | 
					    settings:
 | 
				
			||||||
      dry_run: true
 | 
					      dry_run: true
 | 
				
			||||||
      repo: gitea/gitea
 | 
					      repo: gitea/gitea
 | 
				
			||||||
@@ -906,12 +1100,13 @@ trigger:
 | 
				
			|||||||
steps:
 | 
					steps:
 | 
				
			||||||
  - name: fetch-tags
 | 
					  - name: fetch-tags
 | 
				
			||||||
    image: docker:git
 | 
					    image: docker:git
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    commands:
 | 
					    commands:
 | 
				
			||||||
      - git fetch --tags --force
 | 
					      - git fetch --tags --force
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: publish
 | 
					  - name: publish
 | 
				
			||||||
    pull: always
 | 
					 | 
				
			||||||
    image: techknowlogick/drone-docker:latest
 | 
					    image: techknowlogick/drone-docker:latest
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    settings:
 | 
					    settings:
 | 
				
			||||||
      auto_tag: true
 | 
					      auto_tag: true
 | 
				
			||||||
      auto_tag_suffix: linux-arm64
 | 
					      auto_tag_suffix: linux-arm64
 | 
				
			||||||
@@ -968,12 +1163,13 @@ trigger:
 | 
				
			|||||||
steps:
 | 
					steps:
 | 
				
			||||||
  - name: fetch-tags
 | 
					  - name: fetch-tags
 | 
				
			||||||
    image: docker:git
 | 
					    image: docker:git
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    commands:
 | 
					    commands:
 | 
				
			||||||
      - git fetch --tags --force
 | 
					      - git fetch --tags --force
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: publish
 | 
					  - name: publish
 | 
				
			||||||
    pull: always
 | 
					 | 
				
			||||||
    image: techknowlogick/drone-docker:latest
 | 
					    image: techknowlogick/drone-docker:latest
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    settings:
 | 
					    settings:
 | 
				
			||||||
      auto_tag: false
 | 
					      auto_tag: false
 | 
				
			||||||
      tags: dev-linux-arm64
 | 
					      tags: dev-linux-arm64
 | 
				
			||||||
@@ -1006,6 +1202,69 @@ steps:
 | 
				
			|||||||
      event:
 | 
					      event:
 | 
				
			||||||
        exclude:
 | 
					        exclude:
 | 
				
			||||||
        - pull_request
 | 
					        - 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
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
 | 
					    commands:
 | 
				
			||||||
 | 
					      - git fetch --tags --force
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - name: publish
 | 
				
			||||||
 | 
					    image: techknowlogick/drone-docker:latest
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
 | 
					    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
 | 
					kind: pipeline
 | 
				
			||||||
type: docker
 | 
					type: docker
 | 
				
			||||||
@@ -1017,8 +1276,8 @@ platform:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
steps:
 | 
					steps:
 | 
				
			||||||
  - name: manifest-rootless
 | 
					  - name: manifest-rootless
 | 
				
			||||||
    pull: always
 | 
					 | 
				
			||||||
    image: plugins/manifest
 | 
					    image: plugins/manifest
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    settings:
 | 
					    settings:
 | 
				
			||||||
      auto_tag: true
 | 
					      auto_tag: true
 | 
				
			||||||
      ignore_missing: true
 | 
					      ignore_missing: true
 | 
				
			||||||
@@ -1063,6 +1322,7 @@ steps:
 | 
				
			|||||||
  - name: manifest-rootless
 | 
					  - name: manifest-rootless
 | 
				
			||||||
    pull: always
 | 
					    pull: always
 | 
				
			||||||
    image: plugins/manifest
 | 
					    image: plugins/manifest
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    settings:
 | 
					    settings:
 | 
				
			||||||
      auto_tag: false
 | 
					      auto_tag: false
 | 
				
			||||||
      ignore_missing: true
 | 
					      ignore_missing: true
 | 
				
			||||||
@@ -1086,6 +1346,7 @@ steps:
 | 
				
			|||||||
trigger:
 | 
					trigger:
 | 
				
			||||||
  ref:
 | 
					  ref:
 | 
				
			||||||
  - refs/heads/main
 | 
					  - refs/heads/main
 | 
				
			||||||
 | 
					  - "refs/heads/release/v*"
 | 
				
			||||||
  event:
 | 
					  event:
 | 
				
			||||||
    exclude:
 | 
					    exclude:
 | 
				
			||||||
    - cron
 | 
					    - cron
 | 
				
			||||||
@@ -1093,6 +1354,8 @@ trigger:
 | 
				
			|||||||
depends_on:
 | 
					depends_on:
 | 
				
			||||||
  - docker-linux-amd64-release
 | 
					  - docker-linux-amd64-release
 | 
				
			||||||
  - docker-linux-arm64-release
 | 
					  - docker-linux-arm64-release
 | 
				
			||||||
 | 
					  - docker-linux-amd64-release-branch
 | 
				
			||||||
 | 
					  - docker-linux-arm64-release-branch
 | 
				
			||||||
 | 
					
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
kind: pipeline
 | 
					kind: pipeline
 | 
				
			||||||
@@ -1126,14 +1389,16 @@ depends_on:
 | 
				
			|||||||
  - docker-linux-arm64-release
 | 
					  - docker-linux-arm64-release
 | 
				
			||||||
  - docker-linux-amd64-release-version
 | 
					  - docker-linux-amd64-release-version
 | 
				
			||||||
  - docker-linux-arm64-release-version
 | 
					  - docker-linux-arm64-release-version
 | 
				
			||||||
 | 
					  - docker-linux-amd64-release-branch
 | 
				
			||||||
 | 
					  - docker-linux-arm64-release-branch
 | 
				
			||||||
  - docker-manifest
 | 
					  - docker-manifest
 | 
				
			||||||
  - docker-manifest-version
 | 
					  - docker-manifest-version
 | 
				
			||||||
  - docs
 | 
					  - docs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
steps:
 | 
					steps:
 | 
				
			||||||
  - name: discord
 | 
					  - name: discord
 | 
				
			||||||
    pull: always
 | 
					 | 
				
			||||||
    image: appleboy/drone-discord:1.2.4
 | 
					    image: appleboy/drone-discord:1.2.4
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
    settings:
 | 
					    settings:
 | 
				
			||||||
      message: "{{#success build.status}} ✅  Build #{{build.number}} of `{{repo.name}}` succeeded.\n\n📝 Commit by {{commit.author}} on `{{commit.branch}}`:\n``` {{commit.message}} ```\n\n🌐 {{ build.link }} {{else}} ❌  Build #{{build.number}} of `{{repo.name}}` failed.\n\n📝 Commit by {{commit.author}} on `{{commit.branch}}`:\n``` {{commit.message}} ```\n\n🌐 {{ build.link }} {{/success}}\n"
 | 
					      message: "{{#success build.status}} ✅  Build #{{build.number}} of `{{repo.name}}` succeeded.\n\n📝 Commit by {{commit.author}} on `{{commit.branch}}`:\n``` {{commit.message}} ```\n\n🌐 {{ build.link }} {{else}} ❌  Build #{{build.number}} of `{{repo.name}}` failed.\n\n📝 Commit by {{commit.author}} on `{{commit.branch}}`:\n``` {{commit.message}} ```\n\n🌐 {{ build.link }} {{/success}}\n"
 | 
				
			||||||
      webhook_id:
 | 
					      webhook_id:
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -36,6 +36,8 @@ _testmain.go
 | 
				
			|||||||
coverage.all
 | 
					coverage.all
 | 
				
			||||||
cpu.out
 | 
					cpu.out
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/modules/migration/bindata.go
 | 
				
			||||||
 | 
					/modules/migration/bindata.go.hash
 | 
				
			||||||
/modules/options/bindata.go
 | 
					/modules/options/bindata.go
 | 
				
			||||||
/modules/options/bindata.go.hash
 | 
					/modules/options/bindata.go.hash
 | 
				
			||||||
/modules/public/bindata.go
 | 
					/modules/public/bindata.go
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,6 +23,10 @@ linters:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
run:
 | 
					run:
 | 
				
			||||||
  timeout: 3m
 | 
					  timeout: 3m
 | 
				
			||||||
 | 
					  skip-dirs:
 | 
				
			||||||
 | 
					    - node_modules
 | 
				
			||||||
 | 
					    - public
 | 
				
			||||||
 | 
					    - web_src
 | 
				
			||||||
 | 
					
 | 
				
			||||||
linters-settings:
 | 
					linters-settings:
 | 
				
			||||||
  gocritic:
 | 
					  gocritic:
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										135
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										135
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -4,13 +4,122 @@ 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
 | 
					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).
 | 
					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.3](https://github.com/go-gitea/gitea/releases/tag/v1.16.3) - 2022-03-02
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* SECURITY
 | 
				
			||||||
 | 
					 * Git backend ignore replace objects (#18979) (#18980) 
 | 
				
			||||||
 | 
					* ENHANCEMENTS
 | 
				
			||||||
 | 
					  * Adjust error for already locked db and prevent level db lock on malformed connstr (#18923) (#18938)
 | 
				
			||||||
 | 
					* BUGFIXES
 | 
				
			||||||
 | 
					  * Set max text height to prevent overflow (#18862) (#18977)
 | 
				
			||||||
 | 
					  * Fix newAttachmentPaths deletion for DeleteRepository() (#18973) (#18974)
 | 
				
			||||||
 | 
					  * Accounts with WebAuthn only (no TOTP) now exist ... fix code to handle that case (#18897) (#18964)
 | 
				
			||||||
 | 
					  * Send 404 on `/{org}.gpg` (#18959) (#18962)
 | 
				
			||||||
 | 
					  * Fix admin user list pagination (#18957) (#18960)
 | 
				
			||||||
 | 
					  * Fix lfs management setting (#18947) (#18946)
 | 
				
			||||||
 | 
					  * Fix login with email panic when email is not exist (#18942)
 | 
				
			||||||
 | 
					  * Update go-org to v1.6.1 (#18932) (#18933)
 | 
				
			||||||
 | 
					  * Fix `<strong>` html in translation (#18929) (#18931)
 | 
				
			||||||
 | 
					  * Fix page and missing return on unadopted repos API (#18848) (#18927)
 | 
				
			||||||
 | 
					  * Allow adminstrator teams members to see other teams (#18918) (#18919)
 | 
				
			||||||
 | 
					  * Don't treat BOM escape sequence as hidden character. (#18909) (#18910)
 | 
				
			||||||
 | 
					  * Correctly link URLs to users/repos with dashes, dots or underscores (… (#18908)
 | 
				
			||||||
 | 
					  * Fix redirect when using lowercase repo name (#18775) (#18902)
 | 
				
			||||||
 | 
					  * Fix migration v210 (#18893) (#18892)
 | 
				
			||||||
 | 
					  * Fix team management UI (#18887) (18886)
 | 
				
			||||||
 | 
					  * BeforeSourcePath should point to base commit (#18880) (#18799)
 | 
				
			||||||
 | 
					* TRANSLATION
 | 
				
			||||||
 | 
					  * Backport locales from master (#18944)
 | 
				
			||||||
 | 
					* MISC
 | 
				
			||||||
 | 
					  * Don't update email for organisation (#18905) (#18906)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## [1.16.2](https://github.com/go-gitea/gitea/releases/tag/v1.16.2) - 2022-02-24
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* ENHANCEMENTS
 | 
				
			||||||
 | 
					  * Show fullname on issue edits and gpg/ssh signing info (#18828)
 | 
				
			||||||
 | 
					  * Immediately Hammer if second kill is sent (#18823) (#18826)
 | 
				
			||||||
 | 
					  * Allow mermaid render error to wrap (#18791)
 | 
				
			||||||
 | 
					* BUGFIXES
 | 
				
			||||||
 | 
					  * Fix ldap user sync missed email in email_address table (#18786) (#18876) 
 | 
				
			||||||
 | 
					  * Update assignees check to include any writing team and change org sidebar (#18680) (#18873)
 | 
				
			||||||
 | 
					  * Don't report signal: killed errors in serviceRPC (#18850) (#18865)
 | 
				
			||||||
 | 
					  * Fix bug where certain LDAP settings were reverted (#18859)
 | 
				
			||||||
 | 
					  * Update go-org to 1.6.0 (#18824) (#18839)
 | 
				
			||||||
 | 
					  * Fix login with email for ldap users (#18800) (#18836)
 | 
				
			||||||
 | 
					  * Fix bug for get user by email (#18834)
 | 
				
			||||||
 | 
					  * Fix panic in EscapeReader (#18820) (#18821)
 | 
				
			||||||
 | 
					  * Fix ldap loginname (#18789) (#18804)
 | 
				
			||||||
 | 
					  * Remove redundant call to UpdateRepoStats during migration (#18591) (#18794)
 | 
				
			||||||
 | 
					  * In disk_channel queues synchronously push to disk on shutdown (#18415) (#18788)
 | 
				
			||||||
 | 
					  * Fix template bug of LFS lock (#18784) (#18787)
 | 
				
			||||||
 | 
					  * Attempt to fix the webauthn migration again - part 3 (#18770) (#18771)
 | 
				
			||||||
 | 
					  * Send mail to issue/pr assignee/reviewer also when OnMention is set (#18707) (#18765)
 | 
				
			||||||
 | 
					  * Fix a broken link in commits_list_small.tmpl (#18763) (#18764)
 | 
				
			||||||
 | 
					  * Increase the size of the webauthn_credential credential_id field (#18739) (#18756)
 | 
				
			||||||
 | 
					  * Prevent dangling GetAttribute calls (#18754) (#18755)
 | 
				
			||||||
 | 
					  * Fix isempty detection of git repository (#18746) (#18750)
 | 
				
			||||||
 | 
					  * Fix source code line highlighting on external tracker (#18729) (#18740)
 | 
				
			||||||
 | 
					  * Prevent double encoding of branch names in delete branch (#18714) (#18738)
 | 
				
			||||||
 | 
					  * Always set PullRequestWorkInProgressPrefixes in PrepareViewPullInfo (#18713) (#18737)
 | 
				
			||||||
 | 
					  * Fix forked repositories missed tags (#18719) (#18735)
 | 
				
			||||||
 | 
					  * Fix release typo (#18728) (#18731)
 | 
				
			||||||
 | 
					  * Separate the details links of commit-statuses in headers (#18661) (#18730)
 | 
				
			||||||
 | 
					  * Update object repo with the migrated repository (#18684) (#18726)
 | 
				
			||||||
 | 
					  * Fix bug for version update hint (#18701) (#18705)
 | 
				
			||||||
 | 
					  * Fix issue with docker-rootless shimming script (#18690) (#18699)
 | 
				
			||||||
 | 
					  * Let `MinUnitAccessMode` return correct perm (#18675) (#18689)
 | 
				
			||||||
 | 
					  * Prevent security failure due to bad APP_ID (#18678) (#18682)
 | 
				
			||||||
 | 
					  * Restart zero worker if there is still work to do (#18658) (#18672)
 | 
				
			||||||
 | 
					  * If rendering has failed due to a net.OpError stop rendering (#18642) (#18645)
 | 
				
			||||||
 | 
					* TESTING
 | 
				
			||||||
 | 
					  * Ensure git tag tests and others create test repos in tmpdir (#18447) (#18767)
 | 
				
			||||||
 | 
					* BUILD
 | 
				
			||||||
 | 
					  * Reduce CI go module downloads, add make targets (#18708, #18475, #18443) (#18741)
 | 
				
			||||||
 | 
					* MISC
 | 
				
			||||||
 | 
					  * Put buttons back in org dashboard (#18817) (#18825)
 | 
				
			||||||
 | 
					  * Various Mermaid improvements (#18776) (#18780)
 | 
				
			||||||
 | 
					  * C preprocessor colors improvement (#18671) (#18696)
 | 
				
			||||||
 | 
					  * Fix the missing i18n key for update checker (#18646) (#18665)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## [1.16.1](https://github.com/go-gitea/gitea/releases/tag/v1.16.1) - 2022-02-06
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* SECURITY
 | 
				
			||||||
 | 
					  * Update JS dependencies, fix lint (#18389) (#18540)
 | 
				
			||||||
 | 
					* ENHANCEMENTS
 | 
				
			||||||
 | 
					  * Add dropdown icon to label set template dropdown (#18564) (#18571)
 | 
				
			||||||
 | 
					* BUGFIXES
 | 
				
			||||||
 | 
					  * Comments on migrated issues/prs must link to the comment ID (#18630) (#18637)
 | 
				
			||||||
 | 
					  * Stop logging an error when notes are not found (#18626) (#18635)
 | 
				
			||||||
 | 
					  * Ensure that blob-excerpt links work for wiki (#18587) (#18624)
 | 
				
			||||||
 | 
					  * Only attempt to flush queue if the underlying worker pool is not finished (#18593) (#18620)
 | 
				
			||||||
 | 
					  * Ensure commit-statuses box is sized correctly in headers (#18538) (#18606)
 | 
				
			||||||
 | 
					  * Prevent merge messages from being sorted to the top of email chains (#18566) (#18588)
 | 
				
			||||||
 | 
					  * Prevent panic on prohibited user login with oauth2 (#18562) (#18563)
 | 
				
			||||||
 | 
					  * Collaborator trust model should trust collaborators (#18539) (#18557)
 | 
				
			||||||
 | 
					  * Detect conflicts with 3way merge (#18536) (#18537)
 | 
				
			||||||
 | 
					  * In docker rootless use $GITEA_APP_INI if provided (#18524) (#18535)
 | 
				
			||||||
 | 
					  * Add `GetUserTeams` (#18499) (#18531)
 | 
				
			||||||
 | 
					  * Fix review excerpt (#18502) (#18530)
 | 
				
			||||||
 | 
					  * Fix for AvatarURL database type (#18487) (#18529)
 | 
				
			||||||
 | 
					  * Use `ImagedProvider` for gplus oauth2 provider (#18504) (#18505)
 | 
				
			||||||
 | 
					  * Fix OAuth Source Edit Page (#18495) (#18503)
 | 
				
			||||||
 | 
					  * Use "read" value for General Access (#18496) (#18500)
 | 
				
			||||||
 | 
					  * Prevent NPE on partial match of compare URL and allow short SHA1 compare URLs (#18472) (#18473)
 | 
				
			||||||
 | 
					* BUILD
 | 
				
			||||||
 | 
					  * Make docker gitea/gitea:v1.16-dev etc refer to the latest build on that branch (#18551) (#18569)
 | 
				
			||||||
 | 
					* DOCS
 | 
				
			||||||
 | 
					  * Update 1.16.0 changelog to set #17846 as breaking (#18533) (#18534)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## [1.16.0](https://github.com/go-gitea/gitea/releases/tag/v1.16.0) - 2022-01-30
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* BREAKING
 | 
					* BREAKING
 | 
				
			||||||
  * Remove golang vendored directory (#18277)
 | 
					  * Remove golang vendored directory (#18277)
 | 
				
			||||||
  * Paginate releases page & set default page size to 10 (#16857)
 | 
					  * 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)
 | 
					  * Only allow webhook to send requests to allowed hosts (#17482)
 | 
				
			||||||
* SECURITY
 | 
					* SECURITY
 | 
				
			||||||
 | 
					  * Disable content sniffing on `PlainTextBytes` (#18359) (#18365)
 | 
				
			||||||
 | 
					  * Only view milestones from current repo (#18414) (#18417)
 | 
				
			||||||
  * Sanitize user-input on file name (#17666)
 | 
					  * Sanitize user-input on file name (#17666)
 | 
				
			||||||
  * Use `hostmatcher` to replace `matchlist` to improve blocking of bad hosts in Webhooks (#17605)
 | 
					  * Use `hostmatcher` to replace `matchlist` to improve blocking of bad hosts in Webhooks (#17605)
 | 
				
			||||||
* FEATURES
 | 
					* FEATURES
 | 
				
			||||||
@@ -228,6 +337,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)
 | 
					  * Add left padding for chunk header of split diff view (#13397)
 | 
				
			||||||
  * Allow U2F 2FA without TOTP (#11573)
 | 
					  * Allow U2F 2FA without TOTP (#11573)
 | 
				
			||||||
* BUGFIXES
 | 
					* 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)
 | 
					  * Stop trimming preceding and suffixing spaces from editor filenames (#18334)
 | 
				
			||||||
  * Restore propagation of ErrDependenciesLeft (#18325)
 | 
					  * Restore propagation of ErrDependenciesLeft (#18325)
 | 
				
			||||||
  * Fix PR comments UI (#18323)
 | 
					  * Fix PR comments UI (#18323)
 | 
				
			||||||
@@ -295,10 +414,22 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
 | 
				
			|||||||
* BUILD
 | 
					* BUILD
 | 
				
			||||||
  * Add lockfile-check (#18285)
 | 
					  * Add lockfile-check (#18285)
 | 
				
			||||||
  * Don't store assets modified time into generated files (#18193)
 | 
					  * Don't store assets modified time into generated files (#18193)
 | 
				
			||||||
  * Use shadowing script for docker (#17846)
 | 
					 | 
				
			||||||
* MISC
 | 
					* MISC
 | 
				
			||||||
  * Update JS dependencies (#17611)
 | 
					  * 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
 | 
					## [1.15.10](https://github.com/go-gitea/gitea/releases/tag/v1.15.10) - 2022-01-14
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* BUGFIXES
 | 
					* BUGFIXES
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										18
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								Makefile
									
									
									
									
									
								
							@@ -166,6 +166,9 @@ help:
 | 
				
			|||||||
	@echo " - watch-backend                    watch backend files and continuously rebuild"
 | 
						@echo " - watch-backend                    watch backend files and continuously rebuild"
 | 
				
			||||||
	@echo " - clean                            delete backend and integration files"
 | 
						@echo " - clean                            delete backend and integration files"
 | 
				
			||||||
	@echo " - clean-all                        delete backend, frontend and integration files"
 | 
						@echo " - clean-all                        delete backend, frontend and integration files"
 | 
				
			||||||
 | 
						@echo " - deps                             install dependencies"
 | 
				
			||||||
 | 
						@echo " - deps-frontend                    install frontend dependencies"
 | 
				
			||||||
 | 
						@echo " - deps-backend                     install backend dependencies"
 | 
				
			||||||
	@echo " - lint                             lint everything"
 | 
						@echo " - lint                             lint everything"
 | 
				
			||||||
	@echo " - lint-frontend                    lint frontend files"
 | 
						@echo " - lint-frontend                    lint frontend files"
 | 
				
			||||||
	@echo " - lint-backend                     lint backend files"
 | 
						@echo " - lint-backend                     lint backend files"
 | 
				
			||||||
@@ -396,6 +399,11 @@ test-sqlite-migration:  migrations.sqlite.test migrations.individual.sqlite.test
 | 
				
			|||||||
	GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/sqlite.ini ./migrations.sqlite.test
 | 
						GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/sqlite.ini ./migrations.sqlite.test
 | 
				
			||||||
	GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/sqlite.ini ./migrations.individual.sqlite.test
 | 
						GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/sqlite.ini ./migrations.individual.sqlite.test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.PHONY: test-sqlite-migration\#%
 | 
				
			||||||
 | 
					test-sqlite-migration\#%:  migrations.sqlite.test migrations.individual.sqlite.test generate-ini-sqlite
 | 
				
			||||||
 | 
						GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/sqlite.ini ./migrations.individual.sqlite.test -test.run $(subst .,/,$*)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
generate-ini-mysql:
 | 
					generate-ini-mysql:
 | 
				
			||||||
	sed -e 's|{{TEST_MYSQL_HOST}}|${TEST_MYSQL_HOST}|g' \
 | 
						sed -e 's|{{TEST_MYSQL_HOST}}|${TEST_MYSQL_HOST}|g' \
 | 
				
			||||||
		-e 's|{{TEST_MYSQL_DBNAME}}|${TEST_MYSQL_DBNAME}|g' \
 | 
							-e 's|{{TEST_MYSQL_DBNAME}}|${TEST_MYSQL_DBNAME}|g' \
 | 
				
			||||||
@@ -656,6 +664,16 @@ docs:
 | 
				
			|||||||
	fi
 | 
						fi
 | 
				
			||||||
	cd docs; make trans-copy clean build-offline;
 | 
						cd docs; make trans-copy clean build-offline;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.PHONY: deps
 | 
				
			||||||
 | 
					deps: deps-frontend deps-backend
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.PHONY: deps-frontend
 | 
				
			||||||
 | 
					deps-frontend: node_modules
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.PHONY: deps-backend
 | 
				
			||||||
 | 
					deps-backend:
 | 
				
			||||||
 | 
						$(GO) mod download
 | 
				
			||||||
 | 
					
 | 
				
			||||||
node_modules: package-lock.json
 | 
					node_modules: package-lock.json
 | 
				
			||||||
	npm install --no-save
 | 
						npm install --no-save
 | 
				
			||||||
	@touch node_modules
 | 
						@touch node_modules
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -33,10 +33,8 @@ for i in "$@"; do
 | 
				
			|||||||
done
 | 
					done
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if [ -z "$APP_INI_SET" ]; then
 | 
					if [ -z "$APP_INI_SET" ]; then
 | 
				
			||||||
	CONF_ARG="-c \"$APP_INI\""
 | 
						CONF_ARG=("-c" "${GITEA_APP_INI:-$APP_INI}")
 | 
				
			||||||
fi
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Provide FHS compliant defaults
 | 
					# Provide FHS compliant defaults
 | 
				
			||||||
GITEA_WORK_DIR="${GITEA_WORK_DIR:-$WORK_DIR}" exec -a "$0" "$GITEA" $CONF_ARG "$@"
 | 
					GITEA_WORK_DIR="${GITEA_WORK_DIR:-$WORK_DIR}" exec -a "$0" "$GITEA" "${CONF_ARG[@]}"  "$@"
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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}}
 | 
					{{#if build.tags}}
 | 
				
			||||||
tags:
 | 
					tags:
 | 
				
			||||||
{{#each build.tags}}
 | 
					{{#each build.tags}}
 | 
				
			||||||
@@ -8,12 +8,12 @@ tags:
 | 
				
			|||||||
{{/if}}
 | 
					{{/if}}
 | 
				
			||||||
manifests:
 | 
					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:
 | 
					    platform:
 | 
				
			||||||
      architecture: amd64
 | 
					      architecture: amd64
 | 
				
			||||||
      os: linux
 | 
					      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:
 | 
					    platform:
 | 
				
			||||||
      architecture: arm64
 | 
					      architecture: arm64
 | 
				
			||||||
      os: linux
 | 
					      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}}
 | 
					{{#if build.tags}}
 | 
				
			||||||
tags:
 | 
					tags:
 | 
				
			||||||
{{#each build.tags}}
 | 
					{{#each build.tags}}
 | 
				
			||||||
@@ -8,13 +8,13 @@ tags:
 | 
				
			|||||||
{{/if}}
 | 
					{{/if}}
 | 
				
			||||||
manifests:
 | 
					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:
 | 
					    platform:
 | 
				
			||||||
      architecture: amd64
 | 
					      architecture: amd64
 | 
				
			||||||
      os: linux
 | 
					      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:
 | 
					    platform:
 | 
				
			||||||
      architecture: arm64
 | 
					      architecture: arm64
 | 
				
			||||||
      os: linux
 | 
					      os: linux
 | 
				
			||||||
      variant: v8
 | 
					      variant: v8
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -32,11 +32,9 @@ for i in "$@"; do
 | 
				
			|||||||
done
 | 
					done
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if [ -z "$APP_INI_SET" ]; then
 | 
					if [ -z "$APP_INI_SET" ]; then
 | 
				
			||||||
	CONF_ARG="-c \"$APP_INI\""
 | 
						CONF_ARG=("-c" "${GITEA_APP_INI:-$APP_INI}")
 | 
				
			||||||
fi
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Provide docker defaults
 | 
					# Provide docker defaults
 | 
				
			||||||
GITEA_WORK_DIR="${GITEA_WORK_DIR:-$WORK_DIR}" exec -a "$0" "$GITEA" $CONF_ARG "$@"
 | 
					GITEA_WORK_DIR="${GITEA_WORK_DIR:-$WORK_DIR}" exec -a "$0" "$GITEA" "${CONF_ARG[@]}" "$@"
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,7 +18,7 @@ params:
 | 
				
			|||||||
  description: Git with a cup of tea
 | 
					  description: Git with a cup of tea
 | 
				
			||||||
  author: The Gitea Authors
 | 
					  author: The Gitea Authors
 | 
				
			||||||
  website: https://docs.gitea.io
 | 
					  website: https://docs.gitea.io
 | 
				
			||||||
  version: 1.15.10
 | 
					  version: 1.16.0
 | 
				
			||||||
  minGoVersion: 1.16
 | 
					  minGoVersion: 1.16
 | 
				
			||||||
  goVersion: 1.17
 | 
					  goVersion: 1.17
 | 
				
			||||||
  minNodeVersion: 12.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`.
 | 
					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/`
 | 
					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.
 | 
					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
 | 
					```yaml
 | 
				
			||||||
version: "2"
 | 
					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`.
 | 
					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.
 | 
					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.
 | 
					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
 | 
					```yaml
 | 
				
			||||||
version: "3"
 | 
					version: "3"
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										12
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								go.mod
									
									
									
									
									
								
							@@ -30,7 +30,7 @@ require (
 | 
				
			|||||||
	github.com/denisenkom/go-mssqldb v0.10.0
 | 
						github.com/denisenkom/go-mssqldb v0.10.0
 | 
				
			||||||
	github.com/djherbis/buffer v1.2.0
 | 
						github.com/djherbis/buffer v1.2.0
 | 
				
			||||||
	github.com/djherbis/nio/v3 v3.0.1
 | 
						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/dustin/go-humanize v1.0.0
 | 
				
			||||||
	github.com/editorconfig/editorconfig-core-go/v2 v2.4.2
 | 
						github.com/editorconfig/editorconfig-core-go/v2 v2.4.2
 | 
				
			||||||
	github.com/emirpasic/gods v1.12.0
 | 
						github.com/emirpasic/gods v1.12.0
 | 
				
			||||||
@@ -54,7 +54,7 @@ require (
 | 
				
			|||||||
	github.com/golang-jwt/jwt/v4 v4.2.0
 | 
						github.com/golang-jwt/jwt/v4 v4.2.0
 | 
				
			||||||
	github.com/golang/snappy v0.0.4 // indirect
 | 
						github.com/golang/snappy v0.0.4 // indirect
 | 
				
			||||||
	github.com/google/go-github/v39 v39.2.0
 | 
						github.com/google/go-github/v39 v39.2.0
 | 
				
			||||||
	github.com/google/uuid v1.2.0
 | 
						github.com/google/uuid v1.3.0
 | 
				
			||||||
	github.com/gorilla/feeds v1.1.1
 | 
						github.com/gorilla/feeds v1.1.1
 | 
				
			||||||
	github.com/gorilla/mux v1.8.0 // indirect
 | 
						github.com/gorilla/mux v1.8.0 // indirect
 | 
				
			||||||
	github.com/gorilla/sessions v1.2.1
 | 
						github.com/gorilla/sessions v1.2.1
 | 
				
			||||||
@@ -85,7 +85,7 @@ require (
 | 
				
			|||||||
	github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect
 | 
						github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect
 | 
				
			||||||
	github.com/msteinert/pam v0.0.0-20201130170657-e61372126161
 | 
						github.com/msteinert/pam v0.0.0-20201130170657-e61372126161
 | 
				
			||||||
	github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
 | 
						github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
 | 
				
			||||||
	github.com/niklasfasching/go-org v1.5.0
 | 
						github.com/niklasfasching/go-org v1.6.1
 | 
				
			||||||
	github.com/olekukonko/tablewriter v0.0.5 // indirect
 | 
						github.com/olekukonko/tablewriter v0.0.5 // indirect
 | 
				
			||||||
	github.com/oliamb/cutter v0.2.2
 | 
						github.com/oliamb/cutter v0.2.2
 | 
				
			||||||
	github.com/olivere/elastic/v7 v7.0.25
 | 
						github.com/olivere/elastic/v7 v7.0.25
 | 
				
			||||||
@@ -122,9 +122,9 @@ require (
 | 
				
			|||||||
	go.uber.org/multierr v1.7.0 // indirect
 | 
						go.uber.org/multierr v1.7.0 // indirect
 | 
				
			||||||
	go.uber.org/zap v1.19.0 // indirect
 | 
						go.uber.org/zap v1.19.0 // indirect
 | 
				
			||||||
	golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871
 | 
						golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871
 | 
				
			||||||
	golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
 | 
						golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd
 | 
				
			||||||
	golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914
 | 
						golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914
 | 
				
			||||||
	golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1
 | 
						golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e
 | 
				
			||||||
	golang.org/x/text v0.3.7
 | 
						golang.org/x/text v0.3.7
 | 
				
			||||||
	golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 // indirect
 | 
						golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 // indirect
 | 
				
			||||||
	golang.org/x/tools v0.1.0
 | 
						golang.org/x/tools v0.1.0
 | 
				
			||||||
@@ -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/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
 | 
					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
 | 
					exclude github.com/gofrs/uuid v3.2.0+incompatible
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										22
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								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-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 h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ=
 | 
				
			||||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
 | 
					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-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.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
 | 
				
			||||||
github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48=
 | 
					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 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
 | 
				
			||||||
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
 | 
					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/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 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 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
 | 
				
			||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 | 
					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/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 h1:B44qRUFwz/vxPKPISQ1KhvzRi9kZ28RAf6YtjriBZ5k=
 | 
				
			||||||
github.com/goccy/go-json v0.7.4/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 | 
					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/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/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
 | 
				
			||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 | 
					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.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.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.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
				
			||||||
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
 | 
					github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
 | 
				
			||||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
					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.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 | 
				
			||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 | 
					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=
 | 
					github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 | 
				
			||||||
@@ -913,8 +912,8 @@ github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OS
 | 
				
			|||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
 | 
					github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
 | 
				
			||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
 | 
					github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
 | 
				
			||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
 | 
					github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
 | 
				
			||||||
github.com/niklasfasching/go-org v1.5.0 h1:V8IwoSPm/d61bceyWFxxnQLtlvNT+CjiYIhtZLdnMF0=
 | 
					github.com/niklasfasching/go-org v1.6.1 h1:vaGWr6TPqprkAbOJ/+E08mgZxsLM0SCOqWo9D5plj4U=
 | 
				
			||||||
github.com/niklasfasching/go-org v1.5.0/go.mod h1:sSb8ylwnAG+h8MGFDB3R1D5bxf8wA08REfhjShg3kjA=
 | 
					github.com/niklasfasching/go-org v1.6.1/go.mod h1:3m8LIjGNz0ijv6UQzCpDl/bORThFI80/1xz2r1GgIT0=
 | 
				
			||||||
github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ=
 | 
					github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ=
 | 
				
			||||||
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
 | 
					github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
 | 
				
			||||||
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
 | 
					github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
 | 
				
			||||||
@@ -1354,8 +1353,9 @@ golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5o
 | 
				
			|||||||
golang.org/x/net v0.0.0-20210331060903-cb1fcc7394e5/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 | 
					golang.org/x/net v0.0.0-20210331060903-cb1fcc7394e5/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 | 
				
			||||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 | 
					golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 | 
				
			||||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 | 
					golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 | 
				
			||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
 | 
					 | 
				
			||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 | 
					golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 | 
				
			||||||
 | 
					golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
 | 
				
			||||||
 | 
					golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 | 
				
			||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 | 
					golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 | 
				
			||||||
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 | 
					golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 | 
				
			||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 | 
					golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 | 
				
			||||||
@@ -1461,11 +1461,13 @@ golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBc
 | 
				
			|||||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
					golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
					golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
					golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1 h1:kwrAHlwJ0DUBZwQ238v+Uod/3eZ8B2K5rYsUHBQvzmI=
 | 
					 | 
				
			||||||
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
					golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 | 
					golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 | 
				
			||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
 | 
					 | 
				
			||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
					golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
				
			||||||
 | 
					golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
 | 
				
			||||||
 | 
					golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 | 
				
			||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
					golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
				
			||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
					golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
				
			||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
					golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,6 +8,7 @@ import (
 | 
				
			|||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
 | 
						"net/http/httptest"
 | 
				
			||||||
	"net/url"
 | 
						"net/url"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
@@ -262,23 +263,26 @@ func doAPIMergePullRequest(ctx APITestContext, owner, repo string, index int64)
 | 
				
			|||||||
	return func(t *testing.T) {
 | 
						return func(t *testing.T) {
 | 
				
			||||||
		urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge?token=%s",
 | 
							urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge?token=%s",
 | 
				
			||||||
			owner, repo, index, ctx.Token)
 | 
								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 {
 | 
							for i := 0; i < 6; i++ {
 | 
				
			||||||
			err := api.APIError{}
 | 
					 | 
				
			||||||
			DecodeJSON(t, resp, &err)
 | 
					 | 
				
			||||||
			assert.EqualValues(t, "Please try again later", err.Message)
 | 
					 | 
				
			||||||
			queue.GetManager().FlushAll(context.Background(), 5*time.Second)
 | 
					 | 
				
			||||||
			req = NewRequestWithJSON(t, http.MethodPost, urlStr, &forms.MergePullRequestForm{
 | 
								req = NewRequestWithJSON(t, http.MethodPost, urlStr, &forms.MergePullRequestForm{
 | 
				
			||||||
				MergeMessageField: "doAPIMergePullRequest Merge",
 | 
									MergeMessageField: "doAPIMergePullRequest Merge",
 | 
				
			||||||
				Do:                string(repo_model.MergeStyleMerge),
 | 
									Do:                string(repo_model.MergeStyleMerge),
 | 
				
			||||||
			})
 | 
								})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			resp = ctx.Session.MakeRequest(t, req, NoExpectedStatus)
 | 
								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
 | 
							expected := ctx.ExpectedCode
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -445,7 +445,6 @@ func TestAPIRepoTransfer(t *testing.T) {
 | 
				
			|||||||
		expectedStatus int
 | 
							expectedStatus int
 | 
				
			||||||
	}{
 | 
						}{
 | 
				
			||||||
		// Disclaimer for test story: "user1" is an admin, "user2" is normal user and part of in owner team of org "user3"
 | 
							// Disclaimer for test story: "user1" is an admin, "user2" is normal user and part of in owner team of org "user3"
 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Transfer to a user with teams in another org should fail
 | 
							// Transfer to a user with teams in another org should fail
 | 
				
			||||||
		{ctxUserID: 1, newOwner: "user3", teams: &[]int64{5}, expectedStatus: http.StatusForbidden},
 | 
							{ctxUserID: 1, newOwner: "user3", teams: &[]int64{5}, expectedStatus: http.StatusForbidden},
 | 
				
			||||||
		// Transfer to a user with non-existent team IDs should fail
 | 
							// Transfer to a user with non-existent team IDs should fail
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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) {
 | 
					func doGitCloneFail(u *url.URL) func(*testing.T) {
 | 
				
			||||||
	return func(t *testing.T) {
 | 
						return func(t *testing.T) {
 | 
				
			||||||
		tmpDir, err := os.MkdirTemp("", "doGitCloneFail")
 | 
							tmpDir, err := os.MkdirTemp("", "doGitCloneFail")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -69,6 +69,12 @@ func testGit(t *testing.T, u *url.URL) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		t.Run("Clone", doGitClone(dstPath, u))
 | 
							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)
 | 
							little, big := standardCommitAndPushTest(t, dstPath)
 | 
				
			||||||
		littleLFS, bigLFS := lfsCommitAndPushTest(t, dstPath)
 | 
							littleLFS, bigLFS := lfsCommitAndPushTest(t, dstPath)
 | 
				
			||||||
		rawTest(t, &httpContext, little, big, littleLFS, bigLFS)
 | 
							rawTest(t, &httpContext, little, big, littleLFS, bigLFS)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -83,7 +83,7 @@ PROVIDER_CONFIG = integrations/gitea-integration-mssql/data/sessions
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
[log]
 | 
					[log]
 | 
				
			||||||
MODE                 = test,file
 | 
					MODE                 = test,file
 | 
				
			||||||
ROOT_PATH            = mssql-log
 | 
					ROOT_PATH            = {{REPO_TEST_DIR}}mssql-log
 | 
				
			||||||
ROUTER               = ,
 | 
					ROUTER               = ,
 | 
				
			||||||
XORM                 = file
 | 
					XORM                 = file
 | 
				
			||||||
ENABLE_SSH_LOG       = true
 | 
					ENABLE_SSH_LOG       = true
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -101,7 +101,7 @@ PROVIDER_CONFIG = integrations/gitea-integration-mysql/data/sessions
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
[log]
 | 
					[log]
 | 
				
			||||||
MODE                 = test,file
 | 
					MODE                 = test,file
 | 
				
			||||||
ROOT_PATH            = mysql-log
 | 
					ROOT_PATH            = {{REPO_TEST_DIR}}mysql-log
 | 
				
			||||||
ROUTER               = ,
 | 
					ROUTER               = ,
 | 
				
			||||||
XORM                 = file
 | 
					XORM                 = file
 | 
				
			||||||
ENABLE_SSH_LOG       = true
 | 
					ENABLE_SSH_LOG       = true
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -80,7 +80,7 @@ PROVIDER_CONFIG = integrations/gitea-integration-mysql8/data/sessions
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
[log]
 | 
					[log]
 | 
				
			||||||
MODE                 = test,file
 | 
					MODE                 = test,file
 | 
				
			||||||
ROOT_PATH            = mysql8-log
 | 
					ROOT_PATH            = {{REPO_TEST_DIR}}mysql8-log
 | 
				
			||||||
ROUTER               = ,
 | 
					ROUTER               = ,
 | 
				
			||||||
XORM                 = file
 | 
					XORM                 = file
 | 
				
			||||||
ENABLE_SSH_LOG       = true
 | 
					ENABLE_SSH_LOG       = true
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -84,7 +84,7 @@ PROVIDER_CONFIG = integrations/gitea-integration-pgsql/data/sessions
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
[log]
 | 
					[log]
 | 
				
			||||||
MODE                 = test,file
 | 
					MODE                 = test,file
 | 
				
			||||||
ROOT_PATH            = pgsql-log
 | 
					ROOT_PATH            = {{REPO_TEST_DIR}}pgsql-log
 | 
				
			||||||
ROUTER               = ,
 | 
					ROUTER               = ,
 | 
				
			||||||
XORM                 = file
 | 
					XORM                 = file
 | 
				
			||||||
ENABLE_SSH_LOG       = true
 | 
					ENABLE_SSH_LOG       = true
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -51,8 +51,6 @@ func TestSignin(t *testing.T) {
 | 
				
			|||||||
		{username: "wrongUsername", password: "password", message: i18n.Tr("en", "form.username_password_incorrect")},
 | 
							{username: "wrongUsername", password: "password", message: i18n.Tr("en", "form.username_password_incorrect")},
 | 
				
			||||||
		{username: "user15", password: "wrongPassword", message: i18n.Tr("en", "form.username_password_incorrect")},
 | 
							{username: "user15", password: "wrongPassword", message: i18n.Tr("en", "form.username_password_incorrect")},
 | 
				
			||||||
		{username: "user1@example.com", password: "wrongPassword", message: i18n.Tr("en", "form.username_password_incorrect")},
 | 
							{username: "user1@example.com", password: "wrongPassword", message: i18n.Tr("en", "form.username_password_incorrect")},
 | 
				
			||||||
		// test for duplicate email
 | 
					 | 
				
			||||||
		{username: "user2@example.com", password: "password", message: i18n.Tr("en", "form.email_been_used")},
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, s := range samples {
 | 
						for _, s := range samples {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -79,7 +79,7 @@ PROVIDER_CONFIG = integrations/gitea-integration-sqlite/data/sessions
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
[log]
 | 
					[log]
 | 
				
			||||||
MODE                 = test,file
 | 
					MODE                 = test,file
 | 
				
			||||||
ROOT_PATH            = sqlite-log
 | 
					ROOT_PATH            = {{REPO_TEST_DIR}}sqlite-log
 | 
				
			||||||
ROUTER               = ,
 | 
					ROUTER               = ,
 | 
				
			||||||
XORM                 = file
 | 
					XORM                 = file
 | 
				
			||||||
ENABLE_SSH_LOG       = true
 | 
					ENABLE_SSH_LOG       = true
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -71,7 +71,7 @@ const (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ParseCommitsWithSignature checks if signaute of commits are corresponding to users gpg keys.
 | 
					// 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))
 | 
						newCommits := make([]*SignCommit, 0, len(oldCommits))
 | 
				
			||||||
	keyMap := map[string]bool{}
 | 
						keyMap := map[string]bool{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -81,7 +81,7 @@ func ParseCommitsWithSignature(oldCommits []*user_model.UserCommit, repoTrustMod
 | 
				
			|||||||
			Verification: ParseCommitWithSignature(c.Commit),
 | 
								Verification: ParseCommitWithSignature(c.Commit),
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		_ = CalculateTrustStatus(signCommit.Verification, repoTrustModel, isCodeReader, &keyMap)
 | 
							_ = CalculateTrustStatus(signCommit.Verification, repoTrustModel, isOwnerMemberCollaborator, &keyMap)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		newCommits = append(newCommits, signCommit)
 | 
							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
 | 
					// CalculateTrustStatus will calculate the TrustStatus for a commit verification within a repository
 | 
				
			||||||
// There are several trust models in Gitea
 | 
					// 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 {
 | 
						if !verification.Verified {
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -500,11 +500,11 @@ func CalculateTrustStatus(verification *CommitVerification, repoTrustModel repo_
 | 
				
			|||||||
			var has bool
 | 
								var has bool
 | 
				
			||||||
			isMember, has = (*keyMap)[verification.SigningKey.KeyID]
 | 
								isMember, has = (*keyMap)[verification.SigningKey.KeyID]
 | 
				
			||||||
			if !has {
 | 
								if !has {
 | 
				
			||||||
				isMember, err = isCodeReader(verification.SigningUser)
 | 
									isMember, err = isOwnerMemberCollaborator(verification.SigningUser)
 | 
				
			||||||
				(*keyMap)[verification.SigningKey.KeyID] = isMember
 | 
									(*keyMap)[verification.SigningKey.KeyID] = isMember
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			isMember, err = isCodeReader(verification.SigningUser)
 | 
								isMember, err = isOwnerMemberCollaborator(verification.SigningUser)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if !isMember {
 | 
							if !isMember {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -43,7 +43,7 @@ type WebAuthnCredential struct {
 | 
				
			|||||||
	Name            string
 | 
						Name            string
 | 
				
			||||||
	LowerName       string `xorm:"unique(s)"`
 | 
						LowerName       string `xorm:"unique(s)"`
 | 
				
			||||||
	UserID          int64  `xorm:"INDEX unique(s)"`
 | 
						UserID          int64  `xorm:"INDEX unique(s)"`
 | 
				
			||||||
	CredentialID    string `xorm:"INDEX"`
 | 
						CredentialID    string `xorm:"INDEX VARCHAR(410)"`
 | 
				
			||||||
	PublicKey       []byte
 | 
						PublicKey       []byte
 | 
				
			||||||
	AttestationType string
 | 
						AttestationType string
 | 
				
			||||||
	AAGUID          []byte
 | 
						AAGUID          []byte
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,7 +18,7 @@ func ConvertFromGitCommit(commits []*git.Commit, repo *repo_model.Repository) []
 | 
				
			|||||||
			user_model.ValidateCommitsWithEmails(commits),
 | 
								user_model.ValidateCommitsWithEmails(commits),
 | 
				
			||||||
			repo.GetTrustModel(),
 | 
								repo.GetTrustModel(),
 | 
				
			||||||
			func(user *user_model.User) (bool, error) {
 | 
								func(user *user_model.User) (bool, error) {
 | 
				
			||||||
				return IsUserRepoAdmin(repo, user)
 | 
									return IsOwnerMemberCollaborator(repo, user.ID)
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		),
 | 
							),
 | 
				
			||||||
		repo,
 | 
							repo,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -134,22 +134,6 @@ func GetMilestoneByRepoIDANDName(repoID int64, name string) (*Milestone, error)
 | 
				
			|||||||
	return &mile, nil
 | 
						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.
 | 
					// UpdateMilestone updates information of given milestone.
 | 
				
			||||||
func UpdateMilestone(m *Milestone, oldIsClosed bool) error {
 | 
					func UpdateMilestone(m *Milestone, oldIsClosed bool) error {
 | 
				
			||||||
	ctx, committer, err := db.TxContext()
 | 
						ctx, committer, err := db.TxContext()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -137,6 +137,7 @@ func QueryIssueContentHistoryEditedCountMap(dbCtx context.Context, issueID int64
 | 
				
			|||||||
type IssueContentListItem struct {
 | 
					type IssueContentListItem struct {
 | 
				
			||||||
	UserID         int64
 | 
						UserID         int64
 | 
				
			||||||
	UserName       string
 | 
						UserName       string
 | 
				
			||||||
 | 
						UserFullName   string
 | 
				
			||||||
	UserAvatarLink string
 | 
						UserAvatarLink string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	HistoryID      int64
 | 
						HistoryID      int64
 | 
				
			||||||
@@ -148,7 +149,7 @@ type IssueContentListItem struct {
 | 
				
			|||||||
// FetchIssueContentHistoryList fetch list
 | 
					// FetchIssueContentHistoryList fetch list
 | 
				
			||||||
func FetchIssueContentHistoryList(dbCtx context.Context, issueID, commentID int64) ([]*IssueContentListItem, error) {
 | 
					func FetchIssueContentHistoryList(dbCtx context.Context, issueID, commentID int64) ([]*IssueContentListItem, error) {
 | 
				
			||||||
	res := make([]*IssueContentListItem, 0)
 | 
						res := make([]*IssueContentListItem, 0)
 | 
				
			||||||
	err := db.GetEngine(dbCtx).Select("u.id as user_id, u.name as user_name,"+
 | 
						err := db.GetEngine(dbCtx).Select("u.id as user_id, u.name as user_name, u.full_name as user_full_name,"+
 | 
				
			||||||
		"h.id as history_id, h.edited_unix, h.is_first_created, h.is_deleted").
 | 
							"h.id as history_id, h.edited_unix, h.is_first_created, h.is_deleted").
 | 
				
			||||||
		Table([]string{"issue_content_history", "h"}).
 | 
							Table([]string{"issue_content_history", "h"}).
 | 
				
			||||||
		Join("LEFT", []string{"user", "u"}, "h.poster_id = u.id").
 | 
							Join("LEFT", []string{"user", "u"}, "h.poster_id = u.id").
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -43,8 +43,9 @@ func TestContentHistory(t *testing.T) {
 | 
				
			|||||||
		when the refactor of models are done, this test will be possible to be run then with a real `User` model.
 | 
							when the refactor of models are done, this test will be possible to be run then with a real `User` model.
 | 
				
			||||||
	*/
 | 
						*/
 | 
				
			||||||
	type User struct {
 | 
						type User struct {
 | 
				
			||||||
		ID   int64
 | 
							ID       int64
 | 
				
			||||||
		Name string
 | 
							Name     string
 | 
				
			||||||
 | 
							FullName string
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	_ = dbEngine.Sync2(&User{})
 | 
						_ = dbEngine.Sync2(&User{})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -52,10 +52,6 @@ func InsertIssues(issues ...*Issue) error {
 | 
				
			|||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	err = UpdateRepoStats(ctx, issues[0].RepoID)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return committer.Commit()
 | 
						return committer.Commit()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -147,11 +143,6 @@ func InsertPullRequests(prs ...*PullRequest) error {
 | 
				
			|||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	err = UpdateRepoStats(ctx, prs[0].Issue.RepoID)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return committer.Commit()
 | 
						return committer.Commit()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -32,8 +32,9 @@ func TestMigrate_InsertMilestones(t *testing.T) {
 | 
				
			|||||||
	unittest.CheckConsistencyFor(t, &Milestone{})
 | 
						unittest.CheckConsistencyFor(t, &Milestone{})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func assertCreateIssues(t *testing.T, reponame string, isPull bool) {
 | 
					func assertCreateIssues(t *testing.T, isPull bool) {
 | 
				
			||||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
						assert.NoError(t, unittest.PrepareTestDatabase())
 | 
				
			||||||
 | 
						reponame := "repo1"
 | 
				
			||||||
	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame}).(*repo_model.Repository)
 | 
						repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame}).(*repo_model.Repository)
 | 
				
			||||||
	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}).(*user_model.User)
 | 
						owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}).(*user_model.User)
 | 
				
			||||||
	label := unittest.AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label)
 | 
						label := unittest.AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label)
 | 
				
			||||||
@@ -63,38 +64,14 @@ func assertCreateIssues(t *testing.T, reponame string, isPull bool) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	i := unittest.AssertExistsAndLoadBean(t, &Issue{Title: title}).(*Issue)
 | 
						i := unittest.AssertExistsAndLoadBean(t, &Issue{Title: title}).(*Issue)
 | 
				
			||||||
	unittest.AssertExistsAndLoadBean(t, &Reaction{Type: "heart", UserID: owner.ID, IssueID: i.ID})
 | 
						unittest.AssertExistsAndLoadBean(t, &Reaction{Type: "heart", UserID: owner.ID, IssueID: i.ID})
 | 
				
			||||||
 | 
					 | 
				
			||||||
	labelModified := unittest.AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label)
 | 
					 | 
				
			||||||
	assert.EqualValues(t, label.NumIssues+1, labelModified.NumIssues)
 | 
					 | 
				
			||||||
	assert.EqualValues(t, label.NumClosedIssues+1, labelModified.NumClosedIssues)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	milestoneModified := unittest.AssertExistsAndLoadBean(t, &Milestone{ID: milestone.ID}).(*Milestone)
 | 
					 | 
				
			||||||
	assert.EqualValues(t, milestone.NumIssues+1, milestoneModified.NumIssues)
 | 
					 | 
				
			||||||
	assert.EqualValues(t, milestone.NumClosedIssues+1, milestoneModified.NumClosedIssues)
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestMigrate_CreateIssuesIsPullFalse(t *testing.T) {
 | 
					func TestMigrate_CreateIssuesIsPullFalse(t *testing.T) {
 | 
				
			||||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
						assertCreateIssues(t, false)
 | 
				
			||||||
	reponame := "repo1"
 | 
					 | 
				
			||||||
	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame}).(*repo_model.Repository)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	assertCreateIssues(t, reponame, false)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	repoModified := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID}).(*repo_model.Repository)
 | 
					 | 
				
			||||||
	assert.EqualValues(t, repo.NumIssues+1, repoModified.NumIssues)
 | 
					 | 
				
			||||||
	assert.EqualValues(t, repo.NumClosedIssues+1, repoModified.NumClosedIssues)
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestMigrate_CreateIssuesIsPullTrue(t *testing.T) {
 | 
					func TestMigrate_CreateIssuesIsPullTrue(t *testing.T) {
 | 
				
			||||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
						assertCreateIssues(t, true)
 | 
				
			||||||
	reponame := "repo1"
 | 
					 | 
				
			||||||
	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame}).(*repo_model.Repository)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	assertCreateIssues(t, reponame, true)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	repoModified := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID}).(*repo_model.Repository)
 | 
					 | 
				
			||||||
	assert.EqualValues(t, repo.NumPulls+1, repoModified.NumPulls)
 | 
					 | 
				
			||||||
	assert.EqualValues(t, repo.NumClosedPulls+1, repoModified.NumClosedPulls)
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestMigrate_InsertIssueComments(t *testing.T) {
 | 
					func TestMigrate_InsertIssueComments(t *testing.T) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					-
 | 
				
			||||||
 | 
					  id: 1
 | 
				
			||||||
 | 
					  credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
 | 
				
			||||||
 | 
					-
 | 
				
			||||||
 | 
					  id: 2
 | 
				
			||||||
 | 
					  credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
 | 
				
			||||||
 | 
					-
 | 
				
			||||||
 | 
					  id: 3
 | 
				
			||||||
 | 
					  credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
 | 
				
			||||||
 | 
					-
 | 
				
			||||||
 | 
					  id: 4
 | 
				
			||||||
 | 
					  credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
 | 
				
			||||||
@@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					-
 | 
				
			||||||
 | 
					  id: 1
 | 
				
			||||||
 | 
					  name: "u2fkey-correctly-migrated"
 | 
				
			||||||
 | 
					  user_id: 1
 | 
				
			||||||
 | 
					  raw: 0x05040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf240efe2e213b889daf3fc88e3952e8dd6b4cfd82f1a1212e2ab4b19389455ecf3e67f0aeafc91b9c0d413c9d6215a45177c1d5076358aa6ee20e1b30e3d7467cae2308202bd308201a5a00302010202041e8f8734300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a306e310b300906035504061302534531123010060355040a0c0959756269636f20414231223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3127302506035504030c1e59756269636f205532462045452053657269616c203531323732323734303059301306072a8648ce3d020106082a8648ce3d03010703420004a879f82338ed1494bac0704bcc7fc663d1b271715976243101c7605115d7c1529e281c1c67322d384b5cd55dd3e9818d5fd85c22af326e0c64fc20afe33f2366a36c306a302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e373013060b2b0601040182e51c0201010404030204303021060b2b0601040182e51c010104041204102fc0579f811347eab116bb5a8db9202a300c0603551d130101ff04023000300d06092a864886f70d01010b050003820101008693ff62df0d5779d4748d7fc8d10227318a8e580e6a3a57c108e94e03c38568b366894fce5624be4a3efd7f34118b3d993743f792a1989160c8fc9ae0b04e3df9ee15e3e88c04fc82a8dcbf5818e108dcc2968577ae79ff662b94734e3dec4597305d73e6e55ee2beb9cd9678ca0935e533eb638f8e26fabb817cda441fbe9831832ae5f6e2ad992f9ebbdb4c62238b8f8d7ab481d6d3263bcdbf9e4a57550370988ad5813440fa032cadb6723cadd8f8d7ba809f75b43cffa0a5b9add14232ef9d9e14812638233c4ca4a873b9f8ac98e32ba19167606e15909fcddb4a2dffbdae4620249f9a6646ac81e4832d1119febfaa731a882da25a77827d46d190173046022100b579338a44c236d3f214b2e150011a08cf251193ecfae2244edb0a5794e9b301022100fab468862c47d98204d437cf2be8c54a5a4ecd1ebb1c61a6c23da7b9c75f6841
 | 
				
			||||||
 | 
					  counter: 0
 | 
				
			||||||
 | 
					- id: 2
 | 
				
			||||||
 | 
					  name: "u2fkey-incorrectly-migrated"
 | 
				
			||||||
 | 
					  user_id: 1
 | 
				
			||||||
 | 
					  raw: 0x05040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf240efe2e213b889daf3fc88e3952e8dd6b4cfd82f1a1212e2ab4b19389455ecf3e67f0aeafc91b9c0d413c9d6215a45177c1d5076358aa6ee20e1b30e3d7467cae2308202bd308201a5a00302010202041e8f8734300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a306e310b300906035504061302534531123010060355040a0c0959756269636f20414231223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3127302506035504030c1e59756269636f205532462045452053657269616c203531323732323734303059301306072a8648ce3d020106082a8648ce3d03010703420004a879f82338ed1494bac0704bcc7fc663d1b271715976243101c7605115d7c1529e281c1c67322d384b5cd55dd3e9818d5fd85c22af326e0c64fc20afe33f2366a36c306a302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e373013060b2b0601040182e51c0201010404030204303021060b2b0601040182e51c010104041204102fc0579f811347eab116bb5a8db9202a300c0603551d130101ff04023000300d06092a864886f70d01010b050003820101008693ff62df0d5779d4748d7fc8d10227318a8e580e6a3a57c108e94e03c38568b366894fce5624be4a3efd7f34118b3d993743f792a1989160c8fc9ae0b04e3df9ee15e3e88c04fc82a8dcbf5818e108dcc2968577ae79ff662b94734e3dec4597305d73e6e55ee2beb9cd9678ca0935e533eb638f8e26fabb817cda441fbe9831832ae5f6e2ad992f9ebbdb4c62238b8f8d7ab481d6d3263bcdbf9e4a57550370988ad5813440fa032cadb6723cadd8f8d7ba809f75b43cffa0a5b9add14232ef9d9e14812638233c4ca4a873b9f8ac98e32ba19167606e15909fcddb4a2dffbdae4620249f9a6646ac81e4832d1119febfaa731a882da25a77827d46d190173046022100b579338a44c236d3f214b2e150011a08cf251193ecfae2244edb0a5794e9b301022100fab468862c47d98204d437cf2be8c54a5a4ecd1ebb1c61a6c23da7b9c75f6841
 | 
				
			||||||
 | 
					  counter: 0
 | 
				
			||||||
 | 
					- id: 3
 | 
				
			||||||
 | 
					  name: "u2fkey-deleted"
 | 
				
			||||||
 | 
					  user_id: 1
 | 
				
			||||||
 | 
					  raw: 0x05040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf240efe2e213b889daf3fc88e3952e8dd6b4cfd82f1a1212e2ab4b19389455ecf3e67f0aeafc91b9c0d413c9d6215a45177c1d5076358aa6ee20e1b30e3d7467cae2308202bd308201a5a00302010202041e8f8734300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a306e310b300906035504061302534531123010060355040a0c0959756269636f20414231223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3127302506035504030c1e59756269636f205532462045452053657269616c203531323732323734303059301306072a8648ce3d020106082a8648ce3d03010703420004a879f82338ed1494bac0704bcc7fc663d1b271715976243101c7605115d7c1529e281c1c67322d384b5cd55dd3e9818d5fd85c22af326e0c64fc20afe33f2366a36c306a302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e373013060b2b0601040182e51c0201010404030204303021060b2b0601040182e51c010104041204102fc0579f811347eab116bb5a8db9202a300c0603551d130101ff04023000300d06092a864886f70d01010b050003820101008693ff62df0d5779d4748d7fc8d10227318a8e580e6a3a57c108e94e03c38568b366894fce5624be4a3efd7f34118b3d993743f792a1989160c8fc9ae0b04e3df9ee15e3e88c04fc82a8dcbf5818e108dcc2968577ae79ff662b94734e3dec4597305d73e6e55ee2beb9cd9678ca0935e533eb638f8e26fabb817cda441fbe9831832ae5f6e2ad992f9ebbdb4c62238b8f8d7ab481d6d3263bcdbf9e4a57550370988ad5813440fa032cadb6723cadd8f8d7ba809f75b43cffa0a5b9add14232ef9d9e14812638233c4ca4a873b9f8ac98e32ba19167606e15909fcddb4a2dffbdae4620249f9a6646ac81e4832d1119febfaa731a882da25a77827d46d190173046022100b579338a44c236d3f214b2e150011a08cf251193ecfae2244edb0a5794e9b301022100fab468862c47d98204d437cf2be8c54a5a4ecd1ebb1c61a6c23da7b9c75f6841
 | 
				
			||||||
 | 
					  counter: 0
 | 
				
			||||||
 | 
					- id: 4
 | 
				
			||||||
 | 
					  name: "u2fkey-wrong-user-id"
 | 
				
			||||||
 | 
					  user_id: 2
 | 
				
			||||||
 | 
					  raw: 0x05040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf240efe2e213b889daf3fc88e3952e8dd6b4cfd82f1a1212e2ab4b19389455ecf3e67f0aeafc91b9c0d413c9d6215a45177c1d5076358aa6ee20e1b30e3d7467cae2308202bd308201a5a00302010202041e8f8734300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a306e310b300906035504061302534531123010060355040a0c0959756269636f20414231223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3127302506035504030c1e59756269636f205532462045452053657269616c203531323732323734303059301306072a8648ce3d020106082a8648ce3d03010703420004a879f82338ed1494bac0704bcc7fc663d1b271715976243101c7605115d7c1529e281c1c67322d384b5cd55dd3e9818d5fd85c22af326e0c64fc20afe33f2366a36c306a302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e373013060b2b0601040182e51c0201010404030204303021060b2b0601040182e51c010104041204102fc0579f811347eab116bb5a8db9202a300c0603551d130101ff04023000300d06092a864886f70d01010b050003820101008693ff62df0d5779d4748d7fc8d10227318a8e580e6a3a57c108e94e03c38568b366894fce5624be4a3efd7f34118b3d993743f792a1989160c8fc9ae0b04e3df9ee15e3e88c04fc82a8dcbf5818e108dcc2968577ae79ff662b94734e3dec4597305d73e6e55ee2beb9cd9678ca0935e533eb638f8e26fabb817cda441fbe9831832ae5f6e2ad992f9ebbdb4c62238b8f8d7ab481d6d3263bcdbf9e4a57550370988ad5813440fa032cadb6723cadd8f8d7ba809f75b43cffa0a5b9add14232ef9d9e14812638233c4ca4a873b9f8ac98e32ba19167606e15909fcddb4a2dffbdae4620249f9a6646ac81e4832d1119febfaa731a882da25a77827d46d190173046022100b579338a44c236d3f214b2e150011a08cf251193ecfae2244edb0a5794e9b301022100fab468862c47d98204d437cf2be8c54a5a4ecd1ebb1c61a6c23da7b9c75f6841
 | 
				
			||||||
 | 
					  counter: 0
 | 
				
			||||||
@@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					-
 | 
				
			||||||
 | 
					  id: 1
 | 
				
			||||||
 | 
					  lower_name: "u2fkey-correctly-migrated"
 | 
				
			||||||
 | 
					  name: "u2fkey-correctly-migrated"
 | 
				
			||||||
 | 
					  user_id: 1
 | 
				
			||||||
 | 
					  credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
 | 
				
			||||||
 | 
					  public_key: 0x040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf2
 | 
				
			||||||
 | 
					  attestation_type: 'fido-u2f'
 | 
				
			||||||
 | 
					  sign_count: 1
 | 
				
			||||||
 | 
					  clone_warning: false
 | 
				
			||||||
 | 
					-
 | 
				
			||||||
 | 
					  id: 2
 | 
				
			||||||
 | 
					  lower_name: "u2fkey-incorrectly-migrated"
 | 
				
			||||||
 | 
					  name: "u2fkey-incorrectly-migrated"
 | 
				
			||||||
 | 
					  user_id: 1
 | 
				
			||||||
 | 
					  credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8A"
 | 
				
			||||||
 | 
					  public_key: 0x040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf2
 | 
				
			||||||
 | 
					  attestation_type: 'fido-u2f'
 | 
				
			||||||
 | 
					  sign_count: 1
 | 
				
			||||||
 | 
					  clone_warning: false
 | 
				
			||||||
 | 
					-
 | 
				
			||||||
 | 
					  id: 4
 | 
				
			||||||
 | 
					  lower_name: "u2fkey-wrong-user-id"
 | 
				
			||||||
 | 
					  name: "u2fkey-wrong-user-id"
 | 
				
			||||||
 | 
					  user_id: 1
 | 
				
			||||||
 | 
					  credential_id: "THIS SHOULD CHANGE"
 | 
				
			||||||
 | 
					  public_key: 0x040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf2
 | 
				
			||||||
 | 
					  attestation_type: 'fido-u2f'
 | 
				
			||||||
 | 
					  sign_count: 1
 | 
				
			||||||
 | 
					  clone_warning: false
 | 
				
			||||||
@@ -61,7 +61,6 @@ type Version struct {
 | 
				
			|||||||
// update minDBVersion accordingly
 | 
					// update minDBVersion accordingly
 | 
				
			||||||
var migrations = []Migration{
 | 
					var migrations = []Migration{
 | 
				
			||||||
	// Gitea 1.5.0 ends at v69
 | 
						// Gitea 1.5.0 ends at v69
 | 
				
			||||||
 | 
					 | 
				
			||||||
	// v70 -> v71
 | 
						// v70 -> v71
 | 
				
			||||||
	NewMigration("add issue_dependencies", addIssueDependencies),
 | 
						NewMigration("add issue_dependencies", addIssueDependencies),
 | 
				
			||||||
	// v71 -> v72
 | 
						// v71 -> v72
 | 
				
			||||||
@@ -367,9 +366,13 @@ var migrations = []Migration{
 | 
				
			|||||||
	// v206 -> v207
 | 
						// v206 -> v207
 | 
				
			||||||
	NewMigration("Add authorize column to team_unit table", addAuthorizeColForTeamUnit),
 | 
						NewMigration("Add authorize column to team_unit table", addAuthorizeColForTeamUnit),
 | 
				
			||||||
	// v207 -> v208
 | 
						// v207 -> v208
 | 
				
			||||||
	NewMigration("Add webauthn table and migrate u2f data to webauthn", addWebAuthnCred),
 | 
						NewMigration("Add webauthn table and migrate u2f data to webauthn - NO-OPED", addWebAuthnCred),
 | 
				
			||||||
	// v208 -> v209
 | 
						// v208 -> v209
 | 
				
			||||||
	NewMigration("Use base32.HexEncoding instead of base64 encoding for cred ID as it is case insensitive", useBase32HexForCredIDInWebAuthnCredential),
 | 
						NewMigration("Use base32.HexEncoding instead of base64 encoding for cred ID as it is case insensitive - NO-OPED", useBase32HexForCredIDInWebAuthnCredential),
 | 
				
			||||||
 | 
						// v209 -> v210
 | 
				
			||||||
 | 
						NewMigration("Increase WebAuthentication CredentialID size to 410 - NO-OPED", increaseCredentialIDTo410),
 | 
				
			||||||
 | 
						// v210 -> v211
 | 
				
			||||||
 | 
						NewMigration("v208 was completely broken - remigrate", remigrateU2FCredentials),
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetCurrentDBVersion returns the current db version
 | 
					// GetCurrentDBVersion returns the current db version
 | 
				
			||||||
@@ -450,9 +453,12 @@ Please try upgrading to a lower version first (suggested v1.6.4), then upgrade t
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// Downgrading Gitea's database version not supported
 | 
						// Downgrading Gitea's database version not supported
 | 
				
			||||||
	if int(v-minDBVersion) > len(migrations) {
 | 
						if int(v-minDBVersion) > len(migrations) {
 | 
				
			||||||
		msg := fmt.Sprintf("Downgrading database version from '%d' to '%d' is not supported and may result in loss of data integrity.\nIf you really know what you're doing, execute `UPDATE version SET version=%d WHERE id=1;`\n",
 | 
							msg := fmt.Sprintf("Your database (migration version: %d) is for a newer Gita, you can not use the newer database for this old Gitea release (%d).", v, minDBVersion+len(migrations))
 | 
				
			||||||
			v, minDBVersion+len(migrations), minDBVersion+len(migrations))
 | 
							msg += "\nGitea will exit to keep your database safe and unchanged. Please use the correct Gitea release, do not change the migration version manually (incorrect manual operation may lose data)."
 | 
				
			||||||
		fmt.Fprint(os.Stderr, msg)
 | 
							if !setting.IsProd {
 | 
				
			||||||
 | 
								msg += fmt.Sprintf("\nIf you are in development and really know what you're doing, you can force changing the migration version by executing: UPDATE version SET version=%d WHERE id=1;", minDBVersion+len(migrations))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							_, _ = fmt.Fprintln(os.Stderr, msg)
 | 
				
			||||||
		log.Fatal(msg)
 | 
							log.Fatal(msg)
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,87 +5,11 @@
 | 
				
			|||||||
package migrations
 | 
					package migrations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"crypto/elliptic"
 | 
					 | 
				
			||||||
	"encoding/base64"
 | 
					 | 
				
			||||||
	"strings"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	"code.gitea.io/gitea/modules/timeutil"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	"github.com/tstranex/u2f"
 | 
					 | 
				
			||||||
	"xorm.io/xorm"
 | 
						"xorm.io/xorm"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func addWebAuthnCred(x *xorm.Engine) error {
 | 
					func addWebAuthnCred(x *xorm.Engine) error {
 | 
				
			||||||
 | 
						// NO-OP Don't migrate here - let v210 do this.
 | 
				
			||||||
	// Create webauthnCredential table
 | 
					 | 
				
			||||||
	type webauthnCredential struct {
 | 
					 | 
				
			||||||
		ID              int64 `xorm:"pk autoincr"`
 | 
					 | 
				
			||||||
		Name            string
 | 
					 | 
				
			||||||
		LowerName       string `xorm:"unique(s)"`
 | 
					 | 
				
			||||||
		UserID          int64  `xorm:"INDEX unique(s)"`
 | 
					 | 
				
			||||||
		CredentialID    string `xorm:"INDEX"`
 | 
					 | 
				
			||||||
		PublicKey       []byte
 | 
					 | 
				
			||||||
		AttestationType string
 | 
					 | 
				
			||||||
		AAGUID          []byte
 | 
					 | 
				
			||||||
		SignCount       uint32 `xorm:"BIGINT"`
 | 
					 | 
				
			||||||
		CloneWarning    bool
 | 
					 | 
				
			||||||
		CreatedUnix     timeutil.TimeStamp `xorm:"INDEX created"`
 | 
					 | 
				
			||||||
		UpdatedUnix     timeutil.TimeStamp `xorm:"INDEX updated"`
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if err := x.Sync2(&webauthnCredential{}); err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Now migrate the old u2f registrations to the new format
 | 
					 | 
				
			||||||
	type u2fRegistration struct {
 | 
					 | 
				
			||||||
		ID          int64 `xorm:"pk autoincr"`
 | 
					 | 
				
			||||||
		Name        string
 | 
					 | 
				
			||||||
		UserID      int64 `xorm:"INDEX"`
 | 
					 | 
				
			||||||
		Raw         []byte
 | 
					 | 
				
			||||||
		Counter     uint32             `xorm:"BIGINT"`
 | 
					 | 
				
			||||||
		CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
 | 
					 | 
				
			||||||
		UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var start int
 | 
					 | 
				
			||||||
	regs := make([]*u2fRegistration, 0, 50)
 | 
					 | 
				
			||||||
	for {
 | 
					 | 
				
			||||||
		err := x.OrderBy("id").Limit(50, start).Find(®s)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		for _, reg := range regs {
 | 
					 | 
				
			||||||
			parsed := new(u2f.Registration)
 | 
					 | 
				
			||||||
			err = parsed.UnmarshalBinary(reg.Raw)
 | 
					 | 
				
			||||||
			if err != nil {
 | 
					 | 
				
			||||||
				continue
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			c := &webauthnCredential{
 | 
					 | 
				
			||||||
				ID:              reg.ID,
 | 
					 | 
				
			||||||
				Name:            reg.Name,
 | 
					 | 
				
			||||||
				LowerName:       strings.ToLower(reg.Name),
 | 
					 | 
				
			||||||
				UserID:          reg.UserID,
 | 
					 | 
				
			||||||
				CredentialID:    base64.RawStdEncoding.EncodeToString(parsed.KeyHandle),
 | 
					 | 
				
			||||||
				PublicKey:       elliptic.Marshal(elliptic.P256(), parsed.PubKey.X, parsed.PubKey.Y),
 | 
					 | 
				
			||||||
				AttestationType: "fido-u2f",
 | 
					 | 
				
			||||||
				AAGUID:          []byte{},
 | 
					 | 
				
			||||||
				SignCount:       reg.Counter,
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			_, err := x.Insert(c)
 | 
					 | 
				
			||||||
			if err != nil {
 | 
					 | 
				
			||||||
				return err
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if len(regs) < 50 {
 | 
					 | 
				
			||||||
			break
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		start += 50
 | 
					 | 
				
			||||||
		regs = regs[:0]
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,47 +5,10 @@
 | 
				
			|||||||
package migrations
 | 
					package migrations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"encoding/base32"
 | 
					 | 
				
			||||||
	"encoding/base64"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	"xorm.io/xorm"
 | 
						"xorm.io/xorm"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func useBase32HexForCredIDInWebAuthnCredential(x *xorm.Engine) error {
 | 
					func useBase32HexForCredIDInWebAuthnCredential(x *xorm.Engine) error {
 | 
				
			||||||
 | 
						// noop
 | 
				
			||||||
	// Create webauthnCredential table
 | 
					 | 
				
			||||||
	type webauthnCredential struct {
 | 
					 | 
				
			||||||
		ID           int64  `xorm:"pk autoincr"`
 | 
					 | 
				
			||||||
		CredentialID string `xorm:"INDEX"`
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if err := x.Sync2(&webauthnCredential{}); err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var start int
 | 
					 | 
				
			||||||
	regs := make([]*webauthnCredential, 0, 50)
 | 
					 | 
				
			||||||
	for {
 | 
					 | 
				
			||||||
		err := x.OrderBy("id").Limit(50, start).Find(®s)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		for _, reg := range regs {
 | 
					 | 
				
			||||||
			credID, _ := base64.RawStdEncoding.DecodeString(reg.CredentialID)
 | 
					 | 
				
			||||||
			reg.CredentialID = base32.HexEncoding.EncodeToString(credID)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			_, err := x.Update(reg)
 | 
					 | 
				
			||||||
			if err != nil {
 | 
					 | 
				
			||||||
				return err
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if len(regs) < 50 {
 | 
					 | 
				
			||||||
			break
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		start += 50
 | 
					 | 
				
			||||||
		regs = regs[:0]
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										17
									
								
								models/migrations/v209.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								models/migrations/v209.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					// Copyright 2022 The Gitea Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package migrations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"xorm.io/xorm"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func increaseCredentialIDTo410(x *xorm.Engine) error {
 | 
				
			||||||
 | 
						// no-op
 | 
				
			||||||
 | 
						// V208 is badly broken
 | 
				
			||||||
 | 
						// So now we have to no-op again.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										178
									
								
								models/migrations/v210.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								models/migrations/v210.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,178 @@
 | 
				
			|||||||
 | 
					// Copyright 2022 The Gitea Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package migrations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"crypto/elliptic"
 | 
				
			||||||
 | 
						"encoding/base32"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/timeutil"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/tstranex/u2f"
 | 
				
			||||||
 | 
						"xorm.io/xorm"
 | 
				
			||||||
 | 
						"xorm.io/xorm/schemas"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// v208 migration was completely broken
 | 
				
			||||||
 | 
					func remigrateU2FCredentials(x *xorm.Engine) error {
 | 
				
			||||||
 | 
						// Create webauthnCredential table
 | 
				
			||||||
 | 
						type webauthnCredential struct {
 | 
				
			||||||
 | 
							ID              int64 `xorm:"pk autoincr"`
 | 
				
			||||||
 | 
							Name            string
 | 
				
			||||||
 | 
							LowerName       string `xorm:"unique(s)"`
 | 
				
			||||||
 | 
							UserID          int64  `xorm:"INDEX unique(s)"`
 | 
				
			||||||
 | 
							CredentialID    string `xorm:"INDEX VARCHAR(410)"` // CredentalID in U2F is at most 255bytes / 5 * 8 = 408 - add a few extra characters for safety
 | 
				
			||||||
 | 
							PublicKey       []byte
 | 
				
			||||||
 | 
							AttestationType string
 | 
				
			||||||
 | 
							AAGUID          []byte
 | 
				
			||||||
 | 
							SignCount       uint32 `xorm:"BIGINT"`
 | 
				
			||||||
 | 
							CloneWarning    bool
 | 
				
			||||||
 | 
							CreatedUnix     timeutil.TimeStamp `xorm:"INDEX created"`
 | 
				
			||||||
 | 
							UpdatedUnix     timeutil.TimeStamp `xorm:"INDEX updated"`
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := x.Sync2(&webauthnCredential{}); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch x.Dialect().URI().DBType {
 | 
				
			||||||
 | 
						case schemas.MYSQL:
 | 
				
			||||||
 | 
							_, err := x.Exec("ALTER TABLE webauthn_credential MODIFY COLUMN credential_id VARCHAR(410)")
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case schemas.ORACLE:
 | 
				
			||||||
 | 
							_, err := x.Exec("ALTER TABLE webauthn_credential MODIFY credential_id VARCHAR(410)")
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case schemas.MSSQL:
 | 
				
			||||||
 | 
							// This column has an index on it. I could write all of the code to attempt to change the index OR
 | 
				
			||||||
 | 
							// I could just use recreate table.
 | 
				
			||||||
 | 
							sess := x.NewSession()
 | 
				
			||||||
 | 
							if err := sess.Begin(); err != nil {
 | 
				
			||||||
 | 
								_ = sess.Close()
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := recreateTable(sess, new(webauthnCredential)); err != nil {
 | 
				
			||||||
 | 
								_ = sess.Close()
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if err := sess.Commit(); err != nil {
 | 
				
			||||||
 | 
								_ = sess.Close()
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if err := sess.Close(); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case schemas.POSTGRES:
 | 
				
			||||||
 | 
							_, err := x.Exec("ALTER TABLE webauthn_credential ALTER COLUMN credential_id TYPE VARCHAR(410)")
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							// SQLite doesn't support ALTER COLUMN, and it already makes String _TEXT_ by default so no migration needed
 | 
				
			||||||
 | 
							// nor is there any need to re-migrate
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						exist, err := x.IsTableExist("u2f_registration")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if !exist {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Now migrate the old u2f registrations to the new format
 | 
				
			||||||
 | 
						type u2fRegistration struct {
 | 
				
			||||||
 | 
							ID          int64 `xorm:"pk autoincr"`
 | 
				
			||||||
 | 
							Name        string
 | 
				
			||||||
 | 
							UserID      int64 `xorm:"INDEX"`
 | 
				
			||||||
 | 
							Raw         []byte
 | 
				
			||||||
 | 
							Counter     uint32             `xorm:"BIGINT"`
 | 
				
			||||||
 | 
							CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
 | 
				
			||||||
 | 
							UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var start int
 | 
				
			||||||
 | 
						regs := make([]*u2fRegistration, 0, 50)
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							err := x.OrderBy("id").Limit(50, start).Find(®s)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							err = func() error {
 | 
				
			||||||
 | 
								sess := x.NewSession()
 | 
				
			||||||
 | 
								defer sess.Close()
 | 
				
			||||||
 | 
								if err := sess.Begin(); err != nil {
 | 
				
			||||||
 | 
									return fmt.Errorf("unable to allow start session. Error: %w", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if x.Dialect().URI().DBType == schemas.MSSQL {
 | 
				
			||||||
 | 
									if _, err := sess.Exec("SET IDENTITY_INSERT `webauthn_credential` ON"); err != nil {
 | 
				
			||||||
 | 
										return fmt.Errorf("unable to allow identity insert on webauthn_credential. Error: %w", err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								for _, reg := range regs {
 | 
				
			||||||
 | 
									parsed := new(u2f.Registration)
 | 
				
			||||||
 | 
									err = parsed.UnmarshalBinary(reg.Raw)
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										continue
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									remigrated := &webauthnCredential{
 | 
				
			||||||
 | 
										ID:              reg.ID,
 | 
				
			||||||
 | 
										Name:            reg.Name,
 | 
				
			||||||
 | 
										LowerName:       strings.ToLower(reg.Name),
 | 
				
			||||||
 | 
										UserID:          reg.UserID,
 | 
				
			||||||
 | 
										CredentialID:    base32.HexEncoding.EncodeToString(parsed.KeyHandle),
 | 
				
			||||||
 | 
										PublicKey:       elliptic.Marshal(elliptic.P256(), parsed.PubKey.X, parsed.PubKey.Y),
 | 
				
			||||||
 | 
										AttestationType: "fido-u2f",
 | 
				
			||||||
 | 
										AAGUID:          []byte{},
 | 
				
			||||||
 | 
										SignCount:       reg.Counter,
 | 
				
			||||||
 | 
										UpdatedUnix:     reg.UpdatedUnix,
 | 
				
			||||||
 | 
										CreatedUnix:     reg.CreatedUnix,
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									has, err := sess.ID(reg.ID).Get(new(webauthnCredential))
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										return fmt.Errorf("unable to get webauthn_credential[%d]. Error: %w", reg.ID, err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if !has {
 | 
				
			||||||
 | 
										has, err := sess.Where("`lower_name`=?", remigrated.LowerName).And("`user_id`=?", remigrated.UserID).Exist(new(webauthnCredential))
 | 
				
			||||||
 | 
										if err != nil {
 | 
				
			||||||
 | 
											return fmt.Errorf("unable to check webauthn_credential[lower_name: %s, user_id:%v]. Error: %w", remigrated.LowerName, remigrated.UserID, err)
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										if !has {
 | 
				
			||||||
 | 
											_, err = sess.Insert(remigrated)
 | 
				
			||||||
 | 
											if err != nil {
 | 
				
			||||||
 | 
												return fmt.Errorf("unable to (re)insert webauthn_credential[%d]. Error: %w", reg.ID, err)
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											continue
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									_, err = sess.ID(remigrated.ID).AllCols().Update(remigrated)
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										return fmt.Errorf("unable to update webauthn_credential[%d]. Error: %w", reg.ID, err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return sess.Commit()
 | 
				
			||||||
 | 
							}()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if len(regs) < 50 {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							start += 50
 | 
				
			||||||
 | 
							regs = regs[:0]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										75
									
								
								models/migrations/v210_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								models/migrations/v210_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,75 @@
 | 
				
			|||||||
 | 
					// Copyright 2021 The Gitea Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package migrations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/timeutil"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
 | 
						"xorm.io/xorm/schemas"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Test_remigrateU2FCredentials(t *testing.T) {
 | 
				
			||||||
 | 
						// Create webauthnCredential table
 | 
				
			||||||
 | 
						type WebauthnCredential struct {
 | 
				
			||||||
 | 
							ID              int64 `xorm:"pk autoincr"`
 | 
				
			||||||
 | 
							Name            string
 | 
				
			||||||
 | 
							LowerName       string `xorm:"unique(s)"`
 | 
				
			||||||
 | 
							UserID          int64  `xorm:"INDEX unique(s)"`
 | 
				
			||||||
 | 
							CredentialID    string `xorm:"INDEX VARCHAR(410)"` // CredentalID in U2F is at most 255bytes / 5 * 8 = 408 - add a few extra characters for safety
 | 
				
			||||||
 | 
							PublicKey       []byte
 | 
				
			||||||
 | 
							AttestationType string
 | 
				
			||||||
 | 
							SignCount       uint32 `xorm:"BIGINT"`
 | 
				
			||||||
 | 
							CloneWarning    bool
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Now migrate the old u2f registrations to the new format
 | 
				
			||||||
 | 
						type U2fRegistration struct {
 | 
				
			||||||
 | 
							ID          int64 `xorm:"pk autoincr"`
 | 
				
			||||||
 | 
							Name        string
 | 
				
			||||||
 | 
							UserID      int64 `xorm:"INDEX"`
 | 
				
			||||||
 | 
							Raw         []byte
 | 
				
			||||||
 | 
							Counter     uint32             `xorm:"BIGINT"`
 | 
				
			||||||
 | 
							CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
 | 
				
			||||||
 | 
							UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						type ExpectedWebauthnCredential struct {
 | 
				
			||||||
 | 
							ID           int64  `xorm:"pk autoincr"`
 | 
				
			||||||
 | 
							CredentialID string `xorm:"INDEX VARCHAR(410)"` // CredentalID in U2F is at most 255bytes / 5 * 8 = 408 - add a few extra characters for safety
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Prepare and load the testing database
 | 
				
			||||||
 | 
						x, deferable := prepareTestEnv(t, 0, new(WebauthnCredential), new(U2fRegistration), new(ExpectedWebauthnCredential))
 | 
				
			||||||
 | 
						if x == nil || t.Failed() {
 | 
				
			||||||
 | 
							defer deferable()
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer deferable()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if x.Dialect().URI().DBType == schemas.SQLITE {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Run the migration
 | 
				
			||||||
 | 
						if err := remigrateU2FCredentials(x); err != nil {
 | 
				
			||||||
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						expected := []ExpectedWebauthnCredential{}
 | 
				
			||||||
 | 
						if err := x.Table("expected_webauthn_credential").Asc("id").Find(&expected); !assert.NoError(t, err) {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						got := []ExpectedWebauthnCredential{}
 | 
				
			||||||
 | 
						if err := x.Table("webauthn_credential").Select("id, credential_id").Asc("id").Find(&got); !assert.NoError(t, err) {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assert.EqualValues(t, expected, got)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -49,22 +49,67 @@ func init() {
 | 
				
			|||||||
	db.RegisterModel(new(TeamUnit))
 | 
						db.RegisterModel(new(TeamUnit))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// SearchTeamOptions holds the search options
 | 
					// SearchOrgTeamOptions holds the search options
 | 
				
			||||||
type SearchTeamOptions struct {
 | 
					type SearchOrgTeamOptions struct {
 | 
				
			||||||
	db.ListOptions
 | 
						db.ListOptions
 | 
				
			||||||
	UserID      int64
 | 
					 | 
				
			||||||
	Keyword     string
 | 
						Keyword     string
 | 
				
			||||||
	OrgID       int64
 | 
						OrgID       int64
 | 
				
			||||||
	IncludeDesc bool
 | 
						IncludeDesc bool
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetUserTeamOptions holds the search options.
 | 
				
			||||||
 | 
					type GetUserTeamOptions struct {
 | 
				
			||||||
 | 
						db.ListOptions
 | 
				
			||||||
 | 
						UserID int64
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// SearchMembersOptions holds the search options
 | 
					// SearchMembersOptions holds the search options
 | 
				
			||||||
type SearchMembersOptions struct {
 | 
					type SearchMembersOptions struct {
 | 
				
			||||||
	db.ListOptions
 | 
						db.ListOptions
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// SearchTeam search for teams. Caller is responsible to check permissions.
 | 
					// GetUserTeams search for org teams. Caller is responsible to check permissions.
 | 
				
			||||||
func SearchTeam(opts *SearchTeamOptions) ([]*Team, int64, error) {
 | 
					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 {
 | 
						if opts.Page <= 0 {
 | 
				
			||||||
		opts.Page = 1
 | 
							opts.Page = 1
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -196,7 +241,7 @@ func (t *Team) getRepositories(e db.Engine) error {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetRepositories returns paginated repositories in team of organization.
 | 
					// 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 {
 | 
						if opts.Page == 0 {
 | 
				
			||||||
		return t.getRepositories(db.GetEngine(db.DefaultContext))
 | 
							return t.getRepositories(db.GetEngine(db.DefaultContext))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -716,7 +761,7 @@ func UpdateTeam(t *Team, authChanged, includeAllChanged bool) (err error) {
 | 
				
			|||||||
// DeleteTeam deletes given team.
 | 
					// DeleteTeam deletes given team.
 | 
				
			||||||
// It's caller's responsibility to assign organization ID.
 | 
					// It's caller's responsibility to assign organization ID.
 | 
				
			||||||
func DeleteTeam(t *Team) error {
 | 
					func DeleteTeam(t *Team) error {
 | 
				
			||||||
	if err := t.GetRepositories(&SearchTeamOptions{}); err != nil {
 | 
						if err := t.GetRepositories(&SearchOrgTeamOptions{}); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -858,7 +903,7 @@ func AddTeamMember(team *Team, userID int64) error {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Get team and its repositories.
 | 
						// Get team and its repositories.
 | 
				
			||||||
	if err := team.GetRepositories(&SearchTeamOptions{}); err != nil {
 | 
						if err := team.GetRepositories(&SearchOrgTeamOptions{}); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -46,7 +46,7 @@ func TestTeam_GetRepositories(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	test := func(teamID int64) {
 | 
						test := func(teamID int64) {
 | 
				
			||||||
		team := unittest.AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team)
 | 
							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)
 | 
							assert.Len(t, team.Repos, team.NumRepos)
 | 
				
			||||||
		for _, repo := range team.Repos {
 | 
							for _, repo := range team.Repos {
 | 
				
			||||||
			unittest.AssertExistsAndLoadBean(t, &TeamRepo{TeamID: teamID, RepoID: repo.ID})
 | 
								unittest.AssertExistsAndLoadBean(t, &TeamRepo{TeamID: teamID, RepoID: repo.ID})
 | 
				
			||||||
@@ -292,7 +292,7 @@ func TestGetTeamMembers(t *testing.T) {
 | 
				
			|||||||
func TestGetUserTeams(t *testing.T) {
 | 
					func TestGetUserTeams(t *testing.T) {
 | 
				
			||||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
						assert.NoError(t, unittest.PrepareTestDatabase())
 | 
				
			||||||
	test := func(userID int64) {
 | 
						test := func(userID int64) {
 | 
				
			||||||
		teams, _, err := SearchTeam(&SearchTeamOptions{UserID: userID})
 | 
							teams, _, err := GetUserTeams(&GetUserTeamOptions{UserID: userID})
 | 
				
			||||||
		assert.NoError(t, err)
 | 
							assert.NoError(t, err)
 | 
				
			||||||
		for _, team := range teams {
 | 
							for _, team := range teams {
 | 
				
			||||||
			unittest.AssertExistsAndLoadBean(t, &TeamUser{TeamID: team.ID, UID: userID})
 | 
								unittest.AssertExistsAndLoadBean(t, &TeamUser{TeamID: team.ID, UID: userID})
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -150,27 +150,56 @@ func getRepoAssignees(ctx context.Context, repo *repo_model.Repository) (_ []*us
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	e := db.GetEngine(ctx)
 | 
						e := db.GetEngine(ctx)
 | 
				
			||||||
	accesses := make([]*Access, 0, 10)
 | 
						userIDs := make([]int64, 0, 10)
 | 
				
			||||||
	if err = e.
 | 
						if err = e.Table("access").
 | 
				
			||||||
		Where("repo_id = ? AND mode >= ?", repo.ID, perm.AccessModeWrite).
 | 
							Where("repo_id = ? AND mode >= ?", repo.ID, perm.AccessModeWrite).
 | 
				
			||||||
		Find(&accesses); err != nil {
 | 
							Select("id").
 | 
				
			||||||
 | 
							Find(&userIDs); err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						additionalUserIDs := make([]int64, 0, 10)
 | 
				
			||||||
 | 
						if err = e.Table("team_user").
 | 
				
			||||||
 | 
							Join("INNER", "team_repo", "`team_repo`.team_id = `team_user`.team_id").
 | 
				
			||||||
 | 
							Join("INNER", "team_unit", "`team_unit`.team_id = `team_user`.team_id").
 | 
				
			||||||
 | 
							Where("`team_repo`.repo_id = ? AND `team_unit`.access_mode >= ?", repo.ID, perm.AccessModeWrite).
 | 
				
			||||||
 | 
							Distinct("`team_user`.uid").
 | 
				
			||||||
 | 
							Select("`team_user`.uid").
 | 
				
			||||||
 | 
							Find(&additionalUserIDs); err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						uidMap := map[int64]bool{}
 | 
				
			||||||
 | 
						i := 0
 | 
				
			||||||
 | 
						for _, uid := range userIDs {
 | 
				
			||||||
 | 
							if uidMap[uid] {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							uidMap[uid] = true
 | 
				
			||||||
 | 
							userIDs[i] = uid
 | 
				
			||||||
 | 
							i++
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						userIDs = userIDs[:i]
 | 
				
			||||||
 | 
						userIDs = append(userIDs, additionalUserIDs...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, uid := range additionalUserIDs {
 | 
				
			||||||
 | 
							if uidMap[uid] {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							userIDs[i] = uid
 | 
				
			||||||
 | 
							i++
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						userIDs = userIDs[:i]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Leave a seat for owner itself to append later, but if owner is an organization
 | 
						// Leave a seat for owner itself to append later, but if owner is an organization
 | 
				
			||||||
	// and just waste 1 unit is cheaper than re-allocate memory once.
 | 
						// and just waste 1 unit is cheaper than re-allocate memory once.
 | 
				
			||||||
	users := make([]*user_model.User, 0, len(accesses)+1)
 | 
						users := make([]*user_model.User, 0, len(userIDs)+1)
 | 
				
			||||||
	if len(accesses) > 0 {
 | 
						if len(userIDs) > 0 {
 | 
				
			||||||
		userIDs := make([]int64, len(accesses))
 | 
					 | 
				
			||||||
		for i := 0; i < len(accesses); i++ {
 | 
					 | 
				
			||||||
			userIDs[i] = accesses[i].UserID
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if err = e.In("id", userIDs).Find(&users); err != nil {
 | 
							if err = e.In("id", userIDs).Find(&users); err != nil {
 | 
				
			||||||
			return nil, err
 | 
								return nil, err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if !repo.Owner.IsOrganization() {
 | 
						if !repo.Owner.IsOrganization() && !uidMap[repo.OwnerID] {
 | 
				
			||||||
		users = append(users, repo.Owner)
 | 
							users = append(users, repo.Owner)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -948,7 +977,7 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// Remove attachment with no issue_id and release_id.
 | 
						// Remove attachment with no issue_id and release_id.
 | 
				
			||||||
	for i := range newAttachmentPaths {
 | 
						for i := range newAttachmentPaths {
 | 
				
			||||||
		admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete issue attachment", attachmentPaths[i])
 | 
							admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete issue attachment", newAttachmentPaths[i])
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if len(repo.Avatar) > 0 {
 | 
						if len(repo.Avatar) > 0 {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -328,7 +328,12 @@ func AllUnitKeyNames() []string {
 | 
				
			|||||||
// MinUnitAccessMode returns the minial permission of the permission map
 | 
					// MinUnitAccessMode returns the minial permission of the permission map
 | 
				
			||||||
func MinUnitAccessMode(unitsMap map[Type]perm.AccessMode) perm.AccessMode {
 | 
					func MinUnitAccessMode(unitsMap map[Type]perm.AccessMode) perm.AccessMode {
 | 
				
			||||||
	res := perm.AccessModeNone
 | 
						res := perm.AccessModeNone
 | 
				
			||||||
	for _, mode := range unitsMap {
 | 
						for t, mode := range unitsMap {
 | 
				
			||||||
 | 
							// Don't allow `TypeExternal{Tracker,Wiki}` to influence this as they can only be set to READ perms.
 | 
				
			||||||
 | 
							if t == TypeExternalTracker || t == TypeExternalWiki {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// get the minial permission great than AccessModeNone except all are AccessModeNone
 | 
							// get the minial permission great than AccessModeNone except all are AccessModeNone
 | 
				
			||||||
		if mode > perm.AccessModeNone && (res == perm.AccessModeNone || mode < res) {
 | 
							if mode > perm.AccessModeNone && (res == perm.AccessModeNone || mode < res) {
 | 
				
			||||||
			res = mode
 | 
								res = mode
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -60,7 +60,7 @@ type ExternalLoginUser struct {
 | 
				
			|||||||
	LastName          string
 | 
						LastName          string
 | 
				
			||||||
	NickName          string
 | 
						NickName          string
 | 
				
			||||||
	Description       string
 | 
						Description       string
 | 
				
			||||||
	AvatarURL         string
 | 
						AvatarURL         string `xorm:"TEXT"`
 | 
				
			||||||
	Location          string
 | 
						Location          string
 | 
				
			||||||
	AccessToken       string `xorm:"TEXT"`
 | 
						AccessToken       string `xorm:"TEXT"`
 | 
				
			||||||
	AccessTokenSecret string `xorm:"TEXT"`
 | 
						AccessTokenSecret string `xorm:"TEXT"`
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,13 +30,19 @@ func (users UserList) GetTwoFaStatus() map[int64]bool {
 | 
				
			|||||||
	for _, user := range users {
 | 
						for _, user := range users {
 | 
				
			||||||
		results[user.ID] = false // Set default to false
 | 
							results[user.ID] = false // Set default to false
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	tokenMaps, err := users.loadTwoFactorStatus(db.GetEngine(db.DefaultContext))
 | 
					
 | 
				
			||||||
	if err == nil {
 | 
						if tokenMaps, err := users.loadTwoFactorStatus(db.GetEngine(db.DefaultContext)); err == nil {
 | 
				
			||||||
		for _, token := range tokenMaps {
 | 
							for _, token := range tokenMaps {
 | 
				
			||||||
			results[token.UID] = true
 | 
								results[token.UID] = true
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if ids, err := users.userIDsWithWebAuthn(db.GetEngine(db.DefaultContext)); err == nil {
 | 
				
			||||||
 | 
							for _, id := range ids {
 | 
				
			||||||
 | 
								results[id] = true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return results
 | 
						return results
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -47,15 +53,23 @@ func (users UserList) loadTwoFactorStatus(e db.Engine) (map[int64]*auth.TwoFacto
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	userIDs := users.GetUserIDs()
 | 
						userIDs := users.GetUserIDs()
 | 
				
			||||||
	tokenMaps := make(map[int64]*auth.TwoFactor, len(userIDs))
 | 
						tokenMaps := make(map[int64]*auth.TwoFactor, len(userIDs))
 | 
				
			||||||
	err := e.
 | 
						if err := e.In("uid", userIDs).Find(&tokenMaps); err != nil {
 | 
				
			||||||
		In("uid", userIDs).
 | 
					 | 
				
			||||||
		Find(&tokenMaps)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, fmt.Errorf("find two factor: %v", err)
 | 
							return nil, fmt.Errorf("find two factor: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return tokenMaps, nil
 | 
						return tokenMaps, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (users UserList) userIDsWithWebAuthn(e db.Engine) ([]int64, error) {
 | 
				
			||||||
 | 
						if len(users) == 0 {
 | 
				
			||||||
 | 
							return nil, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						ids := make([]int64, 0, len(users))
 | 
				
			||||||
 | 
						if err := e.Table(new(auth.WebAuthnCredential)).In("user_id", users.GetUserIDs()).Select("user_id").Distinct("user_id").Find(&ids); err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("find two factor: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ids, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetUsersByIDs returns all resolved users from a list of Ids.
 | 
					// GetUsersByIDs returns all resolved users from a list of Ids.
 | 
				
			||||||
func GetUsersByIDs(ids []int64) (UserList, error) {
 | 
					func GetUsersByIDs(ids []int64) (UserList, error) {
 | 
				
			||||||
	ous := make([]*User, 0, len(ids))
 | 
						ous := make([]*User, 0, len(ids))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,6 +20,7 @@ import (
 | 
				
			|||||||
// SearchUserOptions contains the options for searching
 | 
					// SearchUserOptions contains the options for searching
 | 
				
			||||||
type SearchUserOptions struct {
 | 
					type SearchUserOptions struct {
 | 
				
			||||||
	db.ListOptions
 | 
						db.ListOptions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Keyword       string
 | 
						Keyword       string
 | 
				
			||||||
	Type          UserType
 | 
						Type          UserType
 | 
				
			||||||
	UID           int64
 | 
						UID           int64
 | 
				
			||||||
@@ -33,6 +34,8 @@ type SearchUserOptions struct {
 | 
				
			|||||||
	IsRestricted       util.OptionalBool
 | 
						IsRestricted       util.OptionalBool
 | 
				
			||||||
	IsTwoFactorEnabled util.OptionalBool
 | 
						IsTwoFactorEnabled util.OptionalBool
 | 
				
			||||||
	IsProhibitLogin    util.OptionalBool
 | 
						IsProhibitLogin    util.OptionalBool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ExtraParamStrings map[string]string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (opts *SearchUserOptions) toSearchQueryBase() *xorm.Session {
 | 
					func (opts *SearchUserOptions) toSearchQueryBase() *xorm.Session {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -827,8 +827,9 @@ func validateUser(u *User) error {
 | 
				
			|||||||
	return ValidateEmail(u.Email)
 | 
						return ValidateEmail(u.Email)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func updateUser(ctx context.Context, u *User, changePrimaryEmail bool) error {
 | 
					func updateUser(ctx context.Context, u *User, changePrimaryEmail bool, cols ...string) error {
 | 
				
			||||||
	if err := validateUser(u); err != nil {
 | 
						err := validateUser(u)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -860,15 +861,35 @@ func updateUser(ctx context.Context, u *User, changePrimaryEmail bool) error {
 | 
				
			|||||||
		}); err != nil {
 | 
							}); err != nil {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
						} else if !u.IsOrganization() { // check if primary email in email_address table
 | 
				
			||||||
 | 
							primaryEmailExist, err := e.Where("uid=? AND is_primary=?", u.ID, true).Exist(&EmailAddress{})
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if !primaryEmailExist {
 | 
				
			||||||
 | 
								if _, err = e.Insert(&EmailAddress{
 | 
				
			||||||
 | 
									Email:       u.Email,
 | 
				
			||||||
 | 
									UID:         u.ID,
 | 
				
			||||||
 | 
									IsActivated: true,
 | 
				
			||||||
 | 
									IsPrimary:   true,
 | 
				
			||||||
 | 
								}); err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, err := e.ID(u.ID).AllCols().Update(u)
 | 
						if len(cols) == 0 {
 | 
				
			||||||
 | 
							_, err = e.ID(u.ID).AllCols().Update(u)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							_, err = e.ID(u.ID).Cols(cols...).Update(u)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	return err
 | 
						return err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// UpdateUser updates user's information.
 | 
					// UpdateUser updates user's information.
 | 
				
			||||||
func UpdateUser(u *User, emailChanged bool) error {
 | 
					func UpdateUser(u *User, emailChanged bool, cols ...string) error {
 | 
				
			||||||
	return updateUser(db.DefaultContext, u, emailChanged)
 | 
						return updateUser(db.DefaultContext, u, emailChanged, cols...)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// UpdateUserCols update user according special columns
 | 
					// UpdateUserCols update user according special columns
 | 
				
			||||||
@@ -1104,19 +1125,9 @@ func GetUserByEmailContext(ctx context.Context, email string) (*User, error) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	email = strings.ToLower(email)
 | 
						email = strings.ToLower(email)
 | 
				
			||||||
	// First try to find the user by primary email
 | 
					 | 
				
			||||||
	user := &User{Email: email}
 | 
					 | 
				
			||||||
	has, err := db.GetEngine(ctx).Get(user)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if has {
 | 
					 | 
				
			||||||
		return user, nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Otherwise, check in alternative list for activated email addresses
 | 
						// Otherwise, check in alternative list for activated email addresses
 | 
				
			||||||
	emailAddress := &EmailAddress{Email: email, IsActivated: true}
 | 
						emailAddress := &EmailAddress{LowerEmail: email, IsActivated: true}
 | 
				
			||||||
	has, err = db.GetEngine(ctx).Get(emailAddress)
 | 
						has, err := db.GetEngine(ctx).Get(emailAddress)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -235,6 +235,20 @@ func TestCreateUserInvalidEmail(t *testing.T) {
 | 
				
			|||||||
	assert.True(t, IsErrEmailInvalid(err))
 | 
						assert.True(t, IsErrEmailInvalid(err))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestCreateUserEmailAlreadyUsed(t *testing.T) {
 | 
				
			||||||
 | 
						assert.NoError(t, unittest.PrepareTestDatabase())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						user := unittest.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// add new user with user2's email
 | 
				
			||||||
 | 
						user.Name = "testuser"
 | 
				
			||||||
 | 
						user.LowerName = strings.ToLower(user.Name)
 | 
				
			||||||
 | 
						user.ID = 0
 | 
				
			||||||
 | 
						err := CreateUser(user)
 | 
				
			||||||
 | 
						assert.Error(t, err)
 | 
				
			||||||
 | 
						assert.True(t, IsErrEmailAlreadyUsed(err))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestGetUserIDsByNames(t *testing.T) {
 | 
					func TestGetUserIDsByNames(t *testing.T) {
 | 
				
			||||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
						assert.NoError(t, unittest.PrepareTestDatabase())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -63,6 +63,7 @@ func EscapeControlBytes(text []byte) (EscapeStatus, []byte) {
 | 
				
			|||||||
func EscapeControlReader(text io.Reader, output io.Writer) (escaped EscapeStatus, err error) {
 | 
					func EscapeControlReader(text io.Reader, output io.Writer) (escaped EscapeStatus, err error) {
 | 
				
			||||||
	buf := make([]byte, 4096)
 | 
						buf := make([]byte, 4096)
 | 
				
			||||||
	readStart := 0
 | 
						readStart := 0
 | 
				
			||||||
 | 
						runeCount := 0
 | 
				
			||||||
	var n int
 | 
						var n int
 | 
				
			||||||
	var writePos int
 | 
						var writePos int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -74,10 +75,13 @@ readingloop:
 | 
				
			|||||||
	for err == nil {
 | 
						for err == nil {
 | 
				
			||||||
		n, err = text.Read(buf[readStart:])
 | 
							n, err = text.Read(buf[readStart:])
 | 
				
			||||||
		bs := buf[:n+readStart]
 | 
							bs := buf[:n+readStart]
 | 
				
			||||||
 | 
							n = len(bs)
 | 
				
			||||||
		i := 0
 | 
							i := 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		for i < len(bs) {
 | 
							for i < len(bs) {
 | 
				
			||||||
			r, size := utf8.DecodeRune(bs[i:])
 | 
								r, size := utf8.DecodeRune(bs[i:])
 | 
				
			||||||
 | 
								runeCount++
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Now handle the codepoints
 | 
								// Now handle the codepoints
 | 
				
			||||||
			switch {
 | 
								switch {
 | 
				
			||||||
			case r == utf8.RuneError:
 | 
								case r == utf8.RuneError:
 | 
				
			||||||
@@ -112,6 +116,8 @@ readingloop:
 | 
				
			|||||||
				lineHasRTLScript = false
 | 
									lineHasRTLScript = false
 | 
				
			||||||
				lineHasLTRScript = false
 | 
									lineHasLTRScript = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case runeCount == 1 && r == 0xFEFF: // UTF BOM
 | 
				
			||||||
 | 
									// the first BOM is safe
 | 
				
			||||||
			case r == '\r' || r == '\t' || r == ' ':
 | 
								case r == '\r' || r == '\t' || r == ' ':
 | 
				
			||||||
				// These are acceptable control characters and space characters
 | 
									// These are acceptable control characters and space characters
 | 
				
			||||||
			case unicode.IsSpace(r):
 | 
								case unicode.IsSpace(r):
 | 
				
			||||||
@@ -143,7 +149,8 @@ readingloop:
 | 
				
			|||||||
					return
 | 
										return
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				writePos = i + size
 | 
									writePos = i + size
 | 
				
			||||||
			case unicode.Is(unicode.C, r):
 | 
								// 65279 == BOM rune.
 | 
				
			||||||
 | 
								case unicode.Is(unicode.C, r) && r != rune(65279):
 | 
				
			||||||
				escaped.Escaped = true
 | 
									escaped.Escaped = true
 | 
				
			||||||
				escaped.HasControls = true
 | 
									escaped.HasControls = true
 | 
				
			||||||
				if writePos < i {
 | 
									if writePos < i {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -129,6 +129,14 @@ then resh (ר), and finally heh (ה) (which should appear leftmost).`,
 | 
				
			|||||||
			"\n" + `if access_level != "user<span class="escaped-code-point" data-escaped="[U+202E]"><span class="char">` + "\u202e" + `</span></span> <span class="escaped-code-point" data-escaped="[U+2066]"><span class="char">` + "\u2066" + `</span></span>// Check if admin<span class="escaped-code-point" data-escaped="[U+2069]"><span class="char">` + "\u2069" + `</span></span> <span class="escaped-code-point" data-escaped="[U+2066]"><span class="char">` + "\u2066" + `</span></span>" {` + "\n",
 | 
								"\n" + `if access_level != "user<span class="escaped-code-point" data-escaped="[U+202E]"><span class="char">` + "\u202e" + `</span></span> <span class="escaped-code-point" data-escaped="[U+2066]"><span class="char">` + "\u2066" + `</span></span>// Check if admin<span class="escaped-code-point" data-escaped="[U+2069]"><span class="char">` + "\u2069" + `</span></span> <span class="escaped-code-point" data-escaped="[U+2066]"><span class="char">` + "\u2066" + `</span></span>" {` + "\n",
 | 
				
			||||||
		status: EscapeStatus{Escaped: true, HasBIDI: true, BadBIDI: true, HasLTRScript: true, HasRTLScript: true},
 | 
							status: EscapeStatus{Escaped: true, HasBIDI: true, BadBIDI: true, HasLTRScript: true, HasRTLScript: true},
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							// UTF-8/16/32 all use the same codepoint for BOM
 | 
				
			||||||
 | 
							// Gitea could read UTF-16/32 content and convert into UTF-8 internally then render it, so we only process UTF-8 internally
 | 
				
			||||||
 | 
							name:   "UTF BOM",
 | 
				
			||||||
 | 
							text:   "\xef\xbb\xbftest",
 | 
				
			||||||
 | 
							result: "\xef\xbb\xbftest",
 | 
				
			||||||
 | 
							status: EscapeStatus{HasLTRScript: true},
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestEscapeControlString(t *testing.T) {
 | 
					func TestEscapeControlString(t *testing.T) {
 | 
				
			||||||
@@ -163,10 +171,18 @@ func TestEscapeControlReader(t *testing.T) {
 | 
				
			|||||||
	// lets add some control characters to the tests
 | 
						// lets add some control characters to the tests
 | 
				
			||||||
	tests := make([]escapeControlTest, 0, len(escapeControlTests)*3)
 | 
						tests := make([]escapeControlTest, 0, len(escapeControlTests)*3)
 | 
				
			||||||
	copy(tests, escapeControlTests)
 | 
						copy(tests, escapeControlTests)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// if there is a BOM, we should keep the BOM
 | 
				
			||||||
 | 
						addPrefix := func(prefix, s string) string {
 | 
				
			||||||
 | 
							if strings.HasPrefix(s, "\xef\xbb\xbf") {
 | 
				
			||||||
 | 
								return s[:3] + prefix + s[3:]
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return prefix + s
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	for _, test := range escapeControlTests {
 | 
						for _, test := range escapeControlTests {
 | 
				
			||||||
		test.name += " (+Control)"
 | 
							test.name += " (+Control)"
 | 
				
			||||||
		test.text = "\u001E" + test.text
 | 
							test.text = addPrefix("\u001E", test.text)
 | 
				
			||||||
		test.result = `<span class="escaped-code-point" data-escaped="[U+001E]"><span class="char">` + "\u001e" + `</span></span>` + test.result
 | 
							test.result = addPrefix(`<span class="escaped-code-point" data-escaped="[U+001E]"><span class="char">`+"\u001e"+`</span></span>`, test.result)
 | 
				
			||||||
		test.status.Escaped = true
 | 
							test.status.Escaped = true
 | 
				
			||||||
		test.status.HasControls = true
 | 
							test.status.HasControls = true
 | 
				
			||||||
		tests = append(tests, test)
 | 
							tests = append(tests, test)
 | 
				
			||||||
@@ -174,8 +190,8 @@ func TestEscapeControlReader(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	for _, test := range escapeControlTests {
 | 
						for _, test := range escapeControlTests {
 | 
				
			||||||
		test.name += " (+Mark)"
 | 
							test.name += " (+Mark)"
 | 
				
			||||||
		test.text = "\u0300" + test.text
 | 
							test.text = addPrefix("\u0300", test.text)
 | 
				
			||||||
		test.result = `<span class="escaped-code-point" data-escaped="[U+0300]"><span class="char">` + "\u0300" + `</span></span>` + test.result
 | 
							test.result = addPrefix(`<span class="escaped-code-point" data-escaped="[U+0300]"><span class="char">`+"\u0300"+`</span></span>`, test.result)
 | 
				
			||||||
		test.status.Escaped = true
 | 
							test.status.Escaped = true
 | 
				
			||||||
		test.status.HasMarks = true
 | 
							test.status.HasMarks = true
 | 
				
			||||||
		tests = append(tests, test)
 | 
							tests = append(tests, test)
 | 
				
			||||||
@@ -200,3 +216,12 @@ func TestEscapeControlReader(t *testing.T) {
 | 
				
			|||||||
		})
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestEscapeControlReader_panic(t *testing.T) {
 | 
				
			||||||
 | 
						bs := make([]byte, 0, 20479)
 | 
				
			||||||
 | 
						bs = append(bs, 'A')
 | 
				
			||||||
 | 
						for i := 0; i < 6826; i++ {
 | 
				
			||||||
 | 
							bs = append(bs, []byte("—")...)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						_, _ = EscapeControlBytes(bs)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,9 +9,11 @@ import (
 | 
				
			|||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"crypto/sha256"
 | 
						"crypto/sha256"
 | 
				
			||||||
	"encoding/hex"
 | 
						"encoding/hex"
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
	"html"
 | 
						"html"
 | 
				
			||||||
	"html/template"
 | 
						"html/template"
 | 
				
			||||||
	"io"
 | 
						"io"
 | 
				
			||||||
 | 
						"net"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"net/url"
 | 
						"net/url"
 | 
				
			||||||
	"path"
 | 
						"path"
 | 
				
			||||||
@@ -264,6 +266,12 @@ func (ctx *Context) ServerError(logMsg string, logErr error) {
 | 
				
			|||||||
func (ctx *Context) serverErrorInternal(logMsg string, logErr error) {
 | 
					func (ctx *Context) serverErrorInternal(logMsg string, logErr error) {
 | 
				
			||||||
	if logErr != nil {
 | 
						if logErr != nil {
 | 
				
			||||||
		log.ErrorWithSkip(2, "%s: %v", logMsg, logErr)
 | 
							log.ErrorWithSkip(2, "%s: %v", logMsg, logErr)
 | 
				
			||||||
 | 
							if errors.Is(logErr, &net.OpError{}) {
 | 
				
			||||||
 | 
								// This is an error within the underlying connection
 | 
				
			||||||
 | 
								// and further rendering will not work so just return
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if !setting.IsProd {
 | 
							if !setting.IsProd {
 | 
				
			||||||
			ctx.Data["ErrorMsg"] = logErr
 | 
								ctx.Data["ErrorMsg"] = logErr
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -291,6 +299,7 @@ func (ctx *Context) PlainTextBytes(status int, bs []byte) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	ctx.Resp.WriteHeader(status)
 | 
						ctx.Resp.WriteHeader(status)
 | 
				
			||||||
	ctx.Resp.Header().Set("Content-Type", "text/plain;charset=utf-8")
 | 
						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 {
 | 
						if _, err := ctx.Resp.Write(bs); err != nil {
 | 
				
			||||||
		log.Error("Write bytes failed: %v", err)
 | 
							log.Error("Write bytes failed: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -129,7 +129,23 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// Team.
 | 
						// Team.
 | 
				
			||||||
	if ctx.Org.IsMember {
 | 
						if ctx.Org.IsMember {
 | 
				
			||||||
 | 
							shouldSeeAllTeams := false
 | 
				
			||||||
		if ctx.Org.IsOwner {
 | 
							if ctx.Org.IsOwner {
 | 
				
			||||||
 | 
								shouldSeeAllTeams = true
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								teams, err := org.GetUserTeams(ctx.User.ID)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									ctx.ServerError("GetUserTeams", err)
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								for _, team := range teams {
 | 
				
			||||||
 | 
									if team.IncludesAllRepositories && team.AccessMode >= perm.AccessModeAdmin {
 | 
				
			||||||
 | 
										shouldSeeAllTeams = true
 | 
				
			||||||
 | 
										break
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if shouldSeeAllTeams {
 | 
				
			||||||
			ctx.Org.Teams, err = org.LoadTeams()
 | 
								ctx.Org.Teams, err = org.LoadTeams()
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				ctx.ServerError("LoadTeams", err)
 | 
									ctx.ServerError("LoadTeams", err)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -911,7 +911,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
			if refType == RepoRefLegacy {
 | 
								if refType == RepoRefLegacy {
 | 
				
			||||||
				// redirect from old URL scheme to new URL scheme
 | 
									// redirect from old URL scheme to new URL scheme
 | 
				
			||||||
				prefix := strings.TrimPrefix(setting.AppSubURL+strings.TrimSuffix(ctx.Req.URL.Path, ctx.Params("*")), ctx.Repo.RepoLink)
 | 
									prefix := strings.TrimPrefix(setting.AppSubURL+strings.ToLower(strings.TrimSuffix(ctx.Req.URL.Path, ctx.Params("*"))), strings.ToLower(ctx.Repo.RepoLink))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				ctx.Redirect(path.Join(
 | 
									ctx.Redirect(path.Join(
 | 
				
			||||||
					ctx.Repo.RepoLink,
 | 
										ctx.Repo.RepoLink,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -163,6 +163,8 @@ func (c *Command) RunWithContext(rc *RunContext) error {
 | 
				
			|||||||
		fmt.Sprintf("LC_ALL=%s", DefaultLocale),
 | 
							fmt.Sprintf("LC_ALL=%s", DefaultLocale),
 | 
				
			||||||
		// avoid prompting for credentials interactively, supported since git v2.3
 | 
							// avoid prompting for credentials interactively, supported since git v2.3
 | 
				
			||||||
		"GIT_TERMINAL_PROMPT=0",
 | 
							"GIT_TERMINAL_PROMPT=0",
 | 
				
			||||||
 | 
							// ignore replace references (https://git-scm.com/docs/git-replace)
 | 
				
			||||||
 | 
							"GIT_NO_REPLACE_OBJECTS=1",
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// TODO: verify if this is still needed in golang 1.15
 | 
						// TODO: verify if this is still needed in golang 1.15
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,20 +16,25 @@ import (
 | 
				
			|||||||
	"github.com/stretchr/testify/assert"
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const testReposDir = "tests/repos/"
 | 
					const (
 | 
				
			||||||
const benchmarkReposDir = "benchmark/repos/"
 | 
						testReposDir = "tests/repos/"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func cloneRepo(url, dir, name string) (string, error) {
 | 
					func cloneRepo(url, name string) (string, error) {
 | 
				
			||||||
	repoDir := filepath.Join(dir, name)
 | 
						repoDir, err := os.MkdirTemp("", name)
 | 
				
			||||||
	if _, err := os.Stat(repoDir); err == nil {
 | 
						if err != nil {
 | 
				
			||||||
		return repoDir, nil
 | 
							return "", err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return repoDir, Clone(url, repoDir, CloneRepoOptions{
 | 
						if err := Clone(url, repoDir, CloneRepoOptions{
 | 
				
			||||||
		Mirror:  false,
 | 
							Mirror:  false,
 | 
				
			||||||
		Bare:    false,
 | 
							Bare:    false,
 | 
				
			||||||
		Quiet:   true,
 | 
							Quiet:   true,
 | 
				
			||||||
		Timeout: 5 * time.Minute,
 | 
							Timeout: 5 * time.Minute,
 | 
				
			||||||
	})
 | 
						}); err != nil {
 | 
				
			||||||
 | 
							_ = util.RemoveAll(repoDir)
 | 
				
			||||||
 | 
							return "", err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return repoDir, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func testGetCommitsInfo(t *testing.T, repo1 *Repository) {
 | 
					func testGetCommitsInfo(t *testing.T, repo1 *Repository) {
 | 
				
			||||||
@@ -59,20 +64,35 @@ func testGetCommitsInfo(t *testing.T, repo1 *Repository) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	for _, testCase := range testCases {
 | 
						for _, testCase := range testCases {
 | 
				
			||||||
		commit, err := repo1.GetCommit(testCase.CommitID)
 | 
							commit, err := repo1.GetCommit(testCase.CommitID)
 | 
				
			||||||
		assert.NoError(t, err)
 | 
							if err != nil {
 | 
				
			||||||
 | 
								assert.NoError(t, err, "Unable to get commit: %s from testcase due to error: %v", testCase.CommitID, err)
 | 
				
			||||||
 | 
								// no point trying to do anything else for this test.
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		assert.NotNil(t, commit)
 | 
							assert.NotNil(t, commit)
 | 
				
			||||||
		assert.NotNil(t, commit.Tree)
 | 
							assert.NotNil(t, commit.Tree)
 | 
				
			||||||
		assert.NotNil(t, commit.Tree.repo)
 | 
							assert.NotNil(t, commit.Tree.repo)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		tree, err := commit.Tree.SubTree(testCase.Path)
 | 
							tree, err := commit.Tree.SubTree(testCase.Path)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								assert.NoError(t, err, "Unable to get subtree: %s of commit: %s from testcase due to error: %v", testCase.Path, testCase.CommitID, err)
 | 
				
			||||||
 | 
								// no point trying to do anything else for this test.
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		assert.NotNil(t, tree, "tree is nil for testCase CommitID %s in Path %s", testCase.CommitID, testCase.Path)
 | 
							assert.NotNil(t, tree, "tree is nil for testCase CommitID %s in Path %s", testCase.CommitID, testCase.Path)
 | 
				
			||||||
		assert.NotNil(t, tree.repo, "repo is nil for testCase CommitID %s in Path %s", testCase.CommitID, testCase.Path)
 | 
							assert.NotNil(t, tree.repo, "repo is nil for testCase CommitID %s in Path %s", testCase.CommitID, testCase.Path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		assert.NoError(t, err)
 | 
					 | 
				
			||||||
		entries, err := tree.ListEntries()
 | 
							entries, err := tree.ListEntries()
 | 
				
			||||||
		assert.NoError(t, err)
 | 
							if err != nil {
 | 
				
			||||||
		commitsInfo, treeCommit, err := entries.GetCommitsInfo(context.Background(), commit, testCase.Path, nil)
 | 
								assert.NoError(t, err, "Unable to get entries of subtree: %s in commit: %s from testcase due to error: %v", testCase.Path, testCase.CommitID, err)
 | 
				
			||||||
		assert.NoError(t, err)
 | 
								// no point trying to do anything else for this test.
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// FIXME: Context.TODO() - if graceful has started we should use its Shutdown context otherwise use install signals in TestMain.
 | 
				
			||||||
 | 
							commitsInfo, treeCommit, err := entries.GetCommitsInfo(context.TODO(), commit, testCase.Path, nil)
 | 
				
			||||||
 | 
							assert.NoError(t, err, "Unable to get commit information for entries of subtree: %s in commit: %s from testcase due to error: %v", testCase.Path, testCase.CommitID, err)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			t.FailNow()
 | 
								t.FailNow()
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -98,40 +118,52 @@ func TestEntries_GetCommitsInfo(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	testGetCommitsInfo(t, bareRepo1)
 | 
						testGetCommitsInfo(t, bareRepo1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	clonedPath, err := cloneRepo(bareRepo1Path, testReposDir, "repo1_TestEntries_GetCommitsInfo")
 | 
						clonedPath, err := cloneRepo(bareRepo1Path, "repo1_TestEntries_GetCommitsInfo")
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						if err != nil {
 | 
				
			||||||
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	defer util.RemoveAll(clonedPath)
 | 
						defer util.RemoveAll(clonedPath)
 | 
				
			||||||
	clonedRepo1, err := OpenRepository(clonedPath)
 | 
						clonedRepo1, err := OpenRepository(clonedPath)
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						if err != nil {
 | 
				
			||||||
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	defer clonedRepo1.Close()
 | 
						defer clonedRepo1.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	testGetCommitsInfo(t, clonedRepo1)
 | 
						testGetCommitsInfo(t, clonedRepo1)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func BenchmarkEntries_GetCommitsInfo(b *testing.B) {
 | 
					func BenchmarkEntries_GetCommitsInfo(b *testing.B) {
 | 
				
			||||||
	benchmarks := []struct {
 | 
						type benchmarkType struct {
 | 
				
			||||||
		url  string
 | 
							url  string
 | 
				
			||||||
		name string
 | 
							name string
 | 
				
			||||||
	}{
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						benchmarks := []benchmarkType{
 | 
				
			||||||
		{url: "https://github.com/go-gitea/gitea.git", name: "gitea"},
 | 
							{url: "https://github.com/go-gitea/gitea.git", name: "gitea"},
 | 
				
			||||||
		{url: "https://github.com/ethantkoenig/manyfiles.git", name: "manyfiles"},
 | 
							{url: "https://github.com/ethantkoenig/manyfiles.git", name: "manyfiles"},
 | 
				
			||||||
		{url: "https://github.com/moby/moby.git", name: "moby"},
 | 
							{url: "https://github.com/moby/moby.git", name: "moby"},
 | 
				
			||||||
		{url: "https://github.com/golang/go.git", name: "go"},
 | 
							{url: "https://github.com/golang/go.git", name: "go"},
 | 
				
			||||||
		{url: "https://github.com/torvalds/linux.git", name: "linux"},
 | 
							{url: "https://github.com/torvalds/linux.git", name: "linux"},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	for _, benchmark := range benchmarks {
 | 
					
 | 
				
			||||||
 | 
						doBenchmark := func(benchmark benchmarkType) {
 | 
				
			||||||
		var commit *Commit
 | 
							var commit *Commit
 | 
				
			||||||
		var entries Entries
 | 
							var entries Entries
 | 
				
			||||||
		var repo *Repository
 | 
							var repo *Repository
 | 
				
			||||||
		if repoPath, err := cloneRepo(benchmark.url, benchmarkReposDir, benchmark.name); err != nil {
 | 
							repoPath, err := cloneRepo(benchmark.url, benchmark.name)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
			b.Fatal(err)
 | 
								b.Fatal(err)
 | 
				
			||||||
		} else if repo, err = OpenRepository(repoPath); err != nil {
 | 
							}
 | 
				
			||||||
 | 
							defer util.RemoveAll(repoPath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if repo, err = OpenRepository(repoPath); err != nil {
 | 
				
			||||||
			b.Fatal(err)
 | 
								b.Fatal(err)
 | 
				
			||||||
		} else if commit, err = repo.GetBranchCommit("master"); err != nil {
 | 
							}
 | 
				
			||||||
			repo.Close()
 | 
							defer repo.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if commit, err = repo.GetBranchCommit("master"); err != nil {
 | 
				
			||||||
			b.Fatal(err)
 | 
								b.Fatal(err)
 | 
				
			||||||
		} else if entries, err = commit.Tree.ListEntries(); err != nil {
 | 
							} else if entries, err = commit.Tree.ListEntries(); err != nil {
 | 
				
			||||||
			repo.Close()
 | 
					 | 
				
			||||||
			b.Fatal(err)
 | 
								b.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		entries.Sort()
 | 
							entries.Sort()
 | 
				
			||||||
@@ -144,6 +176,9 @@ func BenchmarkEntries_GetCommitsInfo(b *testing.B) {
 | 
				
			|||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
		repo.Close()
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, benchmark := range benchmarks {
 | 
				
			||||||
 | 
							doBenchmark(benchmark)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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))
 | 
						ctx, _, finished := process.GetManager().AddContext(repo.Ctx, fmt.Sprintf("GetRawDiffForFile: [repo_path: %s]", repo.Path))
 | 
				
			||||||
	defer finished()
 | 
						defer finished()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var cmd *exec.Cmd
 | 
						cmd := exec.CommandContext(ctx, GitExecutable, GlobalCommandArgs...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	switch diffType {
 | 
						switch diffType {
 | 
				
			||||||
	case RawDiffNormal:
 | 
						case RawDiffNormal:
 | 
				
			||||||
		if len(startCommit) != 0 {
 | 
							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 {
 | 
							} 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 {
 | 
							} else {
 | 
				
			||||||
			c, _ := commit.Parent(0)
 | 
								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:
 | 
						case RawDiffPatch:
 | 
				
			||||||
		if len(startCommit) != 0 {
 | 
							if len(startCommit) != 0 {
 | 
				
			||||||
			query := fmt.Sprintf("%s...%s", endCommit, startCommit)
 | 
								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 {
 | 
							} 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 {
 | 
							} else {
 | 
				
			||||||
			c, _ := commit.Parent(0)
 | 
								c, _ := commit.Parent(0)
 | 
				
			||||||
			query := fmt.Sprintf("%s...%s", endCommit, c.ID.String())
 | 
								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:
 | 
						default:
 | 
				
			||||||
		return fmt.Errorf("invalid diffType: %s", diffType)
 | 
							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)
 | 
						log.Trace("Searching for git note corresponding to the commit %q in the repository %q", commitID, repo.Path)
 | 
				
			||||||
	notes, err := repo.GetCommit(NotesRef)
 | 
						notes, err := repo.GetCommit(NotesRef)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
 | 
							if IsErrNotExist(err) {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		log.Error("Unable to get commit from ref %q. Error: %v", NotesRef, err)
 | 
							log.Error("Unable to get commit from ref %q. Error: %v", NotesRef, err)
 | 
				
			||||||
		return 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)
 | 
						log.Trace("Searching for git note corresponding to the commit %q in the repository %q", commitID, repo.Path)
 | 
				
			||||||
	notes, err := repo.GetCommit(NotesRef)
 | 
						notes, err := repo.GetCommit(NotesRef)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
 | 
							if IsErrNotExist(err) {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		log.Error("Unable to get commit from ref %q. Error: %v", NotesRef, err)
 | 
							log.Error("Unable to get commit from ref %q. Error: %v", NotesRef, err)
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -79,16 +79,20 @@ func InitRepository(repoPath string, bare bool) error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// IsEmpty Check if repository is empty.
 | 
					// IsEmpty Check if repository is empty.
 | 
				
			||||||
func (repo *Repository) IsEmpty() (bool, error) {
 | 
					func (repo *Repository) IsEmpty() (bool, error) {
 | 
				
			||||||
	var errbuf strings.Builder
 | 
						var errbuf, output strings.Builder
 | 
				
			||||||
	if err := NewCommand("log", "-1").RunInDirPipeline(repo.Path, nil, &errbuf); err != nil {
 | 
						if err := NewCommandContext(repo.Ctx, "show-ref", "--head", "^HEAD$").RunWithContext(&RunContext{
 | 
				
			||||||
		if strings.Contains(errbuf.String(), "fatal: bad default revision 'HEAD'") ||
 | 
							Timeout: -1,
 | 
				
			||||||
			strings.Contains(errbuf.String(), "fatal: your current branch 'master' does not have any commits yet") {
 | 
							Dir:     repo.Path,
 | 
				
			||||||
 | 
							Stdout:  &output,
 | 
				
			||||||
 | 
							Stderr:  &errbuf,
 | 
				
			||||||
 | 
						}); err != nil {
 | 
				
			||||||
 | 
							if err.Error() == "exit status 1" && errbuf.String() == "" {
 | 
				
			||||||
			return true, nil
 | 
								return true, nil
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return true, fmt.Errorf("check empty: %v - %s", err, errbuf.String())
 | 
							return true, fmt.Errorf("check empty: %v - %s", err, errbuf.String())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return false, nil
 | 
						return strings.TrimSpace(output.String()) == "", nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CloneRepoOptions options when clone a repository
 | 
					// CloneRepoOptions options when clone a repository
 | 
				
			||||||
@@ -101,6 +105,7 @@ type CloneRepoOptions struct {
 | 
				
			|||||||
	Shared     bool
 | 
						Shared     bool
 | 
				
			||||||
	NoCheckout bool
 | 
						NoCheckout bool
 | 
				
			||||||
	Depth      int
 | 
						Depth      int
 | 
				
			||||||
 | 
						Filter     string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Clone clones original repository to target path.
 | 
					// Clone clones original repository to target path.
 | 
				
			||||||
@@ -141,7 +146,9 @@ func CloneWithArgs(ctx context.Context, from, to string, args []string, opts Clo
 | 
				
			|||||||
	if opts.Depth > 0 {
 | 
						if opts.Depth > 0 {
 | 
				
			||||||
		cmd.AddArguments("--depth", strconv.Itoa(opts.Depth))
 | 
							cmd.AddArguments("--depth", strconv.Itoa(opts.Depth))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						if opts.Filter != "" {
 | 
				
			||||||
 | 
							cmd.AddArguments("--filter", opts.Filter)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	if len(opts.Branch) > 0 {
 | 
						if len(opts.Branch) > 0 {
 | 
				
			||||||
		cmd.AddArguments("-b", opts.Branch)
 | 
							cmd.AddArguments("-b", opts.Branch)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -87,7 +87,7 @@ func (repo *Repository) CheckAttribute(opts CheckAttributeOpts) (map[string]map[
 | 
				
			|||||||
		return nil, fmt.Errorf("wrong number of fields in return from check-attr")
 | 
							return nil, fmt.Errorf("wrong number of fields in return from check-attr")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var name2attribute2info = make(map[string]map[string]string)
 | 
						name2attribute2info := make(map[string]map[string]string)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for i := 0; i < (len(fields) / 3); i++ {
 | 
						for i := 0; i < (len(fields) / 3); i++ {
 | 
				
			||||||
		filename := string(fields[3*i])
 | 
							filename := string(fields[3*i])
 | 
				
			||||||
@@ -179,17 +179,21 @@ func (c *CheckAttributeReader) Init(ctx context.Context) error {
 | 
				
			|||||||
// Run run cmd
 | 
					// Run run cmd
 | 
				
			||||||
func (c *CheckAttributeReader) Run() error {
 | 
					func (c *CheckAttributeReader) Run() error {
 | 
				
			||||||
	defer func() {
 | 
						defer func() {
 | 
				
			||||||
		_ = c.Close()
 | 
							_ = c.stdinReader.Close()
 | 
				
			||||||
 | 
							_ = c.stdOut.Close()
 | 
				
			||||||
	}()
 | 
						}()
 | 
				
			||||||
	stdErr := new(bytes.Buffer)
 | 
						stdErr := new(bytes.Buffer)
 | 
				
			||||||
	err := c.cmd.RunInDirTimeoutEnvFullPipelineFunc(c.env, -1, c.Repo.Path, c.stdOut, stdErr, c.stdinReader, func(_ context.Context, _ context.CancelFunc) error {
 | 
						err := c.cmd.RunInDirTimeoutEnvFullPipelineFunc(c.env, -1, c.Repo.Path, c.stdOut, stdErr, c.stdinReader, func(_ context.Context, _ context.CancelFunc) error {
 | 
				
			||||||
		close(c.running)
 | 
							select {
 | 
				
			||||||
 | 
							case <-c.running:
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								close(c.running)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	if err != nil && c.ctx.Err() != nil && err.Error() != "signal: killed" {
 | 
						if err != nil && c.ctx.Err() != nil && err.Error() != "signal: killed" {
 | 
				
			||||||
		return fmt.Errorf("failed to run attr-check. Error: %w\nStderr: %s", err, stdErr.String())
 | 
							return fmt.Errorf("failed to run attr-check. Error: %w\nStderr: %s", err, stdErr.String())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -229,10 +233,8 @@ func (c *CheckAttributeReader) CheckPath(path string) (rs map[string]string, err
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Close close pip after use
 | 
					// Close close pip after use
 | 
				
			||||||
func (c *CheckAttributeReader) Close() error {
 | 
					func (c *CheckAttributeReader) Close() error {
 | 
				
			||||||
	err := c.stdinWriter.Close()
 | 
					 | 
				
			||||||
	_ = c.stdinReader.Close()
 | 
					 | 
				
			||||||
	_ = c.stdOut.Close()
 | 
					 | 
				
			||||||
	c.cancel()
 | 
						c.cancel()
 | 
				
			||||||
 | 
						err := c.stdinWriter.Close()
 | 
				
			||||||
	select {
 | 
						select {
 | 
				
			||||||
	case <-c.running:
 | 
						case <-c.running:
 | 
				
			||||||
	default:
 | 
						default:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,17 +17,33 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func TestGetFormatPatch(t *testing.T) {
 | 
					func TestGetFormatPatch(t *testing.T) {
 | 
				
			||||||
	bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
 | 
						bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
 | 
				
			||||||
	clonedPath, err := cloneRepo(bareRepo1Path, testReposDir, "repo1_TestGetFormatPatch")
 | 
						clonedPath, err := cloneRepo(bareRepo1Path, "repo1_TestGetFormatPatch")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	defer util.RemoveAll(clonedPath)
 | 
						defer util.RemoveAll(clonedPath)
 | 
				
			||||||
	assert.NoError(t, err)
 | 
					
 | 
				
			||||||
	repo, err := OpenRepository(clonedPath)
 | 
						repo, err := OpenRepository(clonedPath)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	defer repo.Close()
 | 
						defer repo.Close()
 | 
				
			||||||
	assert.NoError(t, err)
 | 
					
 | 
				
			||||||
	rd := &bytes.Buffer{}
 | 
						rd := &bytes.Buffer{}
 | 
				
			||||||
	err = repo.GetPatch("8d92fc95^", "8d92fc95", rd)
 | 
						err = repo.GetPatch("8d92fc95^", "8d92fc95", rd)
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						if err != nil {
 | 
				
			||||||
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	patchb, err := io.ReadAll(rd)
 | 
						patchb, err := io.ReadAll(rd)
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						if err != nil {
 | 
				
			||||||
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	patch := string(patchb)
 | 
						patch := string(patchb)
 | 
				
			||||||
	assert.Regexp(t, "^From 8d92fc95", patch)
 | 
						assert.Regexp(t, "^From 8d92fc95", patch)
 | 
				
			||||||
	assert.Contains(t, patch, "Subject: [PATCH] Add file2.txt")
 | 
						assert.Contains(t, patch, "Subject: [PATCH] Add file2.txt")
 | 
				
			||||||
@@ -37,17 +53,25 @@ func TestReadPatch(t *testing.T) {
 | 
				
			|||||||
	// Ensure we can read the patch files
 | 
						// Ensure we can read the patch files
 | 
				
			||||||
	bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
 | 
						bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
 | 
				
			||||||
	repo, err := OpenRepository(bareRepo1Path)
 | 
						repo, err := OpenRepository(bareRepo1Path)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	defer repo.Close()
 | 
						defer repo.Close()
 | 
				
			||||||
	assert.NoError(t, err)
 | 
					 | 
				
			||||||
	// This patch doesn't exist
 | 
						// This patch doesn't exist
 | 
				
			||||||
	noFile, err := repo.ReadPatchCommit(0)
 | 
						noFile, err := repo.ReadPatchCommit(0)
 | 
				
			||||||
	assert.Error(t, err)
 | 
						assert.Error(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// This patch is an empty one (sometimes it's a 404)
 | 
						// This patch is an empty one (sometimes it's a 404)
 | 
				
			||||||
	noCommit, err := repo.ReadPatchCommit(1)
 | 
						noCommit, err := repo.ReadPatchCommit(1)
 | 
				
			||||||
	assert.Error(t, err)
 | 
						assert.Error(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// This patch is legit and should return a commit
 | 
						// This patch is legit and should return a commit
 | 
				
			||||||
	oldCommit, err := repo.ReadPatchCommit(2)
 | 
						oldCommit, err := repo.ReadPatchCommit(2)
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						if err != nil {
 | 
				
			||||||
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	assert.Empty(t, noFile)
 | 
						assert.Empty(t, noFile)
 | 
				
			||||||
	assert.Empty(t, noCommit)
 | 
						assert.Empty(t, noCommit)
 | 
				
			||||||
@@ -58,23 +82,45 @@ func TestReadPatch(t *testing.T) {
 | 
				
			|||||||
func TestReadWritePullHead(t *testing.T) {
 | 
					func TestReadWritePullHead(t *testing.T) {
 | 
				
			||||||
	// Ensure we can write SHA1 head corresponding to PR and open them
 | 
						// Ensure we can write SHA1 head corresponding to PR and open them
 | 
				
			||||||
	bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
 | 
						bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
 | 
				
			||||||
	repo, err := OpenRepository(bareRepo1Path)
 | 
					
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						// As we are writing we should clone the repository first
 | 
				
			||||||
 | 
						clonedPath, err := cloneRepo(bareRepo1Path, "TestReadWritePullHead")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer util.RemoveAll(clonedPath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						repo, err := OpenRepository(clonedPath)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	defer repo.Close()
 | 
						defer repo.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Try to open non-existing Pull
 | 
						// Try to open non-existing Pull
 | 
				
			||||||
	_, err = repo.GetRefCommitID(PullPrefix + "0/head")
 | 
						_, err = repo.GetRefCommitID(PullPrefix + "0/head")
 | 
				
			||||||
	assert.Error(t, err)
 | 
						assert.Error(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Write a fake sha1 with only 40 zeros
 | 
						// Write a fake sha1 with only 40 zeros
 | 
				
			||||||
	newCommit := "feaf4ba6bc635fec442f46ddd4512416ec43c2c2"
 | 
						newCommit := "feaf4ba6bc635fec442f46ddd4512416ec43c2c2"
 | 
				
			||||||
	err = repo.SetReference(PullPrefix+"1/head", newCommit)
 | 
						err = repo.SetReference(PullPrefix+"1/head", newCommit)
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						if err != nil {
 | 
				
			||||||
	// Remove file after the test
 | 
							assert.NoError(t, err)
 | 
				
			||||||
	defer func() {
 | 
							return
 | 
				
			||||||
		_ = repo.RemoveReference(PullPrefix + "1/head")
 | 
						}
 | 
				
			||||||
	}()
 | 
					
 | 
				
			||||||
	// Read the file created
 | 
						// Read the file created
 | 
				
			||||||
	headContents, err := repo.GetRefCommitID(PullPrefix + "1/head")
 | 
						headContents, err := repo.GetRefCommitID(PullPrefix + "1/head")
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						if err != nil {
 | 
				
			||||||
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	assert.Len(t, string(headContents), 40)
 | 
						assert.Len(t, string(headContents), 40)
 | 
				
			||||||
	assert.True(t, string(headContents) == newCommit)
 | 
						assert.True(t, string(headContents) == newCommit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Remove file after the test
 | 
				
			||||||
 | 
						err = repo.RemoveReference(PullPrefix + "1/head")
 | 
				
			||||||
 | 
						assert.NoError(t, err)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -88,7 +88,10 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
 | 
				
			|||||||
					}
 | 
										}
 | 
				
			||||||
				}()
 | 
									}()
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			defer cancel()
 | 
								defer func() {
 | 
				
			||||||
 | 
									_ = checker.Close()
 | 
				
			||||||
 | 
									cancel()
 | 
				
			||||||
 | 
								}()
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,11 +16,17 @@ import (
 | 
				
			|||||||
func TestRepository_GetTags(t *testing.T) {
 | 
					func TestRepository_GetTags(t *testing.T) {
 | 
				
			||||||
	bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
 | 
						bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
 | 
				
			||||||
	bareRepo1, err := OpenRepository(bareRepo1Path)
 | 
						bareRepo1, err := OpenRepository(bareRepo1Path)
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						if err != nil {
 | 
				
			||||||
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	defer bareRepo1.Close()
 | 
						defer bareRepo1.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	tags, total, err := bareRepo1.GetTagInfos(0, 0)
 | 
						tags, total, err := bareRepo1.GetTagInfos(0, 0)
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						if err != nil {
 | 
				
			||||||
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	assert.Len(t, tags, 1)
 | 
						assert.Len(t, tags, 1)
 | 
				
			||||||
	assert.Equal(t, len(tags), total)
 | 
						assert.Equal(t, len(tags), total)
 | 
				
			||||||
	assert.EqualValues(t, "test", tags[0].Name)
 | 
						assert.EqualValues(t, "test", tags[0].Name)
 | 
				
			||||||
@@ -31,40 +37,75 @@ func TestRepository_GetTags(t *testing.T) {
 | 
				
			|||||||
func TestRepository_GetTag(t *testing.T) {
 | 
					func TestRepository_GetTag(t *testing.T) {
 | 
				
			||||||
	bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
 | 
						bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	clonedPath, err := cloneRepo(bareRepo1Path, testReposDir, "repo1_TestRepository_GetTag")
 | 
						clonedPath, err := cloneRepo(bareRepo1Path, "TestRepository_GetTag")
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						if err != nil {
 | 
				
			||||||
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	defer util.RemoveAll(clonedPath)
 | 
						defer util.RemoveAll(clonedPath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	bareRepo1, err := OpenRepository(clonedPath)
 | 
						bareRepo1, err := OpenRepository(clonedPath)
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						if err != nil {
 | 
				
			||||||
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	defer bareRepo1.Close()
 | 
						defer bareRepo1.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// LIGHTWEIGHT TAGS
 | 
				
			||||||
	lTagCommitID := "6fbd69e9823458e6c4a2fc5c0f6bc022b2f2acd1"
 | 
						lTagCommitID := "6fbd69e9823458e6c4a2fc5c0f6bc022b2f2acd1"
 | 
				
			||||||
	lTagName := "lightweightTag"
 | 
						lTagName := "lightweightTag"
 | 
				
			||||||
	bareRepo1.CreateTag(lTagName, lTagCommitID)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	aTagCommitID := "8006ff9adbf0cb94da7dad9e537e53817f9fa5c0"
 | 
						// Create the lightweight tag
 | 
				
			||||||
	aTagName := "annotatedTag"
 | 
						err = bareRepo1.CreateTag(lTagName, lTagCommitID)
 | 
				
			||||||
	aTagMessage := "my annotated message \n - test two line"
 | 
						if err != nil {
 | 
				
			||||||
	bareRepo1.CreateAnnotatedTag(aTagName, aTagMessage, aTagCommitID)
 | 
							assert.NoError(t, err, "Unable to create the lightweight tag: %s for ID: %s. Error: %v", lTagName, lTagCommitID, err)
 | 
				
			||||||
	aTagID, _ := bareRepo1.GetTagID(aTagName)
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// and try to get the Tag for lightweight tag
 | 
				
			||||||
	lTag, err := bareRepo1.GetTag(lTagName)
 | 
						lTag, err := bareRepo1.GetTag(lTagName)
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						if err != nil {
 | 
				
			||||||
	assert.NotNil(t, lTag)
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	if lTag == nil {
 | 
						if lTag == nil {
 | 
				
			||||||
 | 
							assert.NotNil(t, lTag)
 | 
				
			||||||
		assert.FailNow(t, "nil lTag: %s", lTagName)
 | 
							assert.FailNow(t, "nil lTag: %s", lTagName)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	assert.EqualValues(t, lTagName, lTag.Name)
 | 
						assert.EqualValues(t, lTagName, lTag.Name)
 | 
				
			||||||
	assert.EqualValues(t, lTagCommitID, lTag.ID.String())
 | 
						assert.EqualValues(t, lTagCommitID, lTag.ID.String())
 | 
				
			||||||
	assert.EqualValues(t, lTagCommitID, lTag.Object.String())
 | 
						assert.EqualValues(t, lTagCommitID, lTag.Object.String())
 | 
				
			||||||
	assert.EqualValues(t, "commit", lTag.Type)
 | 
						assert.EqualValues(t, "commit", lTag.Type)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// ANNOTATED TAGS
 | 
				
			||||||
 | 
						aTagCommitID := "8006ff9adbf0cb94da7dad9e537e53817f9fa5c0"
 | 
				
			||||||
 | 
						aTagName := "annotatedTag"
 | 
				
			||||||
 | 
						aTagMessage := "my annotated message \n - test two line"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Create the annotated tag
 | 
				
			||||||
 | 
						err = bareRepo1.CreateAnnotatedTag(aTagName, aTagMessage, aTagCommitID)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							assert.NoError(t, err, "Unable to create the annotated tag: %s for ID: %s. Error: %v", aTagName, aTagCommitID, err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Now try to get the tag for the annotated Tag
 | 
				
			||||||
 | 
						aTagID, err := bareRepo1.GetTagID(aTagName)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	aTag, err := bareRepo1.GetTag(aTagName)
 | 
						aTag, err := bareRepo1.GetTag(aTagName)
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						if err != nil {
 | 
				
			||||||
	assert.NotNil(t, aTag)
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	if aTag == nil {
 | 
						if aTag == nil {
 | 
				
			||||||
 | 
							assert.NotNil(t, aTag)
 | 
				
			||||||
		assert.FailNow(t, "nil aTag: %s", aTagName)
 | 
							assert.FailNow(t, "nil aTag: %s", aTagName)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	assert.EqualValues(t, aTagName, aTag.Name)
 | 
						assert.EqualValues(t, aTagName, aTag.Name)
 | 
				
			||||||
	assert.EqualValues(t, aTagID, aTag.ID.String())
 | 
						assert.EqualValues(t, aTagID, aTag.ID.String())
 | 
				
			||||||
@@ -72,26 +113,47 @@ func TestRepository_GetTag(t *testing.T) {
 | 
				
			|||||||
	assert.EqualValues(t, aTagCommitID, aTag.Object.String())
 | 
						assert.EqualValues(t, aTagCommitID, aTag.Object.String())
 | 
				
			||||||
	assert.EqualValues(t, "tag", aTag.Type)
 | 
						assert.EqualValues(t, "tag", aTag.Type)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// RELEASE TAGS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	rTagCommitID := "8006ff9adbf0cb94da7dad9e537e53817f9fa5c0"
 | 
						rTagCommitID := "8006ff9adbf0cb94da7dad9e537e53817f9fa5c0"
 | 
				
			||||||
	rTagName := "release/" + lTagName
 | 
						rTagName := "release/" + lTagName
 | 
				
			||||||
	bareRepo1.CreateTag(rTagName, rTagCommitID)
 | 
					
 | 
				
			||||||
 | 
						err = bareRepo1.CreateTag(rTagName, rTagCommitID)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							assert.NoError(t, err, "Unable to create the  tag: %s for ID: %s. Error: %v", rTagName, rTagCommitID, err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	rTagID, err := bareRepo1.GetTagID(rTagName)
 | 
						rTagID, err := bareRepo1.GetTagID(rTagName)
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						if err != nil {
 | 
				
			||||||
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	assert.EqualValues(t, rTagCommitID, rTagID)
 | 
						assert.EqualValues(t, rTagCommitID, rTagID)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	oTagID, err := bareRepo1.GetTagID(lTagName)
 | 
						oTagID, err := bareRepo1.GetTagID(lTagName)
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						if err != nil {
 | 
				
			||||||
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	assert.EqualValues(t, lTagCommitID, oTagID)
 | 
						assert.EqualValues(t, lTagCommitID, oTagID)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestRepository_GetAnnotatedTag(t *testing.T) {
 | 
					func TestRepository_GetAnnotatedTag(t *testing.T) {
 | 
				
			||||||
	bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
 | 
						bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	clonedPath, err := cloneRepo(bareRepo1Path, testReposDir, "repo1_TestRepository_GetTag")
 | 
						clonedPath, err := cloneRepo(bareRepo1Path, "TestRepository_GetAnnotatedTag")
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						if err != nil {
 | 
				
			||||||
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	defer util.RemoveAll(clonedPath)
 | 
						defer util.RemoveAll(clonedPath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	bareRepo1, err := OpenRepository(clonedPath)
 | 
						bareRepo1, err := OpenRepository(clonedPath)
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						if err != nil {
 | 
				
			||||||
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	defer bareRepo1.Close()
 | 
						defer bareRepo1.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	lTagCommitID := "6fbd69e9823458e6c4a2fc5c0f6bc022b2f2acd1"
 | 
						lTagCommitID := "6fbd69e9823458e6c4a2fc5c0f6bc022b2f2acd1"
 | 
				
			||||||
@@ -106,7 +168,10 @@ func TestRepository_GetAnnotatedTag(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// Try an annotated tag
 | 
						// Try an annotated tag
 | 
				
			||||||
	tag, err := bareRepo1.GetAnnotatedTag(aTagID)
 | 
						tag, err := bareRepo1.GetAnnotatedTag(aTagID)
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						if err != nil {
 | 
				
			||||||
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	assert.NotNil(t, tag)
 | 
						assert.NotNil(t, tag)
 | 
				
			||||||
	assert.EqualValues(t, aTagName, tag.Name)
 | 
						assert.EqualValues(t, aTagName, tag.Name)
 | 
				
			||||||
	assert.EqualValues(t, aTagID, tag.ID.String())
 | 
						assert.EqualValues(t, aTagID, tag.ID.String())
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -117,7 +117,7 @@ func (graph *Graph) LoadAndProcessCommits(repository *repo_model.Repository, git
 | 
				
			|||||||
		c.Verification = asymkey_model.ParseCommitWithSignature(c.Commit)
 | 
							c.Verification = asymkey_model.ParseCommitWithSignature(c.Commit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		_ = asymkey_model.CalculateTrustStatus(c.Verification, repository.GetTrustModel(), func(user *user_model.User) (bool, error) {
 | 
							_ = 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)
 | 
							}, &keyMap)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		statuses, _, err := models.GetLatestCommitStatus(repository.ID, c.Commit.ID.String(), db.ListOptions{})
 | 
							statuses, _, err := models.GetLatestCommitStatus(repository.ID, c.Commit.ID.String(), db.ListOptions{})
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -192,6 +192,7 @@ func (g *Manager) RunAtHammer(hammer func()) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
func (g *Manager) doShutdown() {
 | 
					func (g *Manager) doShutdown() {
 | 
				
			||||||
	if !g.setStateTransition(stateRunning, stateShuttingDown) {
 | 
						if !g.setStateTransition(stateRunning, stateShuttingDown) {
 | 
				
			||||||
 | 
							g.DoImmediateHammer()
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	g.lock.Lock()
 | 
						g.lock.Lock()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -168,8 +168,12 @@ func (g *Manager) DoGracefulRestart() {
 | 
				
			|||||||
	if setting.GracefulRestartable {
 | 
						if setting.GracefulRestartable {
 | 
				
			||||||
		log.Info("PID: %d. Forking...", os.Getpid())
 | 
							log.Info("PID: %d. Forking...", os.Getpid())
 | 
				
			||||||
		err := g.doFork()
 | 
							err := g.doFork()
 | 
				
			||||||
		if err != nil && err.Error() != "another process already forked. Ignoring this one" {
 | 
							if err != nil {
 | 
				
			||||||
			log.Error("Error whilst forking from PID: %d : %v", os.Getpid(), err)
 | 
								if err.Error() == "another process already forked. Ignoring this one" {
 | 
				
			||||||
 | 
									g.DoImmediateHammer()
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									log.Error("Error whilst forking from PID: %d : %v", os.Getpid(), err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		log.Info("PID: %d. Not set restartable. Shutting down...", os.Getpid())
 | 
							log.Info("PID: %d. Not set restartable. Shutting down...", os.Getpid())
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -55,7 +55,7 @@ var (
 | 
				
			|||||||
	anySHA1Pattern = regexp.MustCompile(`https?://(?:\S+/){4,5}([0-9a-f]{40})(/[-+~_%.a-zA-Z0-9/]+)?(#[-+~_%.a-zA-Z0-9]+)?`)
 | 
						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 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-]+://`)
 | 
						validLinksPattern = regexp.MustCompile(`^[a-z][\w-]+://`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -99,7 +99,7 @@ var issueFullPatternOnce sync.Once
 | 
				
			|||||||
func getIssueFullPattern() *regexp.Regexp {
 | 
					func getIssueFullPattern() *regexp.Regexp {
 | 
				
			||||||
	issueFullPatternOnce.Do(func() {
 | 
						issueFullPatternOnce.Do(func() {
 | 
				
			||||||
		issueFullPattern = regexp.MustCompile(regexp.QuoteMeta(setting.AppURL) +
 | 
							issueFullPattern = regexp.MustCompile(regexp.QuoteMeta(setting.AppURL) +
 | 
				
			||||||
			`\w+/\w+/(?:issues|pulls)/((?:\w{1,10}-)?[1-9][0-9]*)([\?|#](\S+)?)?\b`)
 | 
								`[\w_.-]+/[\w_.-]+/(?:issues|pulls)/((?:\w{1,10}-)?[1-9][0-9]*)([\?|#](\S+)?)?\b`)
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	return issueFullPattern
 | 
						return issueFullPattern
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -944,6 +944,13 @@ func comparePatternProcessor(ctx *RenderContext, node *html.Node) {
 | 
				
			|||||||
			return
 | 
								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]]
 | 
							urlFull := node.Data[m[0]:m[1]]
 | 
				
			||||||
		text1 := base.ShortSha(node.Data[m[2]:m[3]])
 | 
							text1 := base.ShortSha(node.Data[m[2]:m[3]])
 | 
				
			||||||
		textDots := base.ShortSha(node.Data[m[4]:m[5]])
 | 
							textDots := base.ShortSha(node.Data[m[4]:m[5]])
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -95,6 +95,15 @@ func TestRender_CrossReferences(t *testing.T) {
 | 
				
			|||||||
	test(
 | 
						test(
 | 
				
			||||||
		"/home/gitea/go-gitea/gitea#12345",
 | 
							"/home/gitea/go-gitea/gitea#12345",
 | 
				
			||||||
		`<p>/home/gitea/go-gitea/gitea#12345</p>`)
 | 
							`<p>/home/gitea/go-gitea/gitea#12345</p>`)
 | 
				
			||||||
 | 
						test(
 | 
				
			||||||
 | 
							util.URLJoin(TestAppURL, "gogitea", "gitea", "issues", "12345"),
 | 
				
			||||||
 | 
							`<p><a href="`+util.URLJoin(TestAppURL, "gogitea", "gitea", "issues", "12345")+`" class="ref-issue" rel="nofollow">gogitea/gitea#12345</a></p>`)
 | 
				
			||||||
 | 
						test(
 | 
				
			||||||
 | 
							util.URLJoin(TestAppURL, "go-gitea", "gitea", "issues", "12345"),
 | 
				
			||||||
 | 
							`<p><a href="`+util.URLJoin(TestAppURL, "go-gitea", "gitea", "issues", "12345")+`" class="ref-issue" rel="nofollow">go-gitea/gitea#12345</a></p>`)
 | 
				
			||||||
 | 
						test(
 | 
				
			||||||
 | 
							util.URLJoin(TestAppURL, "gogitea", "some-repo-name", "issues", "12345"),
 | 
				
			||||||
 | 
							`<p><a href="`+util.URLJoin(TestAppURL, "gogitea", "some-repo-name", "issues", "12345")+`" class="ref-issue" rel="nofollow">gogitea/some-repo-name#12345</a></p>`)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestMisc_IsSameDomain(t *testing.T) {
 | 
					func TestMisc_IsSameDomain(t *testing.T) {
 | 
				
			||||||
@@ -546,3 +555,16 @@ func TestFuzz(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						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>")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,10 +5,12 @@
 | 
				
			|||||||
package nosql
 | 
					package nosql
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
	"path"
 | 
						"path"
 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
	"github.com/syndtr/goleveldb/leveldb"
 | 
						"github.com/syndtr/goleveldb/leveldb"
 | 
				
			||||||
	"github.com/syndtr/goleveldb/leveldb/errors"
 | 
						"github.com/syndtr/goleveldb/leveldb/errors"
 | 
				
			||||||
	"github.com/syndtr/goleveldb/leveldb/opt"
 | 
						"github.com/syndtr/goleveldb/leveldb/opt"
 | 
				
			||||||
@@ -20,8 +22,16 @@ func (m *Manager) CloseLevelDB(connection string) error {
 | 
				
			|||||||
	defer m.mutex.Unlock()
 | 
						defer m.mutex.Unlock()
 | 
				
			||||||
	db, ok := m.LevelDBConnections[connection]
 | 
						db, ok := m.LevelDBConnections[connection]
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		connection = ToLevelDBURI(connection).String()
 | 
							// Try the full URI
 | 
				
			||||||
		db, ok = m.LevelDBConnections[connection]
 | 
							uri := ToLevelDBURI(connection)
 | 
				
			||||||
 | 
							db, ok = m.LevelDBConnections[uri.String()]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if !ok {
 | 
				
			||||||
 | 
								// Try the datadir directly
 | 
				
			||||||
 | 
								dataDir := path.Join(uri.Host, uri.Path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								db, ok = m.LevelDBConnections[dataDir]
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
@@ -40,6 +50,12 @@ func (m *Manager) CloseLevelDB(connection string) error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// GetLevelDB gets a levelDB for a particular connection
 | 
					// GetLevelDB gets a levelDB for a particular connection
 | 
				
			||||||
func (m *Manager) GetLevelDB(connection string) (*leveldb.DB, error) {
 | 
					func (m *Manager) GetLevelDB(connection string) (*leveldb.DB, error) {
 | 
				
			||||||
 | 
						// Convert the provided connection description to the common format
 | 
				
			||||||
 | 
						uri := ToLevelDBURI(connection)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get the datadir
 | 
				
			||||||
 | 
						dataDir := path.Join(uri.Host, uri.Path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	m.mutex.Lock()
 | 
						m.mutex.Lock()
 | 
				
			||||||
	defer m.mutex.Unlock()
 | 
						defer m.mutex.Unlock()
 | 
				
			||||||
	db, ok := m.LevelDBConnections[connection]
 | 
						db, ok := m.LevelDBConnections[connection]
 | 
				
			||||||
@@ -48,12 +64,28 @@ func (m *Manager) GetLevelDB(connection string) (*leveldb.DB, error) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		return db.db, nil
 | 
							return db.db, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	uri := ToLevelDBURI(connection)
 | 
					
 | 
				
			||||||
	db = &levelDBHolder{
 | 
						db, ok = m.LevelDBConnections[uri.String()]
 | 
				
			||||||
		name: []string{connection, uri.String()},
 | 
						if ok {
 | 
				
			||||||
 | 
							db.count++
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return db.db, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// if there is already a connection to this leveldb reuse that
 | 
				
			||||||
 | 
						// NOTE: if there differing options then only the first leveldb connection will be used
 | 
				
			||||||
 | 
						db, ok = m.LevelDBConnections[dataDir]
 | 
				
			||||||
 | 
						if ok {
 | 
				
			||||||
 | 
							db.count++
 | 
				
			||||||
 | 
							log.Warn("Duplicate connnection to level db: %s with different connection strings. Initial connection: %s. This connection: %s", dataDir, db.name[0], connection)
 | 
				
			||||||
 | 
							db.name = append(db.name, connection)
 | 
				
			||||||
 | 
							m.LevelDBConnections[connection] = db
 | 
				
			||||||
 | 
							return db.db, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						db = &levelDBHolder{
 | 
				
			||||||
 | 
							name: []string{connection, uri.String(), dataDir},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	dataDir := path.Join(uri.Host, uri.Path)
 | 
					 | 
				
			||||||
	opts := &opt.Options{}
 | 
						opts := &opt.Options{}
 | 
				
			||||||
	for k, v := range uri.Query() {
 | 
						for k, v := range uri.Query() {
 | 
				
			||||||
		switch replacer.Replace(strings.ToLower(k)) {
 | 
							switch replacer.Replace(strings.ToLower(k)) {
 | 
				
			||||||
@@ -134,7 +166,11 @@ func (m *Manager) GetLevelDB(connection string) (*leveldb.DB, error) {
 | 
				
			|||||||
	db.db, err = leveldb.OpenFile(dataDir, opts)
 | 
						db.db, err = leveldb.OpenFile(dataDir, opts)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		if !errors.IsCorrupted(err) {
 | 
							if !errors.IsCorrupted(err) {
 | 
				
			||||||
			return nil, err
 | 
								if strings.Contains(err.Error(), "resource temporarily unavailable") {
 | 
				
			||||||
 | 
									return nil, fmt.Errorf("unable to lock level db at %s: %w", dataDir, err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return nil, fmt.Errorf("unable to open level db at %s: %w", dataDir, err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		db.db, err = leveldb.RecoverFile(dataDir, opts)
 | 
							db.db, err = leveldb.RecoverFile(dataDir, opts)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -115,7 +115,7 @@ func (m *mailNotifier) NotifyPullRequestCodeComment(pr *models.PullRequest, comm
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func (m *mailNotifier) NotifyIssueChangeAssignee(doer *user_model.User, issue *models.Issue, assignee *user_model.User, removed bool, comment *models.Comment) {
 | 
					func (m *mailNotifier) NotifyIssueChangeAssignee(doer *user_model.User, issue *models.Issue, assignee *user_model.User, removed bool, comment *models.Comment) {
 | 
				
			||||||
	// mail only sent to added assignees and not self-assignee
 | 
						// mail only sent to added assignees and not self-assignee
 | 
				
			||||||
	if !removed && doer.ID != assignee.ID && assignee.EmailNotifications() == user_model.EmailNotificationsEnabled {
 | 
						if !removed && doer.ID != assignee.ID && (assignee.EmailNotifications() == user_model.EmailNotificationsEnabled || assignee.EmailNotifications() == user_model.EmailNotificationsOnMention) {
 | 
				
			||||||
		ct := fmt.Sprintf("Assigned #%d.", issue.Index)
 | 
							ct := fmt.Sprintf("Assigned #%d.", issue.Index)
 | 
				
			||||||
		if err := mailer.SendIssueAssignedMail(issue, doer, ct, comment, []*user_model.User{assignee}); err != nil {
 | 
							if err := mailer.SendIssueAssignedMail(issue, doer, ct, comment, []*user_model.User{assignee}); err != nil {
 | 
				
			||||||
			log.Error("Error in SendIssueAssignedMail for issue[%d] to assignee[%d]: %v", issue.ID, assignee.ID, err)
 | 
								log.Error("Error in SendIssueAssignedMail for issue[%d] to assignee[%d]: %v", issue.ID, assignee.ID, err)
 | 
				
			||||||
@@ -124,7 +124,7 @@ func (m *mailNotifier) NotifyIssueChangeAssignee(doer *user_model.User, issue *m
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (m *mailNotifier) NotifyPullReviewRequest(doer *user_model.User, issue *models.Issue, reviewer *user_model.User, isRequest bool, comment *models.Comment) {
 | 
					func (m *mailNotifier) NotifyPullReviewRequest(doer *user_model.User, issue *models.Issue, reviewer *user_model.User, isRequest bool, comment *models.Comment) {
 | 
				
			||||||
	if isRequest && doer.ID != reviewer.ID && reviewer.EmailNotifications() == user_model.EmailNotificationsEnabled {
 | 
						if isRequest && doer.ID != reviewer.ID && (reviewer.EmailNotifications() == user_model.EmailNotificationsEnabled || reviewer.EmailNotifications() == user_model.EmailNotificationsOnMention) {
 | 
				
			||||||
		ct := fmt.Sprintf("Requested to review %s.", issue.HTMLURL())
 | 
							ct := fmt.Sprintf("Requested to review %s.", issue.HTMLURL())
 | 
				
			||||||
		if err := mailer.SendIssueAssignedMail(issue, doer, ct, comment, []*user_model.User{reviewer}); err != nil {
 | 
							if err := mailer.SendIssueAssignedMail(issue, doer, ct, comment, []*user_model.User{reviewer}); err != nil {
 | 
				
			||||||
			log.Error("Error in SendIssueAssignedMail for issue[%d] to reviewer[%d]: %v", issue.ID, reviewer.ID, err)
 | 
								log.Error("Error in SendIssueAssignedMail for issue[%d] to reviewer[%d]: %v", issue.ID, reviewer.ID, err)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -204,7 +204,7 @@ func (ns *notificationService) NotifyPullRevieweDismiss(doer *user_model.User, r
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (ns *notificationService) NotifyIssueChangeAssignee(doer *user_model.User, issue *models.Issue, assignee *user_model.User, removed bool, comment *models.Comment) {
 | 
					func (ns *notificationService) NotifyIssueChangeAssignee(doer *user_model.User, issue *models.Issue, assignee *user_model.User, removed bool, comment *models.Comment) {
 | 
				
			||||||
	if !removed {
 | 
						if !removed && doer.ID != assignee.ID {
 | 
				
			||||||
		var opts = issueNotificationOpts{
 | 
							var opts = issueNotificationOpts{
 | 
				
			||||||
			IssueID:              issue.ID,
 | 
								IssueID:              issue.ID,
 | 
				
			||||||
			NotificationAuthorID: doer.ID,
 | 
								NotificationAuthorID: doer.ID,
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										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
 | 
						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 {
 | 
					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 ..
 | 
						// use clean to keep the file is a valid path with no . or ..
 | 
				
			||||||
	f, err := fs.Open(path.Clean(file))
 | 
						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
 | 
							return true
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						setWellKnownContentType(w, file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	serveContent(w, req, fi, fi.ModTime(), f)
 | 
						serveContent(w, req, fi, fi.ModTime(), f)
 | 
				
			||||||
	return true
 | 
						return true
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,15 +9,12 @@ package public
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"bytes"
 | 
						"bytes"
 | 
				
			||||||
	"compress/gzip"
 | 
					 | 
				
			||||||
	"io"
 | 
						"io"
 | 
				
			||||||
	"mime"
 | 
					 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"path/filepath"
 | 
						"path/filepath"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/modules/log"
 | 
					 | 
				
			||||||
	"code.gitea.io/gitea/modules/timeutil"
 | 
						"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"))
 | 
						encodings := parseAcceptEncoding(req.Header.Get("Accept-Encoding"))
 | 
				
			||||||
	if encodings["gzip"] {
 | 
						if encodings["gzip"] {
 | 
				
			||||||
		if cf, ok := fi.(*vfsgen۰CompressedFileInfo); ok {
 | 
							if cf, ok := fi.(*vfsgen۰CompressedFileInfo); ok {
 | 
				
			||||||
			rd := bytes.NewReader(cf.GzipBytes())
 | 
								rdGzip := bytes.NewReader(cf.GzipBytes())
 | 
				
			||||||
			w.Header().Set("Content-Encoding", "gzip")
 | 
								// all static files are managed by Gitea, so we can make sure every file has the correct ext name
 | 
				
			||||||
			ctype := mime.TypeByExtension(filepath.Ext(fi.Name()))
 | 
								// then we can get the correct Content-Type, we do not need to do http.DetectContentType on the decompressed data
 | 
				
			||||||
			if ctype == "" {
 | 
								mimeType := detectWellKnownMimeType(filepath.Ext(fi.Name()))
 | 
				
			||||||
				// read a chunk to decide between utf-8 text and binary
 | 
								if mimeType == "" {
 | 
				
			||||||
				var buf [512]byte
 | 
									mimeType = "application/octet-stream"
 | 
				
			||||||
				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)
 | 
								w.Header().Set("Content-Type", mimeType)
 | 
				
			||||||
			http.ServeContent(w, req, fi.Name(), modtime, rd)
 | 
								w.Header().Set("Content-Encoding", "gzip")
 | 
				
			||||||
 | 
								http.ServeContent(w, req, fi.Name(), modtime, rdGzip)
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -72,6 +72,8 @@ type ManagedPool interface {
 | 
				
			|||||||
	BoostWorkers() int
 | 
						BoostWorkers() int
 | 
				
			||||||
	// SetPoolSettings sets the user updatable settings for the pool
 | 
						// SetPoolSettings sets the user updatable settings for the pool
 | 
				
			||||||
	SetPoolSettings(maxNumberOfWorkers, boostWorkers int, timeout time.Duration)
 | 
						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
 | 
					// ManagedQueueList implements the sort.Interface
 | 
				
			||||||
@@ -141,7 +143,6 @@ func (m *Manager) Remove(qid int64) {
 | 
				
			|||||||
	delete(m.Queues, qid)
 | 
						delete(m.Queues, qid)
 | 
				
			||||||
	m.mutex.Unlock()
 | 
						m.mutex.Unlock()
 | 
				
			||||||
	log.Trace("Queue Manager removed: QID: %d", qid)
 | 
						log.Trace("Queue Manager removed: QID: %d", qid)
 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetManagedQueue by qid
 | 
					// GetManagedQueue by qid
 | 
				
			||||||
@@ -193,6 +194,17 @@ func (m *Manager) FlushAll(baseCtx context.Context, timeout time.Duration) error
 | 
				
			|||||||
				wg.Done()
 | 
									wg.Done()
 | 
				
			||||||
				continue
 | 
									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
 | 
								allEmpty = false
 | 
				
			||||||
			if flushable, ok := mq.Managed.(Flushable); ok {
 | 
								if flushable, ok := mq.Managed.(Flushable); ok {
 | 
				
			||||||
				log.Debug("Flushing (flushable) queue: %s", mq.Name)
 | 
									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()
 | 
							wg.Wait()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ManagedQueues returns the managed queues
 | 
					// ManagedQueues returns the managed queues
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -195,9 +195,11 @@ loop:
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var errQueueEmpty = fmt.Errorf("empty queue")
 | 
					var (
 | 
				
			||||||
var errEmptyBytes = fmt.Errorf("empty bytes")
 | 
						errQueueEmpty = fmt.Errorf("empty queue")
 | 
				
			||||||
var errUnmarshal = fmt.Errorf("failed to unmarshal")
 | 
						errEmptyBytes = fmt.Errorf("empty bytes")
 | 
				
			||||||
 | 
						errUnmarshal  = fmt.Errorf("failed to unmarshal")
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (q *ByteFIFOQueue) doPop() error {
 | 
					func (q *ByteFIFOQueue) doPop() error {
 | 
				
			||||||
	q.lock.Lock()
 | 
						q.lock.Lock()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -173,7 +173,6 @@ func (q *PersistableChannelQueue) Run(atShutdown, atTerminate func(func())) {
 | 
				
			|||||||
		q.internal.(*LevelQueue).Shutdown()
 | 
							q.internal.(*LevelQueue).Shutdown()
 | 
				
			||||||
		GetManager().Remove(q.internal.(*LevelQueue).qid)
 | 
							GetManager().Remove(q.internal.(*LevelQueue).qid)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Flush flushes the queue and blocks till the queue is empty
 | 
					// Flush flushes the queue and blocks till the queue is empty
 | 
				
			||||||
@@ -252,14 +251,13 @@ func (q *PersistableChannelQueue) Shutdown() {
 | 
				
			|||||||
	q.channelQueue.Wait()
 | 
						q.channelQueue.Wait()
 | 
				
			||||||
	q.internal.(*LevelQueue).Wait()
 | 
						q.internal.(*LevelQueue).Wait()
 | 
				
			||||||
	// Redirect all remaining data in the chan to the internal channel
 | 
						// Redirect all remaining data in the chan to the internal channel
 | 
				
			||||||
	go func() {
 | 
						log.Trace("PersistableChannelQueue: %s Redirecting remaining data", q.delayedStarter.name)
 | 
				
			||||||
		log.Trace("PersistableChannelQueue: %s Redirecting remaining data", q.delayedStarter.name)
 | 
						close(q.channelQueue.dataChan)
 | 
				
			||||||
		for data := range q.channelQueue.dataChan {
 | 
						for data := range q.channelQueue.dataChan {
 | 
				
			||||||
			_ = q.internal.Push(data)
 | 
							_ = q.internal.Push(data)
 | 
				
			||||||
			atomic.AddInt64(&q.channelQueue.numInQueue, -1)
 | 
							atomic.AddInt64(&q.channelQueue.numInQueue, -1)
 | 
				
			||||||
		}
 | 
						}
 | 
				
			||||||
		log.Trace("PersistableChannelQueue: %s Done Redirecting remaining data", q.delayedStarter.name)
 | 
						log.Trace("PersistableChannelQueue: %s Done Redirecting remaining data", q.delayedStarter.name)
 | 
				
			||||||
	}()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	log.Debug("PersistableChannelQueue: %s Shutdown", q.delayedStarter.name)
 | 
						log.Debug("PersistableChannelQueue: %s Shutdown", q.delayedStarter.name)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -188,5 +188,4 @@ func TestPersistableChannelQueue(t *testing.T) {
 | 
				
			|||||||
	for _, callback := range callbacks {
 | 
						for _, callback := range callbacks {
 | 
				
			||||||
		callback()
 | 
							callback()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -238,13 +238,12 @@ func (q *PersistableChannelUniqueQueue) Shutdown() {
 | 
				
			|||||||
	q.channelQueue.Wait()
 | 
						q.channelQueue.Wait()
 | 
				
			||||||
	q.internal.(*LevelUniqueQueue).Wait()
 | 
						q.internal.(*LevelUniqueQueue).Wait()
 | 
				
			||||||
	// Redirect all remaining data in the chan to the internal channel
 | 
						// Redirect all remaining data in the chan to the internal channel
 | 
				
			||||||
	go func() {
 | 
						close(q.channelQueue.dataChan)
 | 
				
			||||||
		log.Trace("PersistableChannelUniqueQueue: %s Redirecting remaining data", q.delayedStarter.name)
 | 
						log.Trace("PersistableChannelUniqueQueue: %s Redirecting remaining data", q.delayedStarter.name)
 | 
				
			||||||
		for data := range q.channelQueue.dataChan {
 | 
						for data := range q.channelQueue.dataChan {
 | 
				
			||||||
			_ = q.internal.Push(data)
 | 
							_ = q.internal.Push(data)
 | 
				
			||||||
		}
 | 
						}
 | 
				
			||||||
		log.Trace("PersistableChannelUniqueQueue: %s Done Redirecting remaining data", q.delayedStarter.name)
 | 
						log.Trace("PersistableChannelUniqueQueue: %s Done Redirecting remaining data", q.delayedStarter.name)
 | 
				
			||||||
	}()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	log.Debug("PersistableChannelUniqueQueue: %s Shutdown", q.delayedStarter.name)
 | 
						log.Debug("PersistableChannelUniqueQueue: %s Shutdown", q.delayedStarter.name)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -65,6 +65,11 @@ func NewWorkerPool(handle HandlerFunc, config WorkerPoolConfiguration) *WorkerPo
 | 
				
			|||||||
	return pool
 | 
						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
 | 
					// Push pushes the data to the internal channel
 | 
				
			||||||
func (p *WorkerPool) Push(data Data) {
 | 
					func (p *WorkerPool) Push(data Data) {
 | 
				
			||||||
	atomic.AddInt64(&p.numInQueue, 1)
 | 
						atomic.AddInt64(&p.numInQueue, 1)
 | 
				
			||||||
@@ -82,6 +87,20 @@ func (p *WorkerPool) Push(data Data) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// HasNoWorkerScaling will return true if the queue has no workers, and has no worker boosting
 | 
				
			||||||
 | 
					func (p *WorkerPool) HasNoWorkerScaling() bool {
 | 
				
			||||||
 | 
						p.lock.Lock()
 | 
				
			||||||
 | 
						defer p.lock.Unlock()
 | 
				
			||||||
 | 
						return p.hasNoWorkerScaling()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (p *WorkerPool) hasNoWorkerScaling() bool {
 | 
				
			||||||
 | 
						return p.numberOfWorkers == 0 && (p.boostTimeout == 0 || p.boostWorkers == 0 || p.maxNumberOfWorkers == 0)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// zeroBoost will add a temporary boost worker for a no worker queue
 | 
				
			||||||
 | 
					// p.lock must be locked at the start of this function BUT it will be unlocked by the end of this function
 | 
				
			||||||
 | 
					// (This is because addWorkers has to be called whilst unlocked)
 | 
				
			||||||
func (p *WorkerPool) zeroBoost() {
 | 
					func (p *WorkerPool) zeroBoost() {
 | 
				
			||||||
	ctx, cancel := context.WithTimeout(p.baseCtx, p.boostTimeout)
 | 
						ctx, cancel := context.WithTimeout(p.baseCtx, p.boostTimeout)
 | 
				
			||||||
	mq := GetManager().GetManagedQueue(p.qid)
 | 
						mq := GetManager().GetManagedQueue(p.qid)
 | 
				
			||||||
@@ -90,7 +109,7 @@ func (p *WorkerPool) zeroBoost() {
 | 
				
			|||||||
		boost = p.maxNumberOfWorkers - p.numberOfWorkers
 | 
							boost = p.maxNumberOfWorkers - p.numberOfWorkers
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if mq != nil {
 | 
						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()
 | 
							start := time.Now()
 | 
				
			||||||
		pid := mq.RegisterWorkers(boost, start, true, start.Add(p.boostTimeout), cancel, false)
 | 
							pid := mq.RegisterWorkers(boost, start, true, start.Add(p.boostTimeout), cancel, false)
 | 
				
			||||||
@@ -98,7 +117,7 @@ func (p *WorkerPool) zeroBoost() {
 | 
				
			|||||||
			mq.RemoveWorkers(pid)
 | 
								mq.RemoveWorkers(pid)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	} else {
 | 
						} 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.lock.Unlock()
 | 
				
			||||||
	p.addWorkers(ctx, cancel, boost)
 | 
						p.addWorkers(ctx, cancel, boost)
 | 
				
			||||||
@@ -272,6 +291,21 @@ func (p *WorkerPool) addWorkers(ctx context.Context, cancel context.CancelFunc,
 | 
				
			|||||||
				p.cond.Broadcast()
 | 
									p.cond.Broadcast()
 | 
				
			||||||
				cancel()
 | 
									cancel()
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								select {
 | 
				
			||||||
 | 
								case <-p.baseCtx.Done():
 | 
				
			||||||
 | 
									// Don't warn if the baseCtx is shutdown
 | 
				
			||||||
 | 
								default:
 | 
				
			||||||
 | 
									if p.hasNoWorkerScaling() {
 | 
				
			||||||
 | 
										log.Warn(
 | 
				
			||||||
 | 
											"Queue: %d is configured to be non-scaling and has no workers - this configuration is likely incorrect.", p.qid)
 | 
				
			||||||
 | 
									} else if p.numberOfWorkers == 0 && atomic.LoadInt64(&p.numInQueue) > 0 {
 | 
				
			||||||
 | 
										// OK there are no workers but... there's still work to be done -> Reboost
 | 
				
			||||||
 | 
										p.zeroBoost()
 | 
				
			||||||
 | 
										// p.lock will be unlocked by zeroBoost
 | 
				
			||||||
 | 
										return
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
			p.lock.Unlock()
 | 
								p.lock.Unlock()
 | 
				
			||||||
		}()
 | 
							}()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -326,7 +360,10 @@ func (p *WorkerPool) FlushWithContext(ctx context.Context) error {
 | 
				
			|||||||
	log.Trace("WorkerPool: %d Flush", p.qid)
 | 
						log.Trace("WorkerPool: %d Flush", p.qid)
 | 
				
			||||||
	for {
 | 
						for {
 | 
				
			||||||
		select {
 | 
							select {
 | 
				
			||||||
		case data := <-p.dataChan:
 | 
							case data, ok := <-p.dataChan:
 | 
				
			||||||
 | 
								if !ok {
 | 
				
			||||||
 | 
									return nil
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
			p.handle(data)
 | 
								p.handle(data)
 | 
				
			||||||
			atomic.AddInt64(&p.numInQueue, -1)
 | 
								atomic.AddInt64(&p.numInQueue, -1)
 | 
				
			||||||
		case <-p.baseCtx.Done():
 | 
							case <-p.baseCtx.Done():
 | 
				
			||||||
@@ -341,7 +378,7 @@ func (p *WorkerPool) FlushWithContext(ctx context.Context) error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func (p *WorkerPool) doWork(ctx context.Context) {
 | 
					func (p *WorkerPool) doWork(ctx context.Context) {
 | 
				
			||||||
	delay := time.Millisecond * 300
 | 
						delay := time.Millisecond * 300
 | 
				
			||||||
	var data = make([]Data, 0, p.batchLength)
 | 
						data := make([]Data, 0, p.batchLength)
 | 
				
			||||||
	for {
 | 
						for {
 | 
				
			||||||
		select {
 | 
							select {
 | 
				
			||||||
		case <-ctx.Done():
 | 
							case <-ctx.Done():
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,7 +23,7 @@ func TestIncludesAllRepositoriesTeams(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	testTeamRepositories := func(teamID int64, repoIds []int64) {
 | 
						testTeamRepositories := func(teamID int64, repoIds []int64) {
 | 
				
			||||||
		team := unittest.AssertExistsAndLoadBean(t, &models.Team{ID: teamID}).(*models.Team)
 | 
							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, team.NumRepos, "%s: len repo", team.Name)
 | 
				
			||||||
		assert.Len(t, team.Repos, len(repoIds), "%s: repo count", team.Name)
 | 
							assert.Len(t, team.Repos, len(repoIds), "%s: repo count", team.Name)
 | 
				
			||||||
		for i, rid := range repoIds {
 | 
							for i, rid := range repoIds {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1022,8 +1022,13 @@ func loadFromConf(allowEmpty bool, extraConfig string) {
 | 
				
			|||||||
		UI.CustomEmojisMap[emoji] = ":" + emoji + ":"
 | 
							UI.CustomEmojisMap[emoji] = ":" + emoji + ":"
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	sec = Cfg.Section("U2F")
 | 
						// FIXME: DEPRECATED to be removed in v1.18.0
 | 
				
			||||||
	U2F.AppID = sec.Key("APP_ID").MustString(strings.TrimSuffix(AppURL, "/"))
 | 
						U2F.AppID = strings.TrimSuffix(AppURL, "/")
 | 
				
			||||||
 | 
						if Cfg.Section("U2F").HasKey("APP_ID") {
 | 
				
			||||||
 | 
							U2F.AppID = Cfg.Section("U2F").Key("APP_ID").MustString(strings.TrimSuffix(AppURL, "/"))
 | 
				
			||||||
 | 
						} else if Cfg.Section("u2f").HasKey("APP_ID") {
 | 
				
			||||||
 | 
							U2F.AppID = Cfg.Section("u2f").Key("APP_ID").MustString(strings.TrimSuffix(AppURL, "/"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func parseAuthorizedPrincipalsAllow(values []string) ([]string, bool) {
 | 
					func parseAuthorizedPrincipalsAllow(values []string) ([]string, bool) {
 | 
				
			||||||
@@ -1162,7 +1167,6 @@ func MakeManifestData(appName, appURL, absoluteAssetURL string) []byte {
 | 
				
			|||||||
			},
 | 
								},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Error("unable to marshal manifest JSON. Error: %v", err)
 | 
							log.Error("unable to marshal manifest JSON. Error: %v", err)
 | 
				
			||||||
		return make([]byte, 0)
 | 
							return make([]byte, 0)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -41,6 +41,7 @@ webauthn_use_twofa=Zwei-Faktor-Authentifizierung via Handy verwenden
 | 
				
			|||||||
webauthn_error=Dein Sicherheitsschlüssel konnte nicht gelesen werden.
 | 
					webauthn_error=Dein Sicherheitsschlüssel konnte nicht gelesen werden.
 | 
				
			||||||
webauthn_unsupported_browser=Dein Browser unterstützt derzeit keinen WebAuthn.
 | 
					webauthn_unsupported_browser=Dein Browser unterstützt derzeit keinen WebAuthn.
 | 
				
			||||||
webauthn_error_unknown=Ein unbekannter Fehler ist aufgetreten. Bitte versuche es erneut.
 | 
					webauthn_error_unknown=Ein unbekannter Fehler ist aufgetreten. Bitte versuche es erneut.
 | 
				
			||||||
 | 
					webauthn_error_insecure=WebAuthn unterstützt nur sichere Verbindungen. Zum Testen über HTTP kannst du "localhost" oder "127.0.0.1" als Host verwenden
 | 
				
			||||||
webauthn_error_unable_to_process=Der Server konnte deine Anfrage nicht bearbeiten.
 | 
					webauthn_error_unable_to_process=Der Server konnte deine Anfrage nicht bearbeiten.
 | 
				
			||||||
webauthn_error_duplicated=Für diese Anfrage ist der Sicherheitsschlüssel nicht erlaubt. Bitte stell sicher, dass er nicht bereits registriert ist.
 | 
					webauthn_error_duplicated=Für diese Anfrage ist der Sicherheitsschlüssel nicht erlaubt. Bitte stell sicher, dass er nicht bereits registriert ist.
 | 
				
			||||||
webauthn_error_timeout=Das Zeitlimit wurde erreicht, bevor dein Schlüssel gelesen werden konnte. Bitte lade die Seite erneut.
 | 
					webauthn_error_timeout=Das Zeitlimit wurde erreicht, bevor dein Schlüssel gelesen werden konnte. Bitte lade die Seite erneut.
 | 
				
			||||||
@@ -140,6 +141,7 @@ charset=Zeichensatz
 | 
				
			|||||||
path=Pfad
 | 
					path=Pfad
 | 
				
			||||||
sqlite_helper=Dateipfad zur SQLite3 Datenbank.<br>Gebe einen absoluten Pfad an, wenn Gitea als Service gestartet wird.
 | 
					sqlite_helper=Dateipfad zur SQLite3 Datenbank.<br>Gebe einen absoluten Pfad an, wenn Gitea als Service gestartet wird.
 | 
				
			||||||
reinstall_error=Du versuchst, in eine bereits existierende Gitea Datenbank zu installieren
 | 
					reinstall_error=Du versuchst, in eine bereits existierende Gitea Datenbank zu installieren
 | 
				
			||||||
 | 
					reinstall_confirm_message=Eine Neuinstallation mit einer bestehenden Gitea-Datenbank kann mehrere Probleme verursachen. In den meisten Fällen solltest du deine vorhandene "app.ini" verwenden, um Gitea auszuführen. Wenn du weist, was du tust, bestätigen die folgenden Angaben:
 | 
				
			||||||
reinstall_confirm_check_3=Du bestätigst, dass du absolut sicher bist, dass diese Gitea mit der richtigen app.ini läuft, und du sicher bist, dass du neu installieren musst. Du bestätigst, dass du die oben genannten Risiken anerkennst.
 | 
					reinstall_confirm_check_3=Du bestätigst, dass du absolut sicher bist, dass diese Gitea mit der richtigen app.ini läuft, und du sicher bist, dass du neu installieren musst. Du bestätigst, dass du die oben genannten Risiken anerkennst.
 | 
				
			||||||
err_empty_db_path=Der SQLite3 Datenbankpfad darf nicht leer sein.
 | 
					err_empty_db_path=Der SQLite3 Datenbankpfad darf nicht leer sein.
 | 
				
			||||||
no_admin_and_disable_registration=Du kannst Selbst-Registrierungen nicht deaktivieren, ohne ein Administratorkonto zu erstellen.
 | 
					no_admin_and_disable_registration=Du kannst Selbst-Registrierungen nicht deaktivieren, ohne ein Administratorkonto zu erstellen.
 | 
				
			||||||
@@ -2050,8 +2052,8 @@ settings.lfs_pointers.accessible=Nutzer hat Zugriff
 | 
				
			|||||||
settings.lfs_pointers.associateAccessible=Ordne %d zugängliche OIDs zu
 | 
					settings.lfs_pointers.associateAccessible=Ordne %d zugängliche OIDs zu
 | 
				
			||||||
settings.rename_branch_failed_exist=Kann den Branch nicht umbenennen, da der Zielbranch %s bereits existiert.
 | 
					settings.rename_branch_failed_exist=Kann den Branch nicht umbenennen, da der Zielbranch %s bereits existiert.
 | 
				
			||||||
settings.rename_branch_failed_not_exist=Kann den Branch %s nicht umbenennen, da er nicht existiert.
 | 
					settings.rename_branch_failed_not_exist=Kann den Branch %s nicht umbenennen, da er nicht existiert.
 | 
				
			||||||
settings.rename_branch_success=Zweig %s wurde erfolgreich in %s umbenannt.
 | 
					settings.rename_branch_success=Branch %s wurde erfolgreich in %s umbenannt.
 | 
				
			||||||
settings.rename_branch_from=alter Zweigname
 | 
					settings.rename_branch_from=alter Branchname
 | 
				
			||||||
settings.rename_branch_to=neuer Branchname
 | 
					settings.rename_branch_to=neuer Branchname
 | 
				
			||||||
settings.rename_branch=Branch umbennen
 | 
					settings.rename_branch=Branch umbennen
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,6 +34,20 @@ twofa=Έλεγχος Ταυτότητας Δύο Παραγόντων
 | 
				
			|||||||
twofa_scratch=Κωδικός Μίας Χρήσης Δύο Παραγόντων
 | 
					twofa_scratch=Κωδικός Μίας Χρήσης Δύο Παραγόντων
 | 
				
			||||||
passcode=Κωδικός
 | 
					passcode=Κωδικός
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					webauthn_insert_key=Εισάγετε το κλειδί ασφαλείας σας
 | 
				
			||||||
 | 
					webauthn_sign_in=Πατήστε το κουμπί στο κλειδί ασφαλείας. Αν το κλειδί ασφαλείας σας δεν έχει κουμπί, τοποθετήστε το ξανά.
 | 
				
			||||||
 | 
					webauthn_press_button=Παρακαλώ πατήστε το κουμπί στο κλειδί ασφαλείας…
 | 
				
			||||||
 | 
					webauthn_use_twofa=Χρησιμοποιήστε έναν κωδικό δύο παραγόντων από το τηλέφωνό σας
 | 
				
			||||||
 | 
					webauthn_error=Αδύνατη η ανάγνωση του κλειδιού ασφαλείας.
 | 
				
			||||||
 | 
					webauthn_unsupported_browser=Το πρόγραμμα περιήγησής σας δεν υποστηρίζει επί του παρόντος WebAuthn.
 | 
				
			||||||
 | 
					webauthn_error_unknown=Παρουσιάστηκε ένα άγνωστο σφάλμα. Παρακαλώ προσπαθήστε ξανά.
 | 
				
			||||||
 | 
					webauthn_error_insecure=Το WebAuthn υποστηρίζει μόνο ασφαλείς συνδέσεις. Για δοκιμές πάνω από HTTP, μπορείτε να χρησιμοποιήσετε την προέλευση "localhost" ή "127.0.0.1"
 | 
				
			||||||
 | 
					webauthn_error_unable_to_process=Ο διακομιστής δεν μπόρεσε να επεξεργαστεί το αίτημά σας.
 | 
				
			||||||
 | 
					webauthn_error_duplicated=Το κλειδί ασφαλείας δεν επιτρέπεται για αυτό το αίτημα. Βεβαιωθείτε ότι το κλειδί δεν έχει ήδη καταχωρηθεί.
 | 
				
			||||||
 | 
					webauthn_error_empty=Πρέπει να ορίσετε ένα όνομα για αυτό το κλειδί.
 | 
				
			||||||
 | 
					webauthn_error_timeout=Το χρονικό όριο έφτασε πριν το κλειδί να διαβαστεί. Παρακαλώ ανανεώστε τη σελίδα και προσπαθήστε ξανά.
 | 
				
			||||||
 | 
					webauthn_u2f_deprecated=Το κλειδί: '%s' πιστοποιεί χρησιμοποιώντας το παρωχημένο πρωτόκολλο U2F. Θα πρέπει να καταχωρήσετε ξανά αυτό το κλειδί και να καταργήσετε την παλιά εγγραφή.
 | 
				
			||||||
 | 
					webauthn_reload=Ανανέωση
 | 
				
			||||||
 | 
					
 | 
				
			||||||
repository=Αποθετήριο
 | 
					repository=Αποθετήριο
 | 
				
			||||||
organization=Οργανισμός
 | 
					organization=Οργανισμός
 | 
				
			||||||
@@ -305,6 +319,9 @@ oauth_signup_submit=Ολοκληρωμένος Λογαριασμός
 | 
				
			|||||||
oauth_signin_tab=Σύνδεση με υπάρχων λογαριασμό
 | 
					oauth_signin_tab=Σύνδεση με υπάρχων λογαριασμό
 | 
				
			||||||
oauth_signin_title=Συνδεθείτε για να εγκρίνετε τον Συνδεδεμένο Λογαριασμό
 | 
					oauth_signin_title=Συνδεθείτε για να εγκρίνετε τον Συνδεδεμένο Λογαριασμό
 | 
				
			||||||
oauth_signin_submit=Σύνδεση Λογαριασμού
 | 
					oauth_signin_submit=Σύνδεση Λογαριασμού
 | 
				
			||||||
 | 
					oauth.signin.error=Παρουσιάστηκε σφάλμα κατά την επεξεργασία του αιτήματος εξουσιοδότησης. Εάν αυτό το σφάλμα επιμένει, παρακαλούμε επικοινωνήστε με το διαχειριστή του ιστοτόπου.
 | 
				
			||||||
 | 
					oauth.signin.error.access_denied=Η αίτηση εξουσιοδότησης απορρίφθηκε.
 | 
				
			||||||
 | 
					oauth.signin.error.temporarily_unavailable=Η εξουσιοδότηση απέτυχε επειδή ο διακομιστής ταυτοποίησης δεν είναι διαθέσιμος προσωρινά. Παρακαλώ προσπαθήστε ξανά αργότερα.
 | 
				
			||||||
openid_connect_submit=Σύνδεση
 | 
					openid_connect_submit=Σύνδεση
 | 
				
			||||||
openid_connect_title=Σύνδεση σε υπάρχων λογαριασμό
 | 
					openid_connect_title=Σύνδεση σε υπάρχων λογαριασμό
 | 
				
			||||||
openid_connect_desc=Το επιλεγμένο OpenID URI είναι άγνωστο. Συνδέστε το με ένα νέο λογαριασμό εδώ.
 | 
					openid_connect_desc=Το επιλεγμένο OpenID URI είναι άγνωστο. Συνδέστε το με ένα νέο λογαριασμό εδώ.
 | 
				
			||||||
@@ -510,6 +527,7 @@ twofa=Έλεγχος Ταυτότητας Δύο Παραγόντων
 | 
				
			|||||||
account_link=Συνδεδεμένοι Λογαριασμοί
 | 
					account_link=Συνδεδεμένοι Λογαριασμοί
 | 
				
			||||||
organization=Οργανισμοί
 | 
					organization=Οργανισμοί
 | 
				
			||||||
uid=Uid
 | 
					uid=Uid
 | 
				
			||||||
 | 
					webauthn=Κλειδιά Ασφαλείας
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public_profile=Δημόσιο Προφίλ
 | 
					public_profile=Δημόσιο Προφίλ
 | 
				
			||||||
biography_placeholder=Πείτε μας λίγο για τον εαυτό σας
 | 
					biography_placeholder=Πείτε μας λίγο για τον εαυτό σας
 | 
				
			||||||
@@ -531,6 +549,7 @@ continue=Συνέχεια
 | 
				
			|||||||
cancel=Ακύρωση
 | 
					cancel=Ακύρωση
 | 
				
			||||||
language=Γλώσσα
 | 
					language=Γλώσσα
 | 
				
			||||||
ui=Θέμα Διεπαφής
 | 
					ui=Θέμα Διεπαφής
 | 
				
			||||||
 | 
					saved_successfully=Οι ρυθμίσεις σας αποθηκεύτηκαν επιτυχώς.
 | 
				
			||||||
privacy=Απόρρητο
 | 
					privacy=Απόρρητο
 | 
				
			||||||
keep_activity_private=Απόκρυψη της δραστηριότητας σας από τη σελίδα προφίλ
 | 
					keep_activity_private=Απόκρυψη της δραστηριότητας σας από τη σελίδα προφίλ
 | 
				
			||||||
keep_activity_private_popup=Με αυτή την επιλογή η δραστηριότητα σας είναι ορατή μόνο σε εσάς και τους διαχειριστές
 | 
					keep_activity_private_popup=Με αυτή την επιλογή η δραστηριότητα σας είναι ορατή μόνο σε εσάς και τους διαχειριστές
 | 
				
			||||||
@@ -730,6 +749,11 @@ passcode_invalid=Ο κωδικός είναι λάθος. Δοκιμάστε ξ
 | 
				
			|||||||
twofa_enrolled=Ο λογαριασμός σας έχει εγγραφεί σε ταυτοποίηση δύο παραγόντων. Αποθηκεύστε το διακριτικό μιας χρήσης (%s) σε ασφαλές μέρος καθώς εμφανίζεται μόνο μία φορά!
 | 
					twofa_enrolled=Ο λογαριασμός σας έχει εγγραφεί σε ταυτοποίηση δύο παραγόντων. Αποθηκεύστε το διακριτικό μιας χρήσης (%s) σε ασφαλές μέρος καθώς εμφανίζεται μόνο μία φορά!
 | 
				
			||||||
twofa_failed_get_secret=Αποτυχία λήψης μυστικού.
 | 
					twofa_failed_get_secret=Αποτυχία λήψης μυστικού.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					webauthn_desc=Τα κλειδιά ασφαλείας είναι συσκευές που περιέχουν κρυπτογραφικά κλειδιά. Μπορούν να χρησιμοποιηθούν για έλεγχο ταυτότητας δύο παραγόντων. Τα κλειδιά ασφαλείας πρέπει να υποστηρίζουν το <a rel="noreferrer" target="_blank" href="https://w3c.github.io/webauthn/#webauthn-authenticator">πρότυπο WebAuthn Authn Authenticator</a>.
 | 
				
			||||||
 | 
					webauthn_register_key=Προσθήκη Κλειδιού Ασφαλείας
 | 
				
			||||||
 | 
					webauthn_nickname=Ψευδώνυμο
 | 
				
			||||||
 | 
					webauthn_delete_key=Αφαίρεση Κλειδιού Ασφαλείας
 | 
				
			||||||
 | 
					webauthn_delete_key_desc=Αν αφαιρέσετε ένα κλειδί ασφαλείας δεν μπορείτε πλέον να συνδεθείτε με αυτό. Συνέχεια;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
manage_account_links=Διαχείριση Συνδεδεμένων Λογαριασμών
 | 
					manage_account_links=Διαχείριση Συνδεδεμένων Λογαριασμών
 | 
				
			||||||
manage_account_links_desc=Αυτοί οι εξωτερικοί λογαριασμοί είναι συνδεδεμένοι στον Gitea λογαριασμό σας.
 | 
					manage_account_links_desc=Αυτοί οι εξωτερικοί λογαριασμοί είναι συνδεδεμένοι στον Gitea λογαριασμό σας.
 | 
				
			||||||
@@ -986,7 +1010,16 @@ file_view_rendered=Προβολή Απόδοσης
 | 
				
			|||||||
file_view_raw=Προβολή Ακατέργαστου
 | 
					file_view_raw=Προβολή Ακατέργαστου
 | 
				
			||||||
file_permalink=Permalink
 | 
					file_permalink=Permalink
 | 
				
			||||||
file_too_large=Το αρχείο είναι πολύ μεγάλο για να εμφανιστεί.
 | 
					file_too_large=Το αρχείο είναι πολύ μεγάλο για να εμφανιστεί.
 | 
				
			||||||
 | 
					bidi_bad_header=`Αυτό το αρχείο περιέχει μη αναμενόμενους χαρακτήρες Unicode!`
 | 
				
			||||||
 | 
					bidi_bad_description=`Αυτό το αρχείο περιέχει μη αναμενόμενους χαρακτήρες Bidirectional Unicode που ίσως να επεξεργάζονται διαφορετικά από ότι εμφανίζεται παρακάτω. Αν η χρήση αυτή είναι σκόπιμη και νόμιμη, μπορείτε να αγνοήσετε με ασφάλεια αυτή την προειδοποίηση. Χρησιμοποιήστε το κουμπί Escape για να αποκαλύψετε κρυμμένους χαρακτήρες.`
 | 
				
			||||||
 | 
					bidi_bad_description_escaped=`Αυτό το αρχείο περιέχει μη αναμενόμενους χαρακτήρες Bidirectional Unicode. Οι κρυμμένοι χαρακτήρες unicode εμφανίζονται κωδικοποιημένοι παρακάτω. Χρησιμοποιήστε το κουμπί Unescape για να δείτε πώς αποδίδονται.`
 | 
				
			||||||
 | 
					unicode_header=`Αυτό το αρχείο περιέχει κρυφούς χαρακτήρες Unicode!`
 | 
				
			||||||
 | 
					unicode_description=`Αυτό το αρχείο περιέχει κρυφούς χαρακτήρες Unicode που μπορεί να επεξεργάζονται διαφορετικά από όπως εμφανίζονται παρακάτω. Αν η χρήση είναι σκόπιμη και νόμιμη, μπορείτε να αγνοήσετε με ασφάλεια αυτή την προειδοποίηση. Χρησιμοποιήστε το κουμπί Escape για να αποκαλύψετε τους κρυφούς χαρακτήρες.`
 | 
				
			||||||
 | 
					unicode_description_escaped=`Αυτό το αρχείο περιέχει κρυφούς χαρακτήρες Unicode. Οι κρυφοί χαρακτήρες unicode εμφανίζονται κωδικοποιημένοι παρακάτω. Χρησιμοποιήστε το κουμπί Unescape για να δείτε πώς αποδίδονται.`
 | 
				
			||||||
 | 
					line_unicode=`Αυτή η γραμμή έχει κρυφούς χαρακτήρες unicode`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					escape_control_characters=Escape
 | 
				
			||||||
 | 
					unescape_control_characters=Unescape
 | 
				
			||||||
file_copy_permalink=Αντιγραφή Permalink
 | 
					file_copy_permalink=Αντιγραφή Permalink
 | 
				
			||||||
video_not_supported_in_browser=Το πρόγραμμα περιήγησής σας δεν υποστηρίζει την ετικέτα HTML5 'video'.
 | 
					video_not_supported_in_browser=Το πρόγραμμα περιήγησής σας δεν υποστηρίζει την ετικέτα HTML5 'video'.
 | 
				
			||||||
audio_not_supported_in_browser=Το πρόγραμμα περιήγησής σας δεν υποστηρίζει την ετικέτα HTML5 'audio'.
 | 
					audio_not_supported_in_browser=Το πρόγραμμα περιήγησής σας δεν υποστηρίζει την ετικέτα HTML5 'audio'.
 | 
				
			||||||
@@ -1081,6 +1114,8 @@ commits.signed_by_untrusted_user_unmatched=Υπογράφηκε από ένα μ
 | 
				
			|||||||
commits.gpg_key_id=ID Κλειδιού GPG
 | 
					commits.gpg_key_id=ID Κλειδιού GPG
 | 
				
			||||||
commits.ssh_key_fingerprint=Αποτύπωμα Κλειδιού SSH
 | 
					commits.ssh_key_fingerprint=Αποτύπωμα Κλειδιού SSH
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ext_issues=Πρόσβαση στα Εξωτερικά Ζητήματα
 | 
				
			||||||
ext_issues.desc=Σύνδεση σε εξωτερικό εφαρμογή ζητημάτων.
 | 
					ext_issues.desc=Σύνδεση σε εξωτερικό εφαρμογή ζητημάτων.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
projects=Έργα
 | 
					projects=Έργα
 | 
				
			||||||
@@ -1560,6 +1595,7 @@ signing.wont_sign.commitssigned=Η συγχώνευση δεν θα υπογρα
 | 
				
			|||||||
signing.wont_sign.approved=Η συγχώνευση δεν θα υπογραφεί καθώς το PR δεν εγκρίνεται
 | 
					signing.wont_sign.approved=Η συγχώνευση δεν θα υπογραφεί καθώς το PR δεν εγκρίνεται
 | 
				
			||||||
signing.wont_sign.not_signed_in=Δεν είστε συνδεδεμένοι
 | 
					signing.wont_sign.not_signed_in=Δεν είστε συνδεδεμένοι
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ext_wiki=Πρόσβαση στο Εξωτερικό Wiki
 | 
				
			||||||
ext_wiki.desc=Σύνδεση σε ένα εξωτερικό wiki.
 | 
					ext_wiki.desc=Σύνδεση σε ένα εξωτερικό wiki.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
wiki=Wiki
 | 
					wiki=Wiki
 | 
				
			||||||
@@ -1816,6 +1852,8 @@ settings.webhook.response=Απάντηση
 | 
				
			|||||||
settings.webhook.headers=Κεφαλίδες
 | 
					settings.webhook.headers=Κεφαλίδες
 | 
				
			||||||
settings.webhook.payload=Περιεχόμενο
 | 
					settings.webhook.payload=Περιεχόμενο
 | 
				
			||||||
settings.webhook.body=Σώμα
 | 
					settings.webhook.body=Σώμα
 | 
				
			||||||
 | 
					settings.webhook.replay.description=Επανάληψη αυτού του webhook.
 | 
				
			||||||
 | 
					settings.webhook.delivery.success=Ένα γεγονός έχει προστεθεί στην ουρά παράδοσης. Μπορεί να χρειαστούν λίγα δευτερόλεπτα μέχρι να εμφανιστεί στο ιστορικό.
 | 
				
			||||||
settings.githooks_desc=Τα Άγκιστρα Git παρέχονται από το ίδιο το Git. Μπορείτε να επεξεργαστείτε τα αρχεία αγκίστρων παρακάτω για να ρυθμίσετε προσαρμοσμένες λειτουργίες.
 | 
					settings.githooks_desc=Τα Άγκιστρα Git παρέχονται από το ίδιο το Git. Μπορείτε να επεξεργαστείτε τα αρχεία αγκίστρων παρακάτω για να ρυθμίσετε προσαρμοσμένες λειτουργίες.
 | 
				
			||||||
settings.githook_edit_desc=Αν το hook είναι ανενεργό, θα παρουσιαστεί ένα παράδειγμα. Αφήνοντας το περιεχόμενο του hook κενό θα το απενεργοποιήσετε.
 | 
					settings.githook_edit_desc=Αν το hook είναι ανενεργό, θα παρουσιαστεί ένα παράδειγμα. Αφήνοντας το περιεχόμενο του hook κενό θα το απενεργοποιήσετε.
 | 
				
			||||||
settings.githook_name=Όνομα Hook
 | 
					settings.githook_name=Όνομα Hook
 | 
				
			||||||
@@ -2079,6 +2117,7 @@ diff.protected=Προστατευμένο
 | 
				
			|||||||
diff.image.side_by_side=Δίπλα Δίπλα
 | 
					diff.image.side_by_side=Δίπλα Δίπλα
 | 
				
			||||||
diff.image.swipe=Σύρσιμο
 | 
					diff.image.swipe=Σύρσιμο
 | 
				
			||||||
diff.image.overlay=Επικάλυψη
 | 
					diff.image.overlay=Επικάλυψη
 | 
				
			||||||
 | 
					diff.has_escaped=Αυτή η γραμμή έχει κρυφούς χαρακτήρες Unicode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
releases.desc=Παρακολούθηση εκδόσεων έργου και λήψεων.
 | 
					releases.desc=Παρακολούθηση εκδόσεων έργου και λήψεων.
 | 
				
			||||||
release.releases=Κυκλοφορίες
 | 
					release.releases=Κυκλοφορίες
 | 
				
			||||||
@@ -2154,6 +2193,7 @@ branch.new_branch_from=Δημιουργία νέου κλάδου από '%s'
 | 
				
			|||||||
branch.renamed=Ο κλάδος %s μετονομάστηκε σε %s.
 | 
					branch.renamed=Ο κλάδος %s μετονομάστηκε σε %s.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
tag.create_tag=Δημιουργία ετικέτας <strong>%s</strong>
 | 
					tag.create_tag=Δημιουργία ετικέτας <strong>%s</strong>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
tag.create_success=Η ετικέτα '%s' έχει δημιουργηθεί.
 | 
					tag.create_success=Η ετικέτα '%s' έχει δημιουργηθεί.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
topic.manage_topics=Διαχείριση Θεμάτων
 | 
					topic.manage_topics=Διαχείριση Θεμάτων
 | 
				
			||||||
@@ -2240,7 +2280,13 @@ teams.leave=Αποχώρηση
 | 
				
			|||||||
teams.leave.detail=Αποχώρηση από %s;
 | 
					teams.leave.detail=Αποχώρηση από %s;
 | 
				
			||||||
teams.can_create_org_repo=Δημιουργία αποθετηρίων
 | 
					teams.can_create_org_repo=Δημιουργία αποθετηρίων
 | 
				
			||||||
teams.can_create_org_repo_helper=Τα μέλη μπορούν να δημιουργήσουν νέα αποθετήρια στον οργανισμό. Ο δημιουργός θα αποκτήσει πρόσβαση διαχειριστή στο νέο αποθετήριο.
 | 
					teams.can_create_org_repo_helper=Τα μέλη μπορούν να δημιουργήσουν νέα αποθετήρια στον οργανισμό. Ο δημιουργός θα αποκτήσει πρόσβαση διαχειριστή στο νέο αποθετήριο.
 | 
				
			||||||
 | 
					teams.none_access=Καμία Πρόσβαση
 | 
				
			||||||
 | 
					teams.none_access_helper=Τα μέλη δεν μπορούν να δουν ή να κάνουν οποιαδήποτε άλλη ενέργεια σε αυτή τη μονάδα.
 | 
				
			||||||
 | 
					teams.general_access=Γενική Πρόσβαση
 | 
				
			||||||
 | 
					teams.general_access_helper=Τα δικαιώματα των μελών αποφασίζονται από το παρακάτω πίνακα αδειών.
 | 
				
			||||||
 | 
					teams.read_access=Ανάγνωση
 | 
				
			||||||
teams.read_access_helper=Τα μέλη μπορούν να δουν και να κλωνοποιήσουν τα αποθετήρια της ομάδας.
 | 
					teams.read_access_helper=Τα μέλη μπορούν να δουν και να κλωνοποιήσουν τα αποθετήρια της ομάδας.
 | 
				
			||||||
 | 
					teams.write_access=Εγγραφή
 | 
				
			||||||
teams.write_access_helper=Τα μέλη μπορούν να δουν και να κλωνοποιήσουν τα αποθετήρια της ομάδας.
 | 
					teams.write_access_helper=Τα μέλη μπορούν να δουν και να κλωνοποιήσουν τα αποθετήρια της ομάδας.
 | 
				
			||||||
teams.admin_access=Πρόσβαση Διαχειριστή
 | 
					teams.admin_access=Πρόσβαση Διαχειριστή
 | 
				
			||||||
teams.admin_access_helper=Τα μέλη μπορούν να κάνουν push και pull στα αποθετήρια της ομάδας όπως και να προσθέσουν συνεργάτες σε αυτά.
 | 
					teams.admin_access_helper=Τα μέλη μπορούν να κάνουν push και pull στα αποθετήρια της ομάδας όπως και να προσθέσουν συνεργάτες σε αυτά.
 | 
				
			||||||
@@ -2869,6 +2915,7 @@ error.probable_bad_signature=ΠΡΟΣΟΧΗ! Αν και υπάρχει ένα 
 | 
				
			|||||||
error.probable_bad_default_signature=ΠΡΟΣΟΧΗ! Αν και το προεπιλεγμένο κλειδί έχει αυτό το ID, δεν επαληθεύει αυτή την υποβολή! Αυτή η υποβολή είναι ΥΠΟΠΤΗ.
 | 
					error.probable_bad_default_signature=ΠΡΟΣΟΧΗ! Αν και το προεπιλεγμένο κλειδί έχει αυτό το ID, δεν επαληθεύει αυτή την υποβολή! Αυτή η υποβολή είναι ΥΠΟΠΤΗ.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[units]
 | 
					[units]
 | 
				
			||||||
 | 
					unit=Μονάδα
 | 
				
			||||||
error.no_unit_allowed_repo=Δεν σας επιτρέπεται να έχετε πρόσβαση σε οποιαδήποτε ενότητα αυτού του αποθετηρίου.
 | 
					error.no_unit_allowed_repo=Δεν σας επιτρέπεται να έχετε πρόσβαση σε οποιαδήποτε ενότητα αυτού του αποθετηρίου.
 | 
				
			||||||
error.unit_not_allowed=Δεν σας επιτρέπεται να έχετε πρόσβαση σε αυτήν την ενότητα αποθετηρίου.
 | 
					error.unit_not_allowed=Δεν σας επιτρέπεται να έχετε πρόσβαση σε αυτήν την ενότητα αποθετηρίου.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2334,6 +2334,7 @@ first_page = First
 | 
				
			|||||||
last_page = Last
 | 
					last_page = Last
 | 
				
			||||||
total = Total: %d
 | 
					total = Total: %d
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					dashboard.new_version_hint = Gitea %s is now available, you are running %s. Check the <a target="_blank" rel="noreferrer" href="https://blog.gitea.io">blog</a> for more details.
 | 
				
			||||||
dashboard.statistic = Summary
 | 
					dashboard.statistic = Summary
 | 
				
			||||||
dashboard.operations = Maintenance Operations
 | 
					dashboard.operations = Maintenance Operations
 | 
				
			||||||
dashboard.system_status = System Status
 | 
					dashboard.system_status = System Status
 | 
				
			||||||
@@ -2407,6 +2408,7 @@ dashboard.last_gc_pause = Last GC Pause
 | 
				
			|||||||
dashboard.gc_times = GC Times
 | 
					dashboard.gc_times = GC Times
 | 
				
			||||||
dashboard.delete_old_actions = Delete all old actions from database
 | 
					dashboard.delete_old_actions = Delete all old actions from database
 | 
				
			||||||
dashboard.delete_old_actions.started = Delete all old actions from database started.
 | 
					dashboard.delete_old_actions.started = Delete all old actions from database started.
 | 
				
			||||||
 | 
					dashboard.update_checker = Update checker
 | 
				
			||||||
 | 
					
 | 
				
			||||||
users.user_manage_panel = User Account Management
 | 
					users.user_manage_panel = User Account Management
 | 
				
			||||||
users.new_account = Create User Account
 | 
					users.new_account = Create User Account
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,6 +34,20 @@ twofa=2要素認証
 | 
				
			|||||||
twofa_scratch=2要素認証スクラッチコード
 | 
					twofa_scratch=2要素認証スクラッチコード
 | 
				
			||||||
passcode=パスコード
 | 
					passcode=パスコード
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					webauthn_insert_key=セキュリティキーを挿入
 | 
				
			||||||
 | 
					webauthn_sign_in=セキュリティキーのボタンを押してください。セキュリティキーにボタンが無い場合は、挿入しなおしてください。
 | 
				
			||||||
 | 
					webauthn_press_button=セキュリティキーのボタンを押してください...
 | 
				
			||||||
 | 
					webauthn_use_twofa=携帯電話から2要素認証コードを使用する
 | 
				
			||||||
 | 
					webauthn_error=セキュリティキーを読み取ることができません。
 | 
				
			||||||
 | 
					webauthn_unsupported_browser=お使いのブラウザは現在 WebAuthn をサポートしていません。
 | 
				
			||||||
 | 
					webauthn_error_unknown=不明なエラーが発生しました。 もう一度やり直してください。
 | 
				
			||||||
 | 
					webauthn_error_insecure=WebAuthn はセキュアな接続のみをサポートしています。HTTP 経由でテストする場合は、"localhost" または "127.0.0.1" のオリジンが使用できます。
 | 
				
			||||||
 | 
					webauthn_error_unable_to_process=サーバーがリクエストを処理できませんでした。
 | 
				
			||||||
 | 
					webauthn_error_duplicated=このリクエストに対しては、許可されていないセキュリティキーです。 キーが未登録であることを確認してください。
 | 
				
			||||||
 | 
					webauthn_error_empty=このキーに名前を設定する必要があります。
 | 
				
			||||||
 | 
					webauthn_error_timeout=キーを読み取る前にタイムアウトになりました。 このページをリロードしてもう一度やり直してください。
 | 
				
			||||||
 | 
					webauthn_u2f_deprecated=キー: '%s' は非推奨のU2Fプロセスを使用して認証しています。このキーを再登録して古い登録を削除したほうが良いでしょう。
 | 
				
			||||||
 | 
					webauthn_reload=リロード
 | 
				
			||||||
 | 
					
 | 
				
			||||||
repository=リポジトリ
 | 
					repository=リポジトリ
 | 
				
			||||||
organization=組織
 | 
					organization=組織
 | 
				
			||||||
@@ -513,6 +527,7 @@ twofa=2要素認証
 | 
				
			|||||||
account_link=連携アカウント
 | 
					account_link=連携アカウント
 | 
				
			||||||
organization=組織
 | 
					organization=組織
 | 
				
			||||||
uid=Uid
 | 
					uid=Uid
 | 
				
			||||||
 | 
					webauthn=セキュリティキー
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public_profile=公開プロフィール
 | 
					public_profile=公開プロフィール
 | 
				
			||||||
biography_placeholder=自己紹介を少しだけ
 | 
					biography_placeholder=自己紹介を少しだけ
 | 
				
			||||||
@@ -534,6 +549,8 @@ continue=続行
 | 
				
			|||||||
cancel=キャンセル
 | 
					cancel=キャンセル
 | 
				
			||||||
language=言語
 | 
					language=言語
 | 
				
			||||||
ui=テーマ
 | 
					ui=テーマ
 | 
				
			||||||
 | 
					hidden_comment_types=非表示にするコメントの種類
 | 
				
			||||||
 | 
					saved_successfully=設定は正常に保存されました。
 | 
				
			||||||
privacy=プライバシー
 | 
					privacy=プライバシー
 | 
				
			||||||
keep_activity_private=プロフィールページのアクティビティ表示を隠す
 | 
					keep_activity_private=プロフィールページのアクティビティ表示を隠す
 | 
				
			||||||
keep_activity_private_popup=アクティビティを、あなたと管理者にのみ表示します
 | 
					keep_activity_private_popup=アクティビティを、あなたと管理者にのみ表示します
 | 
				
			||||||
@@ -733,6 +750,11 @@ passcode_invalid=パスコードが間違っています。 再度お試しく
 | 
				
			|||||||
twofa_enrolled=あなたのアカウントに2要素認証が設定されました。 スクラッチトークン (%s) は一度しか表示しませんので安全な場所に保存してください!
 | 
					twofa_enrolled=あなたのアカウントに2要素認証が設定されました。 スクラッチトークン (%s) は一度しか表示しませんので安全な場所に保存してください!
 | 
				
			||||||
twofa_failed_get_secret=シークレットが取得できません。
 | 
					twofa_failed_get_secret=シークレットが取得できません。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					webauthn_desc=セキュリティキーは暗号化キーを内蔵するハードウェア ・ デバイスです。 2要素認証に使用できます。 セキュリティキーは<a rel="noreferrer" target="_blank" href="https://w3c.github.io/webauthn/#webauthn-authenticator">WebAuthn Authenticator</a>規格をサポートしている必要があります。
 | 
				
			||||||
 | 
					webauthn_register_key=セキュリティキーを追加
 | 
				
			||||||
 | 
					webauthn_nickname=ニックネーム
 | 
				
			||||||
 | 
					webauthn_delete_key=セキュリティキーの登録解除
 | 
				
			||||||
 | 
					webauthn_delete_key_desc=セキュリティキーの登録を解除すると、今後そのセキュリティキーでサインインすることはできなくなります。 続行しますか?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
manage_account_links=連携アカウントの管理
 | 
					manage_account_links=連携アカウントの管理
 | 
				
			||||||
manage_account_links_desc=これらの外部アカウントがGiteaアカウントと連携されています。
 | 
					manage_account_links_desc=これらの外部アカウントがGiteaアカウントと連携されています。
 | 
				
			||||||
@@ -1038,6 +1060,10 @@ editor.add_tmpl='<ファイル名>' を追加
 | 
				
			|||||||
editor.add='%s' を追加
 | 
					editor.add='%s' を追加
 | 
				
			||||||
editor.update='%s' を更新
 | 
					editor.update='%s' を更新
 | 
				
			||||||
editor.delete='%s' を削除
 | 
					editor.delete='%s' を削除
 | 
				
			||||||
 | 
					editor.patch=パッチの適用
 | 
				
			||||||
 | 
					editor.patching=パッチ:
 | 
				
			||||||
 | 
					editor.fail_to_apply_patch=パッチを適用できません '%s'
 | 
				
			||||||
 | 
					editor.new_patch=新しいパッチ
 | 
				
			||||||
editor.commit_message_desc=詳細な説明を追加…
 | 
					editor.commit_message_desc=詳細な説明を追加…
 | 
				
			||||||
editor.signoff_desc=コミットログメッセージの最後にコミッターの Signed-off-by 行を追加
 | 
					editor.signoff_desc=コミットログメッセージの最後にコミッターの Signed-off-by 行を追加
 | 
				
			||||||
editor.commit_directly_to_this_branch=ブランチ<strong class="branch-name">%s</strong>へ直接コミットする。
 | 
					editor.commit_directly_to_this_branch=ブランチ<strong class="branch-name">%s</strong>へ直接コミットする。
 | 
				
			||||||
@@ -1073,6 +1099,8 @@ editor.cannot_commit_to_protected_branch=保護されたブランチ '%s' にコ
 | 
				
			|||||||
editor.no_commit_to_branch=ブランチに直接コミットすることはできません、なぜなら:
 | 
					editor.no_commit_to_branch=ブランチに直接コミットすることはできません、なぜなら:
 | 
				
			||||||
editor.user_no_push_to_branch=ユーザーはブランチにプッシュできません
 | 
					editor.user_no_push_to_branch=ユーザーはブランチにプッシュできません
 | 
				
			||||||
editor.require_signed_commit=ブランチでは署名されたコミットが必須です
 | 
					editor.require_signed_commit=ブランチでは署名されたコミットが必須です
 | 
				
			||||||
 | 
					editor.cherry_pick=チェリーピック %s:
 | 
				
			||||||
 | 
					editor.revert=リバート %s:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
commits.desc=ソースコードの変更履歴を参照します。
 | 
					commits.desc=ソースコードの変更履歴を参照します。
 | 
				
			||||||
commits.commits=コミット
 | 
					commits.commits=コミット
 | 
				
			||||||
@@ -1093,6 +1121,7 @@ commits.signed_by_untrusted_user_unmatched=コミッターと一致しない信
 | 
				
			|||||||
commits.gpg_key_id=GPGキーID
 | 
					commits.gpg_key_id=GPGキーID
 | 
				
			||||||
commits.ssh_key_fingerprint=SSH鍵のフィンガープリント
 | 
					commits.ssh_key_fingerprint=SSH鍵のフィンガープリント
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ext_issues=外部イシューへのアクセス
 | 
					ext_issues=外部イシューへのアクセス
 | 
				
			||||||
ext_issues.desc=外部のイシュートラッカーへのリンク。
 | 
					ext_issues.desc=外部のイシュートラッカーへのリンク。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -2314,6 +2343,7 @@ first_page=最初
 | 
				
			|||||||
last_page=最後
 | 
					last_page=最後
 | 
				
			||||||
total=合計: %d
 | 
					total=合計: %d
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					dashboard.new_version_hint=Gitea %s が入手可能になりました。 現在実行しているのは %s です。 詳細は <a target="_blank" rel="noreferrer" href="https://blog.gitea.io">ブログ</a> を確認してください。
 | 
				
			||||||
dashboard.statistic=サマリー
 | 
					dashboard.statistic=サマリー
 | 
				
			||||||
dashboard.operations=メンテナンス操作
 | 
					dashboard.operations=メンテナンス操作
 | 
				
			||||||
dashboard.system_status=システム状況
 | 
					dashboard.system_status=システム状況
 | 
				
			||||||
@@ -2387,6 +2417,7 @@ dashboard.last_gc_pause=前回のGC停止時間
 | 
				
			|||||||
dashboard.gc_times=GC実行回数
 | 
					dashboard.gc_times=GC実行回数
 | 
				
			||||||
dashboard.delete_old_actions=データベースから古い操作履歴をすべて削除
 | 
					dashboard.delete_old_actions=データベースから古い操作履歴をすべて削除
 | 
				
			||||||
dashboard.delete_old_actions.started=データベースからの古い操作履歴の削除を開始しました。
 | 
					dashboard.delete_old_actions.started=データベースからの古い操作履歴の削除を開始しました。
 | 
				
			||||||
 | 
					dashboard.update_checker=更新チェック
 | 
				
			||||||
 | 
					
 | 
				
			||||||
users.user_manage_panel=ユーザーアカウント管理
 | 
					users.user_manage_panel=ユーザーアカウント管理
 | 
				
			||||||
users.new_account=ユーザーアカウントを作成
 | 
					users.new_account=ユーザーアカウントを作成
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -837,7 +837,7 @@ default_branch=Ramo principal
 | 
				
			|||||||
default_branch_helper=O ramo principal é o ramo base para pedidos de integração e cometimentos.
 | 
					default_branch_helper=O ramo principal é o ramo base para pedidos de integração e cometimentos.
 | 
				
			||||||
mirror_prune=Podar
 | 
					mirror_prune=Podar
 | 
				
			||||||
mirror_prune_desc=Remover referências obsoletas de seguimento remoto
 | 
					mirror_prune_desc=Remover referências obsoletas de seguimento remoto
 | 
				
			||||||
mirror_interval=Intervalo de espelhamento (as unidade de tempo válidas são 'h', 'm' e 's'). O valor zero desabilita a sincronização automática.
 | 
					mirror_interval=Intervalo de espelhamento (as unidades de tempo válidas são 'h', 'm' e 's'). O valor zero desabilita a sincronização automática.
 | 
				
			||||||
mirror_interval_invalid=O intervalo do espelhamento não é válido.
 | 
					mirror_interval_invalid=O intervalo do espelhamento não é válido.
 | 
				
			||||||
mirror_address=Clonar a partir do URL
 | 
					mirror_address=Clonar a partir do URL
 | 
				
			||||||
mirror_address_desc=Coloque, na secção de Autorização, as credenciais que, eventualmente, sejam necessárias.
 | 
					mirror_address_desc=Coloque, na secção de Autorização, as credenciais que, eventualmente, sejam necessárias.
 | 
				
			||||||
@@ -2334,6 +2334,7 @@ first_page=Primeira
 | 
				
			|||||||
last_page=Última
 | 
					last_page=Última
 | 
				
			||||||
total=total: %d
 | 
					total=total: %d
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					dashboard.new_version_hint=O Gitea %s está agora disponível, você está a correr a versão %s. Verifique o <a target="_blank" rel="noreferrer" href="https://blog.gitea.io">blog</a> para mais detalhes.
 | 
				
			||||||
dashboard.statistic=Resumo
 | 
					dashboard.statistic=Resumo
 | 
				
			||||||
dashboard.operations=Operações de manutenção
 | 
					dashboard.operations=Operações de manutenção
 | 
				
			||||||
dashboard.system_status=Estado do sistema
 | 
					dashboard.system_status=Estado do sistema
 | 
				
			||||||
@@ -2407,6 +2408,7 @@ dashboard.last_gc_pause=Última pausa da recolha de lixo
 | 
				
			|||||||
dashboard.gc_times=Tempos da recolha de lixo
 | 
					dashboard.gc_times=Tempos da recolha de lixo
 | 
				
			||||||
dashboard.delete_old_actions=Eliminar todas as operações antigas da base de dados
 | 
					dashboard.delete_old_actions=Eliminar todas as operações antigas da base de dados
 | 
				
			||||||
dashboard.delete_old_actions.started=Foi iniciado o processo de eliminação de todas as operações antigas da base de dados.
 | 
					dashboard.delete_old_actions.started=Foi iniciado o processo de eliminação de todas as operações antigas da base de dados.
 | 
				
			||||||
 | 
					dashboard.update_checker=Verificador de novas versões
 | 
				
			||||||
 | 
					
 | 
				
			||||||
users.user_manage_panel=Gestão das contas de utilizadores
 | 
					users.user_manage_panel=Gestão das contas de utilizadores
 | 
				
			||||||
users.new_account=Criar conta de utilizador
 | 
					users.new_account=Criar conta de utilizador
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -781,7 +781,7 @@ template_helper=Сделать репозиторий шаблоном
 | 
				
			|||||||
template_description=Шаблонные репозитории дают возможность пользователям создавать новые репозитории с той же структурой каталогов, файлами и дополнительными настройками.
 | 
					template_description=Шаблонные репозитории дают возможность пользователям создавать новые репозитории с той же структурой каталогов, файлами и дополнительными настройками.
 | 
				
			||||||
visibility=Видимость
 | 
					visibility=Видимость
 | 
				
			||||||
visibility_description=Только владелец или члены организации, при наличии прав, смогут увидеть это.
 | 
					visibility_description=Только владелец или члены организации, при наличии прав, смогут увидеть это.
 | 
				
			||||||
visibility_helper=Сделать репозиторий приватным
 | 
					visibility_helper=Сделать репозиторий частным
 | 
				
			||||||
visibility_helper_forced=Администратор сайта настроил параметр видимости новых репозиториев. Репозиторий приватный по умолчанию.
 | 
					visibility_helper_forced=Администратор сайта настроил параметр видимости новых репозиториев. Репозиторий приватный по умолчанию.
 | 
				
			||||||
visibility_fork_helper=(Изменение этого повлияет на все форки.)
 | 
					visibility_fork_helper=(Изменение этого повлияет на все форки.)
 | 
				
			||||||
clone_helper=Нужна помощь в клонировании? Посетите страницу <a target="_blank" rel="noopener noreferrer" href="%s">помощи</a>.
 | 
					clone_helper=Нужна помощь в клонировании? Посетите страницу <a target="_blank" rel="noopener noreferrer" href="%s">помощи</a>.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,6 +34,7 @@ twofa=Двофакторна авторизація
 | 
				
			|||||||
twofa_scratch=Двофакторний одноразовий пароль
 | 
					twofa_scratch=Двофакторний одноразовий пароль
 | 
				
			||||||
passcode=Код доступу
 | 
					passcode=Код доступу
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					webauthn_reload=Оновити
 | 
				
			||||||
 | 
					
 | 
				
			||||||
repository=Репозиторій
 | 
					repository=Репозиторій
 | 
				
			||||||
organization=Організація
 | 
					organization=Організація
 | 
				
			||||||
@@ -61,7 +62,7 @@ forks=Форки
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
activities=Дії
 | 
					activities=Дії
 | 
				
			||||||
pull_requests=Запити на злиття
 | 
					pull_requests=Запити на злиття
 | 
				
			||||||
issues=Проблеми
 | 
					issues=Задачі
 | 
				
			||||||
milestones=Етапи
 | 
					milestones=Етапи
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ok=OK
 | 
					ok=OK
 | 
				
			||||||
@@ -92,7 +93,9 @@ error404=Сторінка, до якої ви намагаєтеся зверн
 | 
				
			|||||||
never=Ніколи
 | 
					never=Ніколи
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[error]
 | 
					[error]
 | 
				
			||||||
 | 
					occurred=Сталася помилка
 | 
				
			||||||
missing_csrf=Некоректний запит: токен CSRF не задано
 | 
					missing_csrf=Некоректний запит: токен CSRF не задано
 | 
				
			||||||
 | 
					network_error=Помилка мережі
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[startpage]
 | 
					[startpage]
 | 
				
			||||||
app_desc=Зручний власний сервіс хостингу репозиторіїв Git
 | 
					app_desc=Зручний власний сервіс хостингу репозиторіїв Git
 | 
				
			||||||
@@ -346,7 +349,7 @@ reset_password.text=Перейдіть за цим посиланням, щоб
 | 
				
			|||||||
register_success=Реєстрація успішна
 | 
					register_success=Реєстрація успішна
 | 
				
			||||||
 | 
					
 | 
				
			||||||
issue_assigned.pull=@%[1]s призначив вам запит злиття %[2]s в репозиторії %[3]s.
 | 
					issue_assigned.pull=@%[1]s призначив вам запит злиття %[2]s в репозиторії %[3]s.
 | 
				
			||||||
issue_assigned.issue=@%[1]s призначив вам завдання %[2]s у репозиторії %[3]s.
 | 
					issue_assigned.issue=@%[1]s призначив вам задачу %[2]s у репозиторії %[3]s.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
issue.x_mentioned_you=<b>@%s</b> згадав вас:
 | 
					issue.x_mentioned_you=<b>@%s</b> згадав вас:
 | 
				
			||||||
issue.action.force_push=<b>%[1]s</b> force-pushed <b>%[2]s</b> з %[3]s в %[4]s.
 | 
					issue.action.force_push=<b>%[1]s</b> force-pushed <b>%[2]s</b> з %[3]s в %[4]s.
 | 
				
			||||||
@@ -474,7 +477,7 @@ activity=Публічна активність
 | 
				
			|||||||
followers=Читачі
 | 
					followers=Читачі
 | 
				
			||||||
starred=Обрані Репозиторії
 | 
					starred=Обрані Репозиторії
 | 
				
			||||||
watched=Відстежувані репозиторії
 | 
					watched=Відстежувані репозиторії
 | 
				
			||||||
projects=Проекти
 | 
					projects=Проєкт
 | 
				
			||||||
following=Читає
 | 
					following=Читає
 | 
				
			||||||
follow=Підписатися
 | 
					follow=Підписатися
 | 
				
			||||||
unfollow=Відписатися
 | 
					unfollow=Відписатися
 | 
				
			||||||
@@ -611,6 +614,7 @@ gpg_token_help=Ви можете створити підпис за допомо
 | 
				
			|||||||
gpg_token_code=echo "%s" | gpg -a --default-key %s --detach-sig
 | 
					gpg_token_code=echo "%s" | gpg -a --default-key %s --detach-sig
 | 
				
			||||||
gpg_token_signature=Текстовий (armored) підпис GPG
 | 
					gpg_token_signature=Текстовий (armored) підпис GPG
 | 
				
			||||||
key_signature_gpg_placeholder=Починається з "-----BEGIN PGP SIGNATURE-----"
 | 
					key_signature_gpg_placeholder=Починається з "-----BEGIN PGP SIGNATURE-----"
 | 
				
			||||||
 | 
					ssh_token=Токен
 | 
				
			||||||
subkeys=Підключі
 | 
					subkeys=Підключі
 | 
				
			||||||
key_id=ID ключа
 | 
					key_id=ID ключа
 | 
				
			||||||
key_name=Ім'я ключа
 | 
					key_name=Ім'я ключа
 | 
				
			||||||
@@ -743,7 +747,7 @@ visibility.private=Приватний
 | 
				
			|||||||
visibility.private_tooltip=Видимий лише членам організації
 | 
					visibility.private_tooltip=Видимий лише членам організації
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[repo]
 | 
					[repo]
 | 
				
			||||||
new_repo_helper=Репозиторій містить усі файли проекту, включаючи історію ревізій. Ще десь є? <a href="%s">Мігрувати репозиторій.</a>
 | 
					new_repo_helper=Репозиторій містить усі файли проєкту, включаючи історію ревізій. Ще десь є? <a href="%s">Мігрувати репозиторій.</a>
 | 
				
			||||||
owner=Власник
 | 
					owner=Власник
 | 
				
			||||||
owner_helper=Деякі організації можуть не відображатися у випадаючому списку через максимальну кількість репозиторііїв.
 | 
					owner_helper=Деякі організації можуть не відображатися у випадаючому списку через максимальну кількість репозиторііїв.
 | 
				
			||||||
repo_name=Назва репозиторію
 | 
					repo_name=Назва репозиторію
 | 
				
			||||||
@@ -774,14 +778,14 @@ repo_desc_helper=Введіть короткий опис (опціональн
 | 
				
			|||||||
repo_lang=Мова
 | 
					repo_lang=Мова
 | 
				
			||||||
repo_gitignore_helper=Виберіть шаблон .gitignore.
 | 
					repo_gitignore_helper=Виберіть шаблон .gitignore.
 | 
				
			||||||
repo_gitignore_helper_desc=Оберіть з списку мовних шаблонів файли, які не будуть відстежуватись. Типові артефакти, які генеруються за допомогою інструментів побудови кожної мови, за замовчуванням включені до .gitignor.
 | 
					repo_gitignore_helper_desc=Оберіть з списку мовних шаблонів файли, які не будуть відстежуватись. Типові артефакти, які генеруються за допомогою інструментів побудови кожної мови, за замовчуванням включені до .gitignor.
 | 
				
			||||||
issue_labels=Мітки проблем
 | 
					issue_labels=Мітки задачі
 | 
				
			||||||
issue_labels_helper=Вибрати мітку для проблеми.
 | 
					issue_labels_helper=Вибрати мітку для задачі.
 | 
				
			||||||
license=Ліцензія
 | 
					license=Ліцензія
 | 
				
			||||||
license_helper=Виберіть ліцензійний файл.
 | 
					license_helper=Виберіть ліцензійний файл.
 | 
				
			||||||
license_helper_desc=Ліцензія регулює те, що інші можуть і не можуть робити з вашим кодом. Не впевнені, що саме підходить для вашого проекту? Дивіться <a target="_blank" rel="noopener noreferrer" href="%s">Виберіть ліцензію.</a>
 | 
					license_helper_desc=Ліцензія регулює те, що інші можуть і не можуть робити з вашим кодом. Не впевнені, що саме підходить для вашого проєкту? Дивіться <a target="_blank" rel="noopener noreferrer" href="%s">Виберіть ліцензію.</a>
 | 
				
			||||||
readme=README
 | 
					readme=README
 | 
				
			||||||
readme_helper=Виберіть шаблон README.
 | 
					readme_helper=Виберіть шаблон README.
 | 
				
			||||||
readme_helper_desc=Це місце, де ви можете написати повний опис вашого проекту.
 | 
					readme_helper_desc=Це місце, де ви можете написати повний опис вашого проєкту.
 | 
				
			||||||
auto_init=Ініціалізувати репозиторій (Додає .gitignore, LICENSE та README)
 | 
					auto_init=Ініціалізувати репозиторій (Додає .gitignore, LICENSE та README)
 | 
				
			||||||
trust_model_helper=Виберіть модель довіри для підтвердження підпису. Можливі варіанти:
 | 
					trust_model_helper=Виберіть модель довіри для підтвердження підпису. Можливі варіанти:
 | 
				
			||||||
trust_model_helper_collaborator=Співавтор: підписи довіри від співавторів
 | 
					trust_model_helper_collaborator=Співавтор: підписи довіри від співавторів
 | 
				
			||||||
@@ -846,12 +850,12 @@ template.git_hooks=Перехоплювачі Git
 | 
				
			|||||||
template.webhooks=Webhook'и
 | 
					template.webhooks=Webhook'и
 | 
				
			||||||
template.topics=Теми
 | 
					template.topics=Теми
 | 
				
			||||||
template.avatar=Аватар
 | 
					template.avatar=Аватар
 | 
				
			||||||
template.issue_labels=Мітки проблем
 | 
					template.issue_labels=Мітки задачі
 | 
				
			||||||
template.one_item=Слід обрати хоча б один елемент шаблону
 | 
					template.one_item=Слід обрати хоча б один елемент шаблону
 | 
				
			||||||
template.invalid=Слід обрати шаблонний репозиторій
 | 
					template.invalid=Слід обрати шаблонний репозиторій
 | 
				
			||||||
 | 
					
 | 
				
			||||||
archive.title=Це архівний репозитарій. Ви можете переглядати і клонувати файли, але не можете робити пуш або відкривати питання/запити.
 | 
					archive.title=Цей репозиторій архівовано. Ви можете переглядати файли та клонувати його, але не можете виконувати push чи відкривати задачі та запити злиття.
 | 
				
			||||||
archive.issue.nocomment=Це архівний репозитарій. Ви не можете коментувати запити.
 | 
					archive.issue.nocomment=Цей репозиторій архівовано. Ви не можете коментувати задачі.
 | 
				
			||||||
archive.pull.nocomment=Це архівний репозитарій. Ви не можете коментувати пулл-реквести.
 | 
					archive.pull.nocomment=Це архівний репозитарій. Ви не можете коментувати пулл-реквести.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
form.reach_limit_of_creation_1=Ви вже досягли ліміту в %d репозиторіїв.
 | 
					form.reach_limit_of_creation_1=Ви вже досягли ліміту в %d репозиторіїв.
 | 
				
			||||||
@@ -873,7 +877,7 @@ migrate_items=Деталі міграції
 | 
				
			|||||||
migrate_items_wiki=Вікі
 | 
					migrate_items_wiki=Вікі
 | 
				
			||||||
migrate_items_milestones=Етапи
 | 
					migrate_items_milestones=Етапи
 | 
				
			||||||
migrate_items_labels=Мітки
 | 
					migrate_items_labels=Мітки
 | 
				
			||||||
migrate_items_issues=Проблеми
 | 
					migrate_items_issues=Задачі
 | 
				
			||||||
migrate_items_pullrequests=Запити на злиття
 | 
					migrate_items_pullrequests=Запити на злиття
 | 
				
			||||||
migrate_items_merge_requests=Запити на злиття
 | 
					migrate_items_merge_requests=Запити на злиття
 | 
				
			||||||
migrate_items_releases=Релізи
 | 
					migrate_items_releases=Релізи
 | 
				
			||||||
@@ -906,7 +910,7 @@ migrate.migrating_topics=Міграція тем
 | 
				
			|||||||
migrate.migrating_milestones=Міграція етапів
 | 
					migrate.migrating_milestones=Міграція етапів
 | 
				
			||||||
migrate.migrating_labels=Міграція міток
 | 
					migrate.migrating_labels=Міграція міток
 | 
				
			||||||
migrate.migrating_releases=Міграція релізів
 | 
					migrate.migrating_releases=Міграція релізів
 | 
				
			||||||
migrate.migrating_issues=Міграція проблем
 | 
					migrate.migrating_issues=Міграція задач
 | 
				
			||||||
migrate.migrating_pulls=Міграція запитів на злиття
 | 
					migrate.migrating_pulls=Міграція запитів на злиття
 | 
				
			||||||
 | 
					
 | 
				
			||||||
mirror_from=дзеркало
 | 
					mirror_from=дзеркало
 | 
				
			||||||
@@ -939,7 +943,7 @@ filter_branch_and_tag=Фільтрувати гілку або тег
 | 
				
			|||||||
find_tag=Знайти тег
 | 
					find_tag=Знайти тег
 | 
				
			||||||
branches=Гілки
 | 
					branches=Гілки
 | 
				
			||||||
tags=Теги
 | 
					tags=Теги
 | 
				
			||||||
issues=Проблеми
 | 
					issues=Задачі
 | 
				
			||||||
pulls=Запити на злиття
 | 
					pulls=Запити на злиття
 | 
				
			||||||
project_board=Проєкти
 | 
					project_board=Проєкти
 | 
				
			||||||
labels=Мітки
 | 
					labels=Мітки
 | 
				
			||||||
@@ -1053,10 +1057,12 @@ commits.signed_by_untrusted_user=Підписаний недовіреним к
 | 
				
			|||||||
commits.signed_by_untrusted_user_unmatched=Підписаний недовіреним користувачем, який не відповідає комітеру
 | 
					commits.signed_by_untrusted_user_unmatched=Підписаний недовіреним користувачем, який не відповідає комітеру
 | 
				
			||||||
commits.gpg_key_id=Ідентифікатор GPG ключа
 | 
					commits.gpg_key_id=Ідентифікатор GPG ключа
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ext_issues.desc=Посилання на зовнішню систему відстеження проблем.
 | 
					
 | 
				
			||||||
 | 
					ext_issues=Доступ до зовнішніх задач
 | 
				
			||||||
 | 
					ext_issues.desc=Посилання на зовнішню систему відстеження задач.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
projects=Проєкти
 | 
					projects=Проєкти
 | 
				
			||||||
projects.desc=Керуйте проблемами та запитами злиття на дошках проєкту.
 | 
					projects.desc=Керуйте задачами та запитами злиття на дошках проєкту.
 | 
				
			||||||
projects.description=Опис (необов'язково)
 | 
					projects.description=Опис (необов'язково)
 | 
				
			||||||
projects.description_placeholder=Опис
 | 
					projects.description_placeholder=Опис
 | 
				
			||||||
projects.create=Створити проєкт
 | 
					projects.create=Створити проєкт
 | 
				
			||||||
@@ -1065,10 +1071,10 @@ projects.new=Новий проєкт
 | 
				
			|||||||
projects.new_subheader=Координуйте, відстежуйте та оновлюйте інформацію про виконувану роботу в одному місці, аби проєкти залишалися прозорими та за розкладом.
 | 
					projects.new_subheader=Координуйте, відстежуйте та оновлюйте інформацію про виконувану роботу в одному місці, аби проєкти залишалися прозорими та за розкладом.
 | 
				
			||||||
projects.create_success=Проєкт '%s' створено.
 | 
					projects.create_success=Проєкт '%s' створено.
 | 
				
			||||||
projects.deletion=Видалити проєкт
 | 
					projects.deletion=Видалити проєкт
 | 
				
			||||||
projects.deletion_desc=Видалення проєкту видаляє його з усіх пов'язаних проблем. Продовжити?
 | 
					projects.deletion_desc=Видалення проєкту видаляє його з усіх пов'язаних задач. Продовжити?
 | 
				
			||||||
projects.deletion_success=Проєкт видалено.
 | 
					projects.deletion_success=Проєкт видалено.
 | 
				
			||||||
projects.edit=Редагувати проєкти
 | 
					projects.edit=Редагувати проєкти
 | 
				
			||||||
projects.edit_subheader=Проєкти призначені для організації проблем та відстеження поступу.
 | 
					projects.edit_subheader=Проєкти організовують задачі та відстежують прогрес.
 | 
				
			||||||
projects.modify=Оновити проєкт
 | 
					projects.modify=Оновити проєкт
 | 
				
			||||||
projects.edit_success=Проєкт '%s' оновлено.
 | 
					projects.edit_success=Проєкт '%s' оновлено.
 | 
				
			||||||
projects.type.none=Відсутній
 | 
					projects.type.none=Відсутній
 | 
				
			||||||
@@ -1083,9 +1089,9 @@ projects.board.new_title=Назва нової дошки
 | 
				
			|||||||
projects.board.new_submit=Створити
 | 
					projects.board.new_submit=Створити
 | 
				
			||||||
projects.board.new=Нова дошка
 | 
					projects.board.new=Нова дошка
 | 
				
			||||||
projects.board.set_default=Встановити за замовчуванням
 | 
					projects.board.set_default=Встановити за замовчуванням
 | 
				
			||||||
projects.board.set_default_desc=Встановити цю дошку за замовчуванням для проблем без категорії та витягувань
 | 
					projects.board.set_default_desc=Встановити цю дошку за замовчуванням для задач без категорії та витягувань
 | 
				
			||||||
projects.board.delete=Видалити дошку
 | 
					projects.board.delete=Видалити дошку
 | 
				
			||||||
projects.board.deletion_desc=Видалення дошки проєкту перенесе всі пов'язані проблеми в дошку 'Без категорії'. Продовжити?
 | 
					projects.board.deletion_desc=Видалення дошки проєкту перенесе всі пов'язані задачі в дошку 'Без категорії'. Продовжити?
 | 
				
			||||||
projects.board.color=Колір
 | 
					projects.board.color=Колір
 | 
				
			||||||
projects.open=Відкрити
 | 
					projects.open=Відкрити
 | 
				
			||||||
projects.close=Закрити
 | 
					projects.close=Закрити
 | 
				
			||||||
@@ -1096,7 +1102,7 @@ issues.filter_milestones=Фільтр етапів
 | 
				
			|||||||
issues.filter_projects=Фільтр проєктів
 | 
					issues.filter_projects=Фільтр проєктів
 | 
				
			||||||
issues.filter_labels=Фільтр міток
 | 
					issues.filter_labels=Фільтр міток
 | 
				
			||||||
issues.filter_reviewers=Фільтр рецензентів
 | 
					issues.filter_reviewers=Фільтр рецензентів
 | 
				
			||||||
issues.new=Нова проблема
 | 
					issues.new=Нова задача
 | 
				
			||||||
issues.new.title_empty=Заголовок не може бути пустим
 | 
					issues.new.title_empty=Заголовок не може бути пустим
 | 
				
			||||||
issues.new.labels=Мітки
 | 
					issues.new.labels=Мітки
 | 
				
			||||||
issues.new.add_labels_title=Застосувати мітки
 | 
					issues.new.add_labels_title=Застосувати мітки
 | 
				
			||||||
@@ -1125,7 +1131,7 @@ issues.choose.get_started=Початок роботи
 | 
				
			|||||||
issues.choose.blank=Типово
 | 
					issues.choose.blank=Типово
 | 
				
			||||||
issues.choose.blank_about=Створити задачу із шаблону за замовчуванням.
 | 
					issues.choose.blank_about=Створити задачу із шаблону за замовчуванням.
 | 
				
			||||||
issues.no_ref=Не вказана гілка або тег
 | 
					issues.no_ref=Не вказана гілка або тег
 | 
				
			||||||
issues.create=Створити проблему
 | 
					issues.create=Створити задачу
 | 
				
			||||||
issues.new_label=Нова мітка
 | 
					issues.new_label=Нова мітка
 | 
				
			||||||
issues.new_label_placeholder=Назва мітки
 | 
					issues.new_label_placeholder=Назва мітки
 | 
				
			||||||
issues.new_label_desc_placeholder=Опис
 | 
					issues.new_label_desc_placeholder=Опис
 | 
				
			||||||
@@ -1167,7 +1173,7 @@ issues.filter_milestone_no_select=Всі етапи
 | 
				
			|||||||
issues.filter_assignee=Виконавець
 | 
					issues.filter_assignee=Виконавець
 | 
				
			||||||
issues.filter_assginee_no_select=Всі виконавці
 | 
					issues.filter_assginee_no_select=Всі виконавці
 | 
				
			||||||
issues.filter_type=Тип
 | 
					issues.filter_type=Тип
 | 
				
			||||||
issues.filter_type.all_issues=Всі проблеми
 | 
					issues.filter_type.all_issues=Всі задачі
 | 
				
			||||||
issues.filter_type.assigned_to_you=Призначене вам
 | 
					issues.filter_type.assigned_to_you=Призначене вам
 | 
				
			||||||
issues.filter_type.created_by_you=Створено вами
 | 
					issues.filter_type.created_by_you=Створено вами
 | 
				
			||||||
issues.filter_type.mentioning_you=Вас згадано
 | 
					issues.filter_type.mentioning_you=Вас згадано
 | 
				
			||||||
@@ -1203,7 +1209,7 @@ issues.commented_at=`прокоментував(ла) <a href="#%s">%s</a>`
 | 
				
			|||||||
issues.delete_comment_confirm=Ви впевнені, що хочете видалити цей коментар?
 | 
					issues.delete_comment_confirm=Ви впевнені, що хочете видалити цей коментар?
 | 
				
			||||||
issues.context.copy_link=Скопіювати посилання
 | 
					issues.context.copy_link=Скопіювати посилання
 | 
				
			||||||
issues.context.quote_reply=Цитувати відповідь
 | 
					issues.context.quote_reply=Цитувати відповідь
 | 
				
			||||||
issues.context.reference_issue=Посилання в новій проблемі
 | 
					issues.context.reference_issue=Посилання в новій задачі
 | 
				
			||||||
issues.context.edit=Редагувати
 | 
					issues.context.edit=Редагувати
 | 
				
			||||||
issues.context.delete=Видалити
 | 
					issues.context.delete=Видалити
 | 
				
			||||||
issues.no_content=Тут ще немає жодного змісту.
 | 
					issues.no_content=Тут ще немає жодного змісту.
 | 
				
			||||||
@@ -1214,15 +1220,15 @@ issues.close_comment_issue=Прокоментувати і закрити
 | 
				
			|||||||
issues.reopen_issue=Відкрити знову
 | 
					issues.reopen_issue=Відкрити знову
 | 
				
			||||||
issues.reopen_comment_issue=Прокоментувати та відкрити знову
 | 
					issues.reopen_comment_issue=Прокоментувати та відкрити знову
 | 
				
			||||||
issues.create_comment=Коментар
 | 
					issues.create_comment=Коментар
 | 
				
			||||||
issues.closed_at=`закрив цю проблему <a id="%[1]s" href="#%[1]s">%[2]s</a>`
 | 
					issues.closed_at=`закрив цю задачу <a id="%[1]s" href="#%[1]s">%[2]s</a>`
 | 
				
			||||||
issues.reopened_at=`повторно відкрив цю проблему <a id="%[1]s" href="#%[1]s">%[2]s</a>`
 | 
					issues.reopened_at=`повторно відкрив цю задачу <a id="%[1]s" href="#%[1]s">%[2]s</a>`
 | 
				
			||||||
issues.commit_ref_at=`згадано цю проблему в коміті <a id="%[1]s" href="#%[1]s">%[2]s</a>`
 | 
					issues.commit_ref_at=`згадано цю задачу в коміті <a id="%[1]s" href="#%[1]s">%[2]s</a>`
 | 
				
			||||||
issues.ref_issue_from=`<a href="%[3]s">послався на цю проблему %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
 | 
					issues.ref_issue_from=`<a href="%[3]s">посилання на цю задачу %[4]</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
 | 
				
			||||||
issues.ref_pull_from=`<a href="%[3]s">послався на цей запит злиття %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
 | 
					issues.ref_pull_from=`<a href="%[3]s">послався на цей запит злиття %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
 | 
				
			||||||
issues.ref_closing_from=`<a href="%[3]s">послався на запит злиття %[4]s, який закриває цю проблему</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
 | 
					issues.ref_closing_from=`<a href="%[3]s">згадав запит на злиття %[4]с, які закриють цю задачу</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
 | 
				
			||||||
issues.ref_reopening_from=`<a href="%[3]s">послався на запит злиття %[4]s, який повторно відкриває цю проблему</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
 | 
					issues.ref_reopening_from=`<a href="%[3]s">згадав запит на злиття %[4]с, які повторно відкриють цю задачу</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
 | 
				
			||||||
issues.ref_closed_from=`<a href="%[3]s">закрив цю проблему %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
 | 
					issues.ref_closed_from=`<a href="%[3]s">закрив цю задачу %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
 | 
				
			||||||
issues.ref_reopened_from=`<a href="%[3]s">повторно відкрив цю проблему %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
 | 
					issues.ref_reopened_from=`<a href="%[3]s">повторно відкрито цю задачу %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
 | 
				
			||||||
issues.ref_from=`із %[1]s`
 | 
					issues.ref_from=`із %[1]s`
 | 
				
			||||||
issues.poster=Автор
 | 
					issues.poster=Автор
 | 
				
			||||||
issues.collaborator=Співавтор
 | 
					issues.collaborator=Співавтор
 | 
				
			||||||
@@ -1241,12 +1247,12 @@ issues.label_title=Назва мітки
 | 
				
			|||||||
issues.label_description=Опис мітки
 | 
					issues.label_description=Опис мітки
 | 
				
			||||||
issues.label_color=Колір мітки
 | 
					issues.label_color=Колір мітки
 | 
				
			||||||
issues.label_count=%d міток
 | 
					issues.label_count=%d міток
 | 
				
			||||||
issues.label_open_issues=%d відкритих проблем
 | 
					issues.label_open_issues=%d відкритих задач
 | 
				
			||||||
issues.label_edit=Редагувати
 | 
					issues.label_edit=Редагувати
 | 
				
			||||||
issues.label_delete=Видалити
 | 
					issues.label_delete=Видалити
 | 
				
			||||||
issues.label_modify=Редагувати мітку
 | 
					issues.label_modify=Редагувати мітку
 | 
				
			||||||
issues.label_deletion=Видалити мітку
 | 
					issues.label_deletion=Видалити мітку
 | 
				
			||||||
issues.label_deletion_desc=Видалення мітки видаляє її з усіх обговорень. Продовжити?
 | 
					issues.label_deletion_desc=Видалення мітки видаляє її з усіх задач. Продовжити?
 | 
				
			||||||
issues.label_deletion_success=Мітку було видалено.
 | 
					issues.label_deletion_success=Мітку було видалено.
 | 
				
			||||||
issues.label.filter_sort.alphabetically=За алфавітом
 | 
					issues.label.filter_sort.alphabetically=За алфавітом
 | 
				
			||||||
issues.label.filter_sort.reverse_alphabetically=З кінця алфавіту
 | 
					issues.label.filter_sort.reverse_alphabetically=З кінця алфавіту
 | 
				
			||||||
@@ -1259,29 +1265,29 @@ issues.subscribe=Підписатися
 | 
				
			|||||||
issues.unsubscribe=Відписатися
 | 
					issues.unsubscribe=Відписатися
 | 
				
			||||||
issues.lock=Блокування обговорення
 | 
					issues.lock=Блокування обговорення
 | 
				
			||||||
issues.unlock=Розблокування обговорення
 | 
					issues.unlock=Розблокування обговорення
 | 
				
			||||||
issues.lock.unknown_reason=Неможливо заблокувати проблему з невідомою причиною.
 | 
					issues.lock.unknown_reason=Неможливо заблокувати задачу з невідомою причиною.
 | 
				
			||||||
issues.lock_duplicate=Проблема не може бути заблокованим двічі.
 | 
					issues.lock_duplicate=Задача не може бути заблокованим двічі.
 | 
				
			||||||
issues.unlock_error=Не можливо розблокувати проблему, яка не заблокована.
 | 
					issues.unlock_error=Не можливо розблокувати задачу, яка не заблокована.
 | 
				
			||||||
issues.lock_with_reason=заблоковано як <strong>%s</strong> та обмежене обговорення для співавторів %s
 | 
					issues.lock_with_reason=заблоковано як <strong>%s</strong> та обмежене обговорення для співавторів %s
 | 
				
			||||||
issues.lock_no_reason=заблоковано та обмежене обговорення для співавторів %s
 | 
					issues.lock_no_reason=заблоковано та обмежене обговорення для співавторів %s
 | 
				
			||||||
issues.unlock_comment=розблоковане обговорення %s
 | 
					issues.unlock_comment=розблоковане обговорення %s
 | 
				
			||||||
issues.lock_confirm=Заблокувати
 | 
					issues.lock_confirm=Заблокувати
 | 
				
			||||||
issues.unlock_confirm=Розблокувати
 | 
					issues.unlock_confirm=Розблокувати
 | 
				
			||||||
issues.lock.notice_1=- Інші користувачі не можуть додавати нові коментарі до цієї проблеми.
 | 
					issues.lock.notice_1=- Інші користувачі не можуть додавати нові коментарі до цієї задачі.
 | 
				
			||||||
issues.lock.notice_2=- Ви й інші співавтори, які мають доступ до цього репозиторію, можете залишати коментарі, які інші можуть бачити.
 | 
					issues.lock.notice_2=- Ви й інші співавтори, які мають доступ до цього репозиторію, можете залишати коментарі, які інші можуть бачити.
 | 
				
			||||||
issues.lock.notice_3=- Ви завжди зможете розблокувати цю проблему в майбутньому.
 | 
					issues.lock.notice_3=- Ви завжди зможете розблокувати цю задачу в майбутньому.
 | 
				
			||||||
issues.unlock.notice_1=- Кожен зможе прокоментувати це питання ще раз.
 | 
					issues.unlock.notice_1=- Кожен зможе прокоментувати цю задачу ще раз.
 | 
				
			||||||
issues.unlock.notice_2=- Ви завжди зможете заблокувати цю проблему в майбутньому.
 | 
					issues.unlock.notice_2=- Ви завжди зможете заблокувати цю задачу в майбутньому.
 | 
				
			||||||
issues.lock.reason=Причина блокування
 | 
					issues.lock.reason=Причина блокування
 | 
				
			||||||
issues.lock.title=Заблокувати обговорення цієї проблеми.
 | 
					issues.lock.title=Заблокувати обговорення цієї задачі.
 | 
				
			||||||
issues.unlock.title=Розблокувати обговорення цієї проблеми.
 | 
					issues.unlock.title=Розблокувати обговорення цієї задачі.
 | 
				
			||||||
issues.comment_on_locked=Ви не можете коментувати заблоковану проблему.
 | 
					issues.comment_on_locked=Ви не можете коментувати заблоковану задачу.
 | 
				
			||||||
issues.tracker=Відстеження часу
 | 
					issues.tracker=Відстеження часу
 | 
				
			||||||
issues.start_tracking_short=Запустити таймер
 | 
					issues.start_tracking_short=Запустити таймер
 | 
				
			||||||
issues.start_tracking=Почати відстеження часу
 | 
					issues.start_tracking=Почати відстеження часу
 | 
				
			||||||
issues.start_tracking_history=`почав працювати %s`
 | 
					issues.start_tracking_history=`почав працювати %s`
 | 
				
			||||||
issues.tracker_auto_close=Таймер буде автоматично зупинено, коли ця проблема буде закрита
 | 
					issues.tracker_auto_close=Таймер буде автоматично зупинено, коли ця задача буде закрита
 | 
				
			||||||
issues.tracking_already_started=`Ви вже почали відстежувати час для <a href="%s">іншої проблеми</a>!`
 | 
					issues.tracking_already_started=`Ви вже почали відстежувати час для <a href="%s">іншої задачі</a>!`
 | 
				
			||||||
issues.stop_tracking=Зупинити таймер
 | 
					issues.stop_tracking=Зупинити таймер
 | 
				
			||||||
issues.stop_tracking_history=`перестав(-ла) працювати %s`
 | 
					issues.stop_tracking_history=`перестав(-ла) працювати %s`
 | 
				
			||||||
issues.cancel_tracking=Скасувати
 | 
					issues.cancel_tracking=Скасувати
 | 
				
			||||||
@@ -1308,7 +1314,7 @@ issues.due_date_form=рррр-мм-дд
 | 
				
			|||||||
issues.due_date_form_add=Додати дату завершення
 | 
					issues.due_date_form_add=Додати дату завершення
 | 
				
			||||||
issues.due_date_form_edit=Редагувати
 | 
					issues.due_date_form_edit=Редагувати
 | 
				
			||||||
issues.due_date_form_remove=Видалити
 | 
					issues.due_date_form_remove=Видалити
 | 
				
			||||||
issues.due_date_not_writer=Вам потрібен доступ до запису в репозиторії, щоб оновити дату завершення проблем.
 | 
					issues.due_date_not_writer=Вам потрібен доступ до запису в репозиторії, щоб оновити дату завершення задач.
 | 
				
			||||||
issues.due_date_not_set=Термін виконання не встановлений.
 | 
					issues.due_date_not_set=Термін виконання не встановлений.
 | 
				
			||||||
issues.due_date_added=додав(ла) дату завершення %s %s
 | 
					issues.due_date_added=додав(ла) дату завершення %s %s
 | 
				
			||||||
issues.due_date_modified=термін змінено з %s %s на %s
 | 
					issues.due_date_modified=термін змінено з %s %s на %s
 | 
				
			||||||
@@ -1316,7 +1322,7 @@ issues.due_date_remove=видалив(ла) дату завершення %s %s
 | 
				
			|||||||
issues.due_date_overdue=Прострочено
 | 
					issues.due_date_overdue=Прострочено
 | 
				
			||||||
issues.due_date_invalid=Термін дії не дійсний або знаходиться за межами допустимого діапазону. Будь ласка використовуйте формат 'yyyy-mm-dd'.
 | 
					issues.due_date_invalid=Термін дії не дійсний або знаходиться за межами допустимого діапазону. Будь ласка використовуйте формат 'yyyy-mm-dd'.
 | 
				
			||||||
issues.dependency.title=Залежності
 | 
					issues.dependency.title=Залежності
 | 
				
			||||||
issues.dependency.issue_no_dependencies=Ця проблема в даний час не має залежностей.
 | 
					issues.dependency.issue_no_dependencies=Ця задача тепер не має залежностей.
 | 
				
			||||||
issues.dependency.pr_no_dependencies=Цей запит на злиття в даний час не має залежностей.
 | 
					issues.dependency.pr_no_dependencies=Цей запит на злиття в даний час не має залежностей.
 | 
				
			||||||
issues.dependency.add=Додати залежність…
 | 
					issues.dependency.add=Додати залежність…
 | 
				
			||||||
issues.dependency.cancel=Відмінити
 | 
					issues.dependency.cancel=Відмінити
 | 
				
			||||||
@@ -1324,24 +1330,24 @@ issues.dependency.remove=Видалити
 | 
				
			|||||||
issues.dependency.remove_info=Видалити цю залежність
 | 
					issues.dependency.remove_info=Видалити цю залежність
 | 
				
			||||||
issues.dependency.added_dependency=`додав нову залежність %s`
 | 
					issues.dependency.added_dependency=`додав нову залежність %s`
 | 
				
			||||||
issues.dependency.removed_dependency=`видалив залежність %s`
 | 
					issues.dependency.removed_dependency=`видалив залежність %s`
 | 
				
			||||||
issues.dependency.pr_closing_blockedby=Закриття цього запиту злиття заблоковано наступними проблемами
 | 
					issues.dependency.pr_closing_blockedby=Закриття цього запиту злиття заблоковано наступними задачами
 | 
				
			||||||
issues.dependency.issue_closing_blockedby=Закриття цієї проблеми заблоковано наступними проблемами
 | 
					issues.dependency.issue_closing_blockedby=Закриття цієї задачи заблоковано наступними задачами
 | 
				
			||||||
issues.dependency.issue_close_blocks=Ця проблема блокує закриття залежних проблем
 | 
					issues.dependency.issue_close_blocks=Ця задача блокує закриття залежних задач
 | 
				
			||||||
issues.dependency.pr_close_blocks=Цей пулл-реквест блокує закриття залежних проблем
 | 
					issues.dependency.pr_close_blocks=Цей запит на злиття блокує закриття залежних задач
 | 
				
			||||||
issues.dependency.issue_close_blocked=Вам потрібно закрити всі проблеми, що блокують цю проблему, перед її закриттям.
 | 
					issues.dependency.issue_close_blocked=Вам потрібно закрити всі задачі, що блокують цю задачу, перед її закриттям.
 | 
				
			||||||
issues.dependency.pr_close_blocked=Вам потрібно закрити всі проблеми, що блокують цей пулл-реквест, перед його злиттям.
 | 
					issues.dependency.pr_close_blocked=Вам потрібно закрити всі задачі, що блокують цей запит, перед його злиттям.
 | 
				
			||||||
issues.dependency.blocks_short=Блоки
 | 
					issues.dependency.blocks_short=Блоки
 | 
				
			||||||
issues.dependency.blocked_by_short=Залежить від
 | 
					issues.dependency.blocked_by_short=Залежить від
 | 
				
			||||||
issues.dependency.remove_header=Видалити залежність
 | 
					issues.dependency.remove_header=Видалити залежність
 | 
				
			||||||
issues.dependency.issue_remove_text=Це призведе до видалення залежності з цієї проблеми. Продовжити?
 | 
					issues.dependency.issue_remove_text=Це призведе до видалення залежності з цієї задачі. Продовжити?
 | 
				
			||||||
issues.dependency.pr_remove_text=Це призведе до видалення залежності з цього пулл-реквесту. Продовжити?
 | 
					issues.dependency.pr_remove_text=Це призведе до видалення залежності з цього пулл-реквесту. Продовжити?
 | 
				
			||||||
issues.dependency.setting=Увімкнути залежності для проблем та пулл-реквестів
 | 
					issues.dependency.setting=Увімкнути залежності для задач та запитів на злиття
 | 
				
			||||||
issues.dependency.add_error_same_issue=Ви не можете зробити проблему залежною від себе.
 | 
					issues.dependency.add_error_same_issue=Ви не можете зробити задачу залежною від себе.
 | 
				
			||||||
issues.dependency.add_error_dep_issue_not_exist=Залежність для проблеми не існує.
 | 
					issues.dependency.add_error_dep_issue_not_exist=Залежність для задачі не існує.
 | 
				
			||||||
issues.dependency.add_error_dep_not_exist=Залежність не існує.
 | 
					issues.dependency.add_error_dep_not_exist=Залежність не існує.
 | 
				
			||||||
issues.dependency.add_error_dep_exists=Залежність уже існує.
 | 
					issues.dependency.add_error_dep_exists=Залежність уже існує.
 | 
				
			||||||
issues.dependency.add_error_cannot_create_circular=Ви не можете створити залежність з двома проблемами, які блокують одна одну.
 | 
					issues.dependency.add_error_cannot_create_circular=Ви не можете створити залежність з двома задачами, які блокують одна одну.
 | 
				
			||||||
issues.dependency.add_error_dep_not_same_repo=Обидві проблеми повинні бути в одному репозиторії.
 | 
					issues.dependency.add_error_dep_not_same_repo=Обидві задачі повинні бути в одному репозиторії.
 | 
				
			||||||
issues.review.self.approval=Ви не можете схвалити власний пулл-реквест.
 | 
					issues.review.self.approval=Ви не можете схвалити власний пулл-реквест.
 | 
				
			||||||
issues.review.self.rejection=Ви не можете надіслати запит на зміну на власний пулл-реквест.
 | 
					issues.review.self.rejection=Ви не можете надіслати запит на зміну на власний пулл-реквест.
 | 
				
			||||||
issues.review.approve=зміни затверджено %s
 | 
					issues.review.approve=зміни затверджено %s
 | 
				
			||||||
@@ -1475,7 +1481,7 @@ pulls.closed_at=`закрив цей запит на злиття <a id="%[1]s"
 | 
				
			|||||||
pulls.reopened_at=`повторно відкрив цей запит на злиття <a id="%[1]s" href="#%[1]s">%[2]s</a>`
 | 
					pulls.reopened_at=`повторно відкрив цей запит на злиття <a id="%[1]s" href="#%[1]s">%[2]s</a>`
 | 
				
			||||||
pulls.merge_instruction_hint=`Також можна переглянути <a class="show-instruction">інструкції для командного рядка</a>.`
 | 
					pulls.merge_instruction_hint=`Також можна переглянути <a class="show-instruction">інструкції для командного рядка</a>.`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pulls.merge_instruction_step1_desc=У репозиторії вашого проекту перевірте нову гілку і протестуйте зміни.
 | 
					pulls.merge_instruction_step1_desc=У репозиторії вашого проєкту перевірте нову гілку і протестуйте зміни.
 | 
				
			||||||
pulls.merge_instruction_step2_desc=Об'єднати зміни і оновити на Gitea.
 | 
					pulls.merge_instruction_step2_desc=Об'єднати зміни і оновити на Gitea.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
milestones.new=Новий етап
 | 
					milestones.new=Новий етап
 | 
				
			||||||
@@ -1486,7 +1492,7 @@ milestones.update_ago=Оновлено %s назад
 | 
				
			|||||||
milestones.no_due_date=Немає дати завершення
 | 
					milestones.no_due_date=Немає дати завершення
 | 
				
			||||||
milestones.open=Відкрити
 | 
					milestones.open=Відкрити
 | 
				
			||||||
milestones.close=Закрити
 | 
					milestones.close=Закрити
 | 
				
			||||||
milestones.new_subheader=Створюйте етапи для організації ваших завдань.
 | 
					milestones.new_subheader=Створюйте етапи для організації ваших задач.
 | 
				
			||||||
milestones.completeness=%d%% завершено
 | 
					milestones.completeness=%d%% завершено
 | 
				
			||||||
milestones.create=Створити етап
 | 
					milestones.create=Створити етап
 | 
				
			||||||
milestones.title=Заголовок
 | 
					milestones.title=Заголовок
 | 
				
			||||||
@@ -1496,19 +1502,19 @@ milestones.clear=Очистити
 | 
				
			|||||||
milestones.invalid_due_date_format=Дата завершення має бути в форматі 'рррр-мм-дд'.
 | 
					milestones.invalid_due_date_format=Дата завершення має бути в форматі 'рррр-мм-дд'.
 | 
				
			||||||
milestones.create_success=Етап '%s' створений.
 | 
					milestones.create_success=Етап '%s' створений.
 | 
				
			||||||
milestones.edit=Редагувати етап
 | 
					milestones.edit=Редагувати етап
 | 
				
			||||||
milestones.edit_subheader=Використовуйте кращий опис контрольної точки, щоб уникнути нерозуміння з боку інших людей.
 | 
					milestones.edit_subheader=Створюйте етапи для організації ваших задач.
 | 
				
			||||||
milestones.cancel=Відмінити
 | 
					milestones.cancel=Відмінити
 | 
				
			||||||
milestones.modify=Оновити етап
 | 
					milestones.modify=Оновити етап
 | 
				
			||||||
milestones.edit_success=Етап '%s' був оновлений.
 | 
					milestones.edit_success=Етап '%s' був оновлений.
 | 
				
			||||||
milestones.deletion=Видалити етап
 | 
					milestones.deletion=Видалити етап
 | 
				
			||||||
milestones.deletion_desc=Видалення етапу призведе до його видалення з усіх пов'язаних завдань. Продовжити?
 | 
					milestones.deletion_desc=Видалення етапу призведе до його видалення з усіх пов'язаних задач. Продовжити?
 | 
				
			||||||
milestones.deletion_success=Етап успішно видалено.
 | 
					milestones.deletion_success=Етап успішно видалено.
 | 
				
			||||||
milestones.filter_sort.closest_due_date=Найближче за датою
 | 
					milestones.filter_sort.closest_due_date=Найближче за датою
 | 
				
			||||||
milestones.filter_sort.furthest_due_date=Далі за датою
 | 
					milestones.filter_sort.furthest_due_date=Далі за датою
 | 
				
			||||||
milestones.filter_sort.least_complete=Менш повне
 | 
					milestones.filter_sort.least_complete=Менш повне
 | 
				
			||||||
milestones.filter_sort.most_complete=Більш повне
 | 
					milestones.filter_sort.most_complete=Більш повне
 | 
				
			||||||
milestones.filter_sort.most_issues=Найбільш проблем
 | 
					milestones.filter_sort.most_issues=Найбільш задач
 | 
				
			||||||
milestones.filter_sort.least_issues=Найменш проблем
 | 
					milestones.filter_sort.least_issues=Найменш задач
 | 
				
			||||||
 | 
					
 | 
				
			||||||
signing.will_sign=Цей коміт буде підписано ключем '%s'
 | 
					signing.will_sign=Цей коміт буде підписано ключем '%s'
 | 
				
			||||||
signing.wont_sign.error=Під час підписання коміту, сталася помилка
 | 
					signing.wont_sign.error=Під час підписання коміту, сталася помилка
 | 
				
			||||||
@@ -1574,21 +1580,21 @@ activity.title.prs_merged_by=%s злито %s
 | 
				
			|||||||
activity.title.prs_opened_by=%s запропоновано %s
 | 
					activity.title.prs_opened_by=%s запропоновано %s
 | 
				
			||||||
activity.merged_prs_label=Злито
 | 
					activity.merged_prs_label=Злито
 | 
				
			||||||
activity.opened_prs_label=Запропоновано
 | 
					activity.opened_prs_label=Запропоновано
 | 
				
			||||||
activity.active_issues_count_1=<strong>%d</strong> Активна проблема
 | 
					activity.active_issues_count_1=<strong>%d</strong> Активна задача
 | 
				
			||||||
activity.active_issues_count_n=<strong>%d</strong> Активні проблеми
 | 
					activity.active_issues_count_n=<strong>%d</strong> Активні задачі
 | 
				
			||||||
activity.closed_issues_count_1=Закрита проблема
 | 
					activity.closed_issues_count_1=Закрита задача
 | 
				
			||||||
activity.closed_issues_count_n=Закриті проблеми
 | 
					activity.closed_issues_count_n=Закриті задачі
 | 
				
			||||||
activity.title.issues_1=%d Проблема
 | 
					activity.title.issues_1=%d Задач
 | 
				
			||||||
activity.title.issues_n=%d Проблеми
 | 
					activity.title.issues_n=%d Задач
 | 
				
			||||||
activity.title.issues_closed_from=%s закрито %s
 | 
					activity.title.issues_closed_from=%s закрито %s
 | 
				
			||||||
activity.title.issues_created_by=%s створена(і) %s
 | 
					activity.title.issues_created_by=%s створена(і) %s
 | 
				
			||||||
activity.closed_issue_label=Закрито
 | 
					activity.closed_issue_label=Закрито
 | 
				
			||||||
activity.new_issues_count_1=Нова Проблема
 | 
					activity.new_issues_count_1=Нова задача
 | 
				
			||||||
activity.new_issues_count_n=%d Проблем
 | 
					activity.new_issues_count_n=Нові Задачі
 | 
				
			||||||
activity.new_issue_label=Відкриті
 | 
					activity.new_issue_label=Відкриті
 | 
				
			||||||
activity.title.unresolved_conv_1=%d Незавершене обговорення
 | 
					activity.title.unresolved_conv_1=%d Незавершене обговорення
 | 
				
			||||||
activity.title.unresolved_conv_n=%d Незавершених обговорень
 | 
					activity.title.unresolved_conv_n=%d Незавершених обговорень
 | 
				
			||||||
activity.unresolved_conv_desc=Список всіх старих тікетів і Pull Request'ів з недавньої активністю, але ще не закритих або прийнятих.
 | 
					activity.unresolved_conv_desc=Список всіх старих задач і Pull Request'ів з недавньої активністю, але ще не закритих або прийнятих.
 | 
				
			||||||
activity.unresolved_conv_label=Відкрити
 | 
					activity.unresolved_conv_label=Відкрити
 | 
				
			||||||
activity.title.releases_1=%d Реліз
 | 
					activity.title.releases_1=%d Реліз
 | 
				
			||||||
activity.title.releases_n=%d Релізів
 | 
					activity.title.releases_n=%d Релізів
 | 
				
			||||||
@@ -1635,7 +1641,7 @@ settings.hooks=Веб-хуки
 | 
				
			|||||||
settings.githooks=Git хуки
 | 
					settings.githooks=Git хуки
 | 
				
			||||||
settings.basic_settings=Базові налаштування
 | 
					settings.basic_settings=Базові налаштування
 | 
				
			||||||
settings.mirror_settings=Налаштування дзеркала
 | 
					settings.mirror_settings=Налаштування дзеркала
 | 
				
			||||||
settings.mirror_settings.docs=Налаштуйте свій проект, щоб автоматично відправляти/отримувати зміни з іншого репозиторію. Гілки, теги і коміти будуть синхронізуватися автоматично. <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/repo-mirror/">Як я можу відзеркалити репозиторії?</a>
 | 
					settings.mirror_settings.docs=Налаштуйте свій проєкт, щоб автоматично відправляти/отримувати зміни з іншого репозиторію. Гілки, теги та коміти будуть синхронізуватися автоматично. <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/repo-mirror/">Як я можу відзеркалити репозиторії?</a>
 | 
				
			||||||
settings.mirror_settings.mirrored_repository=Віддзеркалений репозиторій
 | 
					settings.mirror_settings.mirrored_repository=Віддзеркалений репозиторій
 | 
				
			||||||
settings.mirror_settings.direction=Напрямок
 | 
					settings.mirror_settings.direction=Напрямок
 | 
				
			||||||
settings.mirror_settings.direction.pull=Pull
 | 
					settings.mirror_settings.direction.pull=Pull
 | 
				
			||||||
@@ -1660,12 +1666,12 @@ settings.use_external_wiki=Використовувати зовнішні Ві
 | 
				
			|||||||
settings.external_wiki_url=URL зовнішньої вікі
 | 
					settings.external_wiki_url=URL зовнішньої вікі
 | 
				
			||||||
settings.external_wiki_url_error=Зовнішня URL-адреса wiki не є допустимою URL-адресою.
 | 
					settings.external_wiki_url_error=Зовнішня URL-адреса wiki не є допустимою URL-адресою.
 | 
				
			||||||
settings.external_wiki_url_desc=Відвідувачі будуть перенаправлені на URL-адресу, коли вони клацають по вкладці.
 | 
					settings.external_wiki_url_desc=Відвідувачі будуть перенаправлені на URL-адресу, коли вони клацають по вкладці.
 | 
				
			||||||
settings.issues_desc=Увімкнути відстеження проблем в репозиторію
 | 
					settings.issues_desc=Увімкнути відстеження задач в репозиторію
 | 
				
			||||||
settings.use_internal_issue_tracker=Використовувати вбудовану систему відстеження проблем
 | 
					settings.use_internal_issue_tracker=Використовувати вбудовану систему відстеження задач
 | 
				
			||||||
settings.use_external_issue_tracker=Використовувати зовнішню систему обліку завдань
 | 
					settings.use_external_issue_tracker=Використовувати зовнішню систему обліку задач
 | 
				
			||||||
settings.external_tracker_url=URL зовнішньої системи відстеження проблем
 | 
					settings.external_tracker_url=URL зовнішньої системи відстеження задач
 | 
				
			||||||
settings.external_tracker_url_error=URL зовнішнього баг-трекера не є допустимою URL-адресою.
 | 
					settings.external_tracker_url_error=URL зовнішнього баг-трекера не є допустимою URL-адресою.
 | 
				
			||||||
settings.external_tracker_url_desc=Відвідувачі перенаправляються на зовнішню URL-адресу, коли натискають вкладку 'Проблеми'.
 | 
					settings.external_tracker_url_desc=Відвідувачі перенаправляються на зовнішню URL-адресу, коли натискають вкладку 'Задачі'.
 | 
				
			||||||
settings.tracker_url_format=Формат URL зовнішнього трекера задач
 | 
					settings.tracker_url_format=Формат URL зовнішнього трекера задач
 | 
				
			||||||
settings.tracker_url_format_error=Неправильний формат URL-адреси зовнішнього баг-трекера.
 | 
					settings.tracker_url_format_error=Неправильний формат URL-адреси зовнішнього баг-трекера.
 | 
				
			||||||
settings.tracker_issue_style=Формат номеру для зовнішньої системи обліку задач
 | 
					settings.tracker_issue_style=Формат номеру для зовнішньої системи обліку задач
 | 
				
			||||||
@@ -1686,7 +1692,7 @@ settings.pulls.default_delete_branch_after_merge=Видаляти гілку з
 | 
				
			|||||||
settings.projects_desc=Увімкнути проєкти у репозиторії
 | 
					settings.projects_desc=Увімкнути проєкти у репозиторії
 | 
				
			||||||
settings.admin_settings=Налаштування адміністратора
 | 
					settings.admin_settings=Налаштування адміністратора
 | 
				
			||||||
settings.admin_enable_health_check=Включити перевірки працездатності репозиторію (git fsck)
 | 
					settings.admin_enable_health_check=Включити перевірки працездатності репозиторію (git fsck)
 | 
				
			||||||
settings.admin_enable_close_issues_via_commit_in_any_branch=Закрити проблему за допомогою коміта, зробленого не головній гілці
 | 
					settings.admin_enable_close_issues_via_commit_in_any_branch=Закрити задачу за допомогою коміта, зробленого не в головній гілці
 | 
				
			||||||
settings.danger_zone=Небезпечна зона
 | 
					settings.danger_zone=Небезпечна зона
 | 
				
			||||||
settings.new_owner_has_same_repo=Новий власник вже має репозиторій з такою назвою. Будь ласка, виберіть інше ім'я.
 | 
					settings.new_owner_has_same_repo=Новий власник вже має репозиторій з такою назвою. Будь ласка, виберіть інше ім'я.
 | 
				
			||||||
settings.convert=Перетворити на звичайний репозиторій
 | 
					settings.convert=Перетворити на звичайний репозиторій
 | 
				
			||||||
@@ -1736,7 +1742,7 @@ settings.wiki_deletion_success=Дані wiki були видалені.
 | 
				
			|||||||
settings.delete=Видалити цей репозиторій
 | 
					settings.delete=Видалити цей репозиторій
 | 
				
			||||||
settings.delete_desc=Будьте уважні! Як тільки ви видалите репозиторій - шляху назад не буде.
 | 
					settings.delete_desc=Будьте уважні! Як тільки ви видалите репозиторій - шляху назад не буде.
 | 
				
			||||||
settings.delete_notices_1=- Цю операцію <strong>НЕ МОЖНА</strong> відмінити.
 | 
					settings.delete_notices_1=- Цю операцію <strong>НЕ МОЖНА</strong> відмінити.
 | 
				
			||||||
settings.delete_notices_2=- Ця операція назавжди видалить все з репозиторію <strong>%s</strong>, включаючи дані Git, пов'язані з ним завдання, коментарі і права доступу для співробітників.
 | 
					settings.delete_notices_2=- Ця операція остаточно видалить <strong>%s</strong> репозиторій, включаючи код, задачі, коментарі, вікі та налаштування співавторів.
 | 
				
			||||||
settings.delete_notices_fork_1=- Всі форки стануть незалежними репозиторіями після видалення.
 | 
					settings.delete_notices_fork_1=- Всі форки стануть незалежними репозиторіями після видалення.
 | 
				
			||||||
settings.deletion_success=Репозиторій успішно видалено.
 | 
					settings.deletion_success=Репозиторій успішно видалено.
 | 
				
			||||||
settings.update_settings_success=Налаштування репозиторію було оновлено.
 | 
					settings.update_settings_success=Налаштування репозиторію було оновлено.
 | 
				
			||||||
@@ -1806,16 +1812,16 @@ settings.event_push_desc=Git push до репозиторію.
 | 
				
			|||||||
settings.event_repository=Репозиторій
 | 
					settings.event_repository=Репозиторій
 | 
				
			||||||
settings.event_repository_desc=Репозиторій створений або видалено.
 | 
					settings.event_repository_desc=Репозиторій створений або видалено.
 | 
				
			||||||
settings.event_header_issue=Події задачі
 | 
					settings.event_header_issue=Події задачі
 | 
				
			||||||
settings.event_issues=Проблеми
 | 
					settings.event_issues=Задачі
 | 
				
			||||||
settings.event_issues_desc=Проблема відкрита, закрита, повторно відкрита або відредагована.
 | 
					settings.event_issues_desc=Задача відкрита, закрита, повторно відкрита або відредагована.
 | 
				
			||||||
settings.event_issue_assign=Проблема прив'язана
 | 
					settings.event_issue_assign=Задача прив'язана
 | 
				
			||||||
settings.event_issue_assign_desc=Проблема призначена або скасована.
 | 
					settings.event_issue_assign_desc=Задачу призначено або скасовано.
 | 
				
			||||||
settings.event_issue_label=Проблема з міткою
 | 
					settings.event_issue_label=Задача з міткою
 | 
				
			||||||
settings.event_issue_label_desc=Мітки проблем оновлено або видалено.
 | 
					settings.event_issue_label_desc=Мітки задачі оновлено або видалено.
 | 
				
			||||||
settings.event_issue_milestone=Проблеми етапу
 | 
					settings.event_issue_milestone=Задача з етапом
 | 
				
			||||||
settings.event_issue_milestone_desc=Проблема призначена на етап або видалена з етапу.
 | 
					settings.event_issue_milestone_desc=Задача призначена на етап або видалена з етапу.
 | 
				
			||||||
settings.event_issue_comment=Коментар проблеми
 | 
					settings.event_issue_comment=Коментар задачі
 | 
				
			||||||
settings.event_issue_comment_desc=Коментар проблеми створено, видалено чи відредаговано.
 | 
					settings.event_issue_comment_desc=Коментар задачі створено, видалено чи відредаговано.
 | 
				
			||||||
settings.event_header_pull_request=Події запиту злиття
 | 
					settings.event_header_pull_request=Події запиту злиття
 | 
				
			||||||
settings.event_pull_request=Запити до злиття
 | 
					settings.event_pull_request=Запити до злиття
 | 
				
			||||||
settings.event_pull_request_desc=Запит до злиття відкрито, закрито, перевідкрито або відредаговано.
 | 
					settings.event_pull_request_desc=Запит до злиття відкрито, закрито, перевідкрито або відредаговано.
 | 
				
			||||||
@@ -1942,7 +1948,7 @@ settings.matrix.access_token=Токен Доступу
 | 
				
			|||||||
settings.matrix.message_type=Тип повідомлення
 | 
					settings.matrix.message_type=Тип повідомлення
 | 
				
			||||||
settings.archive.button=Архівний репозиторій
 | 
					settings.archive.button=Архівний репозиторій
 | 
				
			||||||
settings.archive.header=Відправити репозиторій в архів
 | 
					settings.archive.header=Відправити репозиторій в архів
 | 
				
			||||||
settings.archive.text=Архівування репозиторія зробить його доступним лише для читання. Він не відображається на панелі, в нього не можуть вноситись зміни і не можна створювати запити з проблем та пулл-реквести.
 | 
					settings.archive.text=Архівування репозиторія зробить його доступним лише для читання. Він не відображається на панелі, в нього не можуть вноситись зміни і не можна створювати запити з задач та пулл-реквести.
 | 
				
			||||||
settings.archive.success=Репозиторію успішно присвоєно статус архівного.
 | 
					settings.archive.success=Репозиторію успішно присвоєно статус архівного.
 | 
				
			||||||
settings.archive.error=Сталася помилка при спробі архівувати репозиторій. Докладнішу інформацію див. у журналі.
 | 
					settings.archive.error=Сталася помилка при спробі архівувати репозиторій. Докладнішу інформацію див. у журналі.
 | 
				
			||||||
settings.archive.error_ismirror=Неможливо архівувати дзеркальний репозиротрій.
 | 
					settings.archive.error_ismirror=Неможливо архівувати дзеркальний репозиротрій.
 | 
				
			||||||
@@ -1950,7 +1956,7 @@ settings.archive.branchsettings_unavailable=Параметри гілки не 
 | 
				
			|||||||
settings.archive.tagsettings_unavailable=Параметри міток недоступні, якщо репозиторій архівний.
 | 
					settings.archive.tagsettings_unavailable=Параметри міток недоступні, якщо репозиторій архівний.
 | 
				
			||||||
settings.unarchive.button=Зняти архівний статус
 | 
					settings.unarchive.button=Зняти архівний статус
 | 
				
			||||||
settings.unarchive.header=Зняти архівний статус для репозиторія
 | 
					settings.unarchive.header=Зняти архівний статус для репозиторія
 | 
				
			||||||
settings.unarchive.text=Зняття статусу архівного відновить запис в репозиторій, а також відкриє можливість створювати запити з нових проблем та пулл-запити.
 | 
					settings.unarchive.text=Зняття статусу архівного відновить запис в репозиторій, а також відкриє можливість створювати запити з нових задачах та пулл-запити.
 | 
				
			||||||
settings.unarchive.success=Статус архівний успішно знято.
 | 
					settings.unarchive.success=Статус архівний успішно знято.
 | 
				
			||||||
settings.unarchive.error=Сталася помилка при спробі скасувати архівний статус репозиторія. Докладнішу інформацію див. у журналі.
 | 
					settings.unarchive.error=Сталася помилка при спробі скасувати архівний статус репозиторія. Докладнішу інформацію див. у журналі.
 | 
				
			||||||
settings.update_avatar_success=Аватар репозиторію оновлений.
 | 
					settings.update_avatar_success=Аватар репозиторію оновлений.
 | 
				
			||||||
@@ -2037,7 +2043,7 @@ diff.image.side_by_side=Пліч-о-пліч
 | 
				
			|||||||
diff.image.swipe=Свайп
 | 
					diff.image.swipe=Свайп
 | 
				
			||||||
diff.image.overlay=Оверлей
 | 
					diff.image.overlay=Оверлей
 | 
				
			||||||
 | 
					
 | 
				
			||||||
releases.desc=Відслідковувати версії проекту (релізи) та завантаження.
 | 
					releases.desc=Відслідковувати версії проєкту і завантаження.
 | 
				
			||||||
release.releases=Релізи
 | 
					release.releases=Релізи
 | 
				
			||||||
release.detail=Деталі релізу
 | 
					release.detail=Деталі релізу
 | 
				
			||||||
release.tags=Теги
 | 
					release.tags=Теги
 | 
				
			||||||
@@ -2050,8 +2056,8 @@ release.edit=редагувати
 | 
				
			|||||||
release.ahead.commits=<strong>%d</strong> коміт(ів)
 | 
					release.ahead.commits=<strong>%d</strong> коміт(ів)
 | 
				
			||||||
release.ahead.target=до %s з моменту цього випуску
 | 
					release.ahead.target=до %s з моменту цього випуску
 | 
				
			||||||
release.source_code=Код
 | 
					release.source_code=Код
 | 
				
			||||||
release.new_subheader=Публікація релізів допоможе зберігати чітку історію розвитку вашого проекту.
 | 
					release.new_subheader=Публікація релізів допоможе вам організувати версію проєкту.
 | 
				
			||||||
release.edit_subheader=Публікація релізів допоможе зберігати чітку історію розвитку вашого проекту.
 | 
					release.edit_subheader=Публікація релізів допоможе вам організувати версію проєкту.
 | 
				
			||||||
release.tag_name=Назва тегу
 | 
					release.tag_name=Назва тегу
 | 
				
			||||||
release.target=Ціль
 | 
					release.target=Ціль
 | 
				
			||||||
release.tag_helper=Виберіть існуючий тег або створіть новий.
 | 
					release.tag_helper=Виберіть існуючий тег або створіть новий.
 | 
				
			||||||
@@ -2175,7 +2181,7 @@ settings.delete_org_title=Видалити організацію
 | 
				
			|||||||
settings.delete_org_desc=Ця організація буде безповоротно видалена. Продовжити?
 | 
					settings.delete_org_desc=Ця організація буде безповоротно видалена. Продовжити?
 | 
				
			||||||
settings.hooks_desc=Додайте webhooks, який буде викликатися для <strong>всіх репозиторіїв</strong> якими володіє ця організація.
 | 
					settings.hooks_desc=Додайте webhooks, який буде викликатися для <strong>всіх репозиторіїв</strong> якими володіє ця організація.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
settings.labels_desc=Додайте мітки, які можуть використовуватися для <strong>всіх репозиторіїв</strong> цієї органцізації.
 | 
					settings.labels_desc=Додати мітки, які можуть бути використані для задач для <strong>всіх репозиторіїв</strong> в цій організації.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
members.membership_visibility=Видимість учасника:
 | 
					members.membership_visibility=Видимість учасника:
 | 
				
			||||||
members.public=Показувати
 | 
					members.public=Показувати
 | 
				
			||||||
@@ -2400,7 +2406,7 @@ repos.private=Приватний
 | 
				
			|||||||
repos.watches=Стежать
 | 
					repos.watches=Стежать
 | 
				
			||||||
repos.stars=В обраному
 | 
					repos.stars=В обраному
 | 
				
			||||||
repos.forks=Форки
 | 
					repos.forks=Форки
 | 
				
			||||||
repos.issues=Проблеми
 | 
					repos.issues=Задачі
 | 
				
			||||||
repos.size=Розмір
 | 
					repos.size=Розмір
 | 
				
			||||||
 | 
					
 | 
				
			||||||
defaulthooks=Веб-хуки за замовчуванням
 | 
					defaulthooks=Веб-хуки за замовчуванням
 | 
				
			||||||
@@ -2583,7 +2589,7 @@ config.default_enable_timetracking=Увімкнути відстеження ч
 | 
				
			|||||||
config.default_allow_only_contributors_to_track_time=Враховувати тільки учасників розробки в підрахунку часу
 | 
					config.default_allow_only_contributors_to_track_time=Враховувати тільки учасників розробки в підрахунку часу
 | 
				
			||||||
config.no_reply_address=Прихований домен електронної пошти
 | 
					config.no_reply_address=Прихований домен електронної пошти
 | 
				
			||||||
config.default_visibility_organization=Видимість за замовчуванням для нових організацій
 | 
					config.default_visibility_organization=Видимість за замовчуванням для нових організацій
 | 
				
			||||||
config.default_enable_dependencies=Увімкнути залежності проблем за замовчуванням
 | 
					config.default_enable_dependencies=Увімкнути залежності задачі за замовчуванням
 | 
				
			||||||
 | 
					
 | 
				
			||||||
config.webhook_config=Конфігурація web-хуків
 | 
					config.webhook_config=Конфігурація web-хуків
 | 
				
			||||||
config.queue_length=Довжина черги
 | 
					config.queue_length=Довжина черги
 | 
				
			||||||
@@ -2738,13 +2744,13 @@ notices.delete_success=Сповіщення системи були видале
 | 
				
			|||||||
create_repo=створив(ла) репозиторій <a href="%s">%s</a>
 | 
					create_repo=створив(ла) репозиторій <a href="%s">%s</a>
 | 
				
			||||||
rename_repo=репозиторій перейменовано з <code>%[1]s</code> на <a href="%[2]s">%[3]s</a>
 | 
					rename_repo=репозиторій перейменовано з <code>%[1]s</code> на <a href="%[2]s">%[3]s</a>
 | 
				
			||||||
commit_repo=надіслав зміни (push) до <a href="%[2]s">%[3]s</a> о <a href="%[1]s">%[4]s</a>
 | 
					commit_repo=надіслав зміни (push) до <a href="%[2]s">%[3]s</a> о <a href="%[1]s">%[4]s</a>
 | 
				
			||||||
create_issue=`відкрив проблему <a href="%[1]s">%[3]s#%[2]s</a>`
 | 
					create_issue=`відкрив задачу <a href="%[1]s">%[3]s#%[2]s</a>`
 | 
				
			||||||
close_issue=`закрив проблему <a href="%[1]s">%[3]s#%[2]s</a>`
 | 
					close_issue=`закрив задачу <a href="%[1]s">%[3]s#%[2]s</a>`
 | 
				
			||||||
reopen_issue=`повторно відкрив проблему <a href="%[1]s">%[3]s#%[2]s</a>`
 | 
					reopen_issue=`повторно відкрив задачу <a href="%[1]s">%[3]s#%[2]s</a>`
 | 
				
			||||||
create_pull_request=`створив запит злиття <a href="%[1]s">%[3]s#%[2]s</a>`
 | 
					create_pull_request=`створив запит злиття <a href="%[1]s">%[3]s#%[2]s</a>`
 | 
				
			||||||
close_pull_request=`закрив запит злиття <a href="%[1]s">%[3]s#%[2]s</a>`
 | 
					close_pull_request=`закрив запит злиття <a href="%[1]s">%[3]s#%[2]s</a>`
 | 
				
			||||||
reopen_pull_request=`повторно відкрив запит злиття <a href="%[1]s">%[3]s#%[2]s</a>`
 | 
					reopen_pull_request=`повторно відкрив запит злиття <a href="%[1]s">%[3]s#%[2]s</a>`
 | 
				
			||||||
comment_issue=`прокоментував проблему <a href="%[1]s">%[3]s#%[2]s</a>`
 | 
					comment_issue=`прокоментував задачу <a href="%[1]s">%[3]s#%[2]s</a>`
 | 
				
			||||||
comment_pull=`прокоментував запит злиття <a href="%[1]s">%[3]s#%[2]s</a>`
 | 
					comment_pull=`прокоментував запит злиття <a href="%[1]s">%[3]s#%[2]s</a>`
 | 
				
			||||||
merge_pull_request=`прийняв запит злиття <a href="%[1]s">%[3]s#%[2]s</a>`
 | 
					merge_pull_request=`прийняв запит злиття <a href="%[1]s">%[3]s#%[2]s</a>`
 | 
				
			||||||
transfer_repo=перенесено репозиторій <code>%s</code> у <a href="%s">%s</a>
 | 
					transfer_repo=перенесено репозиторій <code>%s</code> у <a href="%s">%s</a>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -940,14 +940,14 @@ migrate.migrating=正在从 <b>%s</b> 迁移...
 | 
				
			|||||||
migrate.migrating_failed=从 <b>%s</b> 迁移失败。
 | 
					migrate.migrating_failed=从 <b>%s</b> 迁移失败。
 | 
				
			||||||
migrate.migrating_failed.error=错误:%s
 | 
					migrate.migrating_failed.error=错误:%s
 | 
				
			||||||
migrate.migrating_failed_no_addr=迁移失败。
 | 
					migrate.migrating_failed_no_addr=迁移失败。
 | 
				
			||||||
migrate.github.description=从 github.com 或其他 GitHub 实例迁移数据。
 | 
					migrate.github.description=从 github.com 或其他 GitHub 实例迁移数据
 | 
				
			||||||
migrate.git.description=从任意 Git 服务迁移仓库。
 | 
					migrate.git.description=从任意 Git 服务迁移仓库。
 | 
				
			||||||
migrate.gitlab.description=从 gitlab.com 或其他 GitLab 实例迁移数据。
 | 
					migrate.gitlab.description=从 gitlab.com 或其他 GitLab 实例迁移数据
 | 
				
			||||||
migrate.gitea.description=从 gitea.com 或其他 Gitea 实例迁移数据。
 | 
					migrate.gitea.description=从 gitea.com 或其他 Gitea 实例迁移数据
 | 
				
			||||||
migrate.gogs.description=从 notabug.org 或其他 Gogs 实例迁移数据。
 | 
					migrate.gogs.description=从 notabug.org 或其他 Gogs 实例迁移数据。
 | 
				
			||||||
migrate.onedev.description=从 code.onedev.io 或其他 OneDev 实例迁移数据。
 | 
					migrate.onedev.description=从 code.onedev.io 或其他 OneDev 实例迁移数据
 | 
				
			||||||
migrate.codebase.description=从 codebasehq.com 迁移数据。
 | 
					migrate.codebase.description=从 codebasehq.com 迁移数据
 | 
				
			||||||
migrate.gitbucket.description=从 GitBucket 实例迁移数据。
 | 
					migrate.gitbucket.description=从 GitBucket 实例迁移数据
 | 
				
			||||||
migrate.migrating_git=迁移Git数据
 | 
					migrate.migrating_git=迁移Git数据
 | 
				
			||||||
migrate.migrating_topics=迁移主题
 | 
					migrate.migrating_topics=迁移主题
 | 
				
			||||||
migrate.migrating_milestones=迁移里程碑
 | 
					migrate.migrating_milestones=迁移里程碑
 | 
				
			||||||
@@ -1729,7 +1729,7 @@ settings.use_internal_wiki=使用内置百科
 | 
				
			|||||||
settings.use_external_wiki=使用外部百科
 | 
					settings.use_external_wiki=使用外部百科
 | 
				
			||||||
settings.external_wiki_url=外部 Wiki 链接
 | 
					settings.external_wiki_url=外部 Wiki 链接
 | 
				
			||||||
settings.external_wiki_url_error=外部百科链接无效
 | 
					settings.external_wiki_url_error=外部百科链接无效
 | 
				
			||||||
settings.external_wiki_url_desc=当点击工单标签时,访问者将被重定向到外部工单系统的URL。
 | 
					settings.external_wiki_url_desc=当点击百科标签时,访问者将被重定向到外部百科系统的URL。
 | 
				
			||||||
settings.issues_desc=启用工单系统
 | 
					settings.issues_desc=启用工单系统
 | 
				
			||||||
settings.use_internal_issue_tracker=使用内置的轻量级工单管理系统
 | 
					settings.use_internal_issue_tracker=使用内置的轻量级工单管理系统
 | 
				
			||||||
settings.use_external_issue_tracker=使用外部的工单管理系统
 | 
					settings.use_external_issue_tracker=使用外部的工单管理系统
 | 
				
			||||||
@@ -2334,6 +2334,7 @@ first_page=首页
 | 
				
			|||||||
last_page=末页
 | 
					last_page=末页
 | 
				
			||||||
total=总计:%d
 | 
					total=总计:%d
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					dashboard.new_version_hint=Gitea %s 现已可用,您正在运行 %s。查看 <a target="_blank" rel="noreferrer" href="https://blog.gitea.io">博客</a> 了解更多详情。
 | 
				
			||||||
dashboard.statistic=摘要
 | 
					dashboard.statistic=摘要
 | 
				
			||||||
dashboard.operations=维护操作
 | 
					dashboard.operations=维护操作
 | 
				
			||||||
dashboard.system_status=系统状态
 | 
					dashboard.system_status=系统状态
 | 
				
			||||||
@@ -2407,6 +2408,7 @@ dashboard.last_gc_pause=上次 GC 暂停时间
 | 
				
			|||||||
dashboard.gc_times=GC 执行次数
 | 
					dashboard.gc_times=GC 执行次数
 | 
				
			||||||
dashboard.delete_old_actions=从数据库中删除所有旧操作记录
 | 
					dashboard.delete_old_actions=从数据库中删除所有旧操作记录
 | 
				
			||||||
dashboard.delete_old_actions.started=已开始从数据库中删除所有旧操作记录。
 | 
					dashboard.delete_old_actions.started=已开始从数据库中删除所有旧操作记录。
 | 
				
			||||||
 | 
					dashboard.update_checker=更新检查器
 | 
				
			||||||
 | 
					
 | 
				
			||||||
users.user_manage_panel=用户帐户管理
 | 
					users.user_manage_panel=用户帐户管理
 | 
				
			||||||
users.new_account=创建新帐户
 | 
					users.new_account=创建新帐户
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,6 +34,20 @@ twofa=兩步驟驗證
 | 
				
			|||||||
twofa_scratch=兩步驟驗證備用驗證碼
 | 
					twofa_scratch=兩步驟驗證備用驗證碼
 | 
				
			||||||
passcode=驗證碼
 | 
					passcode=驗證碼
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					webauthn_insert_key=插入您的安全金鑰
 | 
				
			||||||
 | 
					webauthn_sign_in=按下您安全金鑰上的按鈕。如果您的安全金鑰沒有按鈕,請重新插入。
 | 
				
			||||||
 | 
					webauthn_press_button=請按下您安全金鑰上的按鈕…
 | 
				
			||||||
 | 
					webauthn_use_twofa=使用來自手機的兩步驟驗證碼
 | 
				
			||||||
 | 
					webauthn_error=無法讀取您的安全金鑰。
 | 
				
			||||||
 | 
					webauthn_unsupported_browser=您的瀏覽器還不支援 WebAuthn。
 | 
				
			||||||
 | 
					webauthn_error_unknown=發生未知的錯誤,請再試一次。
 | 
				
			||||||
 | 
					webauthn_error_insecure=WebAuthn 只支援安全連線。想在 HTTP 上測試,您可以使用「localhost」或「127.0.0.1」
 | 
				
			||||||
 | 
					webauthn_error_unable_to_process=伺服器無法執行您的請求。
 | 
				
			||||||
 | 
					webauthn_error_duplicated=此請求不允許使用這個安全金鑰。請確保該金鑰尚未註冊。
 | 
				
			||||||
 | 
					webauthn_error_empty=您必須命名此金鑰。
 | 
				
			||||||
 | 
					webauthn_error_timeout=在成功讀取金鑰之前已逾時,請重新載入此頁面並重試。
 | 
				
			||||||
 | 
					webauthn_u2f_deprecated=「%s」金鑰使用已廢棄的 U2F 流程進行驗證。您應該重新註冊此金鑰並將先前註冊的移除。
 | 
				
			||||||
 | 
					webauthn_reload=重新載入
 | 
				
			||||||
 | 
					
 | 
				
			||||||
repository=儲存庫
 | 
					repository=儲存庫
 | 
				
			||||||
organization=組織
 | 
					organization=組織
 | 
				
			||||||
@@ -389,8 +403,8 @@ repo.collaborator.added.subject=%s 把您加入到 %s
 | 
				
			|||||||
repo.collaborator.added.text=您已被新增為儲存庫的協作者:
 | 
					repo.collaborator.added.text=您已被新增為儲存庫的協作者:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[modal]
 | 
					[modal]
 | 
				
			||||||
yes=確認操作
 | 
					yes=是
 | 
				
			||||||
no=取消操作
 | 
					no=否
 | 
				
			||||||
modify=更新
 | 
					modify=更新
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[form]
 | 
					[form]
 | 
				
			||||||
@@ -513,10 +527,11 @@ twofa=兩步驟驗證
 | 
				
			|||||||
account_link=已連結帳號
 | 
					account_link=已連結帳號
 | 
				
			||||||
organization=組織
 | 
					organization=組織
 | 
				
			||||||
uid=用戶 ID
 | 
					uid=用戶 ID
 | 
				
			||||||
 | 
					webauthn=安全金鑰
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public_profile=公開的個人資料
 | 
					public_profile=公開的個人資料
 | 
				
			||||||
biography_placeholder=告訴我們一些關於你的事
 | 
					biography_placeholder=告訴我們一些關於你的事
 | 
				
			||||||
profile_desc=您的電子信箱將被用於通知提醒和其他操作。
 | 
					profile_desc=您的電子信箱將被用於通知提醒和其他作業。
 | 
				
			||||||
password_username_disabled=非本地使用者不允許更改他們的帳號。詳細資訊請聯絡您的系統管理員。
 | 
					password_username_disabled=非本地使用者不允許更改他們的帳號。詳細資訊請聯絡您的系統管理員。
 | 
				
			||||||
full_name=全名
 | 
					full_name=全名
 | 
				
			||||||
website=個人網站
 | 
					website=個人網站
 | 
				
			||||||
@@ -530,7 +545,7 @@ update_profile_success=已更新您的個人資料。
 | 
				
			|||||||
change_username=您的帳號已更改。
 | 
					change_username=您的帳號已更改。
 | 
				
			||||||
change_username_prompt=注意:修改帳號也會更改您的帳戶的 URL。
 | 
					change_username_prompt=注意:修改帳號也會更改您的帳戶的 URL。
 | 
				
			||||||
change_username_redirect_prompt=舊的帳號被領用前,會重新導向您的新帳號。
 | 
					change_username_redirect_prompt=舊的帳號被領用前,會重新導向您的新帳號。
 | 
				
			||||||
continue=繼續操作
 | 
					continue=繼續
 | 
				
			||||||
cancel=取消
 | 
					cancel=取消
 | 
				
			||||||
language=語言
 | 
					language=語言
 | 
				
			||||||
ui=佈景主題
 | 
					ui=佈景主題
 | 
				
			||||||
@@ -561,7 +576,7 @@ emails=電子信箱
 | 
				
			|||||||
manage_emails=管理電子信箱
 | 
					manage_emails=管理電子信箱
 | 
				
			||||||
manage_themes=選擇預設佈景主題
 | 
					manage_themes=選擇預設佈景主題
 | 
				
			||||||
manage_openid=管理 OpenID 位址
 | 
					manage_openid=管理 OpenID 位址
 | 
				
			||||||
email_desc=您的主要電子信箱將被用於通知提醒和其他操作。
 | 
					email_desc=您的主要電子信箱將被用於通知提醒和其他作業。
 | 
				
			||||||
theme_desc=這將是您在整個網站上的預設佈景主題。
 | 
					theme_desc=這將是您在整個網站上的預設佈景主題。
 | 
				
			||||||
primary=主要
 | 
					primary=主要
 | 
				
			||||||
activated=已啟用
 | 
					activated=已啟用
 | 
				
			||||||
@@ -706,7 +721,7 @@ oauth2_regenerate_secret_hint=遺失您的密鑰?
 | 
				
			|||||||
oauth2_client_secret_hint=請備份您的祕鑰。祕鑰在您離開這個頁面後將不會再顯示。
 | 
					oauth2_client_secret_hint=請備份您的祕鑰。祕鑰在您離開這個頁面後將不會再顯示。
 | 
				
			||||||
oauth2_application_edit=編輯
 | 
					oauth2_application_edit=編輯
 | 
				
			||||||
oauth2_application_create_description=OAuth2 應用程式讓您的第三方應用程式可以存取此 Gitea 上的帳戶。
 | 
					oauth2_application_create_description=OAuth2 應用程式讓您的第三方應用程式可以存取此 Gitea 上的帳戶。
 | 
				
			||||||
oauth2_application_remove_description=刪除 OAuth2 應用會拒絕它存取此 Gitea 上已授權的帳戶。是否繼續?
 | 
					oauth2_application_remove_description=刪除 OAuth2 應用程式會拒絕它存取此 Gitea 上已授權的帳戶。是否繼續?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
authorized_oauth2_applications=已授權的 OAuth2 應用程式
 | 
					authorized_oauth2_applications=已授權的 OAuth2 應用程式
 | 
				
			||||||
authorized_oauth2_applications_description=您已授權給這些第三方應用程式存取您個人 Gitea 帳戶。請對不再需要的應用程式撤銷存取權。
 | 
					authorized_oauth2_applications_description=您已授權給這些第三方應用程式存取您個人 Gitea 帳戶。請對不再需要的應用程式撤銷存取權。
 | 
				
			||||||
@@ -733,6 +748,11 @@ passcode_invalid=無效的驗證碼,請重試。
 | 
				
			|||||||
twofa_enrolled=您的帳戶已經啟用了兩步驟驗證。請將備用驗證碼 (%s) 保存到一個安全的地方,它只會顯示這麼一次!
 | 
					twofa_enrolled=您的帳戶已經啟用了兩步驟驗證。請將備用驗證碼 (%s) 保存到一個安全的地方,它只會顯示這麼一次!
 | 
				
			||||||
twofa_failed_get_secret=取得密鑰(Secret)失敗。
 | 
					twofa_failed_get_secret=取得密鑰(Secret)失敗。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					webauthn_desc=安全金鑰是包含加密密鑰的硬體設備,它們可以用於兩步驟驗證。安全金鑰必須支援 <a rel="noreferrer" target="_blank" href="https://w3c.github.io/webauthn/#webauthn-authenticator">WebAuthn Authenticator</a> 標準。
 | 
				
			||||||
 | 
					webauthn_register_key=新增安全金鑰
 | 
				
			||||||
 | 
					webauthn_nickname=暱稱
 | 
				
			||||||
 | 
					webauthn_delete_key=移除安全金鑰
 | 
				
			||||||
 | 
					webauthn_delete_key_desc=如果您移除安全金鑰,將不能再使用它登入。是否繼續?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
manage_account_links=管理已連結的帳戶
 | 
					manage_account_links=管理已連結的帳戶
 | 
				
			||||||
manage_account_links_desc=這些外部帳戶已連結到您的 Gitea 帳戶。
 | 
					manage_account_links_desc=這些外部帳戶已連結到您的 Gitea 帳戶。
 | 
				
			||||||
@@ -920,14 +940,14 @@ migrate.migrating=正在從 <b>%s</b> 遷移...
 | 
				
			|||||||
migrate.migrating_failed=從 <b>%s</b> 遷移失敗
 | 
					migrate.migrating_failed=從 <b>%s</b> 遷移失敗
 | 
				
			||||||
migrate.migrating_failed.error=錯誤:%s
 | 
					migrate.migrating_failed.error=錯誤:%s
 | 
				
			||||||
migrate.migrating_failed_no_addr=遷移失敗。
 | 
					migrate.migrating_failed_no_addr=遷移失敗。
 | 
				
			||||||
migrate.github.description=從 github.com 或其他 GitHub 實例遷移資料。
 | 
					migrate.github.description=從 github.com 或其他 GitHub 執行個體遷移資料。
 | 
				
			||||||
migrate.git.description=從任何 Git 服務遷移儲存庫。
 | 
					migrate.git.description=從任何 Git 服務遷移儲存庫。
 | 
				
			||||||
migrate.gitlab.description=從 gitlab.com 或其他 GitLab 實例遷移資料。
 | 
					migrate.gitlab.description=從 gitlab.com 或其他 GitLab 執行個體遷移資料。
 | 
				
			||||||
migrate.gitea.description=從 gitea.com 或其他 Gitea 實例遷移資料。
 | 
					migrate.gitea.description=從 gitea.com 或其他 Gitea 執行個體遷移資料。
 | 
				
			||||||
migrate.gogs.description=從 notabug.org 或其他 Gogs 實例遷移資料。
 | 
					migrate.gogs.description=從 notabug.org 或其他 Gogs 執行個體遷移資料。
 | 
				
			||||||
migrate.onedev.description=從 code.onedev.io 或其他 OneDev 實例遷移資料。
 | 
					migrate.onedev.description=從 code.onedev.io 或其他 OneDev 執行個體遷移資料。
 | 
				
			||||||
migrate.codebase.description=從 codebasehq.com 遷移資料。
 | 
					migrate.codebase.description=從 codebasehq.com 遷移資料。
 | 
				
			||||||
migrate.gitbucket.description=從 GitBucket 實例遷移資料。
 | 
					migrate.gitbucket.description=從 GitBucket 執行個體遷移資料。
 | 
				
			||||||
migrate.migrating_git=正在遷移 Git 資料
 | 
					migrate.migrating_git=正在遷移 Git 資料
 | 
				
			||||||
migrate.migrating_topics=正在遷移主題
 | 
					migrate.migrating_topics=正在遷移主題
 | 
				
			||||||
migrate.migrating_milestones=正在遷移里程碑
 | 
					migrate.migrating_milestones=正在遷移里程碑
 | 
				
			||||||
@@ -956,7 +976,7 @@ clone_this_repo=Clone 此儲存庫
 | 
				
			|||||||
create_new_repo_command=從命令列建立新儲存庫。
 | 
					create_new_repo_command=從命令列建立新儲存庫。
 | 
				
			||||||
push_exist_repo=從命令行推送已經建立的儲存庫
 | 
					push_exist_repo=從命令行推送已經建立的儲存庫
 | 
				
			||||||
empty_message=此儲存庫未包含任何內容。
 | 
					empty_message=此儲存庫未包含任何內容。
 | 
				
			||||||
broken_message=無法讀取此儲存庫底層的 Git 資料。請聯絡此 Gitea 實例的管理員或刪除此儲存庫。
 | 
					broken_message=無法讀取此儲存庫底層的 Git 資料。請聯絡此 Gitea 執行個體的管理員或刪除此儲存庫。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
code=程式碼
 | 
					code=程式碼
 | 
				
			||||||
code.desc=存取原始碼、檔案、提交和分支。
 | 
					code.desc=存取原始碼、檔案、提交和分支。
 | 
				
			||||||
@@ -1084,7 +1104,7 @@ commits.find=搜尋
 | 
				
			|||||||
commits.search_all=所有分支
 | 
					commits.search_all=所有分支
 | 
				
			||||||
commits.author=作者
 | 
					commits.author=作者
 | 
				
			||||||
commits.message=備註
 | 
					commits.message=備註
 | 
				
			||||||
commits.date=提交日期
 | 
					commits.date=日期
 | 
				
			||||||
commits.older=更舊的提交
 | 
					commits.older=更舊的提交
 | 
				
			||||||
commits.newer=更新的提交
 | 
					commits.newer=更新的提交
 | 
				
			||||||
commits.signed_by=簽署人
 | 
					commits.signed_by=簽署人
 | 
				
			||||||
@@ -1132,7 +1152,7 @@ projects.open=開啟
 | 
				
			|||||||
projects.close=關閉
 | 
					projects.close=關閉
 | 
				
			||||||
 | 
					
 | 
				
			||||||
issues.desc=管理錯誤報告、任務和里程碑。
 | 
					issues.desc=管理錯誤報告、任務和里程碑。
 | 
				
			||||||
issues.filter_assignees=篩選成員
 | 
					issues.filter_assignees=篩選負責人
 | 
				
			||||||
issues.filter_milestones=篩選里程碑
 | 
					issues.filter_milestones=篩選里程碑
 | 
				
			||||||
issues.filter_projects=篩選專案
 | 
					issues.filter_projects=篩選專案
 | 
				
			||||||
issues.filter_labels=篩選標籤
 | 
					issues.filter_labels=篩選標籤
 | 
				
			||||||
@@ -1156,10 +1176,10 @@ issues.new.no_milestone=未選擇里程碑
 | 
				
			|||||||
issues.new.clear_milestone=清除已選取里程碑
 | 
					issues.new.clear_milestone=清除已選取里程碑
 | 
				
			||||||
issues.new.open_milestone=開放中的里程碑
 | 
					issues.new.open_milestone=開放中的里程碑
 | 
				
			||||||
issues.new.closed_milestone=已關閉的里程碑
 | 
					issues.new.closed_milestone=已關閉的里程碑
 | 
				
			||||||
issues.new.assignees=成員
 | 
					issues.new.assignees=負責人
 | 
				
			||||||
issues.new.add_assignees_title=指派成員
 | 
					issues.new.add_assignees_title=指派負責人
 | 
				
			||||||
issues.new.clear_assignees=清除成員
 | 
					issues.new.clear_assignees=清除負責人
 | 
				
			||||||
issues.new.no_assignees=沒有成員
 | 
					issues.new.no_assignees=沒有負責人
 | 
				
			||||||
issues.new.no_reviewers=沒有審核者
 | 
					issues.new.no_reviewers=沒有審核者
 | 
				
			||||||
issues.new.add_reviewer_title=請求審核
 | 
					issues.new.add_reviewer_title=請求審核
 | 
				
			||||||
issues.choose.get_started=開始
 | 
					issues.choose.get_started=開始
 | 
				
			||||||
@@ -1205,8 +1225,8 @@ issues.filter_label_exclude=`使用 <code>alt</code> + <code>click/enter</code>
 | 
				
			|||||||
issues.filter_label_no_select=所有標籤
 | 
					issues.filter_label_no_select=所有標籤
 | 
				
			||||||
issues.filter_milestone=里程碑
 | 
					issues.filter_milestone=里程碑
 | 
				
			||||||
issues.filter_milestone_no_select=所有里程碑
 | 
					issues.filter_milestone_no_select=所有里程碑
 | 
				
			||||||
issues.filter_assignee=成員
 | 
					issues.filter_assignee=負責人
 | 
				
			||||||
issues.filter_assginee_no_select=所有成員
 | 
					issues.filter_assginee_no_select=所有負責人
 | 
				
			||||||
issues.filter_type=類型
 | 
					issues.filter_type=類型
 | 
				
			||||||
issues.filter_type.all_issues=所有問題
 | 
					issues.filter_type.all_issues=所有問題
 | 
				
			||||||
issues.filter_type.assigned_to_you=指派給您的
 | 
					issues.filter_type.assigned_to_you=指派給您的
 | 
				
			||||||
@@ -1231,8 +1251,8 @@ issues.action_close=關閉
 | 
				
			|||||||
issues.action_label=標籤
 | 
					issues.action_label=標籤
 | 
				
			||||||
issues.action_milestone=里程碑
 | 
					issues.action_milestone=里程碑
 | 
				
			||||||
issues.action_milestone_no_select=無里程碑
 | 
					issues.action_milestone_no_select=無里程碑
 | 
				
			||||||
issues.action_assignee=成員
 | 
					issues.action_assignee=負責人
 | 
				
			||||||
issues.action_assignee_no_select=沒有成員
 | 
					issues.action_assignee_no_select=沒有負責人
 | 
				
			||||||
issues.opened_by=建立於 %[1]s 由 <a href="%[2]s">%[3]s</a>
 | 
					issues.opened_by=建立於 %[1]s 由 <a href="%[2]s">%[3]s</a>
 | 
				
			||||||
pulls.merged_by=由 <a href="%[2]s">%[3]s</a> 建立,合併於 %[1]s
 | 
					pulls.merged_by=由 <a href="%[2]s">%[3]s</a> 建立,合併於 %[1]s
 | 
				
			||||||
pulls.merged_by_fake=由 %[2]s 建立,合併於 %[1]s
 | 
					pulls.merged_by_fake=由 %[2]s 建立,合併於 %[1]s
 | 
				
			||||||
@@ -1411,7 +1431,7 @@ issues.review.hide_resolved=隱藏已解決
 | 
				
			|||||||
issues.review.resolve_conversation=解決對話
 | 
					issues.review.resolve_conversation=解決對話
 | 
				
			||||||
issues.review.un_resolve_conversation=取消解決對話
 | 
					issues.review.un_resolve_conversation=取消解決對話
 | 
				
			||||||
issues.review.resolved_by=標記了此對話為已解決
 | 
					issues.review.resolved_by=標記了此對話為已解決
 | 
				
			||||||
issues.assignee.error=因為未預期的錯誤,未能成功指派所有成員。
 | 
					issues.assignee.error=因為未預期的錯誤,未能成功加入所有負責人。
 | 
				
			||||||
issues.reference_issue.body=內容
 | 
					issues.reference_issue.body=內容
 | 
				
			||||||
issues.content_history.deleted=刪除
 | 
					issues.content_history.deleted=刪除
 | 
				
			||||||
issues.content_history.edited=編輯
 | 
					issues.content_history.edited=編輯
 | 
				
			||||||
@@ -1552,8 +1572,8 @@ milestones.edit_success=已更新里程碑「%s」。
 | 
				
			|||||||
milestones.deletion=刪除里程碑
 | 
					milestones.deletion=刪除里程碑
 | 
				
			||||||
milestones.deletion_desc=刪除里程碑會從所有相關的問題移除它。是否繼續?
 | 
					milestones.deletion_desc=刪除里程碑會從所有相關的問題移除它。是否繼續?
 | 
				
			||||||
milestones.deletion_success=里程碑已刪除
 | 
					milestones.deletion_success=里程碑已刪除
 | 
				
			||||||
milestones.filter_sort.closest_due_date=到期日由近到遠
 | 
					milestones.filter_sort.closest_due_date=截止日期由近到遠
 | 
				
			||||||
milestones.filter_sort.furthest_due_date=到期日由遠到近
 | 
					milestones.filter_sort.furthest_due_date=截止日期由遠到近
 | 
				
			||||||
milestones.filter_sort.least_complete=完成度由低到高
 | 
					milestones.filter_sort.least_complete=完成度由低到高
 | 
				
			||||||
milestones.filter_sort.most_complete=完成度由高到低
 | 
					milestones.filter_sort.most_complete=完成度由高到低
 | 
				
			||||||
milestones.filter_sort.most_issues=問題由多到少
 | 
					milestones.filter_sort.most_issues=問題由多到少
 | 
				
			||||||
@@ -1867,7 +1887,7 @@ settings.event_repository_desc=建立或刪除儲存庫。
 | 
				
			|||||||
settings.event_header_issue=問題事件
 | 
					settings.event_header_issue=問題事件
 | 
				
			||||||
settings.event_issues=問題
 | 
					settings.event_issues=問題
 | 
				
			||||||
settings.event_issues_desc=建立、編輯、關閉及重新開放問題。
 | 
					settings.event_issues_desc=建立、編輯、關閉及重新開放問題。
 | 
				
			||||||
settings.event_issue_assign=指派
 | 
					settings.event_issue_assign=指派問題
 | 
				
			||||||
settings.event_issue_assign_desc=指派或取消指派問題。
 | 
					settings.event_issue_assign_desc=指派或取消指派問題。
 | 
				
			||||||
settings.event_issue_label=標籤
 | 
					settings.event_issue_label=標籤
 | 
				
			||||||
settings.event_issue_label_desc=更新或清除問題標籤。
 | 
					settings.event_issue_label_desc=更新或清除問題標籤。
 | 
				
			||||||
@@ -1878,7 +1898,7 @@ settings.event_issue_comment_desc=已經建立、編輯或刪除的問題留言
 | 
				
			|||||||
settings.event_header_pull_request=合併請求事件
 | 
					settings.event_header_pull_request=合併請求事件
 | 
				
			||||||
settings.event_pull_request=合併請求
 | 
					settings.event_pull_request=合併請求
 | 
				
			||||||
settings.event_pull_request_desc=建立、編輯、關閉及重新開放合併請求。
 | 
					settings.event_pull_request_desc=建立、編輯、關閉及重新開放合併請求。
 | 
				
			||||||
settings.event_pull_request_assign=合併請求指派
 | 
					settings.event_pull_request_assign=指派合併請求
 | 
				
			||||||
settings.event_pull_request_assign_desc=指派或取消指派合併請求。
 | 
					settings.event_pull_request_assign_desc=指派或取消指派合併請求。
 | 
				
			||||||
settings.event_pull_request_label=合併請求標籤
 | 
					settings.event_pull_request_label=合併請求標籤
 | 
				
			||||||
settings.event_pull_request_label_desc=更新或清除合併請求標籤。
 | 
					settings.event_pull_request_label_desc=更新或清除合併請求標籤。
 | 
				
			||||||
@@ -2314,11 +2334,12 @@ first_page=首頁
 | 
				
			|||||||
last_page=末頁
 | 
					last_page=末頁
 | 
				
			||||||
total=總計:%d
 | 
					total=總計:%d
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					dashboard.new_version_hint=現已推出 Gitea %s,您正在執行 %s。查看<a target="_blank" rel="noreferrer" href="https://blog.gitea.io">部落格</a>以獲得更多資訊。
 | 
				
			||||||
dashboard.statistic=摘要
 | 
					dashboard.statistic=摘要
 | 
				
			||||||
dashboard.operations=維護操作
 | 
					dashboard.operations=維護作業
 | 
				
			||||||
dashboard.system_status=系統狀態
 | 
					dashboard.system_status=系統狀態
 | 
				
			||||||
dashboard.statistic_info=Gitea 資料庫統計:<b>%d</b> 位使用者,<b>%d</b> 個組織,<b>%d</b> 個公鑰,<b>%d</b> 個儲存庫,<b>%d</b> 個儲存庫關注,<b>%d</b> 個星號,<b>%d</b> 次行為,<b>%d</b> 條權限記錄,<b>%d</b> 個問題,<b>%d</b> 則留言,<b>%d</b> 個社群帳戶,<b>%d</b> 個用戶關注,<b>%d</b> 個鏡像,<b>%d</b> 個版本發佈,<b>%d</b> 個認證來源,<b>%d</b> 個 Webhook ,<b>%d</b> 個里程碑,<b>%d</b> 個標籤,<b>%d</b> 個 Hook 任務,<b>%d</b> 個團隊,<b>%d</b> 個更新任務,<b>%d</b> 個附件。
 | 
					dashboard.statistic_info=Gitea 資料庫統計:<b>%d</b> 位使用者,<b>%d</b> 個組織,<b>%d</b> 個公鑰,<b>%d</b> 個儲存庫,<b>%d</b> 個儲存庫關注,<b>%d</b> 個星號,<b>%d</b> 次行為,<b>%d</b> 條權限記錄,<b>%d</b> 個問題,<b>%d</b> 則留言,<b>%d</b> 個社群帳戶,<b>%d</b> 個用戶關注,<b>%d</b> 個鏡像,<b>%d</b> 個版本發佈,<b>%d</b> 個認證來源,<b>%d</b> 個 Webhook ,<b>%d</b> 個里程碑,<b>%d</b> 個標籤,<b>%d</b> 個 Hook 任務,<b>%d</b> 個團隊,<b>%d</b> 個更新任務,<b>%d</b> 個附件。
 | 
				
			||||||
dashboard.operation_name=操作名稱
 | 
					dashboard.operation_name=作業名稱
 | 
				
			||||||
dashboard.operation_switch=開關
 | 
					dashboard.operation_switch=開關
 | 
				
			||||||
dashboard.operation_run=執行
 | 
					dashboard.operation_run=執行
 | 
				
			||||||
dashboard.clean_unbind_oauth=清理未綁定的 OAuth 連結
 | 
					dashboard.clean_unbind_oauth=清理未綁定的 OAuth 連結
 | 
				
			||||||
@@ -2387,6 +2408,7 @@ dashboard.last_gc_pause=上次 GC 暫停時間
 | 
				
			|||||||
dashboard.gc_times=GC 執行次數
 | 
					dashboard.gc_times=GC 執行次數
 | 
				
			||||||
dashboard.delete_old_actions=從資料庫刪除所有舊行為
 | 
					dashboard.delete_old_actions=從資料庫刪除所有舊行為
 | 
				
			||||||
dashboard.delete_old_actions.started=從資料庫刪除所有舊行為的任務已啟動。
 | 
					dashboard.delete_old_actions.started=從資料庫刪除所有舊行為的任務已啟動。
 | 
				
			||||||
 | 
					dashboard.update_checker=更新檢查器
 | 
				
			||||||
 | 
					
 | 
				
			||||||
users.user_manage_panel=使用者帳戶管理
 | 
					users.user_manage_panel=使用者帳戶管理
 | 
				
			||||||
users.new_account=建立使用者帳戶
 | 
					users.new_account=建立使用者帳戶
 | 
				
			||||||
@@ -2567,7 +2589,7 @@ auths.tips.oauth2.general=OAuth2 認證
 | 
				
			|||||||
auths.tips.oauth2.general.tip=註冊新的 OAuth2 認證時,callback/redirect 網址應為:<host>/user/oauth2/<Authentication Name>/callback
 | 
					auths.tips.oauth2.general.tip=註冊新的 OAuth2 認證時,callback/redirect 網址應為:<host>/user/oauth2/<Authentication Name>/callback
 | 
				
			||||||
auths.tip.oauth2_provider=OAuth2 提供者
 | 
					auths.tip.oauth2_provider=OAuth2 提供者
 | 
				
			||||||
auths.tip.bitbucket=註冊新的 OAuth 客戶端並加入權限「Account - Read」。網址:https://bitbucket.org/account/user/<your username>/oauth-consumers/new
 | 
					auths.tip.bitbucket=註冊新的 OAuth 客戶端並加入權限「Account - Read」。網址:https://bitbucket.org/account/user/<your username>/oauth-consumers/new
 | 
				
			||||||
auths.tip.nextcloud=在您的 Nextcloud 使用「設定 -> 安全性 -> OAuth 2.0 客戶端」註冊新的 OAuth 客戶端
 | 
					auths.tip.nextcloud=在您的執行個體中,於選單「設定 -> 安全性 -> OAuth 2.0 客戶端」註冊新的 OAuth 客戶端
 | 
				
			||||||
auths.tip.dropbox=建立一個新的 App。網址:https://www.dropbox.com/developers/apps
 | 
					auths.tip.dropbox=建立一個新的 App。網址:https://www.dropbox.com/developers/apps
 | 
				
			||||||
auths.tip.facebook=註冊一個新的應用程式並新增產品「Facebook 登入」。網址:https://developers.facebook.com/apps
 | 
					auths.tip.facebook=註冊一個新的應用程式並新增產品「Facebook 登入」。網址:https://developers.facebook.com/apps
 | 
				
			||||||
auths.tip.github=註冊新的 OAuth 應用程式。網址:https://github.com/settings/applications/new
 | 
					auths.tip.github=註冊新的 OAuth 應用程式。網址:https://github.com/settings/applications/new
 | 
				
			||||||
@@ -2874,7 +2896,7 @@ notifications=通知
 | 
				
			|||||||
unread=未讀
 | 
					unread=未讀
 | 
				
			||||||
read=已讀
 | 
					read=已讀
 | 
				
			||||||
no_unread=沒有未讀通知
 | 
					no_unread=沒有未讀通知
 | 
				
			||||||
no_read=沒有通知
 | 
					no_read=沒有已讀通知
 | 
				
			||||||
pin=固定通知
 | 
					pin=固定通知
 | 
				
			||||||
mark_as_read=標記為已讀
 | 
					mark_as_read=標記為已讀
 | 
				
			||||||
mark_as_unread=標記為未讀
 | 
					mark_as_unread=標記為未讀
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										748
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										748
									
								
								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",
 | 
					    "@claviska/jquery-minicolors": "2.3.6",
 | 
				
			||||||
    "@primer/octicons": "16.2.0",
 | 
					    "@primer/octicons": "16.2.0",
 | 
				
			||||||
    "add-asset-webpack-plugin": "2.0.1",
 | 
					    "add-asset-webpack-plugin": "2.0.1",
 | 
				
			||||||
    "codemirror": "5.65.0",
 | 
					    "codemirror": "5.65.1",
 | 
				
			||||||
    "css-loader": "6.5.1",
 | 
					    "css-loader": "6.5.1",
 | 
				
			||||||
    "dropzone": "6.0.0-beta.2",
 | 
					    "dropzone": "6.0.0-beta.2",
 | 
				
			||||||
    "easymde": "2.16.1",
 | 
					    "easymde": "2.16.1",
 | 
				
			||||||
@@ -23,13 +23,13 @@
 | 
				
			|||||||
    "less": "4.1.2",
 | 
					    "less": "4.1.2",
 | 
				
			||||||
    "less-loader": "10.2.0",
 | 
					    "less-loader": "10.2.0",
 | 
				
			||||||
    "license-checker-webpack-plugin": "0.2.1",
 | 
					    "license-checker-webpack-plugin": "0.2.1",
 | 
				
			||||||
    "mermaid": "8.13.9",
 | 
					    "mermaid": "8.14.0",
 | 
				
			||||||
    "mini-css-extract-plugin": "2.5.2",
 | 
					    "mini-css-extract-plugin": "2.5.2",
 | 
				
			||||||
    "monaco-editor": "0.31.1",
 | 
					    "monaco-editor": "0.31.1",
 | 
				
			||||||
    "monaco-editor-webpack-plugin": "7.0.1",
 | 
					    "monaco-editor-webpack-plugin": "7.0.1",
 | 
				
			||||||
    "pretty-ms": "7.0.1",
 | 
					    "pretty-ms": "7.0.1",
 | 
				
			||||||
    "sortablejs": "1.14.0",
 | 
					    "sortablejs": "1.14.0",
 | 
				
			||||||
    "swagger-ui-dist": "4.1.3",
 | 
					    "swagger-ui-dist": "4.2.1",
 | 
				
			||||||
    "tributejs": "5.1.3",
 | 
					    "tributejs": "5.1.3",
 | 
				
			||||||
    "uint8-to-base64": "0.2.0",
 | 
					    "uint8-to-base64": "0.2.0",
 | 
				
			||||||
    "vue": "2.6.14",
 | 
					    "vue": "2.6.14",
 | 
				
			||||||
@@ -37,8 +37,8 @@
 | 
				
			|||||||
    "vue-calendar-heatmap": "0.8.4",
 | 
					    "vue-calendar-heatmap": "0.8.4",
 | 
				
			||||||
    "vue-loader": "15.9.8",
 | 
					    "vue-loader": "15.9.8",
 | 
				
			||||||
    "vue-template-compiler": "2.6.14",
 | 
					    "vue-template-compiler": "2.6.14",
 | 
				
			||||||
    "webpack": "5.66.0",
 | 
					    "webpack": "5.67.0",
 | 
				
			||||||
    "webpack-cli": "4.9.1",
 | 
					    "webpack-cli": "4.9.2",
 | 
				
			||||||
    "workbox-routing": "6.4.2",
 | 
					    "workbox-routing": "6.4.2",
 | 
				
			||||||
    "workbox-strategies": "6.4.2",
 | 
					    "workbox-strategies": "6.4.2",
 | 
				
			||||||
    "worker-loader": "3.0.8",
 | 
					    "worker-loader": "3.0.8",
 | 
				
			||||||
@@ -55,7 +55,7 @@
 | 
				
			|||||||
    "jest-extended": "1.2.0",
 | 
					    "jest-extended": "1.2.0",
 | 
				
			||||||
    "jest-raw-loader": "1.0.1",
 | 
					    "jest-raw-loader": "1.0.1",
 | 
				
			||||||
    "postcss-less": "6.0.0",
 | 
					    "postcss-less": "6.0.0",
 | 
				
			||||||
    "stylelint": "14.2.0",
 | 
					    "stylelint": "14.3.0",
 | 
				
			||||||
    "stylelint-config-standard": "24.0.0",
 | 
					    "stylelint-config-standard": "24.0.0",
 | 
				
			||||||
    "svgo": "2.8.0",
 | 
					    "svgo": "2.8.0",
 | 
				
			||||||
    "updates": "13.0.0"
 | 
					    "updates": "13.0.0"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -43,9 +43,13 @@ func ListUnadoptedRepositories(ctx *context.APIContext) {
 | 
				
			|||||||
	//     "$ref": "#/responses/forbidden"
 | 
						//     "$ref": "#/responses/forbidden"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	listOptions := utils.GetListOptions(ctx)
 | 
						listOptions := utils.GetListOptions(ctx)
 | 
				
			||||||
 | 
						if listOptions.Page == 0 {
 | 
				
			||||||
 | 
							listOptions.Page = 1
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	repoNames, count, err := repo_service.ListUnadoptedRepositories(ctx.FormString("query"), &listOptions)
 | 
						repoNames, count, err := repo_service.ListUnadoptedRepositories(ctx.FormString("query"), &listOptions)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		ctx.InternalServerError(err)
 | 
							ctx.InternalServerError(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx.SetTotalCountHeader(int64(count))
 | 
						ctx.SetTotalCountHeader(int64(count))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -47,7 +47,7 @@ func ListTeams(ctx *context.APIContext) {
 | 
				
			|||||||
	//   "200":
 | 
						//   "200":
 | 
				
			||||||
	//     "$ref": "#/responses/TeamList"
 | 
						//     "$ref": "#/responses/TeamList"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	teams, count, err := models.SearchTeam(&models.SearchTeamOptions{
 | 
						teams, count, err := models.SearchOrgTeams(&models.SearchOrgTeamOptions{
 | 
				
			||||||
		ListOptions: utils.GetListOptions(ctx),
 | 
							ListOptions: utils.GetListOptions(ctx),
 | 
				
			||||||
		OrgID:       ctx.Org.Organization.ID,
 | 
							OrgID:       ctx.Org.Organization.ID,
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
@@ -90,7 +90,7 @@ func ListUserTeams(ctx *context.APIContext) {
 | 
				
			|||||||
	//   "200":
 | 
						//   "200":
 | 
				
			||||||
	//     "$ref": "#/responses/TeamList"
 | 
						//     "$ref": "#/responses/TeamList"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	teams, count, err := models.SearchTeam(&models.SearchTeamOptions{
 | 
						teams, count, err := models.GetUserTeams(&models.GetUserTeamOptions{
 | 
				
			||||||
		ListOptions: utils.GetListOptions(ctx),
 | 
							ListOptions: utils.GetListOptions(ctx),
 | 
				
			||||||
		UserID:      ctx.User.ID,
 | 
							UserID:      ctx.User.ID,
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
@@ -533,7 +533,7 @@ func GetTeamRepos(ctx *context.APIContext) {
 | 
				
			|||||||
	//     "$ref": "#/responses/RepositoryList"
 | 
						//     "$ref": "#/responses/RepositoryList"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	team := ctx.Org.Team
 | 
						team := ctx.Org.Team
 | 
				
			||||||
	if err := team.GetRepositories(&models.SearchTeamOptions{
 | 
						if err := team.GetRepositories(&models.SearchOrgTeamOptions{
 | 
				
			||||||
		ListOptions: utils.GetListOptions(ctx),
 | 
							ListOptions: utils.GetListOptions(ctx),
 | 
				
			||||||
	}); err != nil {
 | 
						}); err != nil {
 | 
				
			||||||
		ctx.Error(http.StatusInternalServerError, "GetTeamRepos", err)
 | 
							ctx.Error(http.StatusInternalServerError, "GetTeamRepos", err)
 | 
				
			||||||
@@ -707,15 +707,14 @@ func SearchTeam(ctx *context.APIContext) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	listOptions := utils.GetListOptions(ctx)
 | 
						listOptions := utils.GetListOptions(ctx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	opts := &models.SearchTeamOptions{
 | 
						opts := &models.SearchOrgTeamOptions{
 | 
				
			||||||
		UserID:      ctx.User.ID,
 | 
					 | 
				
			||||||
		Keyword:     ctx.FormTrim("q"),
 | 
							Keyword:     ctx.FormTrim("q"),
 | 
				
			||||||
		OrgID:       ctx.Org.Organization.ID,
 | 
							OrgID:       ctx.Org.Organization.ID,
 | 
				
			||||||
		IncludeDesc: ctx.FormString("include_desc") == "" || ctx.FormBool("include_desc"),
 | 
							IncludeDesc: ctx.FormString("include_desc") == "" || ctx.FormBool("include_desc"),
 | 
				
			||||||
		ListOptions: listOptions,
 | 
							ListOptions: listOptions,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	teams, maxResults, err := models.SearchTeam(opts)
 | 
						teams, maxResults, err := models.SearchOrgTeams(opts)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Error("SearchTeam failed: %v", err)
 | 
							log.Error("SearchTeam failed: %v", err)
 | 
				
			||||||
		ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
 | 
							ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -203,7 +203,7 @@ func Migrate(ctx *context.APIContext) {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}()
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if _, err = migrations.MigrateRepository(graceful.GetManager().HammerContext(), ctx.User, repoOwner.Name, opts, nil); err != nil {
 | 
						if repo, err = migrations.MigrateRepository(graceful.GetManager().HammerContext(), ctx.User, repoOwner.Name, opts, nil); err != nil {
 | 
				
			||||||
		handleMigrateError(ctx, repoOwner, remoteAddr, err)
 | 
							handleMigrateError(ctx, repoOwner, remoteAddr, err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -192,6 +192,9 @@ func parseOAuth2Config(form forms.AuthenticationForm) *oauth2.Source {
 | 
				
			|||||||
		RequiredClaimName:             form.Oauth2RequiredClaimName,
 | 
							RequiredClaimName:             form.Oauth2RequiredClaimName,
 | 
				
			||||||
		RequiredClaimValue:            form.Oauth2RequiredClaimValue,
 | 
							RequiredClaimValue:            form.Oauth2RequiredClaimValue,
 | 
				
			||||||
		SkipLocalTwoFA:                form.SkipLocalTwoFA,
 | 
							SkipLocalTwoFA:                form.SkipLocalTwoFA,
 | 
				
			||||||
 | 
							GroupClaimName:                form.Oauth2GroupClaimName,
 | 
				
			||||||
 | 
							RestrictedGroup:               form.Oauth2RestrictedGroup,
 | 
				
			||||||
 | 
							AdminGroup:                    form.Oauth2AdminGroup,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user