mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-03 08:02:36 +09:00 
			
		
		
		
	Compare commits
	
		
			51 Commits
		
	
	
		
			v1.22.0-de
			...
			v1.15.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					f7e7477c45 | ||
| 
						 | 
					0840a508b4 | ||
| 
						 | 
					5ceff8fda2 | ||
| 
						 | 
					778a0bf758 | ||
| 
						 | 
					f19ccd8f6a | ||
| 
						 | 
					b6e4688874 | ||
| 
						 | 
					25437672bf | ||
| 
						 | 
					0dc808212b | ||
| 
						 | 
					b6508b872b | ||
| 
						 | 
					d89029ebac | ||
| 
						 | 
					62315ea731 | ||
| 
						 | 
					86861ee135 | ||
| 
						 | 
					d2d99a25b7 | ||
| 
						 | 
					e483ec8b0d | ||
| 
						 | 
					46d62ad896 | ||
| 
						 | 
					428d58f8da | ||
| 
						 | 
					1a2256bf44 | ||
| 
						 | 
					20601f8463 | ||
| 
						 | 
					619e6d6400 | ||
| 
						 | 
					f438b6f33b | ||
| 
						 | 
					c47065cc29 | ||
| 
						 | 
					719e2f26d5 | ||
| 
						 | 
					40687a2160 | ||
| 
						 | 
					f9120092c1 | ||
| 
						 | 
					a17edf446f | ||
| 
						 | 
					ff8fadd2be | ||
| 
						 | 
					5fe7c0ed7b | ||
| 
						 | 
					763e4196ba | ||
| 
						 | 
					903bdefb58 | ||
| 
						 | 
					840d240a61 | ||
| 
						 | 
					7365b4e757 | ||
| 
						 | 
					e10cd3da1e | ||
| 
						 | 
					693275455e | ||
| 
						 | 
					91527434d0 | ||
| 
						 | 
					89f680aa04 | ||
| 
						 | 
					67942ac1a9 | ||
| 
						 | 
					0b06b2019f | ||
| 
						 | 
					057205a4b7 | ||
| 
						 | 
					1b6c0c6bdc | ||
| 
						 | 
					c4f3f5bdf2 | ||
| 
						 | 
					1f5011dff7 | ||
| 
						 | 
					cf9aeca508 | ||
| 
						 | 
					09a4364b21 | ||
| 
						 | 
					0c3467ffb7 | ||
| 
						 | 
					d268c9d6e1 | ||
| 
						 | 
					7f6019e492 | ||
| 
						 | 
					0f11c5f592 | ||
| 
						 | 
					bae0e1d773 | ||
| 
						 | 
					0877d497f3 | ||
| 
						 | 
					e5fde7ef00 | ||
| 
						 | 
					6243638c11 | 
							
								
								
									
										18
									
								
								.drone.yml
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								.drone.yml
									
									
									
									
									
								
							@@ -15,12 +15,12 @@ trigger:
 | 
			
		||||
steps:
 | 
			
		||||
  - name: deps-frontend
 | 
			
		||||
    pull: always
 | 
			
		||||
    image: node:16
 | 
			
		||||
    image: node:14
 | 
			
		||||
    commands:
 | 
			
		||||
      - make node_modules
 | 
			
		||||
 | 
			
		||||
  - name: lint-frontend
 | 
			
		||||
    image: node:16
 | 
			
		||||
    image: node:14
 | 
			
		||||
    commands:
 | 
			
		||||
      - make lint-frontend
 | 
			
		||||
    depends_on: [deps-frontend]
 | 
			
		||||
@@ -58,7 +58,7 @@ steps:
 | 
			
		||||
      TAGS: bindata gogit sqlite sqlite_unlock_notify
 | 
			
		||||
 | 
			
		||||
  - name: checks-frontend
 | 
			
		||||
    image: node:16
 | 
			
		||||
    image: node:14
 | 
			
		||||
    commands:
 | 
			
		||||
      - make checks-frontend
 | 
			
		||||
    depends_on: [deps-frontend]
 | 
			
		||||
@@ -71,20 +71,20 @@ steps:
 | 
			
		||||
    depends_on: [lint-backend]
 | 
			
		||||
 | 
			
		||||
  - name: test-frontend
 | 
			
		||||
    image: node:16
 | 
			
		||||
    image: node:14
 | 
			
		||||
    commands:
 | 
			
		||||
      - make test-frontend
 | 
			
		||||
    depends_on: [lint-frontend]
 | 
			
		||||
 | 
			
		||||
  - name: build-frontend
 | 
			
		||||
    image: node:16
 | 
			
		||||
    image: node:14
 | 
			
		||||
    commands:
 | 
			
		||||
      - make frontend
 | 
			
		||||
    depends_on: [test-frontend]
 | 
			
		||||
 | 
			
		||||
  - name: build-backend-no-gcc
 | 
			
		||||
    pull: always
 | 
			
		||||
    image: golang:1.14 # 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
 | 
			
		||||
    environment:
 | 
			
		||||
      GO111MODULE: on
 | 
			
		||||
      GOPROXY: off
 | 
			
		||||
@@ -404,7 +404,7 @@ steps:
 | 
			
		||||
 | 
			
		||||
  - name: update
 | 
			
		||||
    pull: default
 | 
			
		||||
    image: alpine:3.14
 | 
			
		||||
    image: alpine:3.13
 | 
			
		||||
    commands:
 | 
			
		||||
      - ./build/update-locales.sh
 | 
			
		||||
 | 
			
		||||
@@ -503,7 +503,7 @@ steps:
 | 
			
		||||
    pull: always
 | 
			
		||||
    image: techknowlogick/xgo:go-1.16.x
 | 
			
		||||
    commands:
 | 
			
		||||
      - curl -sL https://deb.nodesource.com/setup_16.x | bash - && apt-get install -y nodejs
 | 
			
		||||
      - curl -sL https://deb.nodesource.com/setup_14.x | bash - && apt-get install -y nodejs
 | 
			
		||||
      - export PATH=$PATH:$GOPATH/bin
 | 
			
		||||
      - make release
 | 
			
		||||
    environment:
 | 
			
		||||
@@ -599,7 +599,7 @@ steps:
 | 
			
		||||
    pull: always
 | 
			
		||||
    image: techknowlogick/xgo:go-1.16.x
 | 
			
		||||
    commands:
 | 
			
		||||
      - curl -sL https://deb.nodesource.com/setup_16.x | bash - && apt-get install -y nodejs
 | 
			
		||||
      - curl -sL https://deb.nodesource.com/setup_14.x | bash - && apt-get install -y nodejs
 | 
			
		||||
      - export PATH=$PATH:$GOPATH/bin
 | 
			
		||||
      - make release
 | 
			
		||||
    environment:
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@ root: true
 | 
			
		||||
reportUnusedDisableDirectives: true
 | 
			
		||||
 | 
			
		||||
ignorePatterns:
 | 
			
		||||
  - /web_src/js/vendor
 | 
			
		||||
  - /templates/base/head.tmpl
 | 
			
		||||
  - /templates/repo/activity.tmpl
 | 
			
		||||
  - /templates/repo/view_file.tmpl
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										70
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										70
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -4,7 +4,7 @@ This changelog goes through all the changes that have been made in each release
 | 
			
		||||
without substantial changes to our git log; to see the highlights of what has
 | 
			
		||||
been added to each release, please refer to the [blog](https://blog.gitea.io).
 | 
			
		||||
 | 
			
		||||
## [1.15.0-rc1](https://github.com/go-gitea/gitea/releases/tag/v1.15.0-rc1) - 2021-07-15
 | 
			
		||||
## [1.15.0](https://github.com/go-gitea/gitea/releases/tag/v1.15.0) - 2021-08-21
 | 
			
		||||
 | 
			
		||||
* BREAKING
 | 
			
		||||
  * Make app.ini permissions more restrictive (#16266)
 | 
			
		||||
@@ -19,9 +19,15 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
 | 
			
		||||
  * Move (custom) assets into subpath `/assets` (#15219)
 | 
			
		||||
  * Use level config in log section when sub log section not set level (#15176)
 | 
			
		||||
  * Links in markdown should be absolute to the repository not the server (#15088)
 | 
			
		||||
  * Upgrade to the latest version of golang-jwt (#16590) (#16606)
 | 
			
		||||
  * Set minimum supported version of go to 1.16 (#16710)
 | 
			
		||||
* SECURITY
 | 
			
		||||
  * Encrypt LDAP bind password in db with SECRET_KEY (#15547)
 | 
			
		||||
  * Remove random password in Dockerfiles (#15362)
 | 
			
		||||
  * Upgrade to the latest version of golang-jwt and increase minimum go to 1.15 (#16590) (#16606)
 | 
			
		||||
  * Correctly create of git-daemon-export-ok files (#16508) (#16514)
 | 
			
		||||
  * Don't show private user's repo in explore view (#16550) (#16554)
 | 
			
		||||
  * Update node tar dependency to 6.1.6 (#16622) (#16623)
 | 
			
		||||
* FEATURES
 | 
			
		||||
  * Update Go-Git to take advantage of LargeObjectThreshold (#16316)
 | 
			
		||||
  * Support custom mime type mapping for text files (#16304)
 | 
			
		||||
@@ -42,7 +48,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
 | 
			
		||||
  * Add LFS Migration and Mirror (#14726)
 | 
			
		||||
  * Improve notifications for WIP draft PR's (#14663)
 | 
			
		||||
  * Disable Stars config option (#14653)
 | 
			
		||||
  * Add option to provide signature for a token to verify key ownership (#14054)
 | 
			
		||||
  * GPG Key Ownership verification with Signed Token (#14054)
 | 
			
		||||
  * OAuth2 auto-register (#5123)
 | 
			
		||||
* API
 | 
			
		||||
  * Return updated repository when changing repository using API (#16420)
 | 
			
		||||
@@ -62,6 +68,8 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
 | 
			
		||||
  * Add Active and ProhibitLogin to API (#15689)
 | 
			
		||||
  * Add Location, Website and Description to API (#15675)
 | 
			
		||||
  * Expose resolver via API (#15167)
 | 
			
		||||
  * Swagger AccessToken fixes (#16574) (#16597)
 | 
			
		||||
  * Set AllowedHeaders on API CORS handler (#16524) (#16618)
 | 
			
		||||
* ENHANCEMENTS
 | 
			
		||||
  * Support HTTP/2 in Let's Encrypt (#16371)
 | 
			
		||||
  * Introduce NotifySubjectType (#16320)
 | 
			
		||||
@@ -187,6 +195,41 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
 | 
			
		||||
  * Add NeedPostProcess for Parser interface to improve performance of csv parser and some external parser (#15153)
 | 
			
		||||
  * Add code block highlight to orgmode back (#14222)
 | 
			
		||||
  * Remove User.GetOrganizations() (#14032)
 | 
			
		||||
  * Restore Accessibility for Dropdown (#16576) (#16617)
 | 
			
		||||
  * Pass down SignedUserName down to AccessLogger context (#16605) (#16616)
 | 
			
		||||
  * Fix table alignment in markdown (#16596) (#16602)
 | 
			
		||||
  * Fix 500 on first wiki page (#16586) (#16598)
 | 
			
		||||
  * Lock goth/gothic and Re-attempt OAuth2 registration on login if registration failed at startup (#16564) (#16570)
 | 
			
		||||
  * Upgrade levelqueue to v0.4.0 (#16560) (#16561)
 | 
			
		||||
  * Handle too long PR titles correctly (#16517) (#16549)
 | 
			
		||||
  * Fix data race in bleve indexer (#16474) (#16509)
 | 
			
		||||
  * Restore CORS on git smart http protocol (#16496) (#16506)
 | 
			
		||||
  * Fix race in log (#16490) (#16505)
 | 
			
		||||
  * Fix prepareWikiFileName to respect existing unescaped files (#16487) (#16498)
 | 
			
		||||
  * Make cancel from CatFileBatch and CatFileBatchCheck wait for the command to end (#16479) (#16480)
 | 
			
		||||
  * Update notification table with only latest data (#16445) (#16469)
 | 
			
		||||
  * Fix crash following ldap authentication update (#16447) (#16448)
 | 
			
		||||
  * Fix direct creation of external users on admin page (partial #16612) (#16613)
 | 
			
		||||
  * Prevent 500 on draft releases without tag (#16634) (#16636)
 | 
			
		||||
  * Restore creation of git-daemon-export-ok files (#16508) (#16514)
 | 
			
		||||
  * Fix data race in bleve indexer (#16474) (#16509)
 | 
			
		||||
  * Restore CORS on git smart http protocol (#16496) (#16506)
 | 
			
		||||
  * Fix race in log (#16490) (#16505)
 | 
			
		||||
  * Fix prepareWikiFileName to respect existing unescaped files (#16487) (#16498)
 | 
			
		||||
  * Make cancel from CatFileBatch and CatFileBatchCheck wait for the command to end (#16479) (#16480)
 | 
			
		||||
  * Update notification table with only latest data (#16445) (#16469)
 | 
			
		||||
  * Fix crash following ldap authentication update (#16447) (#16448)
 | 
			
		||||
  * Restore compatibility with SQLServer 2008 R2 in migrations (#16638)
 | 
			
		||||
  * Fix direct creation of external users on admin page (#16613)
 | 
			
		||||
  * Fix go-git implementation of GetNote when passed a non-existent commit (#16658) (#16659)
 | 
			
		||||
  * Fix NPE in fuzzer (#16680) (#16682)
 | 
			
		||||
  * Set issue_index when finishing migration (#16685) (#16687)
 | 
			
		||||
  * Skip patch download when no patch file exists (#16356) (#16681)
 | 
			
		||||
  * Ensure empty lines are copiable and final new line too (#16678) (#16692)
 | 
			
		||||
  * Fix wrong user in OpenID response (#16736) (#16741)
 | 
			
		||||
  * Do not use thin scrollbars on Firefox (#16738) (#16745)
 | 
			
		||||
  * Recreate Tables should Recreate indexes on MySQL (#16718) (#16739)
 | 
			
		||||
  * Keep attachments on tasklist update (#16750) (#16757)
 | 
			
		||||
* TESTING
 | 
			
		||||
  * Bump `postgres` and `mysql` versions (#15710)
 | 
			
		||||
  * Add tests for clone from wiki (#15513)
 | 
			
		||||
@@ -197,7 +240,6 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
 | 
			
		||||
  * Fix mirror_lfs source string in en-US locale (#15369)
 | 
			
		||||
* BUILD
 | 
			
		||||
  * Upgrade xorm to v1.1.1 (#16339)
 | 
			
		||||
  * Alpine 3.14 released (#16170)
 | 
			
		||||
  * Disable legal comments in esbuild (#15929)
 | 
			
		||||
  * Switch to Node 16 to build fronted  (#15804)
 | 
			
		||||
  * Use esbuild to minify CSS (#15756)
 | 
			
		||||
@@ -216,6 +258,28 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
 | 
			
		||||
  * Remove utf8 option from installation page (#16126)
 | 
			
		||||
  * Use Wants= over Requires= in systemd file (#15897)
 | 
			
		||||
 | 
			
		||||
## [1.14.6](https://github.com/go-gitea/gitea/releases/tag/v1.14.6) - 2021-08-04
 | 
			
		||||
 | 
			
		||||
* SECURITY
 | 
			
		||||
  * Bump github.com/markbates/goth from v1.67.1 to v1.68.0 (#16538) (#16540)
 | 
			
		||||
  * Switch to maintained JWT lib (#16532) (#16535)
 | 
			
		||||
  * Upgrade to latest version of golang-jwt (as forked for 1.14) (#16590) (#16607)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Add basic edit ldap auth test & actually fix #16252 (#16465) (#16495)
 | 
			
		||||
  * Make cancel from CatFileBatch and CatFileBatchCheck wait for the command to end (#16479) (#16481)
 | 
			
		||||
 | 
			
		||||
## [1.14.5](https://github.com/go-gitea/gitea/releases/tag/v1.14.5) - 2021-07-16
 | 
			
		||||
 | 
			
		||||
* SECURITY
 | 
			
		||||
  * Hide mirror passwords on repo settings page (#16022) (#16355)
 | 
			
		||||
  * Update bluemonday to v1.0.15 (#16379) (#16380)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Retry rename on lock induced failures (#16435) (#16439)
 | 
			
		||||
  * Validate issue index before querying DB (#16406) (#16410)
 | 
			
		||||
  * Fix crash following ldap authentication update (#16447) (#16449)
 | 
			
		||||
* ENHANCEMENTS
 | 
			
		||||
  * Redirect on bad CSRF instead of presenting bad page (#14937) (#16378)
 | 
			
		||||
 | 
			
		||||
## [1.14.4](https://github.com/go-gitea/gitea/releases/tag/v1.14.4) - 2021-07-06
 | 
			
		||||
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
 | 
			
		||||
###################################
 | 
			
		||||
#Build stage
 | 
			
		||||
FROM golang:1.16-alpine3.14 AS build-env
 | 
			
		||||
FROM golang:1.16-alpine3.13 AS build-env
 | 
			
		||||
 | 
			
		||||
ARG GOPROXY
 | 
			
		||||
ENV GOPROXY ${GOPROXY:-direct}
 | 
			
		||||
@@ -25,7 +25,7 @@ RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \
 | 
			
		||||
# Begin env-to-ini build
 | 
			
		||||
RUN go build contrib/environment-to-ini/environment-to-ini.go
 | 
			
		||||
 | 
			
		||||
FROM alpine:3.14
 | 
			
		||||
FROM alpine:3.13
 | 
			
		||||
LABEL maintainer="maintainers@gitea.io"
 | 
			
		||||
 | 
			
		||||
EXPOSE 22 3000
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
 | 
			
		||||
###################################
 | 
			
		||||
#Build stage
 | 
			
		||||
FROM golang:1.16-alpine3.14 AS build-env
 | 
			
		||||
FROM golang:1.16-alpine3.13 AS build-env
 | 
			
		||||
 | 
			
		||||
ARG GOPROXY
 | 
			
		||||
ENV GOPROXY ${GOPROXY:-direct}
 | 
			
		||||
@@ -25,7 +25,7 @@ RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \
 | 
			
		||||
# Begin env-to-ini build
 | 
			
		||||
RUN go build contrib/environment-to-ini/environment-to-ini.go
 | 
			
		||||
 | 
			
		||||
FROM alpine:3.14
 | 
			
		||||
FROM alpine:3.13
 | 
			
		||||
LABEL maintainer="maintainers@gitea.io"
 | 
			
		||||
 | 
			
		||||
EXPOSE 2222 3000
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										5
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								Makefile
									
									
									
									
									
								
							@@ -25,7 +25,7 @@ HAS_GO = $(shell hash $(GO) > /dev/null 2>&1 && echo "GO" || echo "NOGO" )
 | 
			
		||||
COMMA := ,
 | 
			
		||||
 | 
			
		||||
XGO_VERSION := go-1.16.x
 | 
			
		||||
MIN_GO_VERSION := 001014000
 | 
			
		||||
MIN_GO_VERSION := 001016000
 | 
			
		||||
MIN_NODE_VERSION := 012017000
 | 
			
		||||
 | 
			
		||||
DOCKER_IMAGE ?= gitea/gitea
 | 
			
		||||
@@ -200,7 +200,7 @@ help:
 | 
			
		||||
go-check:
 | 
			
		||||
	$(eval GO_VERSION := $(shell printf "%03d%03d%03d" $(shell $(GO) version | grep -Eo '[0-9]+\.[0-9.]+' | tr '.' ' ');))
 | 
			
		||||
	@if [ "$(GO_VERSION)" -lt "$(MIN_GO_VERSION)" ]; then \
 | 
			
		||||
		echo "Gitea requires Go 1.14 or greater to build. You can get it at https://golang.org/dl/"; \
 | 
			
		||||
		echo "Gitea requires Go 1.16 or greater to build. You can get it at https://golang.org/dl/"; \
 | 
			
		||||
		exit 1; \
 | 
			
		||||
	fi
 | 
			
		||||
 | 
			
		||||
@@ -699,6 +699,7 @@ fomantic:
 | 
			
		||||
	cd $(FOMANTIC_WORK_DIR) && npm install --no-save
 | 
			
		||||
	cp -f $(FOMANTIC_WORK_DIR)/theme.config.less $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/theme.config
 | 
			
		||||
	cp -rf $(FOMANTIC_WORK_DIR)/_site $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/
 | 
			
		||||
	cp -f web_src/js/vendor/dropdown.js $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/definitions/modules
 | 
			
		||||
	cd $(FOMANTIC_WORK_DIR) && npx gulp -f node_modules/fomantic-ui/gulpfile.js build
 | 
			
		||||
 | 
			
		||||
.PHONY: webpack
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/services/lfs"
 | 
			
		||||
 | 
			
		||||
	"github.com/dgrijalva/jwt-go"
 | 
			
		||||
	"github.com/golang-jwt/jwt"
 | 
			
		||||
	jsoniter "github.com/json-iterator/go"
 | 
			
		||||
	"github.com/kballard/go-shellquote"
 | 
			
		||||
	"github.com/urfave/cli"
 | 
			
		||||
 
 | 
			
		||||
@@ -18,8 +18,8 @@ params:
 | 
			
		||||
  description: Git with a cup of tea
 | 
			
		||||
  author: The Gitea Authors
 | 
			
		||||
  website: https://docs.gitea.io
 | 
			
		||||
  version: 1.14.4
 | 
			
		||||
  minGoVersion: 1.14
 | 
			
		||||
  version: 1.14.6
 | 
			
		||||
  minGoVersion: 1.16
 | 
			
		||||
  goVersion: 1.16
 | 
			
		||||
  minNodeVersion: 12.17
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -32,7 +32,7 @@ You absolutely must not place a general ToS or privacy statement that implies th
 | 
			
		||||
Create or append to `/path/to/custom/templates/custom/extra_links_footer.tmpl`:
 | 
			
		||||
 | 
			
		||||
```go
 | 
			
		||||
<a class="item" href="{{AppSubUrl}}/privacy.html">Privacy Policy</a>
 | 
			
		||||
<a class="item" href="{{AppSubUrl}}/assets/privacy.html">Privacy Policy</a>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Restart Gitea to see the changes.
 | 
			
		||||
 
 | 
			
		||||
@@ -102,7 +102,7 @@ For instance, let's say you are in Germany and must add the famously legally-req
 | 
			
		||||
just place it under your "$GITEA_CUSTOM/public/" directory (for instance `$GITEA_CUSTOM/public/impressum.html`) and put a link to it in either `$GITEA_CUSTOM/templates/custom/extra_links.tmpl` or `$GITEA_CUSTOM/templates/custom/extra_links_footer.tmpl`.
 | 
			
		||||
 | 
			
		||||
To match the current style, the link should have the class name "item", and you can use `{{AppSubUrl}}` to get the base URL:
 | 
			
		||||
`<a class="item" href="{{AppSubUrl}}/impressum.html">Impressum</a>`
 | 
			
		||||
`<a class="item" href="{{AppSubUrl}}/assets/impressum.html">Impressum</a>`
 | 
			
		||||
 | 
			
		||||
For more information, see [Adding Legal Pages](https://docs.gitea.io/en-us/adding-legal-pages).
 | 
			
		||||
 | 
			
		||||
@@ -174,13 +174,13 @@ You can display STL file directly in Gitea by adding:
 | 
			
		||||
 | 
			
		||||
  if ($('.view-raw>a[href$=".stl" i]').length) {
 | 
			
		||||
    $("body").append(
 | 
			
		||||
      '<link href="/Madeleine.js/src/css/Madeleine.css" rel="stylesheet">'
 | 
			
		||||
      '<link href="/assets/Madeleine.js/src/css/Madeleine.css" rel="stylesheet">'
 | 
			
		||||
    );
 | 
			
		||||
    Promise.all([
 | 
			
		||||
      lS("/Madeleine.js/src/lib/stats.js"),
 | 
			
		||||
      lS("/Madeleine.js/src/lib/detector.js"),
 | 
			
		||||
      lS("/Madeleine.js/src/lib/three.min.js"),
 | 
			
		||||
      lS("/Madeleine.js/src/Madeleine.js"),
 | 
			
		||||
      lS("/assets/Madeleine.js/src/lib/stats.js"),
 | 
			
		||||
      lS("/assets/Madeleine.js/src/lib/detector.js"),
 | 
			
		||||
      lS("/assets/Madeleine.js/src/lib/three.min.js"),
 | 
			
		||||
      lS("/assets/Madeleine.js/src/Madeleine.js"),
 | 
			
		||||
    ]).then(function () {
 | 
			
		||||
      $(".view-raw")
 | 
			
		||||
        .attr("id", "view-raw")
 | 
			
		||||
@@ -188,7 +188,7 @@ You can display STL file directly in Gitea by adding:
 | 
			
		||||
      new Madeleine({
 | 
			
		||||
        target: "view-raw",
 | 
			
		||||
        data: $('.view-raw>a[href$=".stl" i]').attr("href"),
 | 
			
		||||
        path: "/Madeleine.js/src",
 | 
			
		||||
        path: "/assets/Madeleine.js/src",
 | 
			
		||||
      });
 | 
			
		||||
      $('.view-raw>a[href$=".stl"]').remove();
 | 
			
		||||
    });
 | 
			
		||||
 
 | 
			
		||||
@@ -61,7 +61,7 @@ Gitea 引用 `custom` 目录中的自定义配置文件来覆盖配置、模板
 | 
			
		||||
"custom/public/"目录下(比如 `custom/public/impressum.html`)并且将它与 `custom/templates/custom/extra_links.tmpl` 链接起来即可。
 | 
			
		||||
 | 
			
		||||
这个链接应当使用一个名为“item”的 class 来匹配当前样式,您可以使用 `{{AppSubUrl}}` 来获取 base URL:
 | 
			
		||||
`<a class="item" href="{{AppSubUrl}}/impressum.html">Impressum</a>`
 | 
			
		||||
`<a class="item" href="{{AppSubUrl}}/assets/impressum.html">Impressum</a>`
 | 
			
		||||
 | 
			
		||||
同理,您可以将页签添加到 `extra_tabs.tmpl` 中,使用同样的方式来添加页签。它的具体样式需要与
 | 
			
		||||
`templates/repo/header.tmpl` 中已有的其他选项卡的样式匹配
 | 
			
		||||
 
 | 
			
		||||
@@ -164,5 +164,5 @@ And so you could write some CSS:
 | 
			
		||||
 | 
			
		||||
Add your stylesheet to your custom directory e.g `custom/public/css/my-style-XXXXX.css` and import it using a custom header file `custom/templates/custom/header.tmpl`:
 | 
			
		||||
```html
 | 
			
		||||
<link type="text/css" href="{{AppSubUrl}}/css/my-style-XXXXX.css" />
 | 
			
		||||
<link type="text/css" href="{{AppSubUrl}}/assets/css/my-style-XXXXX.css" />
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								go.mod
									
									
									
									
									
								
							@@ -10,7 +10,7 @@ require (
 | 
			
		||||
	gitea.com/go-chi/cache v0.0.0-20210110083709-82c4c9ce2d5e
 | 
			
		||||
	gitea.com/go-chi/captcha v0.0.0-20210110083842-e7696c336a1e
 | 
			
		||||
	gitea.com/go-chi/session v0.0.0-20210108030337-0cb48c5ba8ee
 | 
			
		||||
	gitea.com/lunny/levelqueue v0.3.0
 | 
			
		||||
	gitea.com/lunny/levelqueue v0.4.1
 | 
			
		||||
	github.com/Microsoft/go-winio v0.5.0 // indirect
 | 
			
		||||
	github.com/NYTimes/gziphandler v1.1.1
 | 
			
		||||
	github.com/ProtonMail/go-crypto v0.0.0-20210705153151-cc34b1f6908b // indirect
 | 
			
		||||
@@ -28,7 +28,6 @@ require (
 | 
			
		||||
	github.com/couchbase/gomemcached v0.1.2 // indirect
 | 
			
		||||
	github.com/couchbase/goutils v0.0.0-20210118111533-e33d3ffb5401 // indirect
 | 
			
		||||
	github.com/denisenkom/go-mssqldb v0.10.0
 | 
			
		||||
	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 | 
			
		||||
	github.com/djherbis/buffer v1.2.0
 | 
			
		||||
	github.com/djherbis/nio/v3 v3.0.1
 | 
			
		||||
	github.com/dustin/go-humanize v1.0.0
 | 
			
		||||
@@ -51,6 +50,7 @@ require (
 | 
			
		||||
	github.com/gogs/chardet v0.0.0-20191104214054-4b6791f73a28
 | 
			
		||||
	github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14
 | 
			
		||||
	github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
 | 
			
		||||
	github.com/golang-jwt/jwt v3.2.2+incompatible
 | 
			
		||||
	github.com/golang/snappy v0.0.4 // indirect
 | 
			
		||||
	github.com/google/go-github/v32 v32.1.0
 | 
			
		||||
	github.com/google/go-querystring v1.1.0 // indirect
 | 
			
		||||
@@ -75,7 +75,7 @@ require (
 | 
			
		||||
	github.com/lafriks/xormstore v1.4.0
 | 
			
		||||
	github.com/lib/pq v1.10.2
 | 
			
		||||
	github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96
 | 
			
		||||
	github.com/markbates/goth v1.67.1
 | 
			
		||||
	github.com/markbates/goth v1.68.0
 | 
			
		||||
	github.com/mattn/go-isatty v0.0.13
 | 
			
		||||
	github.com/mattn/go-runewidth v0.0.13 // indirect
 | 
			
		||||
	github.com/mattn/go-sqlite3 v1.14.7
 | 
			
		||||
@@ -143,3 +143,5 @@ require (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1
 | 
			
		||||
 | 
			
		||||
replace github.com/golang-jwt/jwt v3.2.1+incompatible => github.com/golang-jwt/jwt v3.2.2+incompatible
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								go.sum
									
									
									
									
									
								
							@@ -49,8 +49,8 @@ gitea.com/go-chi/captcha v0.0.0-20210110083842-e7696c336a1e h1:YjaQU6XFicdhPN+Ml
 | 
			
		||||
gitea.com/go-chi/captcha v0.0.0-20210110083842-e7696c336a1e/go.mod h1:nfA7JaGv3hbGQ1ktdhAsZhdS84qKffI8NMlHr+Opsog=
 | 
			
		||||
gitea.com/go-chi/session v0.0.0-20210108030337-0cb48c5ba8ee h1:9U6HuKUBt/cGK6T/64dEuz0r7Yp97WAAEJvXHDlY3ws=
 | 
			
		||||
gitea.com/go-chi/session v0.0.0-20210108030337-0cb48c5ba8ee/go.mod h1:Ozg8IchVNb/Udg+ui39iHRYqVHSvf3C99ixdpLR8Vu0=
 | 
			
		||||
gitea.com/lunny/levelqueue v0.3.0 h1:MHn1GuSZkxvVEDMyAPqlc7A3cOW+q8RcGhRgH/xtm6I=
 | 
			
		||||
gitea.com/lunny/levelqueue v0.3.0/go.mod h1:HBqmLbz56JWpfEGG0prskAV97ATNRoj5LDmPicD22hU=
 | 
			
		||||
gitea.com/lunny/levelqueue v0.4.1 h1:RZ+AFx5gBsZuyqCvofhAkPQ9uaVDPJnsULoJZIYaJNw=
 | 
			
		||||
gitea.com/lunny/levelqueue v0.4.1/go.mod h1:HBqmLbz56JWpfEGG0prskAV97ATNRoj5LDmPicD22hU=
 | 
			
		||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
 | 
			
		||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
 | 
			
		||||
github.com/6543/go-version v1.3.1 h1:HvOp+Telns7HWJ2Xo/05YXQSB2bE0WmVgbHqwMPZT4U=
 | 
			
		||||
@@ -241,7 +241,6 @@ github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xb
 | 
			
		||||
github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
 | 
			
		||||
github.com/denisenkom/go-mssqldb v0.10.0 h1:QykgLZBorFE95+gO3u9esLd0BmbvpWp0/waNNZfHBM8=
 | 
			
		||||
github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
 | 
			
		||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
 | 
			
		||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 | 
			
		||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
 | 
			
		||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
 | 
			
		||||
@@ -476,6 +475,8 @@ github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14 h1:yXtpJr/LV6PFu4nTLgfjQ
 | 
			
		||||
github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14/go.mod h1:jPoNZLWDAqA5N3G5amEoiNbhVrmM+ZQEcnQvNQ2KaZk=
 | 
			
		||||
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 h1:UjoPNDAQ5JPCjlxoJd6K8ALZqSDDhk2ymieAZOVaDg0=
 | 
			
		||||
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85/go.mod h1:fR6z1Ie6rtF7kl/vBYMfgD5/G5B1blui7z426/sj2DU=
 | 
			
		||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
 | 
			
		||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
 | 
			
		||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
 | 
			
		||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
 | 
			
		||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 | 
			
		||||
@@ -762,8 +763,8 @@ github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ
 | 
			
		||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
 | 
			
		||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
 | 
			
		||||
github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA=
 | 
			
		||||
github.com/markbates/goth v1.67.1 h1:gU5B0pzHVyhnJPwGynfFnkfvaQ39C1Sy+ewdl+bhAOw=
 | 
			
		||||
github.com/markbates/goth v1.67.1/go.mod h1:EyLFHGU5ySr2GXRDyJH5nu2dA7parbC8QwIYW/rGcWg=
 | 
			
		||||
github.com/markbates/goth v1.68.0 h1:90sKvjRAKHcl9V2uC9x/PJXeD78cFPiBsyP1xVhoQfA=
 | 
			
		||||
github.com/markbates/goth v1.68.0/go.mod h1:V2VcDMzDiMHW+YmqYl7i0cMiAUeCkAe4QE6jRKBhXZw=
 | 
			
		||||
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
 | 
			
		||||
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
 | 
			
		||||
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
 | 
			
		||||
 
 | 
			
		||||
@@ -144,6 +144,60 @@ func TestLDAPUserSignin(t *testing.T) {
 | 
			
		||||
	assert.Equal(t, u.Email, htmlDoc.Find(`label[for="email"]`).Siblings().First().Text())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestLDAPAuthChange(t *testing.T) {
 | 
			
		||||
	defer prepareTestEnv(t)()
 | 
			
		||||
	addAuthSourceLDAP(t, "")
 | 
			
		||||
 | 
			
		||||
	session := loginUser(t, "user1")
 | 
			
		||||
	req := NewRequest(t, "GET", "/admin/auths")
 | 
			
		||||
	resp := session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
	doc := NewHTMLParser(t, resp.Body)
 | 
			
		||||
	href, exists := doc.Find("table.table td a").Attr("href")
 | 
			
		||||
	if !exists {
 | 
			
		||||
		assert.True(t, exists, "No authentication source found")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	req = NewRequest(t, "GET", href)
 | 
			
		||||
	resp = session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
	doc = NewHTMLParser(t, resp.Body)
 | 
			
		||||
	csrf := doc.GetCSRF()
 | 
			
		||||
	host, _ := doc.Find(`input[name="host"]`).Attr("value")
 | 
			
		||||
	assert.Equal(t, host, getLDAPServerHost())
 | 
			
		||||
	binddn, _ := doc.Find(`input[name="bind_dn"]`).Attr("value")
 | 
			
		||||
	assert.Equal(t, binddn, "uid=gitea,ou=service,dc=planetexpress,dc=com")
 | 
			
		||||
 | 
			
		||||
	req = NewRequestWithValues(t, "POST", href, map[string]string{
 | 
			
		||||
		"_csrf":                    csrf,
 | 
			
		||||
		"type":                     "2",
 | 
			
		||||
		"name":                     "ldap",
 | 
			
		||||
		"host":                     getLDAPServerHost(),
 | 
			
		||||
		"port":                     "389",
 | 
			
		||||
		"bind_dn":                  "uid=gitea,ou=service,dc=planetexpress,dc=com",
 | 
			
		||||
		"bind_password":            "password",
 | 
			
		||||
		"user_base":                "ou=people,dc=planetexpress,dc=com",
 | 
			
		||||
		"filter":                   "(&(objectClass=inetOrgPerson)(memberOf=cn=git,ou=people,dc=planetexpress,dc=com)(uid=%s))",
 | 
			
		||||
		"admin_filter":             "(memberOf=cn=admin_staff,ou=people,dc=planetexpress,dc=com)",
 | 
			
		||||
		"restricted_filter":        "(uid=leela)",
 | 
			
		||||
		"attribute_username":       "uid",
 | 
			
		||||
		"attribute_name":           "givenName",
 | 
			
		||||
		"attribute_surname":        "sn",
 | 
			
		||||
		"attribute_mail":           "mail",
 | 
			
		||||
		"attribute_ssh_public_key": "",
 | 
			
		||||
		"is_sync_enabled":          "on",
 | 
			
		||||
		"is_active":                "on",
 | 
			
		||||
	})
 | 
			
		||||
	session.MakeRequest(t, req, http.StatusFound)
 | 
			
		||||
 | 
			
		||||
	req = NewRequest(t, "GET", href)
 | 
			
		||||
	resp = session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
	doc = NewHTMLParser(t, resp.Body)
 | 
			
		||||
	host, _ = doc.Find(`input[name="host"]`).Attr("value")
 | 
			
		||||
	assert.Equal(t, host, getLDAPServerHost())
 | 
			
		||||
	binddn, _ = doc.Find(`input[name="bind_dn"]`).Attr("value")
 | 
			
		||||
	assert.Equal(t, binddn, "uid=gitea,ou=service,dc=planetexpress,dc=com")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestLDAPUserSync(t *testing.T) {
 | 
			
		||||
	if skipLDAPTests() {
 | 
			
		||||
		t.Skip()
 | 
			
		||||
 
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							@@ -1 +1 @@
 | 
			
		||||
423313fbd38093bb10d0c8387db9105409c6f196
 | 
			
		||||
0dca5bd9b5d7ef937710e056f575e86c0184ba85
 | 
			
		||||
 
 | 
			
		||||
@@ -160,7 +160,7 @@ func getLatestCommitStatus(e Engine, repoID int64, sha string, listOptions ListO
 | 
			
		||||
	if len(ids) == 0 {
 | 
			
		||||
		return statuses, nil
 | 
			
		||||
	}
 | 
			
		||||
	return statuses, x.In("id", ids).Find(&statuses)
 | 
			
		||||
	return statuses, e.In("id", ids).Find(&statuses)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FindRepoRecentCommitStatusContexts returns repository's recent commit status contexts
 | 
			
		||||
 
 | 
			
		||||
@@ -5,3 +5,19 @@
 | 
			
		||||
  scope: "openid profile"
 | 
			
		||||
  created_unix: 1546869730
 | 
			
		||||
  updated_unix: 1546869730
 | 
			
		||||
 | 
			
		||||
- id: 2
 | 
			
		||||
  user_id: 3
 | 
			
		||||
  application_id: 1
 | 
			
		||||
  counter: 1
 | 
			
		||||
  scope: "openid"
 | 
			
		||||
  created_unix: 1546869730
 | 
			
		||||
  updated_unix: 1546869730
 | 
			
		||||
 | 
			
		||||
- id: 3
 | 
			
		||||
  user_id: 5
 | 
			
		||||
  application_id: 1
 | 
			
		||||
  counter: 1
 | 
			
		||||
  scope: "openid profile email"
 | 
			
		||||
  created_unix: 1546869730
 | 
			
		||||
  updated_unix: 1546869730
 | 
			
		||||
@@ -982,6 +982,31 @@ func newIssue(e *xorm.Session, doer *User, opts NewIssueOptions) (err error) {
 | 
			
		||||
	return opts.Issue.addCrossReferences(e, doer, false)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RecalculateIssueIndexForRepo create issue_index for repo if not exist and
 | 
			
		||||
// update it based on highest index of existing issues assigned to a repo
 | 
			
		||||
func RecalculateIssueIndexForRepo(repoID int64) error {
 | 
			
		||||
	sess := x.NewSession()
 | 
			
		||||
	defer sess.Close()
 | 
			
		||||
	if err := sess.Begin(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := upsertResourceIndex(sess, "issue_index", repoID); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var max int64
 | 
			
		||||
	if _, err := sess.Select(" MAX(`index`)").Table("issue").Where("repo_id=?", repoID).Get(&max); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := sess.Exec("UPDATE `issue_index` SET max_index=? WHERE group_id=?", max, repoID); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return sess.Commit()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewIssue creates new issue with labels for repository.
 | 
			
		||||
func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string) (err error) {
 | 
			
		||||
	idx, err := GetNextResourceIndex("issue_index", repo.ID)
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,8 @@ import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/timeutil"
 | 
			
		||||
 | 
			
		||||
	"xorm.io/xorm"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Stopwatch represents a stopwatch for time tracking.
 | 
			
		||||
@@ -61,8 +63,12 @@ func StopwatchExists(userID, issueID int64) bool {
 | 
			
		||||
 | 
			
		||||
// HasUserStopwatch returns true if the user has a stopwatch
 | 
			
		||||
func HasUserStopwatch(userID int64) (exists bool, sw *Stopwatch, err error) {
 | 
			
		||||
	return hasUserStopwatch(x, userID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func hasUserStopwatch(e Engine, userID int64) (exists bool, sw *Stopwatch, err error) {
 | 
			
		||||
	sw = new(Stopwatch)
 | 
			
		||||
	exists, err = x.
 | 
			
		||||
	exists, err = e.
 | 
			
		||||
		Where("user_id = ?", userID).
 | 
			
		||||
		Get(sw)
 | 
			
		||||
	return
 | 
			
		||||
@@ -70,11 +76,23 @@ func HasUserStopwatch(userID int64) (exists bool, sw *Stopwatch, err error) {
 | 
			
		||||
 | 
			
		||||
// CreateOrStopIssueStopwatch will create or remove a stopwatch and will log it into issue's timeline.
 | 
			
		||||
func CreateOrStopIssueStopwatch(user *User, issue *Issue) error {
 | 
			
		||||
	sw, exists, err := getStopwatch(x, user.ID, issue.ID)
 | 
			
		||||
	sess := x.NewSession()
 | 
			
		||||
	defer sess.Close()
 | 
			
		||||
	if err := sess.Begin(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := createOrStopIssueStopwatch(sess, user, issue); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return sess.Commit()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createOrStopIssueStopwatch(e *xorm.Session, user *User, issue *Issue) error {
 | 
			
		||||
	sw, exists, err := getStopwatch(e, user.ID, issue.ID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := issue.loadRepo(x); err != nil {
 | 
			
		||||
	if err := issue.loadRepo(e); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -90,11 +108,11 @@ func CreateOrStopIssueStopwatch(user *User, issue *Issue) error {
 | 
			
		||||
			Time:    timediff,
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if _, err := x.Insert(tt); err != nil {
 | 
			
		||||
		if _, err := e.Insert(tt); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if _, err := CreateComment(&CreateCommentOptions{
 | 
			
		||||
		if _, err := createComment(e, &CreateCommentOptions{
 | 
			
		||||
			Doer:    user,
 | 
			
		||||
			Issue:   issue,
 | 
			
		||||
			Repo:    issue.Repo,
 | 
			
		||||
@@ -104,21 +122,21 @@ func CreateOrStopIssueStopwatch(user *User, issue *Issue) error {
 | 
			
		||||
		}); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if _, err := x.Delete(sw); err != nil {
 | 
			
		||||
		if _, err := e.Delete(sw); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		// if another stopwatch is running: stop it
 | 
			
		||||
		exists, sw, err := HasUserStopwatch(user.ID)
 | 
			
		||||
		exists, sw, err := hasUserStopwatch(e, user.ID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if exists {
 | 
			
		||||
			issue, err := getIssueByID(x, sw.IssueID)
 | 
			
		||||
			issue, err := getIssueByID(e, sw.IssueID)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			if err := CreateOrStopIssueStopwatch(user, issue); err != nil {
 | 
			
		||||
			if err := createOrStopIssueStopwatch(e, user, issue); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
@@ -129,11 +147,11 @@ func CreateOrStopIssueStopwatch(user *User, issue *Issue) error {
 | 
			
		||||
			IssueID: issue.ID,
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if _, err := x.Insert(sw); err != nil {
 | 
			
		||||
		if _, err := e.Insert(sw); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if _, err := CreateComment(&CreateCommentOptions{
 | 
			
		||||
		if _, err := createComment(e, &CreateCommentOptions{
 | 
			
		||||
			Doer:  user,
 | 
			
		||||
			Issue: issue,
 | 
			
		||||
			Repo:  issue.Repo,
 | 
			
		||||
@@ -147,21 +165,33 @@ func CreateOrStopIssueStopwatch(user *User, issue *Issue) error {
 | 
			
		||||
 | 
			
		||||
// CancelStopwatch removes the given stopwatch and logs it into issue's timeline.
 | 
			
		||||
func CancelStopwatch(user *User, issue *Issue) error {
 | 
			
		||||
	sw, exists, err := getStopwatch(x, user.ID, issue.ID)
 | 
			
		||||
	sess := x.NewSession()
 | 
			
		||||
	defer sess.Close()
 | 
			
		||||
	if err := sess.Begin(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := cancelStopwatch(sess, user, issue); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return sess.Commit()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cancelStopwatch(e *xorm.Session, user *User, issue *Issue) error {
 | 
			
		||||
	sw, exists, err := getStopwatch(e, user.ID, issue.ID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if exists {
 | 
			
		||||
		if _, err := x.Delete(sw); err != nil {
 | 
			
		||||
		if _, err := e.Delete(sw); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := issue.loadRepo(x); err != nil {
 | 
			
		||||
		if err := issue.loadRepo(e); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if _, err := CreateComment(&CreateCommentOptions{
 | 
			
		||||
		if _, err := createComment(e, &CreateCommentOptions{
 | 
			
		||||
			Doer:  user,
 | 
			
		||||
			Issue: issue,
 | 
			
		||||
			Repo:  issue.Repo,
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@ package models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"encoding/binary"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/smtp"
 | 
			
		||||
@@ -70,13 +71,32 @@ var (
 | 
			
		||||
	_ convert.Conversion = &SSPIConfig{}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// jsonUnmarshalIgnoreErroneousBOM - due to a bug in xorm (see https://gitea.com/xorm/xorm/pulls/1957) - it's
 | 
			
		||||
// possible that a Blob may gain an unwanted prefix of 0xff 0xfe.
 | 
			
		||||
func jsonUnmarshalIgnoreErroneousBOM(bs []byte, v interface{}) error {
 | 
			
		||||
// jsonUnmarshalHandleDoubleEncode - due to a bug in xorm (see https://gitea.com/xorm/xorm/pulls/1957) - it's
 | 
			
		||||
// possible that a Blob may be double encoded or gain an unwanted prefix of 0xff 0xfe.
 | 
			
		||||
func jsonUnmarshalHandleDoubleEncode(bs []byte, v interface{}) error {
 | 
			
		||||
	json := jsoniter.ConfigCompatibleWithStandardLibrary
 | 
			
		||||
	err := json.Unmarshal(bs, &v)
 | 
			
		||||
	err := json.Unmarshal(bs, v)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ok := true
 | 
			
		||||
		rs := []byte{}
 | 
			
		||||
		temp := make([]byte, 2)
 | 
			
		||||
		for _, rn := range string(bs) {
 | 
			
		||||
			if rn > 0xffff {
 | 
			
		||||
				ok = false
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
			binary.LittleEndian.PutUint16(temp, uint16(rn))
 | 
			
		||||
			rs = append(rs, temp...)
 | 
			
		||||
		}
 | 
			
		||||
		if ok {
 | 
			
		||||
			if rs[0] == 0xff && rs[1] == 0xfe {
 | 
			
		||||
				rs = rs[2:]
 | 
			
		||||
			}
 | 
			
		||||
			err = json.Unmarshal(rs, v)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil && len(bs) > 2 && bs[0] == 0xff && bs[1] == 0xfe {
 | 
			
		||||
		err = json.Unmarshal(bs[2:], &v)
 | 
			
		||||
		err = json.Unmarshal(bs[2:], v)
 | 
			
		||||
	}
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
@@ -88,7 +108,7 @@ type LDAPConfig struct {
 | 
			
		||||
 | 
			
		||||
// FromDB fills up a LDAPConfig from serialized format.
 | 
			
		||||
func (cfg *LDAPConfig) FromDB(bs []byte) error {
 | 
			
		||||
	err := jsonUnmarshalIgnoreErroneousBOM(bs, &cfg)
 | 
			
		||||
	err := jsonUnmarshalHandleDoubleEncode(bs, &cfg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
@@ -129,7 +149,7 @@ type SMTPConfig struct {
 | 
			
		||||
 | 
			
		||||
// FromDB fills up an SMTPConfig from serialized format.
 | 
			
		||||
func (cfg *SMTPConfig) FromDB(bs []byte) error {
 | 
			
		||||
	return jsonUnmarshalIgnoreErroneousBOM(bs, cfg)
 | 
			
		||||
	return jsonUnmarshalHandleDoubleEncode(bs, cfg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToDB exports an SMTPConfig to a serialized format.
 | 
			
		||||
@@ -146,7 +166,7 @@ type PAMConfig struct {
 | 
			
		||||
 | 
			
		||||
// FromDB fills up a PAMConfig from serialized format.
 | 
			
		||||
func (cfg *PAMConfig) FromDB(bs []byte) error {
 | 
			
		||||
	return jsonUnmarshalIgnoreErroneousBOM(bs, cfg)
 | 
			
		||||
	return jsonUnmarshalHandleDoubleEncode(bs, cfg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToDB exports a PAMConfig to a serialized format.
 | 
			
		||||
@@ -167,7 +187,7 @@ type OAuth2Config struct {
 | 
			
		||||
 | 
			
		||||
// FromDB fills up an OAuth2Config from serialized format.
 | 
			
		||||
func (cfg *OAuth2Config) FromDB(bs []byte) error {
 | 
			
		||||
	return jsonUnmarshalIgnoreErroneousBOM(bs, cfg)
 | 
			
		||||
	return jsonUnmarshalHandleDoubleEncode(bs, cfg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToDB exports an SMTPConfig to a serialized format.
 | 
			
		||||
@@ -187,7 +207,7 @@ type SSPIConfig struct {
 | 
			
		||||
 | 
			
		||||
// FromDB fills up an SSPIConfig from serialized format.
 | 
			
		||||
func (cfg *SSPIConfig) FromDB(bs []byte) error {
 | 
			
		||||
	return jsonUnmarshalIgnoreErroneousBOM(bs, cfg)
 | 
			
		||||
	return jsonUnmarshalHandleDoubleEncode(bs, cfg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToDB exports an SSPIConfig to a serialized format.
 | 
			
		||||
 
 | 
			
		||||
@@ -590,11 +590,26 @@ func recreateTable(sess *xorm.Session, bean interface{}) error {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := sess.Table(tempTableName).DropIndexes(bean); err != nil {
 | 
			
		||||
			log.Error("Unable to drop indexes on temporary table %s. Error: %v", tempTableName, err)
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// SQLite and MySQL will move all the constraints from the temporary table to the new table
 | 
			
		||||
		if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` RENAME TO `%s`", tempTableName, tableName)); err != nil {
 | 
			
		||||
			log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := sess.Table(tableName).CreateIndexes(bean); err != nil {
 | 
			
		||||
			log.Error("Unable to recreate indexes on table %s. Error: %v", tableName, err)
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := sess.Table(tableName).CreateUniques(bean); err != nil {
 | 
			
		||||
			log.Error("Unable to recreate uniques on table %s. Error: %v", tableName, err)
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	case setting.Database.UsePostgreSQL:
 | 
			
		||||
		var originalSequences []string
 | 
			
		||||
		type sequenceData struct {
 | 
			
		||||
@@ -836,7 +851,7 @@ func dropTableColumns(sess *xorm.Session, tableName string, columnNames ...strin
 | 
			
		||||
			}
 | 
			
		||||
			cols += "`" + strings.ToLower(col) + "`"
 | 
			
		||||
		}
 | 
			
		||||
		sql := fmt.Sprintf("SELECT Name FROM SYS.DEFAULT_CONSTRAINTS WHERE PARENT_OBJECT_ID = OBJECT_ID('%[1]s') AND PARENT_COLUMN_ID IN (SELECT column_id FROM sys.columns WHERE lower(NAME) IN (%[2]s) AND object_id = OBJECT_ID('%[1]s'))",
 | 
			
		||||
		sql := fmt.Sprintf("SELECT Name FROM sys.default_constraints WHERE parent_object_id = OBJECT_ID('%[1]s') AND parent_column_id IN (SELECT column_id FROM sys.columns WHERE LOWER(name) IN (%[2]s) AND object_id = OBJECT_ID('%[1]s'))",
 | 
			
		||||
			tableName, strings.ReplaceAll(cols, "`", "'"))
 | 
			
		||||
		constraints := make([]string, 0)
 | 
			
		||||
		if err := sess.SQL(sql).Find(&constraints); err != nil {
 | 
			
		||||
@@ -847,17 +862,14 @@ func dropTableColumns(sess *xorm.Session, tableName string, columnNames ...strin
 | 
			
		||||
				return fmt.Errorf("Drop table `%s` default constraint `%s`: %v", tableName, constraint, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		sql = fmt.Sprintf("SELECT DISTINCT Name FROM SYS.INDEXES INNER JOIN SYS.INDEX_COLUMNS ON INDEXES.INDEX_ID = INDEX_COLUMNS.INDEX_ID AND INDEXES.OBJECT_ID = INDEX_COLUMNS.OBJECT_ID WHERE INDEXES.OBJECT_ID = OBJECT_ID('%[1]s') AND INDEX_COLUMNS.COLUMN_ID IN (SELECT column_id FROM sys.columns WHERE lower(NAME) IN (%[2]s) AND object_id = OBJECT_ID('%[1]s'))",
 | 
			
		||||
		sql = fmt.Sprintf("SELECT DISTINCT Name FROM sys.indexes INNER JOIN sys.index_columns ON indexes.index_id = index_columns.index_id AND indexes.object_id = index_columns.object_id WHERE indexes.object_id = OBJECT_ID('%[1]s') AND index_columns.column_id IN (SELECT column_id FROM sys.columns WHERE LOWER(name) IN (%[2]s) AND object_id = OBJECT_ID('%[1]s'))",
 | 
			
		||||
			tableName, strings.ReplaceAll(cols, "`", "'"))
 | 
			
		||||
		constraints = make([]string, 0)
 | 
			
		||||
		if err := sess.SQL(sql).Find(&constraints); err != nil {
 | 
			
		||||
			return fmt.Errorf("Find constraints: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		for _, constraint := range constraints {
 | 
			
		||||
			if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` DROP CONSTRAINT IF EXISTS `%s`", tableName, constraint)); err != nil {
 | 
			
		||||
				return fmt.Errorf("Drop table `%s` index constraint `%s`: %v", tableName, constraint, err)
 | 
			
		||||
			}
 | 
			
		||||
			if _, err := sess.Exec(fmt.Sprintf("DROP INDEX IF EXISTS `%[2]s` ON `%[1]s`", tableName, constraint)); err != nil {
 | 
			
		||||
			if _, err := sess.Exec(fmt.Sprintf("DROP INDEX `%[2]s` ON `%[1]s`", tableName, constraint)); err != nil {
 | 
			
		||||
				return fmt.Errorf("Drop index `%[2]s` on `%[1]s`: %v", tableName, constraint, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -155,11 +155,6 @@ func initOAuth2LoginSources() error {
 | 
			
		||||
		err := oauth2.RegisterProvider(source.Name, oAuth2Config.Provider, oAuth2Config.ClientID, oAuth2Config.ClientSecret, oAuth2Config.OpenIDConnectAutoDiscoveryURL, oAuth2Config.CustomURLMapping)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Critical("Unable to register source: %s due to Error: %v. This source will be disabled.", source.Name, err)
 | 
			
		||||
			source.IsActived = false
 | 
			
		||||
			if err = UpdateSource(source); err != nil {
 | 
			
		||||
				log.Critical("Unable to update source %s to disable it. Error: %v", err)
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/modules/timeutil"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
 | 
			
		||||
	"github.com/dgrijalva/jwt-go"
 | 
			
		||||
	"github.com/golang-jwt/jwt"
 | 
			
		||||
	uuid "github.com/google/uuid"
 | 
			
		||||
	"golang.org/x/crypto/bcrypt"
 | 
			
		||||
	"xorm.io/xorm"
 | 
			
		||||
 
 | 
			
		||||
@@ -1125,7 +1125,7 @@ func CreateRepository(ctx DBContext, doer, u *User, repo *Repository, overwriteO
 | 
			
		||||
 | 
			
		||||
	// Give access to all members in teams with access to all repositories.
 | 
			
		||||
	if u.IsOrganization() {
 | 
			
		||||
		if err := u.GetTeams(&SearchTeamOptions{}); err != nil {
 | 
			
		||||
		if err := u.getTeams(ctx.e); err != nil {
 | 
			
		||||
			return fmt.Errorf("GetTeams: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		for _, t := range u.Teams {
 | 
			
		||||
@@ -1152,6 +1152,16 @@ func CreateRepository(ctx DBContext, doer, u *User, repo *Repository, overwriteO
 | 
			
		||||
		return fmt.Errorf("recalculateAccesses: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if u.Visibility == api.VisibleTypePublic && !repo.IsPrivate {
 | 
			
		||||
		// Create/Remove git-daemon-export-ok for git-daemon...
 | 
			
		||||
		daemonExportFile := path.Join(repo.RepoPath(), `git-daemon-export-ok`)
 | 
			
		||||
		if f, err := os.Create(daemonExportFile); err != nil {
 | 
			
		||||
			log.Error("Failed to create %s: %v", daemonExportFile, err)
 | 
			
		||||
		} else {
 | 
			
		||||
			f.Close()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if setting.Service.AutoWatchNewRepos {
 | 
			
		||||
		if err = watchRepo(ctx.e, doer.ID, repo.ID, true); err != nil {
 | 
			
		||||
			return fmt.Errorf("watchRepo: %v", err)
 | 
			
		||||
@@ -1310,15 +1320,16 @@ func updateRepository(e Engine, repo *Repository, visibilityChanged bool) (err e
 | 
			
		||||
		// Create/Remove git-daemon-export-ok for git-daemon...
 | 
			
		||||
		daemonExportFile := path.Join(repo.RepoPath(), `git-daemon-export-ok`)
 | 
			
		||||
		isExist, err := util.IsExist(daemonExportFile)
 | 
			
		||||
		isPublic := !repo.IsPrivate && repo.Owner.Visibility == api.VisibleTypePublic
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Error("Unable to check if %s exists. Error: %v", daemonExportFile, err)
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if repo.IsPrivate && isExist {
 | 
			
		||||
		if !isPublic && isExist {
 | 
			
		||||
			if err = util.Remove(daemonExportFile); err != nil {
 | 
			
		||||
				log.Error("Failed to remove %s: %v", daemonExportFile, err)
 | 
			
		||||
			}
 | 
			
		||||
		} else if !repo.IsPrivate && !isExist {
 | 
			
		||||
		} else if isPublic && !isExist {
 | 
			
		||||
			if f, err := os.Create(daemonExportFile); err != nil {
 | 
			
		||||
				log.Error("Failed to create %s: %v", daemonExportFile, err)
 | 
			
		||||
			} else {
 | 
			
		||||
 
 | 
			
		||||
@@ -217,16 +217,14 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
 | 
			
		||||
			cond = cond.And(accessibleRepositoryCondition(opts.Actor))
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		// Not looking at private organisations
 | 
			
		||||
		// Not looking at private organisations and users
 | 
			
		||||
		// We should be able to see all non-private repositories that
 | 
			
		||||
		// isn't in a private or limited organisation.
 | 
			
		||||
		cond = cond.And(
 | 
			
		||||
			builder.Eq{"is_private": false},
 | 
			
		||||
			builder.NotIn("owner_id", builder.Select("id").From("`user`").Where(
 | 
			
		||||
				builder.And(
 | 
			
		||||
					builder.Eq{"type": UserTypeOrganization},
 | 
			
		||||
				builder.Or(builder.Eq{"visibility": structs.VisibleTypeLimited}, builder.Eq{"visibility": structs.VisibleTypePrivate}),
 | 
			
		||||
				))))
 | 
			
		||||
			)))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if opts.IsPrivate != util.OptionalBoolNone {
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,7 @@ type UnitConfig struct{}
 | 
			
		||||
 | 
			
		||||
// FromDB fills up a UnitConfig from serialized format.
 | 
			
		||||
func (cfg *UnitConfig) FromDB(bs []byte) error {
 | 
			
		||||
	return jsonUnmarshalIgnoreErroneousBOM(bs, &cfg)
 | 
			
		||||
	return jsonUnmarshalHandleDoubleEncode(bs, &cfg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToDB exports a UnitConfig to a serialized format.
 | 
			
		||||
@@ -44,7 +44,7 @@ type ExternalWikiConfig struct {
 | 
			
		||||
 | 
			
		||||
// FromDB fills up a ExternalWikiConfig from serialized format.
 | 
			
		||||
func (cfg *ExternalWikiConfig) FromDB(bs []byte) error {
 | 
			
		||||
	return jsonUnmarshalIgnoreErroneousBOM(bs, &cfg)
 | 
			
		||||
	return jsonUnmarshalHandleDoubleEncode(bs, &cfg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToDB exports a ExternalWikiConfig to a serialized format.
 | 
			
		||||
@@ -62,7 +62,7 @@ type ExternalTrackerConfig struct {
 | 
			
		||||
 | 
			
		||||
// FromDB fills up a ExternalTrackerConfig from serialized format.
 | 
			
		||||
func (cfg *ExternalTrackerConfig) FromDB(bs []byte) error {
 | 
			
		||||
	return jsonUnmarshalIgnoreErroneousBOM(bs, &cfg)
 | 
			
		||||
	return jsonUnmarshalHandleDoubleEncode(bs, &cfg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToDB exports a ExternalTrackerConfig to a serialized format.
 | 
			
		||||
@@ -80,7 +80,7 @@ type IssuesConfig struct {
 | 
			
		||||
 | 
			
		||||
// FromDB fills up a IssuesConfig from serialized format.
 | 
			
		||||
func (cfg *IssuesConfig) FromDB(bs []byte) error {
 | 
			
		||||
	return jsonUnmarshalIgnoreErroneousBOM(bs, &cfg)
 | 
			
		||||
	return jsonUnmarshalHandleDoubleEncode(bs, &cfg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToDB exports a IssuesConfig to a serialized format.
 | 
			
		||||
@@ -104,7 +104,7 @@ type PullRequestsConfig struct {
 | 
			
		||||
 | 
			
		||||
// FromDB fills up a PullRequestsConfig from serialized format.
 | 
			
		||||
func (cfg *PullRequestsConfig) FromDB(bs []byte) error {
 | 
			
		||||
	return jsonUnmarshalIgnoreErroneousBOM(bs, &cfg)
 | 
			
		||||
	return jsonUnmarshalHandleDoubleEncode(bs, &cfg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToDB exports a PullRequestsConfig to a serialized format.
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
 | 
			
		||||
	"github.com/dgrijalva/jwt-go"
 | 
			
		||||
	"github.com/golang-jwt/jwt"
 | 
			
		||||
	ini "gopkg.in/ini.v1"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@ package oauth2
 | 
			
		||||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"sync"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
@@ -34,6 +35,7 @@ import (
 | 
			
		||||
var (
 | 
			
		||||
	sessionUsersStoreKey = "gitea-oauth2-sessions"
 | 
			
		||||
	providerHeaderKey    = "gitea-oauth2-provider"
 | 
			
		||||
	gothRWMutex          = sync.RWMutex{}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// CustomURLMapping describes the urls values to use when customizing OAuth2 provider URLs
 | 
			
		||||
@@ -60,6 +62,10 @@ func Init(x *xorm.Engine) error {
 | 
			
		||||
 | 
			
		||||
	// Note, when using the FilesystemStore only the session.ID is written to a browser cookie, so this is explicit for the storage on disk
 | 
			
		||||
	store.MaxLength(setting.OAuth2.MaxTokenLength)
 | 
			
		||||
 | 
			
		||||
	gothRWMutex.Lock()
 | 
			
		||||
	defer gothRWMutex.Unlock()
 | 
			
		||||
 | 
			
		||||
	gothic.Store = store
 | 
			
		||||
 | 
			
		||||
	gothic.SetState = func(req *http.Request) string {
 | 
			
		||||
@@ -82,6 +88,9 @@ func Auth(provider string, request *http.Request, response http.ResponseWriter)
 | 
			
		||||
	// normally the gothic library will write some custom stuff to the response instead of our own nice error page
 | 
			
		||||
	//gothic.BeginAuthHandler(response, request)
 | 
			
		||||
 | 
			
		||||
	gothRWMutex.RLock()
 | 
			
		||||
	defer gothRWMutex.RUnlock()
 | 
			
		||||
 | 
			
		||||
	url, err := gothic.GetAuthURL(response, request)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		http.Redirect(response, request, url, http.StatusTemporaryRedirect)
 | 
			
		||||
@@ -95,6 +104,9 @@ func ProviderCallback(provider string, request *http.Request, response http.Resp
 | 
			
		||||
	// not sure if goth is thread safe (?) when using multiple providers
 | 
			
		||||
	request.Header.Set(providerHeaderKey, provider)
 | 
			
		||||
 | 
			
		||||
	gothRWMutex.RLock()
 | 
			
		||||
	defer gothRWMutex.RUnlock()
 | 
			
		||||
 | 
			
		||||
	user, err := gothic.CompleteUserAuth(response, request)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return user, err
 | 
			
		||||
@@ -108,6 +120,9 @@ func RegisterProvider(providerName, providerType, clientID, clientSecret, openID
 | 
			
		||||
	provider, err := createProvider(providerName, providerType, clientID, clientSecret, openIDConnectAutoDiscoveryURL, customURLMapping)
 | 
			
		||||
 | 
			
		||||
	if err == nil && provider != nil {
 | 
			
		||||
		gothRWMutex.Lock()
 | 
			
		||||
		defer gothRWMutex.Unlock()
 | 
			
		||||
 | 
			
		||||
		goth.UseProviders(provider)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -116,11 +131,17 @@ func RegisterProvider(providerName, providerType, clientID, clientSecret, openID
 | 
			
		||||
 | 
			
		||||
// RemoveProvider removes the given OAuth2 provider from the goth lib
 | 
			
		||||
func RemoveProvider(providerName string) {
 | 
			
		||||
	gothRWMutex.Lock()
 | 
			
		||||
	defer gothRWMutex.Unlock()
 | 
			
		||||
 | 
			
		||||
	delete(goth.GetProviders(), providerName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ClearProviders clears all OAuth2 providers from the goth lib
 | 
			
		||||
func ClearProviders() {
 | 
			
		||||
	gothRWMutex.Lock()
 | 
			
		||||
	defer gothRWMutex.Unlock()
 | 
			
		||||
 | 
			
		||||
	goth.ClearProviders()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@ package context
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"context"
 | 
			
		||||
	"html/template"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"time"
 | 
			
		||||
@@ -22,6 +23,8 @@ type routerLoggerOptions struct {
 | 
			
		||||
	Ctx            map[string]interface{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var signedUserNameStringPointerKey interface{} = "signedUserNameStringPointerKey"
 | 
			
		||||
 | 
			
		||||
// AccessLogger returns a middleware to log access logger
 | 
			
		||||
func AccessLogger() func(http.Handler) http.Handler {
 | 
			
		||||
	logger := log.GetLogger("access")
 | 
			
		||||
@@ -29,11 +32,10 @@ func AccessLogger() func(http.Handler) http.Handler {
 | 
			
		||||
	return func(next http.Handler) http.Handler {
 | 
			
		||||
		return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
 | 
			
		||||
			start := time.Now()
 | 
			
		||||
			next.ServeHTTP(w, req)
 | 
			
		||||
			identity := "-"
 | 
			
		||||
			if val := SignedUserName(req); val != "" {
 | 
			
		||||
				identity = val
 | 
			
		||||
			}
 | 
			
		||||
			r := req.WithContext(context.WithValue(req.Context(), signedUserNameStringPointerKey, &identity))
 | 
			
		||||
 | 
			
		||||
			next.ServeHTTP(w, r)
 | 
			
		||||
			rw := w.(ResponseWriter)
 | 
			
		||||
 | 
			
		||||
			buf := bytes.NewBuffer([]byte{})
 | 
			
		||||
 
 | 
			
		||||
@@ -275,6 +275,17 @@ func APIContexter() func(http.Handler) http.Handler {
 | 
			
		||||
			ctx.Data["CsrfToken"] = html.EscapeString(ctx.csrf.GetToken())
 | 
			
		||||
 | 
			
		||||
			next.ServeHTTP(ctx.Resp, ctx.Req)
 | 
			
		||||
 | 
			
		||||
			// Handle adding signedUserName to the context for the AccessLogger
 | 
			
		||||
			usernameInterface := ctx.Data["SignedUserName"]
 | 
			
		||||
			identityPtrInterface := ctx.Req.Context().Value(signedUserNameStringPointerKey)
 | 
			
		||||
			if usernameInterface != nil && identityPtrInterface != nil {
 | 
			
		||||
				username := usernameInterface.(string)
 | 
			
		||||
				identityPtr := identityPtrInterface.(*string)
 | 
			
		||||
				if identityPtr != nil && username != "" {
 | 
			
		||||
					*identityPtr = username
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -774,6 +774,17 @@ func Contexter() func(next http.Handler) http.Handler {
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			next.ServeHTTP(ctx.Resp, ctx.Req)
 | 
			
		||||
 | 
			
		||||
			// Handle adding signedUserName to the context for the AccessLogger
 | 
			
		||||
			usernameInterface := ctx.Data["SignedUserName"]
 | 
			
		||||
			identityPtrInterface := ctx.Req.Context().Value(signedUserNameStringPointerKey)
 | 
			
		||||
			if usernameInterface != nil && identityPtrInterface != nil {
 | 
			
		||||
				username := usernameInterface.(string)
 | 
			
		||||
				identityPtr := identityPtrInterface.(*string)
 | 
			
		||||
				if identityPtr != nil && username != "" {
 | 
			
		||||
					*identityPtr = username
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,9 @@ package doctor
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"path"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models"
 | 
			
		||||
@@ -14,6 +16,9 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/repository"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/modules/structs"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
	lru "github.com/hashicorp/golang-lru"
 | 
			
		||||
	"xorm.io/builder"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -75,6 +80,7 @@ func checkUserStarNum(logger log.Logger, autofix bool) error {
 | 
			
		||||
func checkEnablePushOptions(logger log.Logger, autofix bool) error {
 | 
			
		||||
	numRepos := 0
 | 
			
		||||
	numNeedUpdate := 0
 | 
			
		||||
 | 
			
		||||
	if err := iterateRepositories(func(repo *models.Repository) error {
 | 
			
		||||
		numRepos++
 | 
			
		||||
		r, err := git.OpenRepository(repo.RepoPath())
 | 
			
		||||
@@ -114,6 +120,66 @@ func checkEnablePushOptions(logger log.Logger, autofix bool) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func checkDaemonExport(logger log.Logger, autofix bool) error {
 | 
			
		||||
	numRepos := 0
 | 
			
		||||
	numNeedUpdate := 0
 | 
			
		||||
	cache, err := lru.New(512)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.Critical("Unable to create cache: %v", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := iterateRepositories(func(repo *models.Repository) error {
 | 
			
		||||
		numRepos++
 | 
			
		||||
 | 
			
		||||
		if owner, has := cache.Get(repo.OwnerID); has {
 | 
			
		||||
			repo.Owner = owner.(*models.User)
 | 
			
		||||
		} else {
 | 
			
		||||
			if err := repo.GetOwner(); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			cache.Add(repo.OwnerID, repo.Owner)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Create/Remove git-daemon-export-ok for git-daemon...
 | 
			
		||||
		daemonExportFile := path.Join(repo.RepoPath(), `git-daemon-export-ok`)
 | 
			
		||||
		isExist, err := util.IsExist(daemonExportFile)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Error("Unable to check if %s exists. Error: %v", daemonExportFile, err)
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		isPublic := !repo.IsPrivate && repo.Owner.Visibility == structs.VisibleTypePublic
 | 
			
		||||
 | 
			
		||||
		if isPublic != isExist {
 | 
			
		||||
			numNeedUpdate++
 | 
			
		||||
			if autofix {
 | 
			
		||||
				if !isPublic && isExist {
 | 
			
		||||
					if err = util.Remove(daemonExportFile); err != nil {
 | 
			
		||||
						log.Error("Failed to remove %s: %v", daemonExportFile, err)
 | 
			
		||||
					}
 | 
			
		||||
				} else if isPublic && !isExist {
 | 
			
		||||
					if f, err := os.Create(daemonExportFile); err != nil {
 | 
			
		||||
						log.Error("Failed to create %s: %v", daemonExportFile, err)
 | 
			
		||||
					} else {
 | 
			
		||||
						f.Close()
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		logger.Critical("Unable to checkDaemonExport: %v", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if autofix {
 | 
			
		||||
		logger.Info("Updated git-daemon-export-ok files for %d of %d repositories.", numNeedUpdate, numRepos)
 | 
			
		||||
	} else {
 | 
			
		||||
		logger.Info("Checked %d repositories, %d need updates.", numRepos, numNeedUpdate)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	Register(&Check{
 | 
			
		||||
		Title:     "Check if SCRIPT_TYPE is available",
 | 
			
		||||
@@ -143,4 +209,11 @@ func init() {
 | 
			
		||||
		Run:       checkEnablePushOptions,
 | 
			
		||||
		Priority:  7,
 | 
			
		||||
	})
 | 
			
		||||
	Register(&Check{
 | 
			
		||||
		Title:     "Check git-daemon-export-ok files",
 | 
			
		||||
		Name:      "check-git-daemon-export-ok",
 | 
			
		||||
		IsDefault: false,
 | 
			
		||||
		Run:       checkDaemonExport,
 | 
			
		||||
		Priority:  8,
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,8 @@ import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
	"github.com/dgrijalva/jwt-go"
 | 
			
		||||
 | 
			
		||||
	"github.com/golang-jwt/jwt"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// NewInternalToken generate a new value intended to be used by INTERNAL_TOKEN.
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@ package git
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"context"
 | 
			
		||||
	"io"
 | 
			
		||||
	"math"
 | 
			
		||||
	"strconv"
 | 
			
		||||
@@ -28,16 +29,20 @@ type WriteCloserError interface {
 | 
			
		||||
func CatFileBatchCheck(repoPath string) (WriteCloserError, *bufio.Reader, func()) {
 | 
			
		||||
	batchStdinReader, batchStdinWriter := io.Pipe()
 | 
			
		||||
	batchStdoutReader, batchStdoutWriter := io.Pipe()
 | 
			
		||||
	ctx, ctxCancel := context.WithCancel(DefaultContext)
 | 
			
		||||
	closed := make(chan struct{})
 | 
			
		||||
	cancel := func() {
 | 
			
		||||
		_ = batchStdinReader.Close()
 | 
			
		||||
		_ = batchStdinWriter.Close()
 | 
			
		||||
		_ = batchStdoutReader.Close()
 | 
			
		||||
		_ = batchStdoutWriter.Close()
 | 
			
		||||
		ctxCancel()
 | 
			
		||||
		<-closed
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		stderr := strings.Builder{}
 | 
			
		||||
		err := NewCommand("cat-file", "--batch-check").RunInDirFullPipeline(repoPath, batchStdoutWriter, &stderr, batchStdinReader)
 | 
			
		||||
		err := NewCommandContext(ctx, "cat-file", "--batch-check").RunInDirFullPipeline(repoPath, batchStdoutWriter, &stderr, batchStdinReader)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			_ = batchStdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String()))
 | 
			
		||||
			_ = batchStdinReader.CloseWithError(ConcatenateError(err, (&stderr).String()))
 | 
			
		||||
@@ -45,6 +50,7 @@ func CatFileBatchCheck(repoPath string) (WriteCloserError, *bufio.Reader, func()
 | 
			
		||||
			_ = batchStdoutWriter.Close()
 | 
			
		||||
			_ = batchStdinReader.Close()
 | 
			
		||||
		}
 | 
			
		||||
		close(closed)
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	// For simplicities sake we'll use a buffered reader to read from the cat-file --batch-check
 | 
			
		||||
@@ -59,16 +65,20 @@ func CatFileBatch(repoPath string) (WriteCloserError, *bufio.Reader, func()) {
 | 
			
		||||
	// so let's create a batch stdin and stdout
 | 
			
		||||
	batchStdinReader, batchStdinWriter := io.Pipe()
 | 
			
		||||
	batchStdoutReader, batchStdoutWriter := nio.Pipe(buffer.New(32 * 1024))
 | 
			
		||||
	ctx, ctxCancel := context.WithCancel(DefaultContext)
 | 
			
		||||
	closed := make(chan struct{})
 | 
			
		||||
	cancel := func() {
 | 
			
		||||
		_ = batchStdinReader.Close()
 | 
			
		||||
		_ = batchStdinWriter.Close()
 | 
			
		||||
		_ = batchStdoutReader.Close()
 | 
			
		||||
		_ = batchStdoutWriter.Close()
 | 
			
		||||
		ctxCancel()
 | 
			
		||||
		<-closed
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		stderr := strings.Builder{}
 | 
			
		||||
		err := NewCommand("cat-file", "--batch").RunInDirFullPipeline(repoPath, batchStdoutWriter, &stderr, batchStdinReader)
 | 
			
		||||
		err := NewCommandContext(ctx, "cat-file", "--batch").RunInDirFullPipeline(repoPath, batchStdoutWriter, &stderr, batchStdinReader)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			_ = batchStdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String()))
 | 
			
		||||
			_ = batchStdinReader.CloseWithError(ConcatenateError(err, (&stderr).String()))
 | 
			
		||||
@@ -76,6 +86,7 @@ func CatFileBatch(repoPath string) (WriteCloserError, *bufio.Reader, func()) {
 | 
			
		||||
			_ = batchStdoutWriter.Close()
 | 
			
		||||
			_ = batchStdinReader.Close()
 | 
			
		||||
		}
 | 
			
		||||
		close(closed)
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	// For simplicities sake we'll us a buffered reader to read from the cat-file --batch
 | 
			
		||||
 
 | 
			
		||||
@@ -36,6 +36,9 @@ func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note)
 | 
			
		||||
			remainingCommitID = remainingCommitID[2:]
 | 
			
		||||
		}
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			if err == object.ErrDirectoryNotFound {
 | 
			
		||||
				return ErrNotExist{ID: remainingCommitID, RelPath: path}
 | 
			
		||||
			}
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -39,3 +39,15 @@ func TestGetNestedNotes(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, []byte("Note 1"), note.Message)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetNonExistentNotes(t *testing.T) {
 | 
			
		||||
	bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
 | 
			
		||||
	bareRepo1, err := OpenRepository(bareRepo1Path)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	defer bareRepo1.Close()
 | 
			
		||||
 | 
			
		||||
	note := Note{}
 | 
			
		||||
	err = GetNote(context.Background(), bareRepo1, "non_existent_sha", ¬e)
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
	assert.IsType(t, ErrNotExist{}, err)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@
 | 
			
		||||
package git
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -45,3 +46,23 @@ func (t *Tree) SubTree(rpath string) (*Tree, error) {
 | 
			
		||||
	}
 | 
			
		||||
	return g, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LsTree checks if the given filenames are in the tree
 | 
			
		||||
func (repo *Repository) LsTree(ref string, filenames ...string) ([]string, error) {
 | 
			
		||||
	cmd := NewCommand("ls-tree", "-z", "--name-only", "--", ref)
 | 
			
		||||
	for _, arg := range filenames {
 | 
			
		||||
		if arg != "" {
 | 
			
		||||
			cmd.AddArguments(arg)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	res, err := cmd.RunInDirBytes(repo.Path)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	filelist := make([]string, 0, len(filenames))
 | 
			
		||||
	for _, line := range bytes.Split(res, []byte{'\000'}) {
 | 
			
		||||
		filelist = append(filelist, string(line))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return filelist, err
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -166,6 +166,11 @@ func File(numLines int, fileName string, code []byte) map[int]string {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	htmlw.Flush()
 | 
			
		||||
	finalNewLine := false
 | 
			
		||||
	if len(code) > 0 {
 | 
			
		||||
		finalNewLine = code[len(code)-1] == '\n'
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	m := make(map[int]string, numLines)
 | 
			
		||||
	for k, v := range strings.SplitN(htmlbuf.String(), "\n", numLines) {
 | 
			
		||||
		line := k + 1
 | 
			
		||||
@@ -173,9 +178,17 @@ func File(numLines int, fileName string, code []byte) map[int]string {
 | 
			
		||||
		//need to keep lines that are only \n so copy/paste works properly in browser
 | 
			
		||||
		if content == "" {
 | 
			
		||||
			content = "\n"
 | 
			
		||||
		} else if content == `</span><span class="w">` {
 | 
			
		||||
			content += "\n</span>"
 | 
			
		||||
		}
 | 
			
		||||
		content = strings.TrimSuffix(content, `<span class="w">`)
 | 
			
		||||
		content = strings.TrimPrefix(content, `</span>`)
 | 
			
		||||
		m[line] = content
 | 
			
		||||
	}
 | 
			
		||||
	if finalNewLine {
 | 
			
		||||
		m[numLines+1] = "<span class=\"w\">\n</span>"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return m
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										103
									
								
								modules/highlight/highlight_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								modules/highlight/highlight_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,103 @@
 | 
			
		||||
// 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 highlight
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"gopkg.in/ini.v1"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestFile(t *testing.T) {
 | 
			
		||||
	setting.Cfg = ini.Empty()
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name     string
 | 
			
		||||
		numLines int
 | 
			
		||||
		fileName string
 | 
			
		||||
		code     string
 | 
			
		||||
		want     map[int]string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name:     ".drone.yml",
 | 
			
		||||
			numLines: 12,
 | 
			
		||||
			fileName: ".drone.yml",
 | 
			
		||||
			code: `kind: pipeline
 | 
			
		||||
name: default
 | 
			
		||||
 | 
			
		||||
steps:
 | 
			
		||||
- name: test
 | 
			
		||||
	image: golang:1.13
 | 
			
		||||
	environment:
 | 
			
		||||
		GOPROXY: https://goproxy.cn
 | 
			
		||||
	commands:
 | 
			
		||||
	- go get -u
 | 
			
		||||
	- go build -v
 | 
			
		||||
	- go test -v -race -coverprofile=coverage.txt -covermode=atomic
 | 
			
		||||
`,
 | 
			
		||||
			want: map[int]string{
 | 
			
		||||
				1: `<span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">pipeline</span>`,
 | 
			
		||||
				2: `<span class="w"></span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">default</span>`,
 | 
			
		||||
				3: `<span class="w">
 | 
			
		||||
</span>`,
 | 
			
		||||
				4: `<span class="w"></span><span class="nt">steps</span><span class="p">:</span>`,
 | 
			
		||||
				5: `<span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">test</span>`,
 | 
			
		||||
				6: `<span class="w">	</span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">golang:1.13</span>`,
 | 
			
		||||
				7: `<span class="w">	</span><span class="nt">environment</span><span class="p">:</span>`,
 | 
			
		||||
				8: `<span class="w"></span><span class="w">		</span><span class="nt">GOPROXY</span><span class="p">:</span><span class="w"> </span><span class="l">https://goproxy.cn</span>`,
 | 
			
		||||
				9: `<span class="w">	</span><span class="nt">commands</span><span class="p">:</span>`,
 | 
			
		||||
				10: `<span class="w"></span><span class="w">	</span>- <span class="l">go get -u</span>`,
 | 
			
		||||
				11: `<span class="w">	</span>- <span class="l">go build -v</span>`,
 | 
			
		||||
				12: `<span class="w">	</span>- <span class="l">go test -v -race -coverprofile=coverage.txt -covermode=atomic</span><span class="w">
 | 
			
		||||
</span>`,
 | 
			
		||||
				13: `<span class="w">
 | 
			
		||||
</span>`,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     ".drone.yml - trailing space",
 | 
			
		||||
			numLines: 13,
 | 
			
		||||
			fileName: ".drone.yml",
 | 
			
		||||
			code: `kind: pipeline
 | 
			
		||||
name: default  ` + `
 | 
			
		||||
 | 
			
		||||
steps:
 | 
			
		||||
- name: test
 | 
			
		||||
	image: golang:1.13
 | 
			
		||||
	environment:
 | 
			
		||||
		GOPROXY: https://goproxy.cn
 | 
			
		||||
	commands:
 | 
			
		||||
	- go get -u
 | 
			
		||||
	- go build -v
 | 
			
		||||
	- go test -v -race -coverprofile=coverage.txt -covermode=atomic
 | 
			
		||||
	`,
 | 
			
		||||
			want: map[int]string{
 | 
			
		||||
				1: `<span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">pipeline</span>`,
 | 
			
		||||
				2: `<span class="w"></span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">default  </span>`,
 | 
			
		||||
				3: `<span class="w">
 | 
			
		||||
</span>`,
 | 
			
		||||
				4: `<span class="w"></span><span class="nt">steps</span><span class="p">:</span>`,
 | 
			
		||||
				5: `<span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">test</span>`,
 | 
			
		||||
				6: `<span class="w">	</span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">golang:1.13</span>`,
 | 
			
		||||
				7: `<span class="w">	</span><span class="nt">environment</span><span class="p">:</span>`,
 | 
			
		||||
				8: `<span class="w"></span><span class="w">		</span><span class="nt">GOPROXY</span><span class="p">:</span><span class="w"> </span><span class="l">https://goproxy.cn</span>`,
 | 
			
		||||
				9: `<span class="w">	</span><span class="nt">commands</span><span class="p">:</span>`,
 | 
			
		||||
				10: `<span class="w"></span><span class="w">	</span>- <span class="l">go get -u</span>`,
 | 
			
		||||
				11: `<span class="w">	</span>- <span class="l">go build -v</span>`,
 | 
			
		||||
				12: `<span class="w">	</span>- <span class="l">go test -v -race -coverprofile=coverage.txt -covermode=atomic</span>`,
 | 
			
		||||
				13: `<span class="w">	</span>`,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			if got := File(tt.numLines, tt.fileName, []byte(tt.code)); !reflect.DeepEqual(got, tt.want) {
 | 
			
		||||
				t.Errorf("File() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										59
									
								
								modules/indexer/bleve/batch.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								modules/indexer/bleve/batch.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,59 @@
 | 
			
		||||
// 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 bleve
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/blevesearch/bleve/v2"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// FlushingBatch is a batch of operations that automatically flushes to the
 | 
			
		||||
// underlying index once it reaches a certain size.
 | 
			
		||||
type FlushingBatch struct {
 | 
			
		||||
	maxBatchSize int
 | 
			
		||||
	batch        *bleve.Batch
 | 
			
		||||
	index        bleve.Index
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewFlushingBatch creates a new flushing batch for the specified index. Once
 | 
			
		||||
// the number of operations in the batch reaches the specified limit, the batch
 | 
			
		||||
// automatically flushes its operations to the index.
 | 
			
		||||
func NewFlushingBatch(index bleve.Index, maxBatchSize int) *FlushingBatch {
 | 
			
		||||
	return &FlushingBatch{
 | 
			
		||||
		maxBatchSize: maxBatchSize,
 | 
			
		||||
		batch:        index.NewBatch(),
 | 
			
		||||
		index:        index,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Index add a new index to batch
 | 
			
		||||
func (b *FlushingBatch) Index(id string, data interface{}) error {
 | 
			
		||||
	if err := b.batch.Index(id, data); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return b.flushIfFull()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Delete add a delete index to batch
 | 
			
		||||
func (b *FlushingBatch) Delete(id string) error {
 | 
			
		||||
	b.batch.Delete(id)
 | 
			
		||||
	return b.flushIfFull()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *FlushingBatch) flushIfFull() error {
 | 
			
		||||
	if b.batch.Size() < b.maxBatchSize {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return b.Flush()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Flush submit the batch and create a new one
 | 
			
		||||
func (b *FlushingBatch) Flush() error {
 | 
			
		||||
	err := b.index.Batch(b.batch)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	b.batch = b.index.NewBatch()
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -18,6 +18,7 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/modules/analyze"
 | 
			
		||||
	"code.gitea.io/gitea/modules/charset"
 | 
			
		||||
	"code.gitea.io/gitea/modules/git"
 | 
			
		||||
	gitea_bleve "code.gitea.io/gitea/modules/indexer/bleve"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/modules/timeutil"
 | 
			
		||||
@@ -176,7 +177,8 @@ func NewBleveIndexer(indexDir string) (*BleveIndexer, bool, error) {
 | 
			
		||||
	return indexer, created, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *BleveIndexer) addUpdate(batchWriter git.WriteCloserError, batchReader *bufio.Reader, commitSha string, update fileUpdate, repo *models.Repository, batch rupture.FlushingBatch) error {
 | 
			
		||||
func (b *BleveIndexer) addUpdate(batchWriter git.WriteCloserError, batchReader *bufio.Reader, commitSha string,
 | 
			
		||||
	update fileUpdate, repo *models.Repository, batch *gitea_bleve.FlushingBatch) error {
 | 
			
		||||
	// Ignore vendored files in code search
 | 
			
		||||
	if setting.Indexer.ExcludeVendored && analyze.IsVendor(update.Filename) {
 | 
			
		||||
		return nil
 | 
			
		||||
@@ -229,7 +231,7 @@ func (b *BleveIndexer) addUpdate(batchWriter git.WriteCloserError, batchReader *
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *BleveIndexer) addDelete(filename string, repo *models.Repository, batch rupture.FlushingBatch) error {
 | 
			
		||||
func (b *BleveIndexer) addDelete(filename string, repo *models.Repository, batch *gitea_bleve.FlushingBatch) error {
 | 
			
		||||
	id := filenameIndexerID(repo.ID, filename)
 | 
			
		||||
	return batch.Delete(id)
 | 
			
		||||
}
 | 
			
		||||
@@ -267,7 +269,7 @@ func (b *BleveIndexer) Close() {
 | 
			
		||||
 | 
			
		||||
// Index indexes the data
 | 
			
		||||
func (b *BleveIndexer) Index(repo *models.Repository, sha string, changes *repoChanges) error {
 | 
			
		||||
	batch := rupture.NewFlushingBatch(b.indexer, maxBatchSize)
 | 
			
		||||
	batch := gitea_bleve.NewFlushingBatch(b.indexer, maxBatchSize)
 | 
			
		||||
	if len(changes.Updates) > 0 {
 | 
			
		||||
 | 
			
		||||
		batchWriter, batchReader, cancel := git.CatFileBatch(repo.RepoPath())
 | 
			
		||||
@@ -296,7 +298,7 @@ func (b *BleveIndexer) Delete(repoID int64) error {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	batch := rupture.NewFlushingBatch(b.indexer, maxBatchSize)
 | 
			
		||||
	batch := gitea_bleve.NewFlushingBatch(b.indexer, maxBatchSize)
 | 
			
		||||
	for _, hit := range result.Hits {
 | 
			
		||||
		if err = batch.Delete(hit.ID); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
 
 | 
			
		||||
@@ -9,8 +9,10 @@ import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"strconv"
 | 
			
		||||
 | 
			
		||||
	gitea_bleve "code.gitea.io/gitea/modules/indexer/bleve"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
 | 
			
		||||
	"github.com/blevesearch/bleve/v2"
 | 
			
		||||
	"github.com/blevesearch/bleve/v2/analysis/analyzer/custom"
 | 
			
		||||
	"github.com/blevesearch/bleve/v2/analysis/token/lowercase"
 | 
			
		||||
@@ -197,7 +199,7 @@ func (b *BleveIndexer) Close() {
 | 
			
		||||
 | 
			
		||||
// Index will save the index data
 | 
			
		||||
func (b *BleveIndexer) Index(issues []*IndexerData) error {
 | 
			
		||||
	batch := rupture.NewFlushingBatch(b.indexer, maxBatchSize)
 | 
			
		||||
	batch := gitea_bleve.NewFlushingBatch(b.indexer, maxBatchSize)
 | 
			
		||||
	for _, issue := range issues {
 | 
			
		||||
		if err := batch.Index(indexerID(issue.ID), struct {
 | 
			
		||||
			RepoID   int64
 | 
			
		||||
@@ -218,7 +220,7 @@ func (b *BleveIndexer) Index(issues []*IndexerData) error {
 | 
			
		||||
 | 
			
		||||
// Delete deletes indexes by ids
 | 
			
		||||
func (b *BleveIndexer) Delete(ids ...int64) error {
 | 
			
		||||
	batch := rupture.NewFlushingBatch(b.indexer, maxBatchSize)
 | 
			
		||||
	batch := gitea_bleve.NewFlushingBatch(b.indexer, maxBatchSize)
 | 
			
		||||
	for _, id := range ids {
 | 
			
		||||
		if err := batch.Delete(indexerID(id)); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
 
 | 
			
		||||
@@ -143,7 +143,7 @@ type MultiChannelledLog struct {
 | 
			
		||||
	name            string
 | 
			
		||||
	bufferLength    int64
 | 
			
		||||
	queue           chan *Event
 | 
			
		||||
	mutex           sync.Mutex
 | 
			
		||||
	rwmutex         sync.RWMutex
 | 
			
		||||
	loggers         map[string]EventLogger
 | 
			
		||||
	flush           chan bool
 | 
			
		||||
	close           chan bool
 | 
			
		||||
@@ -173,10 +173,10 @@ func NewMultiChannelledLog(name string, bufferLength int64) *MultiChannelledLog
 | 
			
		||||
 | 
			
		||||
// AddLogger adds a logger to this MultiChannelledLog
 | 
			
		||||
func (m *MultiChannelledLog) AddLogger(logger EventLogger) error {
 | 
			
		||||
	m.mutex.Lock()
 | 
			
		||||
	m.rwmutex.Lock()
 | 
			
		||||
	name := logger.GetName()
 | 
			
		||||
	if _, has := m.loggers[name]; has {
 | 
			
		||||
		m.mutex.Unlock()
 | 
			
		||||
		m.rwmutex.Unlock()
 | 
			
		||||
		return ErrDuplicateName{name}
 | 
			
		||||
	}
 | 
			
		||||
	m.loggers[name] = logger
 | 
			
		||||
@@ -186,7 +186,7 @@ func (m *MultiChannelledLog) AddLogger(logger EventLogger) error {
 | 
			
		||||
	if logger.GetStacktraceLevel() < m.stacktraceLevel {
 | 
			
		||||
		m.stacktraceLevel = logger.GetStacktraceLevel()
 | 
			
		||||
	}
 | 
			
		||||
	m.mutex.Unlock()
 | 
			
		||||
	m.rwmutex.Unlock()
 | 
			
		||||
	go m.Start()
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -195,15 +195,15 @@ func (m *MultiChannelledLog) AddLogger(logger EventLogger) error {
 | 
			
		||||
// NB: If you delete the last sublogger this logger will simply drop
 | 
			
		||||
// log events
 | 
			
		||||
func (m *MultiChannelledLog) DelLogger(name string) bool {
 | 
			
		||||
	m.mutex.Lock()
 | 
			
		||||
	m.rwmutex.Lock()
 | 
			
		||||
	logger, has := m.loggers[name]
 | 
			
		||||
	if !has {
 | 
			
		||||
		m.mutex.Unlock()
 | 
			
		||||
		m.rwmutex.Unlock()
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	delete(m.loggers, name)
 | 
			
		||||
	m.internalResetLevel()
 | 
			
		||||
	m.mutex.Unlock()
 | 
			
		||||
	m.rwmutex.Unlock()
 | 
			
		||||
	logger.Flush()
 | 
			
		||||
	logger.Close()
 | 
			
		||||
	return true
 | 
			
		||||
@@ -211,15 +211,15 @@ func (m *MultiChannelledLog) DelLogger(name string) bool {
 | 
			
		||||
 | 
			
		||||
// GetEventLogger returns a sub logger from this MultiChannelledLog
 | 
			
		||||
func (m *MultiChannelledLog) GetEventLogger(name string) EventLogger {
 | 
			
		||||
	m.mutex.Lock()
 | 
			
		||||
	defer m.mutex.Unlock()
 | 
			
		||||
	m.rwmutex.RLock()
 | 
			
		||||
	defer m.rwmutex.RUnlock()
 | 
			
		||||
	return m.loggers[name]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetEventLoggerNames returns a list of names
 | 
			
		||||
func (m *MultiChannelledLog) GetEventLoggerNames() []string {
 | 
			
		||||
	m.mutex.Lock()
 | 
			
		||||
	defer m.mutex.Unlock()
 | 
			
		||||
	m.rwmutex.RLock()
 | 
			
		||||
	defer m.rwmutex.RUnlock()
 | 
			
		||||
	var keys []string
 | 
			
		||||
	for k := range m.loggers {
 | 
			
		||||
		keys = append(keys, k)
 | 
			
		||||
@@ -228,12 +228,12 @@ func (m *MultiChannelledLog) GetEventLoggerNames() []string {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MultiChannelledLog) closeLoggers() {
 | 
			
		||||
	m.mutex.Lock()
 | 
			
		||||
	m.rwmutex.Lock()
 | 
			
		||||
	for _, logger := range m.loggers {
 | 
			
		||||
		logger.Flush()
 | 
			
		||||
		logger.Close()
 | 
			
		||||
	}
 | 
			
		||||
	m.mutex.Unlock()
 | 
			
		||||
	m.rwmutex.Unlock()
 | 
			
		||||
	m.closed <- true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -249,8 +249,8 @@ func (m *MultiChannelledLog) Resume() {
 | 
			
		||||
 | 
			
		||||
// ReleaseReopen causes this logger to tell its subloggers to release and reopen
 | 
			
		||||
func (m *MultiChannelledLog) ReleaseReopen() error {
 | 
			
		||||
	m.mutex.Lock()
 | 
			
		||||
	defer m.mutex.Unlock()
 | 
			
		||||
	m.rwmutex.Lock()
 | 
			
		||||
	defer m.rwmutex.Unlock()
 | 
			
		||||
	var accumulatedErr error
 | 
			
		||||
	for _, logger := range m.loggers {
 | 
			
		||||
		if err := logger.ReleaseReopen(); err != nil {
 | 
			
		||||
@@ -266,13 +266,13 @@ func (m *MultiChannelledLog) ReleaseReopen() error {
 | 
			
		||||
 | 
			
		||||
// Start processing the MultiChannelledLog
 | 
			
		||||
func (m *MultiChannelledLog) Start() {
 | 
			
		||||
	m.mutex.Lock()
 | 
			
		||||
	m.rwmutex.Lock()
 | 
			
		||||
	if m.started {
 | 
			
		||||
		m.mutex.Unlock()
 | 
			
		||||
		m.rwmutex.Unlock()
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	m.started = true
 | 
			
		||||
	m.mutex.Unlock()
 | 
			
		||||
	m.rwmutex.Unlock()
 | 
			
		||||
	paused := false
 | 
			
		||||
	for {
 | 
			
		||||
		if paused {
 | 
			
		||||
@@ -286,11 +286,11 @@ func (m *MultiChannelledLog) Start() {
 | 
			
		||||
					m.closeLoggers()
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				m.mutex.Lock()
 | 
			
		||||
				m.rwmutex.RLock()
 | 
			
		||||
				for _, logger := range m.loggers {
 | 
			
		||||
					logger.Flush()
 | 
			
		||||
				}
 | 
			
		||||
				m.mutex.Unlock()
 | 
			
		||||
				m.rwmutex.RUnlock()
 | 
			
		||||
			case <-m.close:
 | 
			
		||||
				m.closeLoggers()
 | 
			
		||||
				return
 | 
			
		||||
@@ -307,24 +307,24 @@ func (m *MultiChannelledLog) Start() {
 | 
			
		||||
				m.closeLoggers()
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			m.mutex.Lock()
 | 
			
		||||
			m.rwmutex.RLock()
 | 
			
		||||
			for _, logger := range m.loggers {
 | 
			
		||||
				err := logger.LogEvent(event)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					fmt.Println(err)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			m.mutex.Unlock()
 | 
			
		||||
			m.rwmutex.RUnlock()
 | 
			
		||||
		case _, ok := <-m.flush:
 | 
			
		||||
			if !ok {
 | 
			
		||||
				m.closeLoggers()
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			m.mutex.Lock()
 | 
			
		||||
			m.rwmutex.RLock()
 | 
			
		||||
			for _, logger := range m.loggers {
 | 
			
		||||
				logger.Flush()
 | 
			
		||||
			}
 | 
			
		||||
			m.mutex.Unlock()
 | 
			
		||||
			m.rwmutex.RUnlock()
 | 
			
		||||
		case <-m.close:
 | 
			
		||||
			m.closeLoggers()
 | 
			
		||||
			return
 | 
			
		||||
@@ -359,11 +359,15 @@ func (m *MultiChannelledLog) Flush() {
 | 
			
		||||
 | 
			
		||||
// GetLevel gets the level of this MultiChannelledLog
 | 
			
		||||
func (m *MultiChannelledLog) GetLevel() Level {
 | 
			
		||||
	m.rwmutex.RLock()
 | 
			
		||||
	defer m.rwmutex.RUnlock()
 | 
			
		||||
	return m.level
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetStacktraceLevel gets the level of this MultiChannelledLog
 | 
			
		||||
func (m *MultiChannelledLog) GetStacktraceLevel() Level {
 | 
			
		||||
	m.rwmutex.RLock()
 | 
			
		||||
	defer m.rwmutex.RUnlock()
 | 
			
		||||
	return m.stacktraceLevel
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -384,8 +388,8 @@ func (m *MultiChannelledLog) internalResetLevel() Level {
 | 
			
		||||
 | 
			
		||||
// ResetLevel will reset the level of this MultiChannelledLog
 | 
			
		||||
func (m *MultiChannelledLog) ResetLevel() Level {
 | 
			
		||||
	m.mutex.Lock()
 | 
			
		||||
	defer m.mutex.Unlock()
 | 
			
		||||
	m.rwmutex.Lock()
 | 
			
		||||
	defer m.rwmutex.Unlock()
 | 
			
		||||
	return m.internalResetLevel()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -778,7 +778,7 @@ func fullIssuePatternProcessor(ctx *RenderContext, node *html.Node) {
 | 
			
		||||
 | 
			
		||||
		// extract repo and org name from matched link like
 | 
			
		||||
		// http://localhost:3000/gituser/myrepo/issues/1
 | 
			
		||||
		linkParts := strings.Split(path.Clean(link), "/")
 | 
			
		||||
		linkParts := strings.Split(link, "/")
 | 
			
		||||
		matchOrg := linkParts[len(linkParts)-4]
 | 
			
		||||
		matchRepo := linkParts[len(linkParts)-3]
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@
 | 
			
		||||
package markup_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"io"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
@@ -526,3 +527,18 @@ func BenchmarkEmojiPostprocess(b *testing.B) {
 | 
			
		||||
		assert.NoError(b, err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFuzz(t *testing.T) {
 | 
			
		||||
	s := "t/l/issues/8#/../../a"
 | 
			
		||||
	renderContext := RenderContext{
 | 
			
		||||
		URLPrefix: "https://example.com/go-gitea/gitea",
 | 
			
		||||
		Metas: map[string]string{
 | 
			
		||||
			"user": "go-gitea",
 | 
			
		||||
			"repo": "gitea",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err := PostProcess(&renderContext, strings.NewReader(s), io.Discard)
 | 
			
		||||
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -87,7 +87,9 @@ func newParserContext(ctx *markup.RenderContext) parser.Context {
 | 
			
		||||
func actualRender(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
 | 
			
		||||
	once.Do(func() {
 | 
			
		||||
		converter = goldmark.New(
 | 
			
		||||
			goldmark.WithExtensions(extension.Table,
 | 
			
		||||
			goldmark.WithExtensions(
 | 
			
		||||
				extension.NewTable(
 | 
			
		||||
					extension.WithTableCellAlignMethod(extension.TableCellAlignAttribute)),
 | 
			
		||||
				extension.Strikethrough,
 | 
			
		||||
				extension.TaskList,
 | 
			
		||||
				extension.DefinitionList,
 | 
			
		||||
 
 | 
			
		||||
@@ -555,6 +555,9 @@ func (g *GiteaLocalUploader) newPullRequest(pr *base.PullRequest) (*models.PullR
 | 
			
		||||
 | 
			
		||||
	// download patch file
 | 
			
		||||
	err := func() error {
 | 
			
		||||
		if pr.PatchURL == "" {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		// pr.PatchURL maybe a local file
 | 
			
		||||
		ret, err := uri.Open(pr.PatchURL)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
@@ -871,6 +874,11 @@ func (g *GiteaLocalUploader) Finish() error {
 | 
			
		||||
		return ErrRepoNotCreated
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// update issue_index
 | 
			
		||||
	if err := models.RecalculateIssueIndexForRepo(g.repo.ID); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	g.repo.Status = models.RepositoryReady
 | 
			
		||||
	return models.UpdateRepositoryCols(g.repo, "status")
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,6 @@ import (
 | 
			
		||||
func TestPersistableChannelQueue(t *testing.T) {
 | 
			
		||||
	handleChan := make(chan *testData)
 | 
			
		||||
	handle := func(data ...Data) {
 | 
			
		||||
		assert.True(t, len(data) == 2)
 | 
			
		||||
		for _, datum := range data {
 | 
			
		||||
			testDatum := datum.(*testData)
 | 
			
		||||
			handleChan <- testDatum
 | 
			
		||||
 
 | 
			
		||||
@@ -6,10 +6,13 @@ package util
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"syscall"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const windowsSharingViolationError syscall.Errno = 32
 | 
			
		||||
 | 
			
		||||
// Remove removes the named file or (empty) directory with at most 5 attempts.
 | 
			
		||||
func Remove(name string) error {
 | 
			
		||||
	var err error
 | 
			
		||||
@@ -25,6 +28,12 @@ func Remove(name string) error {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if unwrapped == windowsSharingViolationError && runtime.GOOS == "windows" {
 | 
			
		||||
			// try again
 | 
			
		||||
			<-time.After(100 * time.Millisecond)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if unwrapped == syscall.ENOENT {
 | 
			
		||||
			// it's already gone
 | 
			
		||||
			return nil
 | 
			
		||||
@@ -48,6 +57,12 @@ func RemoveAll(name string) error {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if unwrapped == windowsSharingViolationError && runtime.GOOS == "windows" {
 | 
			
		||||
			// try again
 | 
			
		||||
			<-time.After(100 * time.Millisecond)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if unwrapped == syscall.ENOENT {
 | 
			
		||||
			// it's already gone
 | 
			
		||||
			return nil
 | 
			
		||||
@@ -64,13 +79,19 @@ func Rename(oldpath, newpath string) error {
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		unwrapped := err.(*os.PathError).Err
 | 
			
		||||
		unwrapped := err.(*os.LinkError).Err
 | 
			
		||||
		if unwrapped == syscall.EBUSY || unwrapped == syscall.ENOTEMPTY || unwrapped == syscall.EPERM || unwrapped == syscall.EMFILE || unwrapped == syscall.ENFILE {
 | 
			
		||||
			// try again
 | 
			
		||||
			<-time.After(100 * time.Millisecond)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if unwrapped == windowsSharingViolationError && runtime.GOOS == "windows" {
 | 
			
		||||
			// try again
 | 
			
		||||
			<-time.After(100 * time.Millisecond)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if i == 0 && os.IsNotExist(err) {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										35
									
								
								modules/util/truncate.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								modules/util/truncate.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
// Copyright 2021 The Gitea Authors. All rights reserved.
 | 
			
		||||
// Use of this source code is governed by a MIT-style
 | 
			
		||||
// license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
package util
 | 
			
		||||
 | 
			
		||||
import "unicode/utf8"
 | 
			
		||||
 | 
			
		||||
// SplitStringAtByteN splits a string at byte n accounting for rune boundaries. (Combining characters are not accounted for.)
 | 
			
		||||
func SplitStringAtByteN(input string, n int) (left, right string) {
 | 
			
		||||
	if len(input) <= n {
 | 
			
		||||
		left = input
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !utf8.ValidString(input) {
 | 
			
		||||
		left = input[:n-3] + "..."
 | 
			
		||||
		right = "..." + input[n-3:]
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// in UTF8 "…" is 3 bytes so doesn't really gain us anything...
 | 
			
		||||
	end := 0
 | 
			
		||||
	for end <= n-3 {
 | 
			
		||||
		_, size := utf8.DecodeRuneInString(input[end:])
 | 
			
		||||
		if end+size > n-3 {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		end += size
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	left = input[:end] + "…"
 | 
			
		||||
	right = "…" + input[end:]
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
@@ -269,6 +269,26 @@ func (r *Route) Get(pattern string, h ...interface{}) {
 | 
			
		||||
	r.R.Get(r.getPattern(pattern), Wrap(middlewares...))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Options delegate options method
 | 
			
		||||
func (r *Route) Options(pattern string, h ...interface{}) {
 | 
			
		||||
	var middlewares = r.getMiddlewares(h)
 | 
			
		||||
	r.R.Options(r.getPattern(pattern), Wrap(middlewares...))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetOptions delegate get and options method
 | 
			
		||||
func (r *Route) GetOptions(pattern string, h ...interface{}) {
 | 
			
		||||
	var middlewares = r.getMiddlewares(h)
 | 
			
		||||
	r.R.Get(r.getPattern(pattern), Wrap(middlewares...))
 | 
			
		||||
	r.R.Options(r.getPattern(pattern), Wrap(middlewares...))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PostOptions delegate post and options method
 | 
			
		||||
func (r *Route) PostOptions(pattern string, h ...interface{}) {
 | 
			
		||||
	var middlewares = r.getMiddlewares(h)
 | 
			
		||||
	r.R.Post(r.getPattern(pattern), Wrap(middlewares...))
 | 
			
		||||
	r.R.Options(r.getPattern(pattern), Wrap(middlewares...))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Head delegate head method
 | 
			
		||||
func (r *Route) Head(pattern string, h ...interface{}) {
 | 
			
		||||
	var middlewares = r.getMiddlewares(h)
 | 
			
		||||
 
 | 
			
		||||
@@ -1248,8 +1248,8 @@ issues.dependency.remove=Odstranit
 | 
			
		||||
issues.dependency.remove_info=Odstranit tuto závislost
 | 
			
		||||
issues.dependency.added_dependency=`přidal(a) novou závislost %s`
 | 
			
		||||
issues.dependency.removed_dependency=`odstranil(a) závislost %s`
 | 
			
		||||
issues.dependency.issue_closing_blockedby=Uzavření tohoto požadavku na natažení je blokováno následujícími úkoly
 | 
			
		||||
issues.dependency.pr_closing_blockedby=Uzavření tohoto úkolu je blokováno následujícími úkoly
 | 
			
		||||
issues.dependency.pr_closing_blockedby=Uzavření tohoto požadavku na natažení je blokováno následujícími úkoly
 | 
			
		||||
issues.dependency.issue_closing_blockedby=Uzavření tohoto úkolu je blokováno následujícími úkoly
 | 
			
		||||
issues.dependency.issue_close_blocks=Tento úkol blokuje uzavření následujících úkolů
 | 
			
		||||
issues.dependency.pr_close_blocks=Tento požadavek na natažení blokuje uzavření následujících úkolů
 | 
			
		||||
issues.dependency.issue_close_blocked=Musíte zavřít všechny úkoly, které blokují tento úkol, aby jej bylo možné zavřít.
 | 
			
		||||
 
 | 
			
		||||
@@ -1326,8 +1326,8 @@ issues.dependency.remove=Entfernen
 | 
			
		||||
issues.dependency.remove_info=Abhängigkeit löschen
 | 
			
		||||
issues.dependency.added_dependency=`hat eine neue Abhängigkeit %s hinzugefügt`
 | 
			
		||||
issues.dependency.removed_dependency=`hat eine Abhängigkeit %s entfernt`
 | 
			
		||||
issues.dependency.issue_closing_blockedby=Das Schließen dieses Pull-Requests wird von den folgenden Issues blockiert
 | 
			
		||||
issues.dependency.pr_closing_blockedby=Das Schließen dieses Issues wird von den folgenden Issues blockiert
 | 
			
		||||
issues.dependency.pr_closing_blockedby=Das Schließen dieses Pull-Requests wird von den folgenden Issues blockiert
 | 
			
		||||
issues.dependency.issue_closing_blockedby=Das Schließen dieses Issues wird von den folgenden Issues blockiert
 | 
			
		||||
issues.dependency.issue_close_blocks=Dieses Issue blockiert die Schließung der folgenden Issues
 | 
			
		||||
issues.dependency.pr_close_blocks=Dieser Pull-Request blockiert die Schließung der folgenden Issues
 | 
			
		||||
issues.dependency.issue_close_blocked=Du musst alle Issues, die dieses Issue blockieren, schließen, bevor du es schließen kannst.
 | 
			
		||||
 
 | 
			
		||||
@@ -1326,8 +1326,8 @@ issues.dependency.remove = Remove
 | 
			
		||||
issues.dependency.remove_info = Remove this dependency
 | 
			
		||||
issues.dependency.added_dependency = `added a new dependency %s`
 | 
			
		||||
issues.dependency.removed_dependency = `removed a dependency %s`
 | 
			
		||||
issues.dependency.issue_closing_blockedby = Closing this pull request is blocked by the following issues
 | 
			
		||||
issues.dependency.pr_closing_blockedby = Closing this issue is blocked by the following issues
 | 
			
		||||
issues.dependency.pr_closing_blockedby = Closing this pull request is blocked by the following issues
 | 
			
		||||
issues.dependency.issue_closing_blockedby = Closing this issue is blocked by the following issues
 | 
			
		||||
issues.dependency.issue_close_blocks = This issue blocks closing of the following issues
 | 
			
		||||
issues.dependency.pr_close_blocks = This pull request blocks closing of the following issues
 | 
			
		||||
issues.dependency.issue_close_blocked = You need to close all issues blocking this issue before you can close it.
 | 
			
		||||
 
 | 
			
		||||
@@ -1326,8 +1326,8 @@ issues.dependency.remove=Eliminar
 | 
			
		||||
issues.dependency.remove_info=Eliminar esta dependencia
 | 
			
		||||
issues.dependency.added_dependency=`añadida una nueva dependencia %s`
 | 
			
		||||
issues.dependency.removed_dependency=`eliminada una dependencia %s`
 | 
			
		||||
issues.dependency.issue_closing_blockedby=Cerrar este pull request está bloqueado por las siguientes issues
 | 
			
		||||
issues.dependency.pr_closing_blockedby=Cierre de esta incidencia es bloqueado por las siguientes incidencias
 | 
			
		||||
issues.dependency.pr_closing_blockedby=Cerrar este pull request está bloqueado por las siguientes issues
 | 
			
		||||
issues.dependency.issue_closing_blockedby=Cierre de esta incidencia es bloqueado por las siguientes incidencias
 | 
			
		||||
issues.dependency.issue_close_blocks=Esta incidencia bloquea el cierre de las siguientes incidencias
 | 
			
		||||
issues.dependency.pr_close_blocks=Este pull request bloquea el cierre de las siguientes incidencias
 | 
			
		||||
issues.dependency.issue_close_blocked=Necesita cerrar todos las incidencias que bloquean esta incidencia antes de que se puede cerrar.
 | 
			
		||||
 
 | 
			
		||||
@@ -1070,8 +1070,8 @@ issues.dependency.remove=حذف/ساقط کردن
 | 
			
		||||
issues.dependency.remove_info=حذف این وابستگی
 | 
			
		||||
issues.dependency.added_dependency=`%s یک مخزن جدید اضافه کرد`
 | 
			
		||||
issues.dependency.removed_dependency=`%s یک وابستگی را حذف کرد`
 | 
			
		||||
issues.dependency.issue_closing_blockedby=بستن این تقاضای واکشی وسط موضوعات زیر رد/ مسدود شده است
 | 
			
		||||
issues.dependency.pr_closing_blockedby=بستن این موضوع وسط موضوعات زیر رد/ مسدود شده است
 | 
			
		||||
issues.dependency.pr_closing_blockedby=بستن این تقاضای واکشی وسط موضوعات زیر رد/ مسدود شده است
 | 
			
		||||
issues.dependency.issue_closing_blockedby=بستن این موضوع وسط موضوعات زیر رد/ مسدود شده است
 | 
			
		||||
issues.dependency.issue_close_blocks=این مسئله با توجه به موضوعات مطرح شده مسدود شده است
 | 
			
		||||
issues.dependency.pr_close_blocks=این تقاضای واکشی با توجه به موضوعات مطرح شده مسدود شده است
 | 
			
		||||
issues.dependency.issue_close_blocked=شما نیاز به بستن تمامی مسائل مسدود شده مسئله قبل بستن آن هستید.
 | 
			
		||||
 
 | 
			
		||||
@@ -1277,8 +1277,8 @@ issues.dependency.remove=Supprimer
 | 
			
		||||
issues.dependency.remove_info=Supprimer cette dépendance
 | 
			
		||||
issues.dependency.added_dependency=`a ajouté une nouvelle dépendance %s`
 | 
			
		||||
issues.dependency.removed_dependency=`a supprimé une dépendance %s`
 | 
			
		||||
issues.dependency.issue_closing_blockedby=La clôture de cette demande d'ajout est bloquée par les tickets suivants
 | 
			
		||||
issues.dependency.pr_closing_blockedby=La clôture de ce ticket est bloquée par les tickets suivants
 | 
			
		||||
issues.dependency.pr_closing_blockedby=La clôture de cette demande d'ajout est bloquée par les tickets suivants
 | 
			
		||||
issues.dependency.issue_closing_blockedby=La clôture de ce ticket est bloquée par les tickets suivants
 | 
			
		||||
issues.dependency.issue_close_blocks=Cette demande d'ajout empêche la clôture des tickets suivants
 | 
			
		||||
issues.dependency.pr_close_blocks=Cette demande d'ajout empêche la clôture des tickets suivants
 | 
			
		||||
issues.dependency.issue_close_blocked=Vous devez fermer tous les tickets qui bloquent ce ticket avant de pouvoir le fermer.
 | 
			
		||||
 
 | 
			
		||||
@@ -1179,8 +1179,8 @@ issues.dependency.remove=Rimuovi
 | 
			
		||||
issues.dependency.remove_info=Rimuovi questa dipendenza
 | 
			
		||||
issues.dependency.added_dependency=`ha aggiunto una nuova dipendenza %s`
 | 
			
		||||
issues.dependency.removed_dependency=`ha rimosso una dipendenza %s`
 | 
			
		||||
issues.dependency.issue_closing_blockedby=La chiusura di questa richiesta pull è bloccata per i seguenti problemi
 | 
			
		||||
issues.dependency.pr_closing_blockedby=La chiusura di questo problema è bloccata per i seguenti problemi
 | 
			
		||||
issues.dependency.pr_closing_blockedby=La chiusura di questa richiesta pull è bloccata per i seguenti problemi
 | 
			
		||||
issues.dependency.issue_closing_blockedby=La chiusura di questo problema è bloccata per i seguenti problemi
 | 
			
		||||
issues.dependency.issue_close_blocks=Questo problema impedisce la chiusura dei seguenti problemi
 | 
			
		||||
issues.dependency.pr_close_blocks=Questa richiesta di pull impedisce la chiusura dei seguenti problemi
 | 
			
		||||
issues.dependency.issue_close_blocked=Devi chiudere tutte le anomalie che bloiccano questo problema prima di chiudelo.
 | 
			
		||||
 
 | 
			
		||||
@@ -1312,8 +1312,8 @@ issues.dependency.remove=削除
 | 
			
		||||
issues.dependency.remove_info=この依存関係を削除
 | 
			
		||||
issues.dependency.added_dependency=`が新しい依存関係を追加 %s`
 | 
			
		||||
issues.dependency.removed_dependency=`が依存関係を削除 %s`
 | 
			
		||||
issues.dependency.issue_closing_blockedby=このプルリクエストのクローズは、これらの課題によりブロックされています
 | 
			
		||||
issues.dependency.pr_closing_blockedby=この課題のクローズは、これらの課題によりブロックされています
 | 
			
		||||
issues.dependency.pr_closing_blockedby=このプルリクエストのクローズは、これらの課題によりブロックされています
 | 
			
		||||
issues.dependency.issue_closing_blockedby=この課題のクローズは、これらの課題によりブロックされています
 | 
			
		||||
issues.dependency.issue_close_blocks=この課題は、これらの課題のクローズをブロックしています
 | 
			
		||||
issues.dependency.pr_close_blocks=このプルリクエストは、これらの課題のクローズをブロックしています
 | 
			
		||||
issues.dependency.issue_close_blocked=この課題をクローズするには、ブロックしている課題をすべてクローズする必要があります。
 | 
			
		||||
 
 | 
			
		||||
@@ -1326,8 +1326,8 @@ issues.dependency.remove=Noņemt
 | 
			
		||||
issues.dependency.remove_info=Noņemt šo atkarību
 | 
			
		||||
issues.dependency.added_dependency=`pievienoja jaunu atkarību %s`
 | 
			
		||||
issues.dependency.removed_dependency=`noņema atkarību %s`
 | 
			
		||||
issues.dependency.issue_closing_blockedby=Šī izmaiņu pieprasījuma sapludināšanu bloķē sekojošas problēmas
 | 
			
		||||
issues.dependency.pr_closing_blockedby=Šīs problēmas aizvēršanu bloķē sekojošas problēmas
 | 
			
		||||
issues.dependency.pr_closing_blockedby=Šī izmaiņu pieprasījuma sapludināšanu bloķē sekojošas problēmas
 | 
			
		||||
issues.dependency.issue_closing_blockedby=Šīs problēmas aizvēršanu bloķē sekojošas problēmas
 | 
			
		||||
issues.dependency.issue_close_blocks=Šī problēma bloķē sekojošu problēmu aizvēršanu
 | 
			
		||||
issues.dependency.pr_close_blocks=Šis izmaiņu pieprasījums bloķē sekojošu problēmu aizvēršanu
 | 
			
		||||
issues.dependency.issue_close_blocked=Nepieciešams aizvērt visas problēmas, kas bloķē šo problēmu, lai to varētu aizērt.
 | 
			
		||||
 
 | 
			
		||||
@@ -738,8 +738,8 @@ issues.deleted_milestone=`(ഇല്ലാതാക്കി)`
 | 
			
		||||
issues.filter_type.all_issues=എല്ലാ ഇഷ്യൂകളും
 | 
			
		||||
issues.label_open_issues=%d തുറന്നനിലയിലുള്ള ഇഷ്യൂകള്
 | 
			
		||||
issues.label_deletion_desc=ഒരു ലേബൽ ഇല്ലാതാക്കിയാല്, അതു് നിയുകതമാക്കിയ എല്ലാ ഇഷ്യൂകളില് നിന്നും നീക്കംചെയ്യും. തുടരട്ടെ?
 | 
			
		||||
issues.dependency.issue_closing_blockedby=ഈ ലയന അഭ്യര്ത്ഥന അടയ്ക്കുന്നത് ഇനിപ്പറയുന്ന ഇഷ്യൂകള് തടയുന്നു്
 | 
			
		||||
issues.dependency.pr_closing_blockedby=ഈ ഇഷ്യു അടയ്ക്കുന്നത് ഇനിപ്പറയുന്ന ലയന അഭ്യര്ത്ഥന തടയുന്നു്
 | 
			
		||||
issues.dependency.pr_closing_blockedby=ഈ ലയന അഭ്യര്ത്ഥന അടയ്ക്കുന്നത് ഇനിപ്പറയുന്ന ഇഷ്യൂകള് തടയുന്നു്
 | 
			
		||||
issues.dependency.issue_closing_blockedby=ഈ ഇഷ്യു അടയ്ക്കുന്നത് ഇനിപ്പറയുന്ന ലയന അഭ്യര്ത്ഥന തടയുന്നു്
 | 
			
		||||
issues.dependency.issue_close_blocks=ഈ ഇഷ്യു അടയ്ക്കുന്നത് ഇനിപ്പറയുന്ന ഇഷ്യൂകള് തടയുന്നു്
 | 
			
		||||
issues.dependency.pr_close_blocks=ഈ ഇഷ്യൂകള് അടയ്ക്കുന്നത് ഈ ലയന അഭ്യര്ത്ഥന തടയുന്നു്
 | 
			
		||||
issues.dependency.issue_close_blocked=ഈ ഇഷ്യൂ അടയ്ക്കുന്നതിന് മുമ്പ് ഇതിനെ തടയുന്ന എല്ലാ ഇഷ്യൂകളും നിങ്ങൾ അടയ്ക്കേണ്ടതുണ്ട്.
 | 
			
		||||
 
 | 
			
		||||
@@ -1168,8 +1168,8 @@ issues.dependency.remove=Verwijder
 | 
			
		||||
issues.dependency.remove_info=Verwijder afhankelijkheid
 | 
			
		||||
issues.dependency.added_dependency=`voegde een nieuwe afhankelijkheid %s toe `
 | 
			
		||||
issues.dependency.removed_dependency=`verwijderde een afhankelijkheid %s`
 | 
			
		||||
issues.dependency.issue_closing_blockedby=Het sluiten van deze pull-aanvraag is geblokkeerd door de volgende kwesties
 | 
			
		||||
issues.dependency.pr_closing_blockedby=Het sluiten van deze kwestie is geblokkeerd door de volgende kwesties
 | 
			
		||||
issues.dependency.pr_closing_blockedby=Het sluiten van deze pull-aanvraag is geblokkeerd door de volgende kwesties
 | 
			
		||||
issues.dependency.issue_closing_blockedby=Het sluiten van deze kwestie is geblokkeerd door de volgende kwesties
 | 
			
		||||
issues.dependency.issue_close_blocks=Deze kwestie blokkeert het sluiten van de volgende kwesties
 | 
			
		||||
issues.dependency.pr_close_blocks=Deze pull-aanvraag blokkeert het sluiten van de volgende kwesties
 | 
			
		||||
issues.dependency.issue_close_blocked=Je moet alle kwesties die deze kwestie blokkeren sluiten voordat je deze kan sluiten.
 | 
			
		||||
 
 | 
			
		||||
@@ -1092,8 +1092,8 @@ issues.dependency.remove=Usuń
 | 
			
		||||
issues.dependency.remove_info=Usuń tę zależność
 | 
			
		||||
issues.dependency.added_dependency=`dodał nową zależność %s`
 | 
			
		||||
issues.dependency.removed_dependency=`usunął zależność %s`
 | 
			
		||||
issues.dependency.issue_closing_blockedby=Zamknięcie tego Pull Requesta jest blokowane przez następujące zgłoszenia
 | 
			
		||||
issues.dependency.pr_closing_blockedby=Zamknięcie tego zgłoszenia jest blokowane przez następujące zgłoszenia
 | 
			
		||||
issues.dependency.pr_closing_blockedby=Zamknięcie tego Pull Requesta jest blokowane przez następujące zgłoszenia
 | 
			
		||||
issues.dependency.issue_closing_blockedby=Zamknięcie tego zgłoszenia jest blokowane przez następujące zgłoszenia
 | 
			
		||||
issues.dependency.issue_close_blocks=To zgłoszenie blokuje zamknięcie następujących zgłoszeń
 | 
			
		||||
issues.dependency.pr_close_blocks=Ten Pull Request blokuje zamknięcie następujących zgłoszeń
 | 
			
		||||
issues.dependency.issue_close_blocked=Musisz zamknąć wszystkie zgłoszenia blokujące to zgłoszenie zanim je zamkniesz.
 | 
			
		||||
 
 | 
			
		||||
@@ -1186,8 +1186,8 @@ issues.dependency.add=Adicione…
 | 
			
		||||
issues.dependency.cancel=Cancelar
 | 
			
		||||
issues.dependency.remove=Remover
 | 
			
		||||
issues.dependency.remove_info=Remover esta dependência
 | 
			
		||||
issues.dependency.issue_closing_blockedby=Fechamento deste pull request está bloqueado pelas seguintes issues
 | 
			
		||||
issues.dependency.pr_closing_blockedby=Fechamento desta issue está bloqueado pelas seguintes issues
 | 
			
		||||
issues.dependency.pr_closing_blockedby=Fechamento deste pull request está bloqueado pelas seguintes issues
 | 
			
		||||
issues.dependency.issue_closing_blockedby=Fechamento desta issue está bloqueado pelas seguintes issues
 | 
			
		||||
issues.dependency.issue_close_blocks=Esta issue bloqueia o fechamento das seguintes issues
 | 
			
		||||
issues.dependency.pr_close_blocks=Este pull request bloqueia o fechamento das seguintes issues
 | 
			
		||||
issues.dependency.issue_close_blocked=Você precisa fechar todas as issues que bloqueiam esta issue antes de poder fechá-la.
 | 
			
		||||
 
 | 
			
		||||
@@ -1326,8 +1326,8 @@ issues.dependency.remove=Remover
 | 
			
		||||
issues.dependency.remove_info=Remover esta dependência
 | 
			
		||||
issues.dependency.added_dependency=`adicionou uma nova dependência %s`
 | 
			
		||||
issues.dependency.removed_dependency=`removeu uma dependência %s`
 | 
			
		||||
issues.dependency.issue_closing_blockedby=O encerramento deste pedido de integração está bloqueado pelas seguintes questões
 | 
			
		||||
issues.dependency.pr_closing_blockedby=O encerramento desta questão está bloqueado pelas seguintes questões
 | 
			
		||||
issues.dependency.pr_closing_blockedby=O encerramento deste pedido de integração está bloqueado pelas seguintes questões
 | 
			
		||||
issues.dependency.issue_closing_blockedby=O encerramento desta questão está bloqueado pelas seguintes questões
 | 
			
		||||
issues.dependency.issue_close_blocks=Esta questão bloqueia o encerramento das seguintes questões
 | 
			
		||||
issues.dependency.pr_close_blocks=Este pedido de integração bloqueia o encerramento das seguintes questões
 | 
			
		||||
issues.dependency.issue_close_blocked=Tem que encerrar todas as questões que bloqueiam esta questão antes de a poder encerrar.
 | 
			
		||||
 
 | 
			
		||||
@@ -1324,8 +1324,8 @@ issues.dependency.remove=Удалить
 | 
			
		||||
issues.dependency.remove_info=Удалить эту зависимость
 | 
			
		||||
issues.dependency.added_dependency=`добавить новую зависимость %s`
 | 
			
		||||
issues.dependency.removed_dependency=`убрал зависимость %s`
 | 
			
		||||
issues.dependency.issue_closing_blockedby=Закрытие этого запроса на слияние невозможно до закрытия следующих задач
 | 
			
		||||
issues.dependency.pr_closing_blockedby=Закрытие этой задачи блокируется следующими задачами
 | 
			
		||||
issues.dependency.pr_closing_blockedby=Закрытие этого запроса на слияние невозможно до закрытия следующих задач
 | 
			
		||||
issues.dependency.issue_closing_blockedby=Закрытие этой задачи блокируется следующими задачами
 | 
			
		||||
issues.dependency.issue_close_blocks=Эта задача блокирует закрытие следующих задач
 | 
			
		||||
issues.dependency.pr_close_blocks=Этот запрос на слияние блокирует закрытие следующих задач
 | 
			
		||||
issues.dependency.issue_close_blocked=Вам необходимо закрыть все задачи, блокирующие эту задачу, прежде чем вы сможете её закрыть.
 | 
			
		||||
 
 | 
			
		||||
@@ -1121,8 +1121,8 @@ issues.dependency.remove=Ta bort
 | 
			
		||||
issues.dependency.remove_info=Ta bort detta beroende
 | 
			
		||||
issues.dependency.added_dependency=`lade till ett nytt beroende %s`
 | 
			
		||||
issues.dependency.removed_dependency=`tog bort ett beroende %s`
 | 
			
		||||
issues.dependency.issue_closing_blockedby=En stängning av denna pull-förfrågan blockeras av följande ärenden
 | 
			
		||||
issues.dependency.pr_closing_blockedby=En stängning av ärendet blockeras av följande ärenden
 | 
			
		||||
issues.dependency.pr_closing_blockedby=En stängning av denna pull-förfrågan blockeras av följande ärenden
 | 
			
		||||
issues.dependency.issue_closing_blockedby=En stängning av ärendet blockeras av följande ärenden
 | 
			
		||||
issues.dependency.issue_close_blocks=Detta ärende blockerar en stängning av följande ärenden
 | 
			
		||||
issues.dependency.pr_close_blocks=Denna pull-förfrågan blockerar stängning av följande ärenden
 | 
			
		||||
issues.dependency.issue_close_blocked=Du måste stänga alla ärenden som blockerar det här ärendet innan du kan stänga det.
 | 
			
		||||
 
 | 
			
		||||
@@ -1252,8 +1252,8 @@ issues.dependency.remove=Kaldır
 | 
			
		||||
issues.dependency.remove_info=Bu bağımlılığı kaldır
 | 
			
		||||
issues.dependency.added_dependency=`yeni bir %s bağımlılığı eklendi`
 | 
			
		||||
issues.dependency.removed_dependency=`bir %s bağımlılığı kaldırıldı`
 | 
			
		||||
issues.dependency.issue_closing_blockedby=Bu değişiklik isteğinin kapatılması aşağıdaki konular nedeniyle engelleniyor
 | 
			
		||||
issues.dependency.pr_closing_blockedby=Bu konunun kapatılması aşağıdaki konular tarafından engelleniyor
 | 
			
		||||
issues.dependency.pr_closing_blockedby=Bu değişiklik isteğinin kapatılması aşağıdaki konular nedeniyle engelleniyor
 | 
			
		||||
issues.dependency.issue_closing_blockedby=Bu konunun kapatılması aşağıdaki konular tarafından engelleniyor
 | 
			
		||||
issues.dependency.issue_close_blocks=Bu konu aşağıdaki konuların kapatılmasını engelliyor
 | 
			
		||||
issues.dependency.pr_close_blocks=Bu değişiklik isteği aşağıdaki sorunların kapatılmasını engelliyor
 | 
			
		||||
issues.dependency.issue_close_blocked=Kapatmadan önce bu konuyu engelleyen tüm konuları kapatmanız gerekir.
 | 
			
		||||
 
 | 
			
		||||
@@ -1306,8 +1306,8 @@ issues.dependency.remove=Видалити
 | 
			
		||||
issues.dependency.remove_info=Видалити цю залежність
 | 
			
		||||
issues.dependency.added_dependency=`додав нову залежність %s`
 | 
			
		||||
issues.dependency.removed_dependency=`видалив залежність %s`
 | 
			
		||||
issues.dependency.issue_closing_blockedby=Закриття цього запиту на злиття заблокує наступні проблеми
 | 
			
		||||
issues.dependency.pr_closing_blockedby=Закриття цієї проблеми заблокує наступні проблеми
 | 
			
		||||
issues.dependency.pr_closing_blockedby=Закриття цього запиту на злиття заблокує наступні проблеми
 | 
			
		||||
issues.dependency.issue_closing_blockedby=Закриття цієї проблеми заблокує наступні проблеми
 | 
			
		||||
issues.dependency.issue_close_blocks=Ця проблема блокує закриття залежних проблем
 | 
			
		||||
issues.dependency.pr_close_blocks=Цей пулл-реквест блокує закриття залежних проблем
 | 
			
		||||
issues.dependency.issue_close_blocked=Вам потрібно закрити всі проблеми, що блокують цю проблему, перед її закриттям.
 | 
			
		||||
 
 | 
			
		||||
@@ -1322,8 +1322,8 @@ issues.dependency.remove=删除
 | 
			
		||||
issues.dependency.remove_info=删除此依赖项
 | 
			
		||||
issues.dependency.added_dependency=`添加了一个新的依赖项 %s`
 | 
			
		||||
issues.dependency.removed_dependency=`移除了一个依赖项 %s`
 | 
			
		||||
issues.dependency.issue_closing_blockedby=以下工单阻止了关闭此合并请求
 | 
			
		||||
issues.dependency.pr_closing_blockedby=以下工单阻止了关闭此工单
 | 
			
		||||
issues.dependency.pr_closing_blockedby=以下工单阻止了关闭此合并请求
 | 
			
		||||
issues.dependency.issue_closing_blockedby=以下工单阻止了关闭此工单
 | 
			
		||||
issues.dependency.issue_close_blocks=此工单阻止了以下工单的关闭
 | 
			
		||||
issues.dependency.pr_close_blocks=此合并请求阻止以下工单的关闭
 | 
			
		||||
issues.dependency.issue_close_blocked=您需要关闭所有阻止此工单的工单, 然后才能关闭它。
 | 
			
		||||
 
 | 
			
		||||
@@ -1311,8 +1311,8 @@ issues.dependency.remove=移除
 | 
			
		||||
issues.dependency.remove_info=移除此先決條件
 | 
			
		||||
issues.dependency.added_dependency=`加入了新的先決條件 %s`
 | 
			
		||||
issues.dependency.removed_dependency=`移除了先決條件 %s`
 | 
			
		||||
issues.dependency.issue_closing_blockedby=此合併請求被下列問題阻擋而無法關閉
 | 
			
		||||
issues.dependency.pr_closing_blockedby=此問題被下列問題阻擋而無法關閉
 | 
			
		||||
issues.dependency.pr_closing_blockedby=此合併請求被下列問題阻擋而無法關閉
 | 
			
		||||
issues.dependency.issue_closing_blockedby=此問題被下列問題阻擋而無法關閉
 | 
			
		||||
issues.dependency.issue_close_blocks=因為此問題的阻擋,下列問題無法被關閉
 | 
			
		||||
issues.dependency.pr_close_blocks=因為此合併請求的阻擋,下列問題無法被關閉
 | 
			
		||||
issues.dependency.issue_close_blocked=在您關閉此問題以前,您必須先關閉所有阻擋它的問題。
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										12
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -12076,9 +12076,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/tar": {
 | 
			
		||||
      "version": "6.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==",
 | 
			
		||||
      "version": "6.1.6",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.6.tgz",
 | 
			
		||||
      "integrity": "sha512-oaWyu5dQbHaYcyZCTfyPpC+VmI62/OM2RTUYavTk1MDr1cwW5Boi3baeYQKiZbY2uSQJGr+iMOzb/JFxLrft+g==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "chownr": "^2.0.0",
 | 
			
		||||
@@ -23018,9 +23018,9 @@
 | 
			
		||||
      "integrity": "sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw=="
 | 
			
		||||
    },
 | 
			
		||||
    "tar": {
 | 
			
		||||
      "version": "6.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==",
 | 
			
		||||
      "version": "6.1.6",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.6.tgz",
 | 
			
		||||
      "integrity": "sha512-oaWyu5dQbHaYcyZCTfyPpC+VmI62/OM2RTUYavTk1MDr1cwW5Boi3baeYQKiZbY2uSQJGr+iMOzb/JFxLrft+g==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "chownr": "^2.0.0",
 | 
			
		||||
 
 | 
			
		||||
@@ -569,6 +569,7 @@ func Routes() *web.Route {
 | 
			
		||||
			//setting.CORSConfig.AllowSubdomain // FIXME: the cors middleware needs allowSubdomain option
 | 
			
		||||
			AllowedMethods:   setting.CORSConfig.Methods,
 | 
			
		||||
			AllowCredentials: setting.CORSConfig.AllowCredentials,
 | 
			
		||||
			AllowedHeaders:   []string{"Authorization", "X-CSRFToken", "X-Gitea-OTP"},
 | 
			
		||||
			MaxAge:           int(setting.CORSConfig.MaxAge.Seconds()),
 | 
			
		||||
		}))
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -1254,5 +1254,6 @@ func GetPullRequestCommits(ctx *context.APIContext) {
 | 
			
		||||
	ctx.Header().Set("X-Total-Count", fmt.Sprintf("%d", totalNumberOfCommits))
 | 
			
		||||
	ctx.Header().Set("X-PageCount", strconv.Itoa(totalNumberOfPages))
 | 
			
		||||
	ctx.Header().Set("X-HasMore", strconv.FormatBool(listOptions.Page < totalNumberOfPages))
 | 
			
		||||
	ctx.Header().Set("Access-Control-Expose-Headers", "X-Total-Count, X-PerPage, X-Total, X-PageCount, X-HasMore, Link")
 | 
			
		||||
	ctx.JSON(http.StatusOK, &apiCommits)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -14,3 +14,10 @@ type swaggerResponseOAuth2Application struct {
 | 
			
		||||
	// in:body
 | 
			
		||||
	Body api.OAuth2Application `json:"body"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AccessToken represents an API access token.
 | 
			
		||||
// swagger:response AccessToken
 | 
			
		||||
type swaggerResponseAccessToken struct {
 | 
			
		||||
	// in:body
 | 
			
		||||
	Body api.AccessToken `json:"body"`
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -164,6 +164,9 @@ type swaggerParameterBodies struct {
 | 
			
		||||
	// in:body
 | 
			
		||||
	CreateTagOption api.CreateTagOption
 | 
			
		||||
 | 
			
		||||
	// in:body
 | 
			
		||||
	CreateAccessTokenOption api.CreateAccessTokenOption
 | 
			
		||||
 | 
			
		||||
	// in:body
 | 
			
		||||
	UserSettingsOptions api.UserSettingsOptions
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -76,15 +76,10 @@ func CreateAccessToken(ctx *context.APIContext) {
 | 
			
		||||
	//   description: username of user
 | 
			
		||||
	//   type: string
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// - name: accessToken
 | 
			
		||||
	// - name: userCreateToken
 | 
			
		||||
	//   in: body
 | 
			
		||||
	//   schema:
 | 
			
		||||
	//     type: object
 | 
			
		||||
	//     required:
 | 
			
		||||
	//       - name
 | 
			
		||||
	//     properties:
 | 
			
		||||
	//       name:
 | 
			
		||||
	//         type: string
 | 
			
		||||
	//     "$ref": "#/definitions/CreateAccessTokenOption"
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "201":
 | 
			
		||||
	//     "$ref": "#/responses/AccessToken"
 | 
			
		||||
 
 | 
			
		||||
@@ -392,11 +392,6 @@ func HookPreReceive(ctx *gitea_context.PrivateContext) {
 | 
			
		||||
				})
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			log.Error("Unexpected ref: %s", refFullName)
 | 
			
		||||
			ctx.JSON(http.StatusInternalServerError, private.Response{
 | 
			
		||||
				Err: fmt.Sprintf("Unexpected ref: %s", refFullName),
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,7 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/modules/upload"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
	"code.gitea.io/gitea/services/gitdiff"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -567,6 +568,18 @@ func PrepareCompareDiff(
 | 
			
		||||
	} else {
 | 
			
		||||
		title = headBranch
 | 
			
		||||
	}
 | 
			
		||||
	if len(title) > 255 {
 | 
			
		||||
		var trailer string
 | 
			
		||||
		title, trailer = util.SplitStringAtByteN(title, 255)
 | 
			
		||||
		if len(trailer) > 0 {
 | 
			
		||||
			if ctx.Data["content"] != nil {
 | 
			
		||||
				ctx.Data["content"] = fmt.Sprintf("%s\n\n%s", trailer, ctx.Data["content"])
 | 
			
		||||
			} else {
 | 
			
		||||
				ctx.Data["content"] = trailer + "\n"
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Data["title"] = title
 | 
			
		||||
	ctx.Data["Username"] = headUser.Name
 | 
			
		||||
	ctx.Data["Reponame"] = headRepo.Name
 | 
			
		||||
 
 | 
			
		||||
@@ -1728,11 +1728,13 @@ func UpdateIssueContent(ctx *context.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	files := ctx.QueryStrings("files[]")
 | 
			
		||||
	if err := updateAttachments(issue, files); err != nil {
 | 
			
		||||
	// when update the request doesn't intend to update attachments (eg: change checkbox state), ignore attachment updates
 | 
			
		||||
	if !ctx.QueryBool("ignore_attachments") {
 | 
			
		||||
		if err := updateAttachments(issue, ctx.QueryStrings("files[]")); err != nil {
 | 
			
		||||
			ctx.ServerError("UpdateAttachments", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	content, err := markdown.RenderString(&markup.RenderContext{
 | 
			
		||||
		URLPrefix: ctx.Query("context"),
 | 
			
		||||
@@ -2128,13 +2130,6 @@ func UpdateCommentContent(ctx *context.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if comment.Type == models.CommentTypeComment {
 | 
			
		||||
		if err := comment.LoadAttachments(); err != nil {
 | 
			
		||||
			ctx.ServerError("LoadAttachments", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !ctx.IsSigned || (ctx.User.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
 | 
			
		||||
		ctx.Error(http.StatusForbidden)
 | 
			
		||||
		return
 | 
			
		||||
@@ -2156,11 +2151,20 @@ func UpdateCommentContent(ctx *context.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	files := ctx.QueryStrings("files[]")
 | 
			
		||||
	if err := updateAttachments(comment, files); err != nil {
 | 
			
		||||
	if comment.Type == models.CommentTypeComment {
 | 
			
		||||
		if err := comment.LoadAttachments(); err != nil {
 | 
			
		||||
			ctx.ServerError("LoadAttachments", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// when the update request doesn't intend to update attachments (eg: change checkbox state), ignore attachment updates
 | 
			
		||||
	if !ctx.QueryBool("ignore_attachments") {
 | 
			
		||||
		if err := updateAttachments(comment, ctx.QueryStrings("files[]")); err != nil {
 | 
			
		||||
			ctx.ServerError("UpdateAttachments", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	content, err := markdown.RenderString(&markup.RenderContext{
 | 
			
		||||
		URLPrefix: ctx.Query("context"),
 | 
			
		||||
 
 | 
			
		||||
@@ -1001,10 +1001,14 @@ func CompareAndPullRequestPost(ctx *context.Context) {
 | 
			
		||||
	ctx.Data["Title"] = ctx.Tr("repo.pulls.compare_changes")
 | 
			
		||||
	ctx.Data["PageIsComparePull"] = true
 | 
			
		||||
	ctx.Data["IsDiffCompare"] = true
 | 
			
		||||
	ctx.Data["IsRepoToolbarCommits"] = true
 | 
			
		||||
	ctx.Data["RequireTribute"] = true
 | 
			
		||||
	ctx.Data["RequireSimpleMDE"] = true
 | 
			
		||||
	ctx.Data["RequireHighlightJS"] = true
 | 
			
		||||
	ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
 | 
			
		||||
	ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
 | 
			
		||||
	upload.AddUploadContext(ctx, "comment")
 | 
			
		||||
	ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWrite(models.UnitTypePullRequests)
 | 
			
		||||
 | 
			
		||||
	var (
 | 
			
		||||
		repo        = ctx.Repo.Repository
 | 
			
		||||
@@ -1037,6 +1041,14 @@ func CompareAndPullRequestPost(ctx *context.Context) {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(form.Title) > 255 {
 | 
			
		||||
			var trailer string
 | 
			
		||||
			form.Title, trailer = util.SplitStringAtByteN(form.Title, 255)
 | 
			
		||||
 | 
			
		||||
			form.Content = trailer + "\n\n" + form.Content
 | 
			
		||||
		}
 | 
			
		||||
		middleware.AssignForm(form, ctx.Data)
 | 
			
		||||
 | 
			
		||||
		ctx.HTML(http.StatusOK, tplCompareDiff)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -81,7 +81,7 @@ func TestWiki(t *testing.T) {
 | 
			
		||||
	Wiki(ctx)
 | 
			
		||||
	assert.EqualValues(t, http.StatusOK, ctx.Resp.Status())
 | 
			
		||||
	assert.EqualValues(t, "Home", ctx.Data["Title"])
 | 
			
		||||
	assertPagesMetas(t, []string{"Home", "Page With Image", "Page With Spaced Name"}, ctx.Data["Pages"])
 | 
			
		||||
	assertPagesMetas(t, []string{"Home", "Page With Image", "Page With Spaced Name", "Unescaped File"}, ctx.Data["Pages"])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestWikiPages(t *testing.T) {
 | 
			
		||||
@@ -91,7 +91,7 @@ func TestWikiPages(t *testing.T) {
 | 
			
		||||
	test.LoadRepo(t, ctx, 1)
 | 
			
		||||
	WikiPages(ctx)
 | 
			
		||||
	assert.EqualValues(t, http.StatusOK, ctx.Resp.Status())
 | 
			
		||||
	assertPagesMetas(t, []string{"Home", "Page With Image", "Page With Spaced Name"}, ctx.Data["Pages"])
 | 
			
		||||
	assertPagesMetas(t, []string{"Home", "Page With Image", "Page With Spaced Name", "Unescaped File"}, ctx.Data["Pages"])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestNewWiki(t *testing.T) {
 | 
			
		||||
 
 | 
			
		||||
@@ -50,6 +50,7 @@ func Notifications(c *context.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if c.QueryBool("div-only") {
 | 
			
		||||
		c.Data["SequenceNumber"] = c.Query("sequence-number")
 | 
			
		||||
		c.HTML(http.StatusOK, tplNotificationDiv)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
@@ -175,6 +176,7 @@ func NotificationStatusPost(c *context.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	c.Data["Link"] = setting.AppURL + "notifications"
 | 
			
		||||
	c.Data["SequenceNumber"] = c.Req.PostFormValue("sequence-number")
 | 
			
		||||
 | 
			
		||||
	c.HTML(http.StatusOK, tplNotificationDiv)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,7 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/services/forms"
 | 
			
		||||
 | 
			
		||||
	"gitea.com/go-chi/binding"
 | 
			
		||||
	"github.com/dgrijalva/jwt-go"
 | 
			
		||||
	"github.com/golang-jwt/jwt"
 | 
			
		||||
	jsoniter "github.com/json-iterator/go"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -187,7 +187,7 @@ func newAccessTokenResponse(grant *models.OAuth2Grant, signingKey oauth2.JWTSign
 | 
			
		||||
				ErrorDescription: "cannot find application",
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		err = app.LoadUser()
 | 
			
		||||
		user, err := models.GetUserByID(grant.UserID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			if models.IsErrUserNotExist(err) {
 | 
			
		||||
				return nil, &AccessTokenError{
 | 
			
		||||
@@ -212,17 +212,17 @@ func newAccessTokenResponse(grant *models.OAuth2Grant, signingKey oauth2.JWTSign
 | 
			
		||||
			Nonce: grant.Nonce,
 | 
			
		||||
		}
 | 
			
		||||
		if grant.ScopeContains("profile") {
 | 
			
		||||
			idToken.Name = app.User.FullName
 | 
			
		||||
			idToken.PreferredUsername = app.User.Name
 | 
			
		||||
			idToken.Profile = app.User.HTMLURL()
 | 
			
		||||
			idToken.Picture = app.User.AvatarLink()
 | 
			
		||||
			idToken.Website = app.User.Website
 | 
			
		||||
			idToken.Locale = app.User.Language
 | 
			
		||||
			idToken.UpdatedAt = app.User.UpdatedUnix
 | 
			
		||||
			idToken.Name = user.FullName
 | 
			
		||||
			idToken.PreferredUsername = user.Name
 | 
			
		||||
			idToken.Profile = user.HTMLURL()
 | 
			
		||||
			idToken.Picture = user.AvatarLink()
 | 
			
		||||
			idToken.Website = user.Website
 | 
			
		||||
			idToken.Locale = user.Language
 | 
			
		||||
			idToken.UpdatedAt = user.UpdatedUnix
 | 
			
		||||
		}
 | 
			
		||||
		if grant.ScopeContains("email") {
 | 
			
		||||
			idToken.Email = app.User.Email
 | 
			
		||||
			idToken.EmailVerified = app.User.IsActive
 | 
			
		||||
			idToken.Email = user.Email
 | 
			
		||||
			idToken.EmailVerified = user.IsActive
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		signedIDToken, err = idToken.SignToken(signingKey)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										75
									
								
								routers/web/user/oauth_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								routers/web/user/oauth_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 user
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models"
 | 
			
		||||
	"code.gitea.io/gitea/modules/auth/oauth2"
 | 
			
		||||
 | 
			
		||||
	"github.com/golang-jwt/jwt"
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func createAndParseToken(t *testing.T, grant *models.OAuth2Grant) *models.OIDCToken {
 | 
			
		||||
	signingKey, err := oauth2.CreateJWTSingingKey("HS256", make([]byte, 32))
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.NotNil(t, signingKey)
 | 
			
		||||
	oauth2.DefaultSigningKey = signingKey
 | 
			
		||||
 | 
			
		||||
	response, terr := newAccessTokenResponse(grant, signingKey)
 | 
			
		||||
	assert.Nil(t, terr)
 | 
			
		||||
	assert.NotNil(t, response)
 | 
			
		||||
 | 
			
		||||
	parsedToken, err := jwt.ParseWithClaims(response.IDToken, &models.OIDCToken{}, func(token *jwt.Token) (interface{}, error) {
 | 
			
		||||
		assert.NotNil(t, token.Method)
 | 
			
		||||
		assert.Equal(t, signingKey.SigningMethod().Alg(), token.Method.Alg())
 | 
			
		||||
		return signingKey.VerifyKey(), nil
 | 
			
		||||
	})
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.True(t, parsedToken.Valid)
 | 
			
		||||
 | 
			
		||||
	oidcToken, ok := parsedToken.Claims.(*models.OIDCToken)
 | 
			
		||||
	assert.True(t, ok)
 | 
			
		||||
	assert.NotNil(t, oidcToken)
 | 
			
		||||
 | 
			
		||||
	return oidcToken
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestNewAccessTokenResponse_OIDCToken(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, models.PrepareTestDatabase())
 | 
			
		||||
 | 
			
		||||
	grants, err := models.GetOAuth2GrantsByUserID(3)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Len(t, grants, 1)
 | 
			
		||||
 | 
			
		||||
	// Scopes: openid
 | 
			
		||||
	oidcToken := createAndParseToken(t, grants[0])
 | 
			
		||||
	assert.Empty(t, oidcToken.Name)
 | 
			
		||||
	assert.Empty(t, oidcToken.PreferredUsername)
 | 
			
		||||
	assert.Empty(t, oidcToken.Profile)
 | 
			
		||||
	assert.Empty(t, oidcToken.Picture)
 | 
			
		||||
	assert.Empty(t, oidcToken.Website)
 | 
			
		||||
	assert.Empty(t, oidcToken.UpdatedAt)
 | 
			
		||||
	assert.Empty(t, oidcToken.Email)
 | 
			
		||||
	assert.False(t, oidcToken.EmailVerified)
 | 
			
		||||
 | 
			
		||||
	user := models.AssertExistsAndLoadBean(t, &models.User{ID: 5}).(*models.User)
 | 
			
		||||
	grants, err = models.GetOAuth2GrantsByUserID(user.ID)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Len(t, grants, 1)
 | 
			
		||||
 | 
			
		||||
	// Scopes: openid profile email
 | 
			
		||||
	oidcToken = createAndParseToken(t, grants[0])
 | 
			
		||||
	assert.Equal(t, user.FullName, oidcToken.Name)
 | 
			
		||||
	assert.Equal(t, user.Name, oidcToken.PreferredUsername)
 | 
			
		||||
	assert.Equal(t, user.HTMLURL(), oidcToken.Profile)
 | 
			
		||||
	assert.Equal(t, user.AvatarLink(), oidcToken.Picture)
 | 
			
		||||
	assert.Equal(t, user.Website, oidcToken.Website)
 | 
			
		||||
	assert.Equal(t, user.UpdatedUnix, oidcToken.UpdatedAt)
 | 
			
		||||
	assert.Equal(t, user.Email, oidcToken.Email)
 | 
			
		||||
	assert.Equal(t, user.IsActive, oidcToken.EmailVerified)
 | 
			
		||||
}
 | 
			
		||||
@@ -822,9 +822,14 @@ func RegisterRoutes(m *web.Route) {
 | 
			
		||||
			}
 | 
			
		||||
			ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
 | 
			
		||||
		})
 | 
			
		||||
		m.Get("/attachments/{uuid}", repo.GetAttachment)
 | 
			
		||||
 | 
			
		||||
	}, ignSignIn, context.RepoAssignment, context.UnitTypes(), reqRepoReleaseReader)
 | 
			
		||||
 | 
			
		||||
	// to maintain compatibility with old attachments
 | 
			
		||||
	m.Group("/{username}/{reponame}", func() {
 | 
			
		||||
		m.Get("/attachments/{uuid}", repo.GetAttachment)
 | 
			
		||||
	}, ignSignIn, context.RepoAssignment, context.UnitTypes())
 | 
			
		||||
 | 
			
		||||
	m.Group("/{username}/{reponame}", func() {
 | 
			
		||||
		m.Post("/topics", repo.TopicsPost)
 | 
			
		||||
	}, context.RepoAssignment, context.RepoMustNotBeArchived(), reqRepoAdmin)
 | 
			
		||||
@@ -1006,17 +1011,17 @@ func RegisterRoutes(m *web.Route) {
 | 
			
		||||
			}, ignSignInAndCsrf, lfsServerEnabled)
 | 
			
		||||
 | 
			
		||||
			m.Group("", func() {
 | 
			
		||||
				m.Post("/git-upload-pack", repo.ServiceUploadPack)
 | 
			
		||||
				m.Post("/git-receive-pack", repo.ServiceReceivePack)
 | 
			
		||||
				m.Get("/info/refs", repo.GetInfoRefs)
 | 
			
		||||
				m.Get("/HEAD", repo.GetTextFile("HEAD"))
 | 
			
		||||
				m.Get("/objects/info/alternates", repo.GetTextFile("objects/info/alternates"))
 | 
			
		||||
				m.Get("/objects/info/http-alternates", repo.GetTextFile("objects/info/http-alternates"))
 | 
			
		||||
				m.Get("/objects/info/packs", repo.GetInfoPacks)
 | 
			
		||||
				m.Get("/objects/info/{file:[^/]*}", repo.GetTextFile(""))
 | 
			
		||||
				m.Get("/objects/{head:[0-9a-f]{2}}/{hash:[0-9a-f]{38}}", repo.GetLooseObject)
 | 
			
		||||
				m.Get("/objects/pack/pack-{file:[0-9a-f]{40}}.pack", repo.GetPackFile)
 | 
			
		||||
				m.Get("/objects/pack/pack-{file:[0-9a-f]{40}}.idx", repo.GetIdxFile)
 | 
			
		||||
				m.PostOptions("/git-upload-pack", repo.ServiceUploadPack)
 | 
			
		||||
				m.PostOptions("/git-receive-pack", repo.ServiceReceivePack)
 | 
			
		||||
				m.GetOptions("/info/refs", repo.GetInfoRefs)
 | 
			
		||||
				m.GetOptions("/HEAD", repo.GetTextFile("HEAD"))
 | 
			
		||||
				m.GetOptions("/objects/info/alternates", repo.GetTextFile("objects/info/alternates"))
 | 
			
		||||
				m.GetOptions("/objects/info/http-alternates", repo.GetTextFile("objects/info/http-alternates"))
 | 
			
		||||
				m.GetOptions("/objects/info/packs", repo.GetInfoPacks)
 | 
			
		||||
				m.GetOptions("/objects/info/{file:[^/]*}", repo.GetTextFile(""))
 | 
			
		||||
				m.GetOptions("/objects/{head:[0-9a-f]{2}}/{hash:[0-9a-f]{38}}", repo.GetLooseObject)
 | 
			
		||||
				m.GetOptions("/objects/pack/pack-{file:[0-9a-f]{40}}.pack", repo.GetPackFile)
 | 
			
		||||
				m.GetOptions("/objects/pack/pack-{file:[0-9a-f]{40}}.idx", repo.GetIdxFile)
 | 
			
		||||
			}, ignSignInAndCsrf)
 | 
			
		||||
 | 
			
		||||
			m.Head("/tasks/trigger", repo.TriggerTask)
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
 | 
			
		||||
	"github.com/dgrijalva/jwt-go"
 | 
			
		||||
	"github.com/golang-jwt/jwt"
 | 
			
		||||
	jsoniter "github.com/json-iterator/go"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -88,8 +88,11 @@ func prepareWikiFileName(gitRepo *git.Repository, wikiName string) (bool, string
 | 
			
		||||
	escaped := NameToFilename(wikiName)
 | 
			
		||||
 | 
			
		||||
	// Look for both files
 | 
			
		||||
	filesInIndex, err := gitRepo.LsFiles(unescaped, escaped)
 | 
			
		||||
	filesInIndex, err := gitRepo.LsTree("master", unescaped, escaped)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if strings.Contains(err.Error(), "Not a valid object name master") {
 | 
			
		||||
			return false, escaped, nil
 | 
			
		||||
		}
 | 
			
		||||
		log.Error("%v", err)
 | 
			
		||||
		return false, escaped, err
 | 
			
		||||
	}
 | 
			
		||||
@@ -308,14 +311,9 @@ func DeleteWikiPage(doer *models.User, repo *models.Repository, wikiName string)
 | 
			
		||||
		return fmt.Errorf("Unable to read HEAD tree to index in: %s %v", basePath, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	wikiPath := NameToFilename(wikiName)
 | 
			
		||||
	filesInIndex, err := gitRepo.LsFiles(wikiPath)
 | 
			
		||||
	found := false
 | 
			
		||||
	for _, file := range filesInIndex {
 | 
			
		||||
		if file == wikiPath {
 | 
			
		||||
			found = true
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	found, wikiPath, err := prepareWikiFileName(gitRepo, wikiName)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if found {
 | 
			
		||||
		err := gitRepo.RemoveFilesFromIndex(wikiPath)
 | 
			
		||||
 
 | 
			
		||||
@@ -5,11 +5,15 @@
 | 
			
		||||
package wiki
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models"
 | 
			
		||||
	"code.gitea.io/gitea/modules/git"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -210,3 +214,79 @@ func TestRepository_DeleteWikiPage(t *testing.T) {
 | 
			
		||||
	_, err = masterTree.GetTreeEntryByPath(wikiPath)
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPrepareWikiFileName(t *testing.T) {
 | 
			
		||||
	models.PrepareTestEnv(t)
 | 
			
		||||
	repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
 | 
			
		||||
	gitRepo, err := git.OpenRepository(repo.WikiPath())
 | 
			
		||||
	defer gitRepo.Close()
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name      string
 | 
			
		||||
		arg       string
 | 
			
		||||
		existence bool
 | 
			
		||||
		wikiPath  string
 | 
			
		||||
		wantErr   bool
 | 
			
		||||
	}{{
 | 
			
		||||
		name:      "add suffix",
 | 
			
		||||
		arg:       "Home",
 | 
			
		||||
		existence: true,
 | 
			
		||||
		wikiPath:  "Home.md",
 | 
			
		||||
		wantErr:   false,
 | 
			
		||||
	}, {
 | 
			
		||||
		name:      "test special chars",
 | 
			
		||||
		arg:       "home of and & or wiki page!",
 | 
			
		||||
		existence: false,
 | 
			
		||||
		wikiPath:  "home-of-and-%26-or-wiki-page%21.md",
 | 
			
		||||
		wantErr:   false,
 | 
			
		||||
	}, {
 | 
			
		||||
		name:      "fount unescaped cases",
 | 
			
		||||
		arg:       "Unescaped File",
 | 
			
		||||
		existence: true,
 | 
			
		||||
		wikiPath:  "Unescaped File.md",
 | 
			
		||||
		wantErr:   false,
 | 
			
		||||
	}}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			existence, newWikiPath, err := prepareWikiFileName(gitRepo, tt.arg)
 | 
			
		||||
			if (err != nil) != tt.wantErr {
 | 
			
		||||
				assert.NoError(t, err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			if existence != tt.existence {
 | 
			
		||||
				if existence {
 | 
			
		||||
					t.Errorf("expect to find no escaped file but we detect one")
 | 
			
		||||
				} else {
 | 
			
		||||
					t.Errorf("expect to find an escaped file but we could not detect one")
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			assert.Equal(t, tt.wikiPath, newWikiPath)
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPrepareWikiFileName_FirstPage(t *testing.T) {
 | 
			
		||||
	models.PrepareTestEnv(t)
 | 
			
		||||
 | 
			
		||||
	// Now create a temporaryDirectory
 | 
			
		||||
	tmpDir, err := ioutil.TempDir("", "empty-wiki")
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if _, err := os.Stat(tmpDir); !os.IsNotExist(err) {
 | 
			
		||||
			_ = util.RemoveAll(tmpDir)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	err = git.InitRepository(tmpDir, true)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	gitRepo, err := git.OpenRepository(tmpDir)
 | 
			
		||||
	defer gitRepo.Close()
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	existence, newWikiPath, err := prepareWikiFileName(gitRepo, "Home")
 | 
			
		||||
	assert.False(t, existence)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, "Home.md", newWikiPath)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -176,10 +176,10 @@
 | 
			
		||||
	{{if .IsNothingToCompare}}
 | 
			
		||||
		{{if and $.IsSigned $.AllowEmptyPr (not .Repository.IsArchived) }}
 | 
			
		||||
			<div class="ui segment">{{.i18n.Tr "repo.pulls.nothing_to_compare_and_allow_empty_pr"}}</div>
 | 
			
		||||
			<div class="ui info message show-form-container">
 | 
			
		||||
			<div class="ui info message show-form-container" {{if .Flash}}style="display: none"{{end}}>
 | 
			
		||||
				<button class="ui button green show-form">{{.i18n.Tr "repo.pulls.new"}}</button>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="pullrequest-form" style="display: none">
 | 
			
		||||
			<div class="pullrequest-form" {{if not .Flash}}style="display: none"{{end}}>
 | 
			
		||||
				{{template "repo/issue/new_form" .}}
 | 
			
		||||
			</div>
 | 
			
		||||
		{{else}}
 | 
			
		||||
@@ -192,7 +192,7 @@
 | 
			
		||||
			</div>
 | 
			
		||||
		{{else}}
 | 
			
		||||
			{{if and $.IsSigned (not .Repository.IsArchived)}}
 | 
			
		||||
				<div class="ui info message show-form-container">
 | 
			
		||||
				<div class="ui info message show-form-container" {{if .Flash}}style="display: none"{{end}}>
 | 
			
		||||
					<button class="ui button green show-form">{{.i18n.Tr "repo.pulls.new"}}</button>
 | 
			
		||||
				</div>
 | 
			
		||||
			{{else if .Repository.IsArchived}}
 | 
			
		||||
@@ -201,7 +201,7 @@
 | 
			
		||||
				</div>
 | 
			
		||||
			{{end}}
 | 
			
		||||
			{{if $.IsSigned}}
 | 
			
		||||
				<div class="pullrequest-form" style="display: none">
 | 
			
		||||
				<div class="pullrequest-form" {{if not .Flash}}style="display: none"{{end}}>
 | 
			
		||||
					{{template "repo/issue/new_form" .}}
 | 
			
		||||
				</div>
 | 
			
		||||
			{{end}}
 | 
			
		||||
 
 | 
			
		||||
@@ -82,8 +82,10 @@
 | 
			
		||||
									<a class="mono" href="{{$.RepoLink}}/src/commit/{{.Sha1}}" rel="nofollow">{{svg "octicon-git-commit" 16 "mr-2"}}{{ShortSha .Sha1}}</a>
 | 
			
		||||
								</span>
 | 
			
		||||
							{{end}}
 | 
			
		||||
							{{if .Sha1 }}
 | 
			
		||||
								{{template "repo/branch_dropdown" dict "root" $ "release" .}}
 | 
			
		||||
							{{end}}
 | 
			
		||||
						{{end}}
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class="ui twelve wide column detail">
 | 
			
		||||
						{{if .IsTag}}
 | 
			
		||||
 
 | 
			
		||||
@@ -11917,18 +11917,10 @@
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "name": "accessToken",
 | 
			
		||||
            "name": "userCreateToken",
 | 
			
		||||
            "in": "body",
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "type": "object",
 | 
			
		||||
              "required": [
 | 
			
		||||
                "name"
 | 
			
		||||
              ],
 | 
			
		||||
              "properties": {
 | 
			
		||||
                "name": {
 | 
			
		||||
                  "type": "string"
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
              "$ref": "#/definitions/CreateAccessTokenOption"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
@@ -12654,6 +12646,17 @@
 | 
			
		||||
      },
 | 
			
		||||
      "x-go-package": "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
    },
 | 
			
		||||
    "CreateAccessTokenOption": {
 | 
			
		||||
      "description": "CreateAccessTokenOption options when create access token",
 | 
			
		||||
      "type": "object",
 | 
			
		||||
      "properties": {
 | 
			
		||||
        "name": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "x-go-name": "Name"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "x-go-package": "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
    },
 | 
			
		||||
    "CreateBranchProtectionOption": {
 | 
			
		||||
      "description": "CreateBranchProtectionOption options for creating a branch protection",
 | 
			
		||||
      "type": "object",
 | 
			
		||||
@@ -17044,20 +17047,8 @@
 | 
			
		||||
  "responses": {
 | 
			
		||||
    "AccessToken": {
 | 
			
		||||
      "description": "AccessToken represents an API access token.",
 | 
			
		||||
      "headers": {
 | 
			
		||||
        "id": {
 | 
			
		||||
          "type": "integer",
 | 
			
		||||
          "format": "int64"
 | 
			
		||||
        },
 | 
			
		||||
        "name": {
 | 
			
		||||
          "type": "string"
 | 
			
		||||
        },
 | 
			
		||||
        "sha1": {
 | 
			
		||||
          "type": "string"
 | 
			
		||||
        },
 | 
			
		||||
        "token_last_eight": {
 | 
			
		||||
          "type": "string"
 | 
			
		||||
        }
 | 
			
		||||
      "schema": {
 | 
			
		||||
        "$ref": "#/definitions/AccessToken"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "AccessTokenList": {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
<div class="page-content user notification" id="notification_div" data-params="{{.Page.GetParams}}">
 | 
			
		||||
<div class="page-content user notification" id="notification_div" data-params="{{.Page.GetParams}}" data-sequence-number="{{.SequenceNumber}}">
 | 
			
		||||
	<div class="ui container">
 | 
			
		||||
		<h1 class="ui dividing header">{{.i18n.Tr "notification.notifications"}}</h1>
 | 
			
		||||
		<div class="ui top attached tabular menu">
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,7 @@ import (
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/markup"
 | 
			
		||||
	"code.gitea.io/gitea/modules/markup/markdown"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Contains fuzzing functions executed by
 | 
			
		||||
@@ -32,6 +33,7 @@ var (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func FuzzMarkdownRenderRaw(data []byte) int {
 | 
			
		||||
	setting.AppURL = "http://localhost:3000/"
 | 
			
		||||
	err := markdown.RenderRaw(&renderContext, bytes.NewReader(data), io.Discard)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0
 | 
			
		||||
@@ -40,6 +42,7 @@ func FuzzMarkdownRenderRaw(data []byte) int {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func FuzzMarkupPostProcess(data []byte) int {
 | 
			
		||||
	setting.AppURL = "http://localhost:3000/"
 | 
			
		||||
	err := markup.PostProcess(&renderContext, bytes.NewReader(data), io.Discard)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										9
									
								
								vendor/gitea.com/lunny/levelqueue/queue.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								vendor/gitea.com/lunny/levelqueue/queue.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@@ -21,8 +21,8 @@ const (
 | 
			
		||||
// Queue defines a queue struct
 | 
			
		||||
type Queue struct {
 | 
			
		||||
	db                *leveldb.DB
 | 
			
		||||
	highLock          sync.Mutex
 | 
			
		||||
	lowLock           sync.Mutex
 | 
			
		||||
	lowLock           sync.Mutex // If you are locking both high and low locks, lock the low lock before the high lock
 | 
			
		||||
	highLock          sync.Mutex // If you are locking both high and low locks, lock the low lock before the high lock
 | 
			
		||||
	low               int64
 | 
			
		||||
	high              int64
 | 
			
		||||
	lowKey            []byte
 | 
			
		||||
@@ -295,6 +295,11 @@ func (queue *Queue) LHandle(h func([]byte) error) error {
 | 
			
		||||
 | 
			
		||||
// Close closes the queue (and the underlying db is set to closeUnderlyingDB)
 | 
			
		||||
func (queue *Queue) Close() error {
 | 
			
		||||
	queue.lowLock.Lock()
 | 
			
		||||
	queue.highLock.Lock()
 | 
			
		||||
	defer queue.lowLock.Unlock()
 | 
			
		||||
	defer queue.highLock.Unlock()
 | 
			
		||||
 | 
			
		||||
	if !queue.closeUnderlyingDB {
 | 
			
		||||
		queue.db = nil
 | 
			
		||||
		return nil
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								vendor/gitea.com/lunny/levelqueue/set.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/gitea.com/lunny/levelqueue/set.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@@ -107,6 +107,8 @@ func (set *Set) Remove(value []byte) (bool, error) {
 | 
			
		||||
 | 
			
		||||
// Close closes the set (and the underlying db if set to closeUnderlyingDB)
 | 
			
		||||
func (set *Set) Close() error {
 | 
			
		||||
	set.lock.Lock()
 | 
			
		||||
	defer set.lock.Unlock()
 | 
			
		||||
	if !set.closeUnderlyingDB {
 | 
			
		||||
		set.db = nil
 | 
			
		||||
		return nil
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								vendor/gitea.com/lunny/levelqueue/uniquequeue.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/gitea.com/lunny/levelqueue/uniquequeue.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@@ -181,6 +181,8 @@ func (queue *UniqueQueue) Len() int64 {
 | 
			
		||||
func (queue *UniqueQueue) Close() error {
 | 
			
		||||
	_ = queue.q.Close()
 | 
			
		||||
	_ = queue.set.Close()
 | 
			
		||||
	queue.set.lock.Lock()
 | 
			
		||||
	defer queue.set.lock.Unlock()
 | 
			
		||||
	if !queue.closeUnderlyingDB {
 | 
			
		||||
		queue.db = nil
 | 
			
		||||
		return nil
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user