mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-03 08:02:36 +09:00 
			
		
		
		
	Compare commits
	
		
			81 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					918d3d96ff | ||
| 
						 | 
					92c91d7d8b | ||
| 
						 | 
					9dc76b2036 | ||
| 
						 | 
					802a4314ef | ||
| 
						 | 
					edd4ab49c8 | ||
| 
						 | 
					55e6cde7c1 | ||
| 
						 | 
					729fa06468 | ||
| 
						 | 
					b228a0aa44 | ||
| 
						 | 
					9e7e11224f | ||
| 
						 | 
					85880b2a0b | ||
| 
						 | 
					211bb911e3 | ||
| 
						 | 
					0554d1dd01 | ||
| 
						 | 
					00e55dd223 | ||
| 
						 | 
					b28c3245cc | ||
| 
						 | 
					ddfb729168 | ||
| 
						 | 
					6ef62e3f8e | ||
| 
						 | 
					2c4f1ed13e | ||
| 
						 | 
					fa3fe1e28a | ||
| 
						 | 
					62f5cf4386 | ||
| 
						 | 
					779d1185e7 | ||
| 
						 | 
					f3d0c76afc | ||
| 
						 | 
					5a4729d5e2 | ||
| 
						 | 
					88a7349375 | ||
| 
						 | 
					c3398906a1 | ||
| 
						 | 
					330fa75945 | ||
| 
						 | 
					55e159ca5f | ||
| 
						 | 
					87074ec860 | ||
| 
						 | 
					1fe5fe419e | ||
| 
						 | 
					67a12b8fac | ||
| 
						 | 
					e861dcbbaf | ||
| 
						 | 
					53c2136a9a | ||
| 
						 | 
					24ebc7e517 | ||
| 
						 | 
					b072907987 | ||
| 
						 | 
					942b0360ad | ||
| 
						 | 
					1ec4913add | ||
| 
						 | 
					16e34025b4 | ||
| 
						 | 
					456d63b6cf | ||
| 
						 | 
					798ac3f85a | ||
| 
						 | 
					460093b952 | ||
| 
						 | 
					38d184d518 | ||
| 
						 | 
					80b55263d8 | ||
| 
						 | 
					32232db55f | ||
| 
						 | 
					cf9b6c281f | ||
| 
						 | 
					a8c6a4a70e | ||
| 
						 | 
					e6050e80f7 | ||
| 
						 | 
					3803b15d76 | ||
| 
						 | 
					af73e1ee35 | ||
| 
						 | 
					4bc8dfc6a3 | ||
| 
						 | 
					33c4e246fe | ||
| 
						 | 
					8ec7beb9f4 | ||
| 
						 | 
					c6eb9b30ae | ||
| 
						 | 
					f75a9b27b0 | ||
| 
						 | 
					2705696d4d | ||
| 
						 | 
					2b68f66e0e | ||
| 
						 | 
					5c7d30cf52 | ||
| 
						 | 
					e520dff4da | ||
| 
						 | 
					2bc759518e | ||
| 
						 | 
					92b2883058 | ||
| 
						 | 
					0ebfc1405c | ||
| 
						 | 
					fd5c67226e | ||
| 
						 | 
					61308825a6 | ||
| 
						 | 
					0cccad04f0 | ||
| 
						 | 
					a0e5c49ac3 | ||
| 
						 | 
					3558310c1f | ||
| 
						 | 
					e99534cfd2 | ||
| 
						 | 
					27acf6165e | ||
| 
						 | 
					f286a28568 | ||
| 
						 | 
					b5c4cb1bde | ||
| 
						 | 
					26b98417ad | ||
| 
						 | 
					8b0cf88c0c | ||
| 
						 | 
					23db3375df | ||
| 
						 | 
					14011d77c9 | ||
| 
						 | 
					5519e26c2f | ||
| 
						 | 
					6feb435867 | ||
| 
						 | 
					61444ed8ca | ||
| 
						 | 
					d770cc9886 | ||
| 
						 | 
					fbaa01998a | ||
| 
						 | 
					ac2ae66ae7 | ||
| 
						 | 
					ed60fe0986 | ||
| 
						 | 
					29e0d62790 | ||
| 
						 | 
					7b464fa67b | 
							
								
								
									
										100
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										100
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -4,14 +4,46 @@ This changelog goes through all the changes that have been made in each release
 | 
			
		||||
without substantial changes to our git log; to see the highlights of what has
 | 
			
		||||
been added to each release, please refer to the [blog](https://blog.gitea.io).
 | 
			
		||||
 | 
			
		||||
## [1.14.0-RC1](https://github.com/go-gitea/gitea/releases/tag/v1.14.0) - 2021-03-19
 | 
			
		||||
## [1.14.1](https://github.com/go-gitea/gitea/releases/tag/v1.14.1) - 2021-04-15
 | 
			
		||||
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Fix bug clone wiki (#15499) (#15502)
 | 
			
		||||
  * Github Migration ignore rate limit, if not enabled (#15490) (#15495)
 | 
			
		||||
  * Use subdir for URL (#15446) (#15493)
 | 
			
		||||
  * Query the DB for the hash before inserting in to email_hash (#15457) (#15491)
 | 
			
		||||
  * Ensure review dismissal only dismisses the correct review (#15477) (#15489)
 | 
			
		||||
  * Use index of the supported tags to choose user lang (#15452) (#15488)
 | 
			
		||||
  * Fix wrong file link in code search page (#15466) (#15486)
 | 
			
		||||
  * Quick template fix for built-in SSH server in admin config (#15464) (#15481)
 | 
			
		||||
  * Prevent superfluous response.WriteHeader (#15456) (#15476)
 | 
			
		||||
  * Fix ambiguous argument error on tags (#15432) (#15474)
 | 
			
		||||
  * Add created_unix instead of expiry to migration (#15458) (#15463)
 | 
			
		||||
  * Fix repository search (#15428) (#15442)
 | 
			
		||||
  * Prevent NPE on avatar direct rendering if federated avatars disabled (#15434) (#15439)
 | 
			
		||||
  * Fix wiki clone urls (#15430) (#15431)
 | 
			
		||||
  * Fix dingtalk icon url at webhook (#15417) (#15426)
 | 
			
		||||
  * Standardise icon on projects PR page (#15387) (#15408)
 | 
			
		||||
* ENHANCEMENTS
 | 
			
		||||
  * Add option to skip LFS/attachment files for `dump` (#15407) (#15492)
 | 
			
		||||
  * Clone panel fixes (#15436)
 | 
			
		||||
  * Use semantic dropdown for code search query type (#15276) (#15364)
 | 
			
		||||
* BUILD
 | 
			
		||||
  * Build go-git variants for windows (#15482) (#15487)
 | 
			
		||||
  * Lock down build-images dependencies (Partial #15479) (#15480)
 | 
			
		||||
* MISC
 | 
			
		||||
  * Performance improvement for list pull requests (#15447) (#15500)
 | 
			
		||||
  * Fix potential copy lfs records failure when fork a repository (#15441) (#15485)
 | 
			
		||||
 | 
			
		||||
## [1.14.0](https://github.com/go-gitea/gitea/releases/tag/v1.14.0) - 2021-04-11
 | 
			
		||||
 | 
			
		||||
* SECURITY
 | 
			
		||||
  * Respect approved email domain list for externally validated user registration (#15014)
 | 
			
		||||
  * Add reverse proxy configuration support for remote IP address detection (#14959)
 | 
			
		||||
  * Ensure validation occurs on clone addresses too (#14994)
 | 
			
		||||
  * Fix several render issues highlighted during fuzzing (#14986)
 | 
			
		||||
* BREAKING
 | 
			
		||||
  * Fix double 'push tag' action feed (#15078) (#15083)
 | 
			
		||||
  * Remove possible resource leak (#15067) (#15082)
 | 
			
		||||
  * Handle unauthorized user events gracefully (#15071) (#15074)
 | 
			
		||||
  * Restore Access.log following migration to Chi framework (Stops access logging of /api/internal routes) (#14475)
 | 
			
		||||
  * Migrate from Macaron to Chi framework (#14293)
 | 
			
		||||
  * Deprecate building for mips (#14174)
 | 
			
		||||
@@ -42,6 +74,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
 | 
			
		||||
  * Dump github/gitlab/gitea repository data to a local directory and restore to gitea (#12244)
 | 
			
		||||
  * Create Rootless Docker image (#10154)
 | 
			
		||||
* API
 | 
			
		||||
  * Speedup issue search (#15179) (#15192)
 | 
			
		||||
  * Get pull, return head branch sha, even if deleted (#14931)
 | 
			
		||||
  * Export LFS & TimeTracking function status (#14753)
 | 
			
		||||
  * Show Gitea version in swagger (#14654)
 | 
			
		||||
@@ -66,6 +99,20 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
 | 
			
		||||
  * Add more filters to issues search (#13514)
 | 
			
		||||
  * Add review request api (#11355)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Fix delete nonexist oauth application 500 and prevent deadlock (#15384) (#15396)
 | 
			
		||||
  * Always set the merge base used to merge the commit (#15352) (#15385)
 | 
			
		||||
  * Upgrade to bluemonday 1.0.7 (#15379) (#15380)
 | 
			
		||||
  * Turn RepoRef and RepoAssignment back into func(*Context) (#15372) (#15377)
 | 
			
		||||
  * Move FCGI req.URL.Path fix-up to the FCGI listener (#15292) (#15361)
 | 
			
		||||
  * Show diff on rename with diff changes (#15338) (#15339)
 | 
			
		||||
  * Fix handling of logout event (#15323) (#15337)
 | 
			
		||||
  * Fix CanCreateRepo check (#15311) (#15321)
 | 
			
		||||
  * Fix xorm log stack level (#15285) (#15316)
 | 
			
		||||
  * Fix bug in Wrap (#15302) (#15309)
 | 
			
		||||
  * Drop the event source if we are unauthorized (#15275) (#15280)
 | 
			
		||||
  * Backport Fix graph pagination (#15225)  (#15249)
 | 
			
		||||
  * Prevent NPE in CommentMustAsDiff if no hunk header (#15199) (#15200)
 | 
			
		||||
  * should run RetrieveRepoMetas() for empty pr (#15187) (#15190)
 | 
			
		||||
  * Move setting to enable closing issue via commit in non default branch to repo settings (#14965)
 | 
			
		||||
  * Show correct issues for team dashboard (#14952)
 | 
			
		||||
  * Ensure that new pull request button works on forked forks owned by owner of the root and reduce ambiguity (#14932)
 | 
			
		||||
@@ -122,6 +169,9 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
 | 
			
		||||
  * Use GO variable in go-check target (#13146) (#13147)
 | 
			
		||||
* ENHANCEMENTS
 | 
			
		||||
  * UI style improvements
 | 
			
		||||
  * Dropzone styling improvements (#15291) (#15374)
 | 
			
		||||
  * Add size to Save function (#15264) (#15270)
 | 
			
		||||
  * Monaco improvements (#15333) (#15345)
 | 
			
		||||
  * Support .mailmap in code activity stats (#15009)
 | 
			
		||||
  * Sort release attachments by name (#15008)  
 | 
			
		||||
  * Add ui.explore settings to control view of explore pages (#14094)
 | 
			
		||||
@@ -267,6 +317,52 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
 | 
			
		||||
  * Reduce make verbosity (#13803)
 | 
			
		||||
  * Add git command error directory on log (#13194)
 | 
			
		||||
 | 
			
		||||
## [1.13.7](https://github.com/go-gitea/gitea/releases/tag/v1.13.7) - 2021-04-07
 | 
			
		||||
 | 
			
		||||
* SECURITY
 | 
			
		||||
  * Update to bluemonday-1.0.6 (#15294) (#15298)
 | 
			
		||||
  * Clusterfuzz found another way (#15160) (#15169)
 | 
			
		||||
* API
 | 
			
		||||
  * Fix wrong user returned in API (#15139) (#15150)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Add 'fonts' into 'KnownPublicEntries' (#15188) (#15317)
 | 
			
		||||
  * Speed up `enry.IsVendor` (#15213) (#15246)
 | 
			
		||||
  * Response 404 for diff/patch of a commit that not exist (#15221) (#15238)
 | 
			
		||||
  * Prevent NPE in CommentMustAsDiff if no hunk header (#15199) (#15201)
 | 
			
		||||
* MISC
 | 
			
		||||
  * Add size to Save function (#15264) (#15271)
 | 
			
		||||
 | 
			
		||||
## [1.13.6](https://github.com/go-gitea/gitea/releases/tag/v1.13.6) - 2021-03-23
 | 
			
		||||
 | 
			
		||||
* SECURITY
 | 
			
		||||
  * Fix bug on avatar middleware (#15124) (#15125)
 | 
			
		||||
  * Fix another clusterfuzz identified issue (#15096) (#15114)
 | 
			
		||||
* API
 | 
			
		||||
  * Fix nil pointer exception in get pull reviews API (#15106)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Fix markdown rendering in milestone content (#15056) (#15092)
 | 
			
		||||
 | 
			
		||||
## [1.13.5](https://github.com/go-gitea/gitea/releases/tag/v1.13.5) - 2021-03-21
 | 
			
		||||
 | 
			
		||||
* SECURITY
 | 
			
		||||
  * Update to goldmark 1.3.3 (#15059) (#15061)
 | 
			
		||||
  * Another clusterfuzz spotted issue (#15032) (#15034)
 | 
			
		||||
* API
 | 
			
		||||
  * Fix set milestone on PR creation (#14981) (#15001)
 | 
			
		||||
  * Prevent panic when editing forked repos by API (#14960) (#14963)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Fix bug when upload on web (#15042) (#15055)
 | 
			
		||||
  * Delete Labels & IssueLabels on Repo Delete too (#15039) (#15051)
 | 
			
		||||
  * Fix postgres ID sequences broken by recreate-table (#15015) (#15029)
 | 
			
		||||
  * Fix several render issues (#14986) (#15013)
 | 
			
		||||
  * Make sure sibling images get a link too (#14979) (#14995)
 | 
			
		||||
  * Fix Anchor jumping with escaped query components (#14969) (#14977)
 | 
			
		||||
  * Fix release mail html template (#14976)
 | 
			
		||||
  * Fix excluding more than two labels on issues list (#14962) (#14973)
 | 
			
		||||
  * Don't mark each comment poster as OP (#14971) (#14972)
 | 
			
		||||
  * Add "captcha" to list of reserved usernames (#14930)
 | 
			
		||||
  * Re-enable import local paths after reversion from #13610 (#14925) (#14927)
 | 
			
		||||
 | 
			
		||||
## [1.13.4](https://github.com/go-gitea/gitea/releases/tag/v1.13.4) - 2021-03-07
 | 
			
		||||
 | 
			
		||||
* SECURITY
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										7
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								Makefile
									
									
									
									
									
								
							@@ -577,6 +577,9 @@ release-windows: | $(DIST_DIRS)
 | 
			
		||||
		$(GO) install src.techknowlogick.com/xgo@latest; \
 | 
			
		||||
	fi
 | 
			
		||||
	CGO_CFLAGS="$(CGO_CFLAGS)" xgo -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) .
 | 
			
		||||
ifeq (,$(findstring gogit,$(TAGS)))
 | 
			
		||||
	CGO_CFLAGS="$(CGO_CFLAGS)" xgo -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'netgo osusergo gogit $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION)-gogit .
 | 
			
		||||
endif
 | 
			
		||||
ifeq ($(CI),drone)
 | 
			
		||||
	cp /build/* $(DIST)/binaries
 | 
			
		||||
endif
 | 
			
		||||
@@ -699,8 +702,8 @@ generate-gitignore:
 | 
			
		||||
	GO111MODULE=on $(GO) run build/generate-gitignores.go
 | 
			
		||||
 | 
			
		||||
.PHONY: generate-images
 | 
			
		||||
generate-images:
 | 
			
		||||
	npm install --no-save --no-package-lock fabric imagemin-zopfli
 | 
			
		||||
generate-images: | node_modules
 | 
			
		||||
	npm install --no-save --no-package-lock fabric@4 imagemin-zopfli@7
 | 
			
		||||
	node build/generate-images.js $(TAGS)
 | 
			
		||||
 | 
			
		||||
.PHONY: generate-manpage
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										16
									
								
								cmd/dump.go
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								cmd/dump.go
									
									
									
									
									
								
							@@ -129,6 +129,14 @@ It can be used for backup and capture Gitea server image to send to maintainer`,
 | 
			
		||||
			Name:  "skip-custom-dir",
 | 
			
		||||
			Usage: "Skip custom directory",
 | 
			
		||||
		},
 | 
			
		||||
		cli.BoolFlag{
 | 
			
		||||
			Name:  "skip-lfs-data",
 | 
			
		||||
			Usage: "Skip LFS data",
 | 
			
		||||
		},
 | 
			
		||||
		cli.BoolFlag{
 | 
			
		||||
			Name:  "skip-attachment-data",
 | 
			
		||||
			Usage: "Skip attachment data",
 | 
			
		||||
		},
 | 
			
		||||
		cli.GenericFlag{
 | 
			
		||||
			Name:  "type",
 | 
			
		||||
			Value: outputTypeEnum,
 | 
			
		||||
@@ -214,7 +222,9 @@ func runDump(ctx *cli.Context) error {
 | 
			
		||||
			fatal("Failed to include repositories: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := storage.LFS.IterateObjects(func(objPath string, object storage.Object) error {
 | 
			
		||||
		if ctx.IsSet("skip-lfs-data") && ctx.Bool("skip-lfs-data") {
 | 
			
		||||
			log.Info("Skip dumping LFS data")
 | 
			
		||||
		} else if err := storage.LFS.IterateObjects(func(objPath string, object storage.Object) error {
 | 
			
		||||
			info, err := object.Stat()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
@@ -313,7 +323,9 @@ func runDump(ctx *cli.Context) error {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := storage.Attachments.IterateObjects(func(objPath string, object storage.Object) error {
 | 
			
		||||
	if ctx.IsSet("skip-attachment-data") && ctx.Bool("skip-attachment-data") {
 | 
			
		||||
		log.Info("Skip dumping attachment data")
 | 
			
		||||
	} else if err := storage.Attachments.IterateObjects(func(objPath string, object storage.Object) error {
 | 
			
		||||
		info, err := object.Stat()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
 
 | 
			
		||||
@@ -9,9 +9,11 @@ import (
 | 
			
		||||
	"net"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/http/fcgi"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/graceful"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func runHTTP(network, listenAddr, name string, m http.Handler) error {
 | 
			
		||||
@@ -48,7 +50,12 @@ func runFCGI(network, listenAddr, name string, m http.Handler) error {
 | 
			
		||||
	fcgiServer := graceful.NewServer(network, listenAddr, name)
 | 
			
		||||
 | 
			
		||||
	err := fcgiServer.ListenAndServe(func(listener net.Listener) error {
 | 
			
		||||
		return fcgi.Serve(listener, m)
 | 
			
		||||
		return fcgi.Serve(listener, http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
 | 
			
		||||
			if setting.AppSubURL != "" {
 | 
			
		||||
				req.URL.Path = strings.TrimPrefix(req.URL.Path, setting.AppSubURL)
 | 
			
		||||
			}
 | 
			
		||||
			m.ServeHTTP(resp, req)
 | 
			
		||||
		}))
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal("Failed to start FCGI main server: %v", err)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								go.mod
									
									
									
									
									
								
							@@ -5,7 +5,7 @@ go 1.14
 | 
			
		||||
require (
 | 
			
		||||
	cloud.google.com/go v0.78.0 // indirect
 | 
			
		||||
	code.gitea.io/gitea-vet v0.2.1
 | 
			
		||||
	code.gitea.io/sdk/gitea v0.13.2
 | 
			
		||||
	code.gitea.io/sdk/gitea v0.14.0
 | 
			
		||||
	gitea.com/go-chi/binding v0.0.0-20210301195521-1fe1c9a555e7
 | 
			
		||||
	gitea.com/go-chi/cache v0.0.0-20210110083709-82c4c9ce2d5e
 | 
			
		||||
	gitea.com/go-chi/captcha v0.0.0-20210110083842-e7696c336a1e
 | 
			
		||||
@@ -86,7 +86,7 @@ require (
 | 
			
		||||
	github.com/mgechev/revive v1.0.3
 | 
			
		||||
	github.com/mholt/acmez v0.1.3 // indirect
 | 
			
		||||
	github.com/mholt/archiver/v3 v3.5.0
 | 
			
		||||
	github.com/microcosm-cc/bluemonday v1.0.4
 | 
			
		||||
	github.com/microcosm-cc/bluemonday v1.0.7
 | 
			
		||||
	github.com/miekg/dns v1.1.40 // indirect
 | 
			
		||||
	github.com/minio/md5-simd v1.1.2 // indirect
 | 
			
		||||
	github.com/minio/minio-go/v7 v7.0.10
 | 
			
		||||
@@ -128,7 +128,7 @@ require (
 | 
			
		||||
	github.com/xanzy/go-gitlab v0.44.0
 | 
			
		||||
	github.com/xanzy/ssh-agent v0.3.0 // indirect
 | 
			
		||||
	github.com/yohcop/openid-go v1.0.0
 | 
			
		||||
	github.com/yuin/goldmark v1.3.2
 | 
			
		||||
	github.com/yuin/goldmark v1.3.3
 | 
			
		||||
	github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691
 | 
			
		||||
	github.com/yuin/goldmark-meta v1.0.0
 | 
			
		||||
	go.jolheiser.com/hcaptcha v0.0.4
 | 
			
		||||
@@ -136,9 +136,9 @@ require (
 | 
			
		||||
	go.uber.org/multierr v1.6.0 // indirect
 | 
			
		||||
	go.uber.org/zap v1.16.0 // indirect
 | 
			
		||||
	golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83
 | 
			
		||||
	golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
 | 
			
		||||
	golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4
 | 
			
		||||
	golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93
 | 
			
		||||
	golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46
 | 
			
		||||
	golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44
 | 
			
		||||
	golang.org/x/text v0.3.5
 | 
			
		||||
	golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
 | 
			
		||||
	golang.org/x/tools v0.1.0
 | 
			
		||||
@@ -153,5 +153,3 @@ require (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
replace github.com/hashicorp/go-version => github.com/6543/go-version v1.2.4
 | 
			
		||||
 | 
			
		||||
replace github.com/microcosm-cc/bluemonday => github.com/lunny/bluemonday v1.0.5-0.20201227154428-ca34796141e8
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										23
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								go.sum
									
									
									
									
									
								
							@@ -38,8 +38,8 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
 | 
			
		||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
 | 
			
		||||
code.gitea.io/gitea-vet v0.2.1 h1:b30by7+3SkmiftK0RjuXqFvZg2q4p68uoPGuxhzBN0s=
 | 
			
		||||
code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
 | 
			
		||||
code.gitea.io/sdk/gitea v0.13.2 h1:wAnT/J7Z62q3fJXbgnecoaOBh8CM1Qq0/DakWxiv4yA=
 | 
			
		||||
code.gitea.io/sdk/gitea v0.13.2/go.mod h1:lee2y8LeV3kQb2iK+hHlMqoadL4bp27QOkOV/hawLKg=
 | 
			
		||||
code.gitea.io/sdk/gitea v0.14.0 h1:m4J352I3p9+bmJUfS+g0odeQzBY/5OXP91Gv6D4fnJ0=
 | 
			
		||||
code.gitea.io/sdk/gitea v0.14.0/go.mod h1:89WiyOX1KEcvjP66sRHdu0RafojGo60bT9UqW17VbWs=
 | 
			
		||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 | 
			
		||||
gitea.com/go-chi/binding v0.0.0-20210301195521-1fe1c9a555e7 h1:xCVJPY823C8RWpgMabTw2kOglDrg0iS3GcQU6wdwHkU=
 | 
			
		||||
gitea.com/go-chi/binding v0.0.0-20210301195521-1fe1c9a555e7/go.mod h1:AyfTrwtfYN54R/HmVvMYPnSTenH5bVoyh8x6tBluxEA=
 | 
			
		||||
@@ -196,8 +196,6 @@ github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+
 | 
			
		||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 | 
			
		||||
github.com/chi-middleware/proxy v1.1.1 h1:4HaXUp8o2+bhHr1OhVy+VjN0+L7/07JDcn6v7YrTjrQ=
 | 
			
		||||
github.com/chi-middleware/proxy v1.1.1/go.mod h1:jQwMEJct2tz9VmtCELxvnXoMfa+SOdikvbVJVHv/M+0=
 | 
			
		||||
github.com/chris-ramon/douceur v0.2.0 h1:IDMEdxlEUUBYBKE4z/mJnFyVXox+MjuEVDJNN27glkU=
 | 
			
		||||
github.com/chris-ramon/douceur v0.2.0/go.mod h1:wDW5xjJdeoMm1mRt4sD4c/LbF/mWdEpRXQKjTR8nIBE=
 | 
			
		||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 | 
			
		||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
 | 
			
		||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
 | 
			
		||||
@@ -776,8 +774,6 @@ github.com/libdns/libdns v0.2.0 h1:ewg3ByWrdUrxrje8ChPVMBNcotg7H9LQYg+u5De2RzI=
 | 
			
		||||
github.com/libdns/libdns v0.2.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
 | 
			
		||||
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
 | 
			
		||||
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
 | 
			
		||||
github.com/lunny/bluemonday v1.0.5-0.20201227154428-ca34796141e8 h1:1omo92DLtxQu6VwVPSZAmduHaK5zssed6cvkHyl1XOg=
 | 
			
		||||
github.com/lunny/bluemonday v1.0.5-0.20201227154428-ca34796141e8/go.mod h1:8iwZnFn2CDDNZ0r6UXhF4xawGvzaqzCRa1n3/lO3W2w=
 | 
			
		||||
github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 h1:uNwtsDp7ci48vBTTxDuwcoTXz4lwtDTe7TjCQ0noaWY=
 | 
			
		||||
github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96/go.mod h1:mmIfjCSQlGYXmJ95jFN84AkQFnVABtKuJL8IrzwvUKQ=
 | 
			
		||||
github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de/go.mod h1:3q8WtuPQsoRbatJuy3nvq/hRSvuBJrHHr+ybPPiNvHQ=
 | 
			
		||||
@@ -834,6 +830,8 @@ github.com/mholt/acmez v0.1.3 h1:J7MmNIk4Qf9b8mAGqAh4XkNeowv3f1zW816yf4zt7Qk=
 | 
			
		||||
github.com/mholt/acmez v0.1.3/go.mod h1:8qnn8QA/Ewx8E3ZSsmscqsIjhhpxuy9vqdgbX2ceceM=
 | 
			
		||||
github.com/mholt/archiver/v3 v3.5.0 h1:nE8gZIrw66cu4osS/U7UW7YDuGMHssxKutU8IfWxwWE=
 | 
			
		||||
github.com/mholt/archiver/v3 v3.5.0/go.mod h1:qqTTPUK/HZPFgFQ/TJ3BzvTpF/dPtFVJXdQbCmeMxwc=
 | 
			
		||||
github.com/microcosm-cc/bluemonday v1.0.7 h1:6yAQfk4XT+PI/dk1ZeBp1gr3Q2Hd1DR0O3aEyPUJVTE=
 | 
			
		||||
github.com/microcosm-cc/bluemonday v1.0.7/go.mod h1:HOT/6NaBlR0f9XlxD3zolN6Z3N8Lp4pvhp+jLS5ihnI=
 | 
			
		||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
 | 
			
		||||
github.com/miekg/dns v1.1.30/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
 | 
			
		||||
github.com/miekg/dns v1.1.40 h1:pyyPFfGMnciYUk/mXpKkVmeMQjfXqt3FAJ2hy7tPiLA=
 | 
			
		||||
@@ -1145,8 +1143,8 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
 | 
			
		||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 | 
			
		||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 | 
			
		||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 | 
			
		||||
github.com/yuin/goldmark v1.3.2 h1:YjHC5TgyMmHpicTgEqDN0Q96Xo8K6tLXPnmNOHXCgs0=
 | 
			
		||||
github.com/yuin/goldmark v1.3.2/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 | 
			
		||||
github.com/yuin/goldmark v1.3.3 h1:37BdQwPx8VOSic8eDSWee6QL9mRpZRm9VJp/QugNrW0=
 | 
			
		||||
github.com/yuin/goldmark v1.3.3/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 | 
			
		||||
github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691 h1:VWSxtAiQNh3zgHJpdpkpVYjTPqRE3P6UZCOPa1nRDio=
 | 
			
		||||
github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691/go.mod h1:YLF3kDffRfUH/bTxOxHhV6lxwIB3Vfj91rEwNMS9MXo=
 | 
			
		||||
github.com/yuin/goldmark-meta v1.0.0 h1:ScsatUIT2gFS6azqzLGUjgOnELsBOxMXerM3ogdJhAM=
 | 
			
		||||
@@ -1321,8 +1319,9 @@ golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwY
 | 
			
		||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 | 
			
		||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 | 
			
		||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 | 
			
		||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
 | 
			
		||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 | 
			
		||||
golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 | 
			
		||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
 | 
			
		||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 | 
			
		||||
@@ -1418,8 +1417,8 @@ golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7w
 | 
			
		||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46 h1:V066+OYJ66oTjnhm4Yrn7SXIwSCiDQJxpBxmvqb1N1c=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44 h1:Bli41pIlzTzf3KEY06n+xnzK/BESIg2ze4Pgfh/aI8c=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 | 
			
		||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
 | 
			
		||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
			
		||||
 
 | 
			
		||||
@@ -239,6 +239,26 @@ func doAPICreatePullRequest(ctx APITestContext, owner, repo, baseBranch, headBra
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func doAPIGetPullRequest(ctx APITestContext, owner, repo string, index int64) func(*testing.T) (api.PullRequest, error) {
 | 
			
		||||
	return func(t *testing.T) (api.PullRequest, error) {
 | 
			
		||||
		urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d?token=%s",
 | 
			
		||||
			owner, repo, index, ctx.Token)
 | 
			
		||||
		req := NewRequest(t, http.MethodGet, urlStr)
 | 
			
		||||
 | 
			
		||||
		expected := 200
 | 
			
		||||
		if ctx.ExpectedCode != 0 {
 | 
			
		||||
			expected = ctx.ExpectedCode
 | 
			
		||||
		}
 | 
			
		||||
		resp := ctx.Session.MakeRequest(t, req, expected)
 | 
			
		||||
 | 
			
		||||
		json := jsoniter.ConfigCompatibleWithStandardLibrary
 | 
			
		||||
		decoder := json.NewDecoder(resp.Body)
 | 
			
		||||
		pr := api.PullRequest{}
 | 
			
		||||
		err := decoder.Decode(&pr)
 | 
			
		||||
		return pr, err
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func doAPIMergePullRequest(ctx APITestContext, owner, repo string, index int64) func(*testing.T) {
 | 
			
		||||
	return func(t *testing.T) {
 | 
			
		||||
		urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge?token=%s",
 | 
			
		||||
 
 | 
			
		||||
@@ -92,6 +92,10 @@ func testAPIDeleteOAuth2Application(t *testing.T) {
 | 
			
		||||
	session.MakeRequest(t, req, http.StatusNoContent)
 | 
			
		||||
 | 
			
		||||
	models.AssertNotExistsBean(t, &models.OAuth2Application{UID: oldApp.UID, Name: oldApp.Name})
 | 
			
		||||
 | 
			
		||||
	// Delete again will return not found
 | 
			
		||||
	req = NewRequest(t, "DELETE", urlStr)
 | 
			
		||||
	session.MakeRequest(t, req, http.StatusNotFound)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func testAPIGetOAuth2Application(t *testing.T) {
 | 
			
		||||
 
 | 
			
		||||
@@ -122,7 +122,7 @@ func TestGetAttachment(t *testing.T) {
 | 
			
		||||
		t.Run(tc.name, func(t *testing.T) {
 | 
			
		||||
			//Write empty file to be available for response
 | 
			
		||||
			if tc.createFile {
 | 
			
		||||
				_, err := storage.Attachments.Save(models.AttachmentRelativePath(tc.uuid), strings.NewReader("hello world"))
 | 
			
		||||
				_, err := storage.Attachments.Save(models.AttachmentRelativePath(tc.uuid), strings.NewReader("hello world"), -1)
 | 
			
		||||
				assert.NoError(t, err)
 | 
			
		||||
			}
 | 
			
		||||
			//Actual test
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@
 | 
			
		||||
package integrations
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/hex"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"math/rand"
 | 
			
		||||
@@ -208,13 +209,13 @@ func rawTest(t *testing.T, ctx *APITestContext, little, big, littleLFS, bigLFS s
 | 
			
		||||
 | 
			
		||||
		// Request raw paths
 | 
			
		||||
		req := NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", little))
 | 
			
		||||
		resp := session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
		assert.Equal(t, littleSize, resp.Body.Len())
 | 
			
		||||
		resp := session.MakeRequestNilResponseRecorder(t, req, http.StatusOK)
 | 
			
		||||
		assert.Equal(t, littleSize, resp.Length)
 | 
			
		||||
 | 
			
		||||
		setting.CheckLFSVersion()
 | 
			
		||||
		if setting.LFS.StartServer {
 | 
			
		||||
			req = NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", littleLFS))
 | 
			
		||||
			resp = session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
			resp := session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
			assert.NotEqual(t, littleSize, resp.Body.Len())
 | 
			
		||||
			assert.LessOrEqual(t, resp.Body.Len(), 1024)
 | 
			
		||||
			if resp.Body.Len() != littleSize && resp.Body.Len() <= 1024 {
 | 
			
		||||
@@ -224,12 +225,12 @@ func rawTest(t *testing.T, ctx *APITestContext, little, big, littleLFS, bigLFS s
 | 
			
		||||
 | 
			
		||||
		if !testing.Short() {
 | 
			
		||||
			req = NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", big))
 | 
			
		||||
			resp = session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
			assert.Equal(t, bigSize, resp.Body.Len())
 | 
			
		||||
			resp := session.MakeRequestNilResponseRecorder(t, req, http.StatusOK)
 | 
			
		||||
			assert.Equal(t, bigSize, resp.Length)
 | 
			
		||||
 | 
			
		||||
			if setting.LFS.StartServer {
 | 
			
		||||
				req = NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", bigLFS))
 | 
			
		||||
				resp = session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
				resp := session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
				assert.NotEqual(t, bigSize, resp.Body.Len())
 | 
			
		||||
				if resp.Body.Len() != bigSize && resp.Body.Len() <= 1024 {
 | 
			
		||||
					assert.Contains(t, resp.Body.String(), models.LFSMetaFileIdentifier)
 | 
			
		||||
@@ -450,27 +451,35 @@ func doMergeFork(ctx, baseCtx APITestContext, baseBranch, headBranch string) fun
 | 
			
		||||
		t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr))
 | 
			
		||||
 | 
			
		||||
		// Then get the diff string
 | 
			
		||||
		var diffStr string
 | 
			
		||||
		var diffHash string
 | 
			
		||||
		var diffLength int
 | 
			
		||||
		t.Run("GetDiff", func(t *testing.T) {
 | 
			
		||||
			req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d.diff", url.PathEscape(baseCtx.Username), url.PathEscape(baseCtx.Reponame), pr.Index))
 | 
			
		||||
			resp := ctx.Session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
			diffStr = resp.Body.String()
 | 
			
		||||
			resp := ctx.Session.MakeRequestNilResponseHashSumRecorder(t, req, http.StatusOK)
 | 
			
		||||
			diffHash = string(resp.Hash.Sum(nil))
 | 
			
		||||
			diffLength = resp.Length
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		// Now: Merge the PR & make sure that doesn't break the PR page or change its diff
 | 
			
		||||
		t.Run("MergePR", doAPIMergePullRequest(baseCtx, baseCtx.Username, baseCtx.Reponame, pr.Index))
 | 
			
		||||
		t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr))
 | 
			
		||||
		t.Run("EnsureDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffStr))
 | 
			
		||||
		t.Run("CheckPR", func(t *testing.T) {
 | 
			
		||||
			oldMergeBase := pr.MergeBase
 | 
			
		||||
			pr2, err := doAPIGetPullRequest(baseCtx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t)
 | 
			
		||||
			assert.NoError(t, err)
 | 
			
		||||
			assert.Equal(t, oldMergeBase, pr2.MergeBase)
 | 
			
		||||
		})
 | 
			
		||||
		t.Run("EnsurDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffHash, diffLength))
 | 
			
		||||
 | 
			
		||||
		// Then: Delete the head branch & make sure that doesn't break the PR page or change its diff
 | 
			
		||||
		t.Run("DeleteHeadBranch", doBranchDelete(baseCtx, baseCtx.Username, baseCtx.Reponame, headBranch))
 | 
			
		||||
		t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr))
 | 
			
		||||
		t.Run("EnsureDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffStr))
 | 
			
		||||
		t.Run("EnsureDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffHash, diffLength))
 | 
			
		||||
 | 
			
		||||
		// Delete the head repository & make sure that doesn't break the PR page or change its diff
 | 
			
		||||
		t.Run("DeleteHeadRepository", doAPIDeleteRepository(ctx))
 | 
			
		||||
		t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr))
 | 
			
		||||
		t.Run("EnsureDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffStr))
 | 
			
		||||
		t.Run("EnsureDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffHash, diffLength))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -514,20 +523,15 @@ func doEnsureCanSeePull(ctx APITestContext, pr api.PullRequest) func(t *testing.
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func doEnsureDiffNoChange(ctx APITestContext, pr api.PullRequest, diffStr string) func(t *testing.T) {
 | 
			
		||||
func doEnsureDiffNoChange(ctx APITestContext, pr api.PullRequest, diffHash string, diffLength int) func(t *testing.T) {
 | 
			
		||||
	return func(t *testing.T) {
 | 
			
		||||
		req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d.diff", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), pr.Index))
 | 
			
		||||
		resp := ctx.Session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
		expectedMaxLen := len(diffStr)
 | 
			
		||||
		if expectedMaxLen > 800 {
 | 
			
		||||
			expectedMaxLen = 800
 | 
			
		||||
		}
 | 
			
		||||
		actual := resp.Body.String()
 | 
			
		||||
		actualMaxLen := len(actual)
 | 
			
		||||
		if actualMaxLen > 800 {
 | 
			
		||||
			actualMaxLen = 800
 | 
			
		||||
		}
 | 
			
		||||
		assert.Equal(t, diffStr, actual, "Unexpected change in the diff string: expected: %s but was actually: %s", diffStr[:expectedMaxLen], actual[:actualMaxLen])
 | 
			
		||||
		resp := ctx.Session.MakeRequestNilResponseHashSumRecorder(t, req, http.StatusOK)
 | 
			
		||||
		actual := string(resp.Hash.Sum(nil))
 | 
			
		||||
		actualLength := resp.Length
 | 
			
		||||
 | 
			
		||||
		equal := diffHash == actual
 | 
			
		||||
		assert.True(t, equal, "Unexpected change in the diff string: expected hash: %s size: %d but was actually: %s size: %d", hex.EncodeToString([]byte(diffHash)), diffLength, hex.EncodeToString([]byte(actual)), actualLength)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,8 @@ import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"database/sql"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"hash"
 | 
			
		||||
	"hash/fnv"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/http/cookiejar"
 | 
			
		||||
@@ -58,6 +60,26 @@ func NewNilResponseRecorder() *NilResponseRecorder {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type NilResponseHashSumRecorder struct {
 | 
			
		||||
	httptest.ResponseRecorder
 | 
			
		||||
	Hash   hash.Hash
 | 
			
		||||
	Length int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (n *NilResponseHashSumRecorder) Write(b []byte) (int, error) {
 | 
			
		||||
	_, _ = n.Hash.Write(b)
 | 
			
		||||
	n.Length += len(b)
 | 
			
		||||
	return len(b), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewRecorder returns an initialized ResponseRecorder.
 | 
			
		||||
func NewNilResponseHashSumRecorder() *NilResponseHashSumRecorder {
 | 
			
		||||
	return &NilResponseHashSumRecorder{
 | 
			
		||||
		Hash:             fnv.New32(),
 | 
			
		||||
		ResponseRecorder: *httptest.NewRecorder(),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMain(m *testing.M) {
 | 
			
		||||
	defer log.Close()
 | 
			
		||||
 | 
			
		||||
@@ -284,6 +306,23 @@ func (s *TestSession) MakeRequestNilResponseRecorder(t testing.TB, req *http.Req
 | 
			
		||||
	return resp
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *TestSession) MakeRequestNilResponseHashSumRecorder(t testing.TB, req *http.Request, expectedStatus int) *NilResponseHashSumRecorder {
 | 
			
		||||
	t.Helper()
 | 
			
		||||
	baseURL, err := url.Parse(setting.AppURL)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	for _, c := range s.jar.Cookies(baseURL) {
 | 
			
		||||
		req.AddCookie(c)
 | 
			
		||||
	}
 | 
			
		||||
	resp := MakeRequestNilResponseHashSumRecorder(t, req, expectedStatus)
 | 
			
		||||
 | 
			
		||||
	ch := http.Header{}
 | 
			
		||||
	ch.Add("Cookie", strings.Join(resp.Header()["Set-Cookie"], ";"))
 | 
			
		||||
	cr := http.Request{Header: ch}
 | 
			
		||||
	s.jar.SetCookies(baseURL, cr.Cookies())
 | 
			
		||||
 | 
			
		||||
	return resp
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const userPassword = "password"
 | 
			
		||||
 | 
			
		||||
var loginSessionCache = make(map[string]*TestSession, 10)
 | 
			
		||||
@@ -429,6 +468,19 @@ func MakeRequestNilResponseRecorder(t testing.TB, req *http.Request, expectedSta
 | 
			
		||||
	return recorder
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func MakeRequestNilResponseHashSumRecorder(t testing.TB, req *http.Request, expectedStatus int) *NilResponseHashSumRecorder {
 | 
			
		||||
	t.Helper()
 | 
			
		||||
	recorder := NewNilResponseHashSumRecorder()
 | 
			
		||||
	c.ServeHTTP(recorder, req)
 | 
			
		||||
	if expectedStatus != NoExpectedStatus {
 | 
			
		||||
		if !assert.EqualValues(t, expectedStatus, recorder.Code,
 | 
			
		||||
			"Request: %s %s", req.Method, req.URL.String()) {
 | 
			
		||||
			logUnexpectedResponse(t, &recorder.ResponseRecorder)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return recorder
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// logUnexpectedResponse logs the contents of an unexpected response.
 | 
			
		||||
func logUnexpectedResponse(t testing.TB, recorder *httptest.ResponseRecorder) {
 | 
			
		||||
	t.Helper()
 | 
			
		||||
 
 | 
			
		||||
@@ -382,7 +382,7 @@ func activityQueryCondition(opts GetFeedsOptions) (builder.Cond, error) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if opts.Date != "" {
 | 
			
		||||
		dateLow, err := time.Parse("2006-01-02", opts.Date)
 | 
			
		||||
		dateLow, err := time.ParseInLocation("2006-01-02", opts.Date, setting.DefaultUILocation)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Warn("Unable to parse %s, filter not applied: %v", opts.Date, err)
 | 
			
		||||
		} else {
 | 
			
		||||
 
 | 
			
		||||
@@ -85,7 +85,7 @@ func (a *Attachment) LinkedRepository() (*Repository, UnitType, error) {
 | 
			
		||||
func NewAttachment(attach *Attachment, buf []byte, file io.Reader) (_ *Attachment, err error) {
 | 
			
		||||
	attach.UUID = gouuid.New().String()
 | 
			
		||||
 | 
			
		||||
	size, err := storage.Attachments.Save(attach.RelativePath(), io.MultiReader(bytes.NewReader(buf), file))
 | 
			
		||||
	size, err := storage.Attachments.Save(attach.RelativePath(), io.MultiReader(bytes.NewReader(buf), file), -1)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Create: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -96,6 +96,11 @@ func HashedAvatarLink(email string) string {
 | 
			
		||||
			// we don't care about any DB problem just return the lowerEmail
 | 
			
		||||
			return lowerEmail, nil
 | 
			
		||||
		}
 | 
			
		||||
		has, err := sess.Where("email = ? AND hash = ?", emailHash.Email, emailHash.Hash).Get(new(EmailHash))
 | 
			
		||||
		if has || err != nil {
 | 
			
		||||
			// Seriously we don't care about any DB problems just return the lowerEmail - we expect the transaction to fail most of the time
 | 
			
		||||
			return lowerEmail, nil
 | 
			
		||||
		}
 | 
			
		||||
		_, _ = sess.Insert(emailHash)
 | 
			
		||||
		if err := sess.Commit(); err != nil {
 | 
			
		||||
			// Seriously we don't care about any DB problems just return the lowerEmail - we expect the transaction to fail most of the time
 | 
			
		||||
 
 | 
			
		||||
@@ -338,7 +338,7 @@ func FixCommentTypeLabelWithEmptyLabel() (int64, error) {
 | 
			
		||||
 | 
			
		||||
// CountCommentTypeLabelWithOutsideLabels count label comments with outside label
 | 
			
		||||
func CountCommentTypeLabelWithOutsideLabels() (int64, error) {
 | 
			
		||||
	return x.Where("comment.type = ? AND (issue.repo_id != label.repo_id OR (label.repo_id = 0 AND repository.owner_id != label.org_id))", CommentTypeLabel).
 | 
			
		||||
	return x.Where("comment.type = ? AND ((label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id))", CommentTypeLabel).
 | 
			
		||||
		Table("comment").
 | 
			
		||||
		Join("inner", "label", "label.id = comment.label_id").
 | 
			
		||||
		Join("inner", "issue", "issue.id = comment.issue_id ").
 | 
			
		||||
@@ -354,8 +354,9 @@ func FixCommentTypeLabelWithOutsideLabels() (int64, error) {
 | 
			
		||||
				FROM comment AS com
 | 
			
		||||
					INNER JOIN label ON com.label_id = label.id
 | 
			
		||||
					INNER JOIN issue on issue.id = com.issue_id
 | 
			
		||||
					INNER JOIN repository ON issue.repo_id = repository.id
 | 
			
		||||
				WHERE
 | 
			
		||||
					com.type = ? AND (issue.repo_id != label.repo_id OR (label.repo_id = 0 AND label.org_id != repo.owner_id))
 | 
			
		||||
					com.type = ? AND ((label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id))
 | 
			
		||||
	) AS il_too)`, CommentTypeLabel)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0, err
 | 
			
		||||
@@ -366,9 +367,9 @@ func FixCommentTypeLabelWithOutsideLabels() (int64, error) {
 | 
			
		||||
 | 
			
		||||
// CountIssueLabelWithOutsideLabels count label comments with outside label
 | 
			
		||||
func CountIssueLabelWithOutsideLabels() (int64, error) {
 | 
			
		||||
	return x.Where(builder.Expr("issue.repo_id != label.repo_id OR (label.repo_id = 0 AND repository.owner_id != label.org_id)")).
 | 
			
		||||
	return x.Where(builder.Expr("(label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id)")).
 | 
			
		||||
		Table("issue_label").
 | 
			
		||||
		Join("inner", "label", "issue_label.id = label.id ").
 | 
			
		||||
		Join("inner", "label", "issue_label.label_id = label.id ").
 | 
			
		||||
		Join("inner", "issue", "issue.id = issue_label.issue_id ").
 | 
			
		||||
		Join("inner", "repository", "issue.repo_id = repository.id").
 | 
			
		||||
		Count(new(IssueLabel))
 | 
			
		||||
@@ -380,11 +381,11 @@ func FixIssueLabelWithOutsideLabels() (int64, error) {
 | 
			
		||||
		SELECT il_too.id FROM (
 | 
			
		||||
			SELECT il_too_too.id
 | 
			
		||||
				FROM issue_label AS il_too_too
 | 
			
		||||
					INNER JOIN label ON il_too_too.id = label.id
 | 
			
		||||
					INNER JOIN label ON il_too_too.label_id = label.id
 | 
			
		||||
					INNER JOIN issue on issue.id = il_too_too.issue_id
 | 
			
		||||
					INNER JOIN repository on repository.id = issue.repo_id
 | 
			
		||||
				WHERE
 | 
			
		||||
					issue.repo_id != label.repo_id OR (label.repo_id = 0 AND label.org_id != repository.owner_id)
 | 
			
		||||
					(label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id)
 | 
			
		||||
	) AS il_too )`)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
 
 | 
			
		||||
@@ -53,6 +53,9 @@ func (issues IssueList) loadRepositories(e Engine) ([]*Repository, error) {
 | 
			
		||||
 | 
			
		||||
	for _, issue := range issues {
 | 
			
		||||
		issue.Repo = repoMaps[issue.RepoID]
 | 
			
		||||
		if issue.PullRequest != nil {
 | 
			
		||||
			issue.PullRequest.BaseRepo = issue.Repo
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return valuesRepository(repoMaps), nil
 | 
			
		||||
}
 | 
			
		||||
@@ -516,6 +519,11 @@ func (issues IssueList) LoadDiscussComments() error {
 | 
			
		||||
	return issues.loadComments(x, builder.Eq{"comment.type": CommentTypeComment})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LoadPullRequests loads pull requests
 | 
			
		||||
func (issues IssueList) LoadPullRequests() error {
 | 
			
		||||
	return issues.loadPullRequests(x)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetApprovalCounts returns a map of issue ID to slice of approval counts
 | 
			
		||||
// FIXME: only returns official counts due to double counting of non-official approvals
 | 
			
		||||
func (issues IssueList) GetApprovalCounts() (map[int64][]*ReviewCount, error) {
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,8 @@ func NewXORMLogger(showSQL bool) xormlog.Logger {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const stackLevel = 8
 | 
			
		||||
 | 
			
		||||
// Log a message with defined skip and at logging level
 | 
			
		||||
func (l *XORMLogBridge) Log(skip int, level log.Level, format string, v ...interface{}) error {
 | 
			
		||||
	return l.logger.Log(skip+1, level, format, v...)
 | 
			
		||||
@@ -33,42 +35,42 @@ func (l *XORMLogBridge) Log(skip int, level log.Level, format string, v ...inter
 | 
			
		||||
 | 
			
		||||
// Debug show debug log
 | 
			
		||||
func (l *XORMLogBridge) Debug(v ...interface{}) {
 | 
			
		||||
	_ = l.Log(2, log.DEBUG, fmt.Sprint(v...))
 | 
			
		||||
	_ = l.Log(stackLevel, log.DEBUG, fmt.Sprint(v...))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Debugf show debug log
 | 
			
		||||
func (l *XORMLogBridge) Debugf(format string, v ...interface{}) {
 | 
			
		||||
	_ = l.Log(2, log.DEBUG, format, v...)
 | 
			
		||||
	_ = l.Log(stackLevel, log.DEBUG, format, v...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Error show error log
 | 
			
		||||
func (l *XORMLogBridge) Error(v ...interface{}) {
 | 
			
		||||
	_ = l.Log(2, log.ERROR, fmt.Sprint(v...))
 | 
			
		||||
	_ = l.Log(stackLevel, log.ERROR, fmt.Sprint(v...))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Errorf show error log
 | 
			
		||||
func (l *XORMLogBridge) Errorf(format string, v ...interface{}) {
 | 
			
		||||
	_ = l.Log(2, log.ERROR, format, v...)
 | 
			
		||||
	_ = l.Log(stackLevel, log.ERROR, format, v...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Info show information level log
 | 
			
		||||
func (l *XORMLogBridge) Info(v ...interface{}) {
 | 
			
		||||
	_ = l.Log(2, log.INFO, fmt.Sprint(v...))
 | 
			
		||||
	_ = l.Log(stackLevel, log.INFO, fmt.Sprint(v...))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Infof show information level log
 | 
			
		||||
func (l *XORMLogBridge) Infof(format string, v ...interface{}) {
 | 
			
		||||
	_ = l.Log(2, log.INFO, format, v...)
 | 
			
		||||
	_ = l.Log(stackLevel, log.INFO, format, v...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Warn show warning log
 | 
			
		||||
func (l *XORMLogBridge) Warn(v ...interface{}) {
 | 
			
		||||
	_ = l.Log(2, log.WARN, fmt.Sprint(v...))
 | 
			
		||||
	_ = l.Log(stackLevel, log.WARN, fmt.Sprint(v...))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Warnf show warnning log
 | 
			
		||||
func (l *XORMLogBridge) Warnf(format string, v ...interface{}) {
 | 
			
		||||
	_ = l.Log(2, log.WARN, format, v...)
 | 
			
		||||
	_ = l.Log(stackLevel, log.WARN, format, v...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Level get logger level
 | 
			
		||||
 
 | 
			
		||||
@@ -39,6 +39,7 @@ func InsertMilestones(ms ...*Milestone) (err error) {
 | 
			
		||||
// InsertIssues insert issues to database
 | 
			
		||||
func InsertIssues(issues ...*Issue) error {
 | 
			
		||||
	sess := x.NewSession()
 | 
			
		||||
	defer sess.Close()
 | 
			
		||||
	if err := sess.Begin(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
@@ -194,6 +195,7 @@ func InsertPullRequests(prs ...*PullRequest) error {
 | 
			
		||||
// InsertReleases migrates release
 | 
			
		||||
func InsertReleases(rels ...*Release) error {
 | 
			
		||||
	sess := x.NewSession()
 | 
			
		||||
	defer sess.Close()
 | 
			
		||||
	if err := sess.Begin(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -12,9 +12,9 @@ import (
 | 
			
		||||
 | 
			
		||||
func addSessionTable(x *xorm.Engine) error {
 | 
			
		||||
	type Session struct {
 | 
			
		||||
		Key         string `xorm:"pk CHAR(16)"`
 | 
			
		||||
		Data        []byte `xorm:"BLOB"`
 | 
			
		||||
		CreatedUnix timeutil.TimeStamp
 | 
			
		||||
		Key    string `xorm:"pk CHAR(16)"`
 | 
			
		||||
		Data   []byte `xorm:"BLOB"`
 | 
			
		||||
		Expiry timeutil.TimeStamp
 | 
			
		||||
	}
 | 
			
		||||
	return x.Sync2(new(Session))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -48,11 +48,11 @@ func removeInvalidLabels(x *xorm.Engine) error {
 | 
			
		||||
		SELECT il_too.id FROM (
 | 
			
		||||
			SELECT il_too_too.id
 | 
			
		||||
				FROM issue_label AS il_too_too
 | 
			
		||||
					INNER JOIN label ON il_too_too.id = label.id
 | 
			
		||||
					INNER JOIN label ON il_too_too.label_id = label.id
 | 
			
		||||
					INNER JOIN issue on issue.id = il_too_too.issue_id
 | 
			
		||||
					INNER JOIN repository on repository.id = issue.repo_id
 | 
			
		||||
				WHERE
 | 
			
		||||
					issue.repo_id != label.repo_id OR (label.repo_id = 0 AND label.org_id != repository.owner_id)
 | 
			
		||||
					(label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id)
 | 
			
		||||
	) AS il_too )`); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
@@ -65,7 +65,7 @@ func removeInvalidLabels(x *xorm.Engine) error {
 | 
			
		||||
					INNER JOIN issue on issue.id = com.issue_id
 | 
			
		||||
					INNER JOIN repository on repository.id = issue.repo_id
 | 
			
		||||
				WHERE
 | 
			
		||||
					com.type = ? AND (issue.repo_id != label.repo_id OR (label.repo_id = 0 AND label.org_id != repository.owner_id))
 | 
			
		||||
					com.type = ? AND ((label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id))
 | 
			
		||||
	) AS il_too)`, 7); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -235,7 +235,7 @@ func deleteOAuth2Application(sess *xorm.Session, id, userid int64) error {
 | 
			
		||||
	if deleted, err := sess.Delete(&OAuth2Application{ID: id, UID: userid}); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	} else if deleted == 0 {
 | 
			
		||||
		return fmt.Errorf("cannot find oauth2 application")
 | 
			
		||||
		return ErrOAuthApplicationNotFound{ID: id}
 | 
			
		||||
	}
 | 
			
		||||
	codes := make([]*OAuth2AuthorizationCode, 0)
 | 
			
		||||
	// delete correlating auth codes
 | 
			
		||||
@@ -261,6 +261,7 @@ func deleteOAuth2Application(sess *xorm.Session, id, userid int64) error {
 | 
			
		||||
// DeleteOAuth2Application deletes the application with the given id and the grants and auth codes related to it. It checks if the userid was the creator of the app.
 | 
			
		||||
func DeleteOAuth2Application(id, userid int64) error {
 | 
			
		||||
	sess := x.NewSession()
 | 
			
		||||
	defer sess.Close()
 | 
			
		||||
	if err := sess.Begin(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -406,7 +406,8 @@ func (pr *PullRequest) SetMerged() (bool, error) {
 | 
			
		||||
		return false, fmt.Errorf("Issue.changeStatus: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := sess.Where("id = ?", pr.ID).Cols("has_merged, status, merged_commit_id, merger_id, merged_unix").Update(pr); err != nil {
 | 
			
		||||
	// We need to save all of the data used to compute this merge as it may have already been changed by TestPatch. FIXME: need to set some state to prevent TestPatch from running whilst we are merging.
 | 
			
		||||
	if _, err := sess.Where("id = ?", pr.ID).Cols("has_merged, status, merge_base, merged_commit_id, merger_id, merged_unix").Update(pr); err != nil {
 | 
			
		||||
		return false, fmt.Errorf("Failed to update pr[%d]: %v", pr.ID, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,7 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
 | 
			
		||||
	"xorm.io/builder"
 | 
			
		||||
	"xorm.io/xorm"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// RepositoryListDefaultPageSize is the default number of repositories
 | 
			
		||||
@@ -363,6 +364,35 @@ func SearchRepository(opts *SearchRepoOptions) (RepositoryList, int64, error) {
 | 
			
		||||
 | 
			
		||||
// SearchRepositoryByCondition search repositories by condition
 | 
			
		||||
func SearchRepositoryByCondition(opts *SearchRepoOptions, cond builder.Cond, loadAttributes bool) (RepositoryList, int64, error) {
 | 
			
		||||
	sess, count, err := searchRepositoryByCondition(opts, cond)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, 0, err
 | 
			
		||||
	}
 | 
			
		||||
	defer sess.Close()
 | 
			
		||||
 | 
			
		||||
	defaultSize := 50
 | 
			
		||||
	if opts.PageSize > 0 {
 | 
			
		||||
		defaultSize = opts.PageSize
 | 
			
		||||
	}
 | 
			
		||||
	repos := make(RepositoryList, 0, defaultSize)
 | 
			
		||||
	if err := sess.Find(&repos); err != nil {
 | 
			
		||||
		return nil, 0, fmt.Errorf("Repo: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if opts.PageSize <= 0 {
 | 
			
		||||
		count = int64(len(repos))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if loadAttributes {
 | 
			
		||||
		if err := repos.loadAttributes(sess); err != nil {
 | 
			
		||||
			return nil, 0, fmt.Errorf("LoadAttributes: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return repos, count, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func searchRepositoryByCondition(opts *SearchRepoOptions, cond builder.Cond) (*xorm.Session, int64, error) {
 | 
			
		||||
	if opts.Page <= 0 {
 | 
			
		||||
		opts.Page = 1
 | 
			
		||||
	}
 | 
			
		||||
@@ -376,31 +406,24 @@ func SearchRepositoryByCondition(opts *SearchRepoOptions, cond builder.Cond, loa
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sess := x.NewSession()
 | 
			
		||||
	defer sess.Close()
 | 
			
		||||
 | 
			
		||||
	count, err := sess.
 | 
			
		||||
		Where(cond).
 | 
			
		||||
		Count(new(Repository))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, 0, fmt.Errorf("Count: %v", err)
 | 
			
		||||
	var count int64
 | 
			
		||||
	if opts.PageSize > 0 {
 | 
			
		||||
		var err error
 | 
			
		||||
		count, err = sess.
 | 
			
		||||
			Where(cond).
 | 
			
		||||
			Count(new(Repository))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			_ = sess.Close()
 | 
			
		||||
			return nil, 0, fmt.Errorf("Count: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	repos := make(RepositoryList, 0, opts.PageSize)
 | 
			
		||||
	sess.Where(cond).OrderBy(opts.OrderBy.String())
 | 
			
		||||
	if opts.PageSize > 0 {
 | 
			
		||||
		sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize)
 | 
			
		||||
	}
 | 
			
		||||
	if err = sess.Find(&repos); err != nil {
 | 
			
		||||
		return nil, 0, fmt.Errorf("Repo: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if loadAttributes {
 | 
			
		||||
		if err = repos.loadAttributes(sess); err != nil {
 | 
			
		||||
			return nil, 0, fmt.Errorf("LoadAttributes: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return repos, count, nil
 | 
			
		||||
	return sess, count, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// accessibleRepositoryCondition takes a user a returns a condition for checking if a repository is accessible
 | 
			
		||||
@@ -456,6 +479,33 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err
 | 
			
		||||
	return SearchRepository(opts)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SearchRepositoryIDs takes keyword and part of repository name to search,
 | 
			
		||||
// it returns results in given range and number of total results.
 | 
			
		||||
func SearchRepositoryIDs(opts *SearchRepoOptions) ([]int64, int64, error) {
 | 
			
		||||
	opts.IncludeDescription = false
 | 
			
		||||
 | 
			
		||||
	cond := SearchRepositoryCondition(opts)
 | 
			
		||||
 | 
			
		||||
	sess, count, err := searchRepositoryByCondition(opts, cond)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, 0, err
 | 
			
		||||
	}
 | 
			
		||||
	defer sess.Close()
 | 
			
		||||
 | 
			
		||||
	defaultSize := 50
 | 
			
		||||
	if opts.PageSize > 0 {
 | 
			
		||||
		defaultSize = opts.PageSize
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ids := make([]int64, 0, defaultSize)
 | 
			
		||||
	err = sess.Select("id").Table("repository").Find(&ids)
 | 
			
		||||
	if opts.PageSize <= 0 {
 | 
			
		||||
		count = int64(len(ids))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ids, count, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AccessibleRepoIDsQuery queries accessible repository ids. Usable as a subquery wherever repo ids need to be filtered.
 | 
			
		||||
func AccessibleRepoIDsQuery(user *User) *builder.Builder {
 | 
			
		||||
	// NB: Please note this code needs to still work if user is nil
 | 
			
		||||
 
 | 
			
		||||
@@ -330,10 +330,10 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) (err e
 | 
			
		||||
			SELECT il_too.id FROM (
 | 
			
		||||
				SELECT il_too_too.id
 | 
			
		||||
					FROM issue_label AS il_too_too
 | 
			
		||||
						INNER JOIN label ON il_too_too.id = label.id
 | 
			
		||||
						INNER JOIN label ON il_too_too.label_id = label.id
 | 
			
		||||
						INNER JOIN issue on issue.id = il_too_too.issue_id
 | 
			
		||||
					WHERE
 | 
			
		||||
						issue.repo_id = ? AND (issue.repo_id != label.repo_id OR (label.repo_id = 0 AND label.org_id != ?))
 | 
			
		||||
						issue.repo_id = ? AND ((label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != ?))
 | 
			
		||||
		) AS il_too )`, repo.ID, newOwner.ID); err != nil {
 | 
			
		||||
			return fmt.Errorf("Unable to remove old org labels: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
@@ -343,9 +343,9 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) (err e
 | 
			
		||||
				SELECT com.id
 | 
			
		||||
					FROM comment AS com
 | 
			
		||||
						INNER JOIN label ON com.label_id = label.id
 | 
			
		||||
						INNER JOIN issue on issue.id = com.issue_id
 | 
			
		||||
						INNER JOIN issue ON issue.id = com.issue_id
 | 
			
		||||
					WHERE
 | 
			
		||||
						com.type = ? AND issue.repo_id = ? AND (issue.repo_id != label.repo_id OR (label.repo_id = 0 AND label.org_id != ?))
 | 
			
		||||
						com.type = ? AND issue.repo_id = ? AND ((label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != ?))
 | 
			
		||||
		) AS il_too)`, CommentTypeLabel, repo.ID, newOwner.ID); err != nil {
 | 
			
		||||
			return fmt.Errorf("Unable to remove old org label comments: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -566,7 +566,11 @@ func DismissReview(review *Review, isDismiss bool) (err error) {
 | 
			
		||||
 | 
			
		||||
	review.Dismissed = isDismiss
 | 
			
		||||
 | 
			
		||||
	_, err = x.Cols("dismissed").Update(review)
 | 
			
		||||
	if review.ID == 0 {
 | 
			
		||||
		return ErrReviewNotExist{}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err = x.ID(review.ID).Cols("dismissed").Update(review)
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -143,11 +143,57 @@ func TestGetReviewersByIssueID(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDismissReview(t *testing.T) {
 | 
			
		||||
	review1 := AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review)
 | 
			
		||||
	review2 := AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review)
 | 
			
		||||
	assert.NoError(t, DismissReview(review1, true))
 | 
			
		||||
	assert.NoError(t, DismissReview(review2, true))
 | 
			
		||||
	assert.NoError(t, DismissReview(review2, true))
 | 
			
		||||
	assert.NoError(t, DismissReview(review2, false))
 | 
			
		||||
	assert.NoError(t, DismissReview(review2, false))
 | 
			
		||||
	assert.NoError(t, PrepareTestDatabase())
 | 
			
		||||
 | 
			
		||||
	rejectReviewExample := AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review)
 | 
			
		||||
	requestReviewExample := AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review)
 | 
			
		||||
	approveReviewExample := AssertExistsAndLoadBean(t, &Review{ID: 8}).(*Review)
 | 
			
		||||
	assert.False(t, rejectReviewExample.Dismissed)
 | 
			
		||||
	assert.False(t, requestReviewExample.Dismissed)
 | 
			
		||||
	assert.False(t, approveReviewExample.Dismissed)
 | 
			
		||||
 | 
			
		||||
	assert.NoError(t, DismissReview(rejectReviewExample, true))
 | 
			
		||||
	rejectReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review)
 | 
			
		||||
	requestReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review)
 | 
			
		||||
	assert.True(t, rejectReviewExample.Dismissed)
 | 
			
		||||
	assert.False(t, requestReviewExample.Dismissed)
 | 
			
		||||
 | 
			
		||||
	assert.NoError(t, DismissReview(requestReviewExample, true))
 | 
			
		||||
	rejectReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review)
 | 
			
		||||
	requestReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review)
 | 
			
		||||
	assert.True(t, rejectReviewExample.Dismissed)
 | 
			
		||||
	assert.False(t, requestReviewExample.Dismissed)
 | 
			
		||||
	assert.False(t, approveReviewExample.Dismissed)
 | 
			
		||||
 | 
			
		||||
	assert.NoError(t, DismissReview(requestReviewExample, true))
 | 
			
		||||
	rejectReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review)
 | 
			
		||||
	requestReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review)
 | 
			
		||||
	assert.True(t, rejectReviewExample.Dismissed)
 | 
			
		||||
	assert.False(t, requestReviewExample.Dismissed)
 | 
			
		||||
	assert.False(t, approveReviewExample.Dismissed)
 | 
			
		||||
 | 
			
		||||
	assert.NoError(t, DismissReview(requestReviewExample, false))
 | 
			
		||||
	rejectReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review)
 | 
			
		||||
	requestReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review)
 | 
			
		||||
	assert.True(t, rejectReviewExample.Dismissed)
 | 
			
		||||
	assert.False(t, requestReviewExample.Dismissed)
 | 
			
		||||
	assert.False(t, approveReviewExample.Dismissed)
 | 
			
		||||
 | 
			
		||||
	assert.NoError(t, DismissReview(requestReviewExample, false))
 | 
			
		||||
	rejectReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review)
 | 
			
		||||
	requestReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review)
 | 
			
		||||
	assert.True(t, rejectReviewExample.Dismissed)
 | 
			
		||||
	assert.False(t, requestReviewExample.Dismissed)
 | 
			
		||||
	assert.False(t, approveReviewExample.Dismissed)
 | 
			
		||||
 | 
			
		||||
	assert.NoError(t, DismissReview(rejectReviewExample, false))
 | 
			
		||||
	assert.False(t, rejectReviewExample.Dismissed)
 | 
			
		||||
	assert.False(t, requestReviewExample.Dismissed)
 | 
			
		||||
	assert.False(t, approveReviewExample.Dismissed)
 | 
			
		||||
 | 
			
		||||
	assert.NoError(t, DismissReview(approveReviewExample, true))
 | 
			
		||||
	assert.False(t, rejectReviewExample.Dismissed)
 | 
			
		||||
	assert.False(t, requestReviewExample.Dismissed)
 | 
			
		||||
	assert.True(t, approveReviewExample.Dismissed)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -239,10 +239,10 @@ func (u *User) GetEmail() string {
 | 
			
		||||
	return u.Email
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetAllUsers returns a slice of all users found in DB.
 | 
			
		||||
// GetAllUsers returns a slice of all individual users found in DB.
 | 
			
		||||
func GetAllUsers() ([]*User, error) {
 | 
			
		||||
	users := make([]*User, 0)
 | 
			
		||||
	return users, x.OrderBy("id").Find(&users)
 | 
			
		||||
	return users, x.OrderBy("id").Where("type = ?", UserTypeIndividual).Find(&users)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsLocal returns true if user login type is LoginPlain.
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										70
									
								
								modules/analyze/vendor.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								modules/analyze/vendor.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
			
		||||
// Copyright 2021 The Gitea Authors. All rights reserved.
 | 
			
		||||
// Use of this source code is governed by a MIT-style
 | 
			
		||||
// license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
package analyze
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/go-enry/go-enry/v2/data"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var isVendorRegExp *regexp.Regexp
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	matchers := data.VendorMatchers
 | 
			
		||||
 | 
			
		||||
	caretStrings := make([]string, 0, 10)
 | 
			
		||||
	caretShareStrings := make([]string, 0, 10)
 | 
			
		||||
 | 
			
		||||
	matcherStrings := make([]string, 0, len(matchers))
 | 
			
		||||
	for _, matcher := range matchers {
 | 
			
		||||
		str := matcher.String()
 | 
			
		||||
		if str[0] == '^' {
 | 
			
		||||
			caretStrings = append(caretStrings, str[1:])
 | 
			
		||||
		} else if str[0:5] == "(^|/)" {
 | 
			
		||||
			caretShareStrings = append(caretShareStrings, str[5:])
 | 
			
		||||
		} else {
 | 
			
		||||
			matcherStrings = append(matcherStrings, str)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sort.Strings(caretShareStrings)
 | 
			
		||||
	sort.Strings(caretStrings)
 | 
			
		||||
	sort.Strings(matcherStrings)
 | 
			
		||||
 | 
			
		||||
	sb := &strings.Builder{}
 | 
			
		||||
	sb.WriteString("(?:^(?:")
 | 
			
		||||
	sb.WriteString(caretStrings[0])
 | 
			
		||||
	for _, matcher := range caretStrings[1:] {
 | 
			
		||||
		sb.WriteString(")|(?:")
 | 
			
		||||
		sb.WriteString(matcher)
 | 
			
		||||
	}
 | 
			
		||||
	sb.WriteString("))")
 | 
			
		||||
	sb.WriteString("|")
 | 
			
		||||
	sb.WriteString("(?:(?:^|/)(?:")
 | 
			
		||||
	sb.WriteString(caretShareStrings[0])
 | 
			
		||||
	for _, matcher := range caretShareStrings[1:] {
 | 
			
		||||
		sb.WriteString(")|(?:")
 | 
			
		||||
		sb.WriteString(matcher)
 | 
			
		||||
	}
 | 
			
		||||
	sb.WriteString("))")
 | 
			
		||||
	sb.WriteString("|")
 | 
			
		||||
	sb.WriteString("(?:")
 | 
			
		||||
	sb.WriteString(matcherStrings[0])
 | 
			
		||||
	for _, matcher := range matcherStrings[1:] {
 | 
			
		||||
		sb.WriteString(")|(?:")
 | 
			
		||||
		sb.WriteString(matcher)
 | 
			
		||||
	}
 | 
			
		||||
	sb.WriteString(")")
 | 
			
		||||
	combined := sb.String()
 | 
			
		||||
	isVendorRegExp = regexp.MustCompile(combined)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsVendor returns whether or not path is a vendor path.
 | 
			
		||||
func IsVendor(path string) bool {
 | 
			
		||||
	return isVendorRegExp.MatchString(path)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										42
									
								
								modules/analyze/vendor_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								modules/analyze/vendor_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
// Copyright 2021 The Gitea Authors. All rights reserved.
 | 
			
		||||
// Use of this source code is governed by a MIT-style
 | 
			
		||||
// license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
package analyze
 | 
			
		||||
 | 
			
		||||
import "testing"
 | 
			
		||||
 | 
			
		||||
func TestIsVendor(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		path string
 | 
			
		||||
		want bool
 | 
			
		||||
	}{
 | 
			
		||||
		{"cache/", true},
 | 
			
		||||
		{"random/cache/", true},
 | 
			
		||||
		{"cache", false},
 | 
			
		||||
		{"dependencies/", true},
 | 
			
		||||
		{"Dependencies/", true},
 | 
			
		||||
		{"dependency/", false},
 | 
			
		||||
		{"dist/", true},
 | 
			
		||||
		{"dist", false},
 | 
			
		||||
		{"random/dist/", true},
 | 
			
		||||
		{"random/dist", false},
 | 
			
		||||
		{"deps/", true},
 | 
			
		||||
		{"configure", true},
 | 
			
		||||
		{"a/configure", true},
 | 
			
		||||
		{"config.guess", true},
 | 
			
		||||
		{"config.guess/", false},
 | 
			
		||||
		{".vscode/", true},
 | 
			
		||||
		{"doc/_build/", true},
 | 
			
		||||
		{"a/docs/_build/", true},
 | 
			
		||||
		{"a/dasdocs/_build-vsdoc.js", true},
 | 
			
		||||
		{"a/dasdocs/_build-vsdoc.j", false},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.path, func(t *testing.T) {
 | 
			
		||||
			if got := IsVendor(tt.path); got != tt.want {
 | 
			
		||||
				t.Errorf("IsVendor() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -8,7 +8,6 @@ package context
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"path"
 | 
			
		||||
	"strings"
 | 
			
		||||
@@ -394,238 +393,231 @@ func RepoIDAssignment() func(ctx *Context) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RepoAssignment returns a middleware to handle repository assignment
 | 
			
		||||
func RepoAssignment() func(http.Handler) http.Handler {
 | 
			
		||||
	return func(next http.Handler) http.Handler {
 | 
			
		||||
		return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
 | 
			
		||||
			var (
 | 
			
		||||
				owner *models.User
 | 
			
		||||
				err   error
 | 
			
		||||
				ctx   = GetContext(req)
 | 
			
		||||
			)
 | 
			
		||||
func RepoAssignment(ctx *Context) {
 | 
			
		||||
	var (
 | 
			
		||||
		owner *models.User
 | 
			
		||||
		err   error
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
			userName := ctx.Params(":username")
 | 
			
		||||
			repoName := ctx.Params(":reponame")
 | 
			
		||||
			repoName = strings.TrimSuffix(repoName, ".git")
 | 
			
		||||
	userName := ctx.Params(":username")
 | 
			
		||||
	repoName := ctx.Params(":reponame")
 | 
			
		||||
	repoName = strings.TrimSuffix(repoName, ".git")
 | 
			
		||||
 | 
			
		||||
			// Check if the user is the same as the repository owner
 | 
			
		||||
			if ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName) {
 | 
			
		||||
				owner = ctx.User
 | 
			
		||||
	// Check if the user is the same as the repository owner
 | 
			
		||||
	if ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName) {
 | 
			
		||||
		owner = ctx.User
 | 
			
		||||
	} else {
 | 
			
		||||
		owner, err = models.GetUserByName(userName)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			if models.IsErrUserNotExist(err) {
 | 
			
		||||
				if ctx.Query("go-get") == "1" {
 | 
			
		||||
					EarlyResponseForGoGetMeta(ctx)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				ctx.NotFound("GetUserByName", nil)
 | 
			
		||||
			} else {
 | 
			
		||||
				owner, err = models.GetUserByName(userName)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					if models.IsErrUserNotExist(err) {
 | 
			
		||||
						if ctx.Query("go-get") == "1" {
 | 
			
		||||
							EarlyResponseForGoGetMeta(ctx)
 | 
			
		||||
							return
 | 
			
		||||
						}
 | 
			
		||||
						ctx.NotFound("GetUserByName", nil)
 | 
			
		||||
					} else {
 | 
			
		||||
						ctx.ServerError("GetUserByName", err)
 | 
			
		||||
					}
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				ctx.ServerError("GetUserByName", err)
 | 
			
		||||
			}
 | 
			
		||||
			ctx.Repo.Owner = owner
 | 
			
		||||
			ctx.Data["Username"] = ctx.Repo.Owner.Name
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Repo.Owner = owner
 | 
			
		||||
	ctx.Data["Username"] = ctx.Repo.Owner.Name
 | 
			
		||||
 | 
			
		||||
			// Get repository.
 | 
			
		||||
			repo, err := models.GetRepositoryByName(owner.ID, repoName)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				if models.IsErrRepoNotExist(err) {
 | 
			
		||||
					redirectRepoID, err := models.LookupRepoRedirect(owner.ID, repoName)
 | 
			
		||||
					if err == nil {
 | 
			
		||||
						RedirectToRepo(ctx, redirectRepoID)
 | 
			
		||||
					} else if models.IsErrRepoRedirectNotExist(err) {
 | 
			
		||||
						if ctx.Query("go-get") == "1" {
 | 
			
		||||
							EarlyResponseForGoGetMeta(ctx)
 | 
			
		||||
							return
 | 
			
		||||
						}
 | 
			
		||||
						ctx.NotFound("GetRepositoryByName", nil)
 | 
			
		||||
					} else {
 | 
			
		||||
						ctx.ServerError("LookupRepoRedirect", err)
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					ctx.ServerError("GetRepositoryByName", err)
 | 
			
		||||
				}
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			repo.Owner = owner
 | 
			
		||||
 | 
			
		||||
			repoAssignment(ctx, repo)
 | 
			
		||||
			if ctx.Written() {
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			ctx.Repo.RepoLink = repo.Link()
 | 
			
		||||
			ctx.Data["RepoLink"] = ctx.Repo.RepoLink
 | 
			
		||||
			ctx.Data["RepoRelPath"] = ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name
 | 
			
		||||
 | 
			
		||||
			unit, err := ctx.Repo.Repository.GetUnit(models.UnitTypeExternalTracker)
 | 
			
		||||
	// Get repository.
 | 
			
		||||
	repo, err := models.GetRepositoryByName(owner.ID, repoName)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if models.IsErrRepoNotExist(err) {
 | 
			
		||||
			redirectRepoID, err := models.LookupRepoRedirect(owner.ID, repoName)
 | 
			
		||||
			if err == nil {
 | 
			
		||||
				ctx.Data["RepoExternalIssuesLink"] = unit.ExternalTrackerConfig().ExternalTrackerURL
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			ctx.Data["NumTags"], err = models.GetReleaseCountByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{
 | 
			
		||||
				IncludeTags: true,
 | 
			
		||||
			})
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				ctx.ServerError("GetReleaseCountByRepoID", err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			ctx.Data["NumReleases"], err = models.GetReleaseCountByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{})
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				ctx.ServerError("GetReleaseCountByRepoID", err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			ctx.Data["Title"] = owner.Name + "/" + repo.Name
 | 
			
		||||
			ctx.Data["Repository"] = repo
 | 
			
		||||
			ctx.Data["Owner"] = ctx.Repo.Repository.Owner
 | 
			
		||||
			ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner()
 | 
			
		||||
			ctx.Data["IsRepositoryAdmin"] = ctx.Repo.IsAdmin()
 | 
			
		||||
			ctx.Data["RepoOwnerIsOrganization"] = repo.Owner.IsOrganization()
 | 
			
		||||
			ctx.Data["CanWriteCode"] = ctx.Repo.CanWrite(models.UnitTypeCode)
 | 
			
		||||
			ctx.Data["CanWriteIssues"] = ctx.Repo.CanWrite(models.UnitTypeIssues)
 | 
			
		||||
			ctx.Data["CanWritePulls"] = ctx.Repo.CanWrite(models.UnitTypePullRequests)
 | 
			
		||||
 | 
			
		||||
			if ctx.Data["CanSignedUserFork"], err = ctx.Repo.Repository.CanUserFork(ctx.User); err != nil {
 | 
			
		||||
				ctx.ServerError("CanUserFork", err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			ctx.Data["DisableSSH"] = setting.SSH.Disabled
 | 
			
		||||
			ctx.Data["ExposeAnonSSH"] = setting.SSH.ExposeAnonymous
 | 
			
		||||
			ctx.Data["DisableHTTP"] = setting.Repository.DisableHTTPGit
 | 
			
		||||
			ctx.Data["RepoSearchEnabled"] = setting.Indexer.RepoIndexerEnabled
 | 
			
		||||
			ctx.Data["CloneLink"] = repo.CloneLink()
 | 
			
		||||
			ctx.Data["WikiCloneLink"] = repo.WikiCloneLink()
 | 
			
		||||
 | 
			
		||||
			if ctx.IsSigned {
 | 
			
		||||
				ctx.Data["IsWatchingRepo"] = models.IsWatching(ctx.User.ID, repo.ID)
 | 
			
		||||
				ctx.Data["IsStaringRepo"] = models.IsStaring(ctx.User.ID, repo.ID)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if repo.IsFork {
 | 
			
		||||
				RetrieveBaseRepo(ctx, repo)
 | 
			
		||||
				if ctx.Written() {
 | 
			
		||||
				RedirectToRepo(ctx, redirectRepoID)
 | 
			
		||||
			} else if models.IsErrRepoRedirectNotExist(err) {
 | 
			
		||||
				if ctx.Query("go-get") == "1" {
 | 
			
		||||
					EarlyResponseForGoGetMeta(ctx)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				ctx.NotFound("GetRepositoryByName", nil)
 | 
			
		||||
			} else {
 | 
			
		||||
				ctx.ServerError("LookupRepoRedirect", err)
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			ctx.ServerError("GetRepositoryByName", err)
 | 
			
		||||
		}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	repo.Owner = owner
 | 
			
		||||
 | 
			
		||||
			if repo.IsGenerated() {
 | 
			
		||||
				RetrieveTemplateRepo(ctx, repo)
 | 
			
		||||
				if ctx.Written() {
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
	repoAssignment(ctx, repo)
 | 
			
		||||
	if ctx.Written() {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
			// Disable everything when the repo is being created
 | 
			
		||||
			if ctx.Repo.Repository.IsBeingCreated() {
 | 
			
		||||
				ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
	ctx.Repo.RepoLink = repo.Link()
 | 
			
		||||
	ctx.Data["RepoLink"] = ctx.Repo.RepoLink
 | 
			
		||||
	ctx.Data["RepoRelPath"] = ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name
 | 
			
		||||
 | 
			
		||||
			gitRepo, err := git.OpenRepository(models.RepoPath(userName, repoName))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				ctx.ServerError("RepoAssignment Invalid repo "+models.RepoPath(userName, repoName), err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			ctx.Repo.GitRepo = gitRepo
 | 
			
		||||
	unit, err := ctx.Repo.Repository.GetUnit(models.UnitTypeExternalTracker)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		ctx.Data["RepoExternalIssuesLink"] = unit.ExternalTrackerConfig().ExternalTrackerURL
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
			// We opened it, we should close it
 | 
			
		||||
			defer func() {
 | 
			
		||||
				// If it's been set to nil then assume someone else has closed it.
 | 
			
		||||
				if ctx.Repo.GitRepo != nil {
 | 
			
		||||
					ctx.Repo.GitRepo.Close()
 | 
			
		||||
				}
 | 
			
		||||
			}()
 | 
			
		||||
	ctx.Data["NumTags"], err = models.GetReleaseCountByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{
 | 
			
		||||
		IncludeTags: true,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("GetReleaseCountByRepoID", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Data["NumReleases"], err = models.GetReleaseCountByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("GetReleaseCountByRepoID", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
			// Stop at this point when the repo is empty.
 | 
			
		||||
			if ctx.Repo.Repository.IsEmpty {
 | 
			
		||||
				ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
 | 
			
		||||
				next.ServeHTTP(w, req)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
	ctx.Data["Title"] = owner.Name + "/" + repo.Name
 | 
			
		||||
	ctx.Data["Repository"] = repo
 | 
			
		||||
	ctx.Data["Owner"] = ctx.Repo.Repository.Owner
 | 
			
		||||
	ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner()
 | 
			
		||||
	ctx.Data["IsRepositoryAdmin"] = ctx.Repo.IsAdmin()
 | 
			
		||||
	ctx.Data["RepoOwnerIsOrganization"] = repo.Owner.IsOrganization()
 | 
			
		||||
	ctx.Data["CanWriteCode"] = ctx.Repo.CanWrite(models.UnitTypeCode)
 | 
			
		||||
	ctx.Data["CanWriteIssues"] = ctx.Repo.CanWrite(models.UnitTypeIssues)
 | 
			
		||||
	ctx.Data["CanWritePulls"] = ctx.Repo.CanWrite(models.UnitTypePullRequests)
 | 
			
		||||
 | 
			
		||||
			tags, err := ctx.Repo.GitRepo.GetTags()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				ctx.ServerError("GetTags", err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			ctx.Data["Tags"] = tags
 | 
			
		||||
	if ctx.Data["CanSignedUserFork"], err = ctx.Repo.Repository.CanUserFork(ctx.User); err != nil {
 | 
			
		||||
		ctx.ServerError("CanUserFork", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
			brs, _, err := ctx.Repo.GitRepo.GetBranches(0, 0)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				ctx.ServerError("GetBranches", err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			ctx.Data["Branches"] = brs
 | 
			
		||||
			ctx.Data["BranchesCount"] = len(brs)
 | 
			
		||||
	ctx.Data["DisableSSH"] = setting.SSH.Disabled
 | 
			
		||||
	ctx.Data["ExposeAnonSSH"] = setting.SSH.ExposeAnonymous
 | 
			
		||||
	ctx.Data["DisableHTTP"] = setting.Repository.DisableHTTPGit
 | 
			
		||||
	ctx.Data["RepoSearchEnabled"] = setting.Indexer.RepoIndexerEnabled
 | 
			
		||||
	ctx.Data["CloneLink"] = repo.CloneLink()
 | 
			
		||||
	ctx.Data["WikiCloneLink"] = repo.WikiCloneLink()
 | 
			
		||||
 | 
			
		||||
			ctx.Data["TagName"] = ctx.Repo.TagName
 | 
			
		||||
	if ctx.IsSigned {
 | 
			
		||||
		ctx.Data["IsWatchingRepo"] = models.IsWatching(ctx.User.ID, repo.ID)
 | 
			
		||||
		ctx.Data["IsStaringRepo"] = models.IsStaring(ctx.User.ID, repo.ID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
			// If not branch selected, try default one.
 | 
			
		||||
			// If default branch doesn't exists, fall back to some other branch.
 | 
			
		||||
			if len(ctx.Repo.BranchName) == 0 {
 | 
			
		||||
				if len(ctx.Repo.Repository.DefaultBranch) > 0 && gitRepo.IsBranchExist(ctx.Repo.Repository.DefaultBranch) {
 | 
			
		||||
					ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch
 | 
			
		||||
				} else if len(brs) > 0 {
 | 
			
		||||
					ctx.Repo.BranchName = brs[0]
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			ctx.Data["BranchName"] = ctx.Repo.BranchName
 | 
			
		||||
			ctx.Data["CommitID"] = ctx.Repo.CommitID
 | 
			
		||||
	if repo.IsFork {
 | 
			
		||||
		RetrieveBaseRepo(ctx, repo)
 | 
			
		||||
		if ctx.Written() {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
			// People who have push access or have forked repository can propose a new pull request.
 | 
			
		||||
			canPush := ctx.Repo.CanWrite(models.UnitTypeCode) || (ctx.IsSigned && ctx.User.HasForkedRepo(ctx.Repo.Repository.ID))
 | 
			
		||||
			canCompare := false
 | 
			
		||||
	if repo.IsGenerated() {
 | 
			
		||||
		RetrieveTemplateRepo(ctx, repo)
 | 
			
		||||
		if ctx.Written() {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
			// Pull request is allowed if this is a fork repository
 | 
			
		||||
			// and base repository accepts pull requests.
 | 
			
		||||
			if repo.BaseRepo != nil && repo.BaseRepo.AllowsPulls() {
 | 
			
		||||
				canCompare = true
 | 
			
		||||
				ctx.Data["BaseRepo"] = repo.BaseRepo
 | 
			
		||||
				ctx.Repo.PullRequest.BaseRepo = repo.BaseRepo
 | 
			
		||||
				ctx.Repo.PullRequest.Allowed = canPush
 | 
			
		||||
				ctx.Repo.PullRequest.HeadInfo = ctx.Repo.Owner.Name + ":" + ctx.Repo.BranchName
 | 
			
		||||
			} else if repo.AllowsPulls() {
 | 
			
		||||
				// Or, this is repository accepts pull requests between branches.
 | 
			
		||||
				canCompare = true
 | 
			
		||||
				ctx.Data["BaseRepo"] = repo
 | 
			
		||||
				ctx.Repo.PullRequest.BaseRepo = repo
 | 
			
		||||
				ctx.Repo.PullRequest.Allowed = canPush
 | 
			
		||||
				ctx.Repo.PullRequest.SameRepo = true
 | 
			
		||||
				ctx.Repo.PullRequest.HeadInfo = ctx.Repo.BranchName
 | 
			
		||||
			}
 | 
			
		||||
			ctx.Data["CanCompareOrPull"] = canCompare
 | 
			
		||||
			ctx.Data["PullRequestCtx"] = ctx.Repo.PullRequest
 | 
			
		||||
	// Disable everything when the repo is being created
 | 
			
		||||
	if ctx.Repo.Repository.IsBeingCreated() {
 | 
			
		||||
		ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
			if ctx.Repo.Repository.Status == models.RepositoryPendingTransfer {
 | 
			
		||||
				repoTransfer, err := models.GetPendingRepositoryTransfer(ctx.Repo.Repository)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					ctx.ServerError("GetPendingRepositoryTransfer", err)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
	gitRepo, err := git.OpenRepository(models.RepoPath(userName, repoName))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("RepoAssignment Invalid repo "+models.RepoPath(userName, repoName), err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Repo.GitRepo = gitRepo
 | 
			
		||||
 | 
			
		||||
				if err := repoTransfer.LoadAttributes(); err != nil {
 | 
			
		||||
					ctx.ServerError("LoadRecipient", err)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
	// We opened it, we should close it
 | 
			
		||||
	defer func() {
 | 
			
		||||
		// If it's been set to nil then assume someone else has closed it.
 | 
			
		||||
		if ctx.Repo.GitRepo != nil {
 | 
			
		||||
			ctx.Repo.GitRepo.Close()
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
				ctx.Data["RepoTransfer"] = repoTransfer
 | 
			
		||||
				if ctx.User != nil {
 | 
			
		||||
					ctx.Data["CanUserAcceptTransfer"] = repoTransfer.CanUserAcceptTransfer(ctx.User)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
	// Stop at this point when the repo is empty.
 | 
			
		||||
	if ctx.Repo.Repository.IsEmpty {
 | 
			
		||||
		ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
			if ctx.Query("go-get") == "1" {
 | 
			
		||||
				ctx.Data["GoGetImport"] = ComposeGoGetImport(owner.Name, repo.Name)
 | 
			
		||||
				prefix := setting.AppURL + path.Join(owner.Name, repo.Name, "src", "branch", ctx.Repo.BranchName)
 | 
			
		||||
				ctx.Data["GoDocDirectory"] = prefix + "{/dir}"
 | 
			
		||||
				ctx.Data["GoDocFile"] = prefix + "{/dir}/{file}#L{line}"
 | 
			
		||||
			}
 | 
			
		||||
			next.ServeHTTP(w, req)
 | 
			
		||||
		})
 | 
			
		||||
	tags, err := ctx.Repo.GitRepo.GetTags()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("GetTags", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Data["Tags"] = tags
 | 
			
		||||
 | 
			
		||||
	brs, _, err := ctx.Repo.GitRepo.GetBranches(0, 0)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("GetBranches", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Data["Branches"] = brs
 | 
			
		||||
	ctx.Data["BranchesCount"] = len(brs)
 | 
			
		||||
 | 
			
		||||
	ctx.Data["TagName"] = ctx.Repo.TagName
 | 
			
		||||
 | 
			
		||||
	// If not branch selected, try default one.
 | 
			
		||||
	// If default branch doesn't exists, fall back to some other branch.
 | 
			
		||||
	if len(ctx.Repo.BranchName) == 0 {
 | 
			
		||||
		if len(ctx.Repo.Repository.DefaultBranch) > 0 && gitRepo.IsBranchExist(ctx.Repo.Repository.DefaultBranch) {
 | 
			
		||||
			ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch
 | 
			
		||||
		} else if len(brs) > 0 {
 | 
			
		||||
			ctx.Repo.BranchName = brs[0]
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Data["BranchName"] = ctx.Repo.BranchName
 | 
			
		||||
	ctx.Data["CommitID"] = ctx.Repo.CommitID
 | 
			
		||||
 | 
			
		||||
	// People who have push access or have forked repository can propose a new pull request.
 | 
			
		||||
	canPush := ctx.Repo.CanWrite(models.UnitTypeCode) || (ctx.IsSigned && ctx.User.HasForkedRepo(ctx.Repo.Repository.ID))
 | 
			
		||||
	canCompare := false
 | 
			
		||||
 | 
			
		||||
	// Pull request is allowed if this is a fork repository
 | 
			
		||||
	// and base repository accepts pull requests.
 | 
			
		||||
	if repo.BaseRepo != nil && repo.BaseRepo.AllowsPulls() {
 | 
			
		||||
		canCompare = true
 | 
			
		||||
		ctx.Data["BaseRepo"] = repo.BaseRepo
 | 
			
		||||
		ctx.Repo.PullRequest.BaseRepo = repo.BaseRepo
 | 
			
		||||
		ctx.Repo.PullRequest.Allowed = canPush
 | 
			
		||||
		ctx.Repo.PullRequest.HeadInfo = ctx.Repo.Owner.Name + ":" + ctx.Repo.BranchName
 | 
			
		||||
	} else if repo.AllowsPulls() {
 | 
			
		||||
		// Or, this is repository accepts pull requests between branches.
 | 
			
		||||
		canCompare = true
 | 
			
		||||
		ctx.Data["BaseRepo"] = repo
 | 
			
		||||
		ctx.Repo.PullRequest.BaseRepo = repo
 | 
			
		||||
		ctx.Repo.PullRequest.Allowed = canPush
 | 
			
		||||
		ctx.Repo.PullRequest.SameRepo = true
 | 
			
		||||
		ctx.Repo.PullRequest.HeadInfo = ctx.Repo.BranchName
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Data["CanCompareOrPull"] = canCompare
 | 
			
		||||
	ctx.Data["PullRequestCtx"] = ctx.Repo.PullRequest
 | 
			
		||||
 | 
			
		||||
	if ctx.Repo.Repository.Status == models.RepositoryPendingTransfer {
 | 
			
		||||
		repoTransfer, err := models.GetPendingRepositoryTransfer(ctx.Repo.Repository)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.ServerError("GetPendingRepositoryTransfer", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := repoTransfer.LoadAttributes(); err != nil {
 | 
			
		||||
			ctx.ServerError("LoadRecipient", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ctx.Data["RepoTransfer"] = repoTransfer
 | 
			
		||||
		if ctx.User != nil {
 | 
			
		||||
			ctx.Data["CanUserAcceptTransfer"] = repoTransfer.CanUserAcceptTransfer(ctx.User)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ctx.Query("go-get") == "1" {
 | 
			
		||||
		ctx.Data["GoGetImport"] = ComposeGoGetImport(owner.Name, repo.Name)
 | 
			
		||||
		prefix := setting.AppURL + path.Join(owner.Name, repo.Name, "src", "branch", ctx.Repo.BranchName)
 | 
			
		||||
		ctx.Data["GoDocDirectory"] = prefix + "{/dir}"
 | 
			
		||||
		ctx.Data["GoDocFile"] = prefix + "{/dir}/{file}#L{line}"
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -651,7 +643,7 @@ const (
 | 
			
		||||
 | 
			
		||||
// RepoRef handles repository reference names when the ref name is not
 | 
			
		||||
// explicitly given
 | 
			
		||||
func RepoRef() func(http.Handler) http.Handler {
 | 
			
		||||
func RepoRef() func(*Context) {
 | 
			
		||||
	// since no ref name is explicitly specified, ok to just use branch
 | 
			
		||||
	return RepoRefByType(RepoRefBranch)
 | 
			
		||||
}
 | 
			
		||||
@@ -730,130 +722,125 @@ func getRefName(ctx *Context, pathType RepoRefType) string {
 | 
			
		||||
 | 
			
		||||
// RepoRefByType handles repository reference name for a specific type
 | 
			
		||||
// of repository reference
 | 
			
		||||
func RepoRefByType(refType RepoRefType) func(http.Handler) http.Handler {
 | 
			
		||||
	return func(next http.Handler) http.Handler {
 | 
			
		||||
		return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
 | 
			
		||||
			ctx := GetContext(req)
 | 
			
		||||
			// Empty repository does not have reference information.
 | 
			
		||||
			if ctx.Repo.Repository.IsEmpty {
 | 
			
		||||
func RepoRefByType(refType RepoRefType) func(*Context) {
 | 
			
		||||
	return func(ctx *Context) {
 | 
			
		||||
		// Empty repository does not have reference information.
 | 
			
		||||
		if ctx.Repo.Repository.IsEmpty {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var (
 | 
			
		||||
			refName string
 | 
			
		||||
			err     error
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		if ctx.Repo.GitRepo == nil {
 | 
			
		||||
			repoPath := models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
 | 
			
		||||
			ctx.Repo.GitRepo, err = git.OpenRepository(repoPath)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				ctx.ServerError("RepoRef Invalid repo "+repoPath, err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			// We opened it, we should close it
 | 
			
		||||
			defer func() {
 | 
			
		||||
				// If it's been set to nil then assume someone else has closed it.
 | 
			
		||||
				if ctx.Repo.GitRepo != nil {
 | 
			
		||||
					ctx.Repo.GitRepo.Close()
 | 
			
		||||
				}
 | 
			
		||||
			}()
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
			var (
 | 
			
		||||
				refName string
 | 
			
		||||
				err     error
 | 
			
		||||
			)
 | 
			
		||||
 | 
			
		||||
			if ctx.Repo.GitRepo == nil {
 | 
			
		||||
				repoPath := models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
 | 
			
		||||
				ctx.Repo.GitRepo, err = git.OpenRepository(repoPath)
 | 
			
		||||
		// Get default branch.
 | 
			
		||||
		if len(ctx.Params("*")) == 0 {
 | 
			
		||||
			refName = ctx.Repo.Repository.DefaultBranch
 | 
			
		||||
			ctx.Repo.BranchName = refName
 | 
			
		||||
			if !ctx.Repo.GitRepo.IsBranchExist(refName) {
 | 
			
		||||
				brs, _, err := ctx.Repo.GitRepo.GetBranches(0, 0)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					ctx.ServerError("RepoRef Invalid repo "+repoPath, err)
 | 
			
		||||
					ctx.ServerError("GetBranches", err)
 | 
			
		||||
					return
 | 
			
		||||
				} else if len(brs) == 0 {
 | 
			
		||||
					err = fmt.Errorf("No branches in non-empty repository %s",
 | 
			
		||||
						ctx.Repo.GitRepo.Path)
 | 
			
		||||
					ctx.ServerError("GetBranches", err)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				// We opened it, we should close it
 | 
			
		||||
				defer func() {
 | 
			
		||||
					// If it's been set to nil then assume someone else has closed it.
 | 
			
		||||
					if ctx.Repo.GitRepo != nil {
 | 
			
		||||
						ctx.Repo.GitRepo.Close()
 | 
			
		||||
					}
 | 
			
		||||
				}()
 | 
			
		||||
				refName = brs[0]
 | 
			
		||||
			}
 | 
			
		||||
			ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				ctx.ServerError("GetBranchCommit", err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
 | 
			
		||||
			ctx.Repo.IsViewBranch = true
 | 
			
		||||
 | 
			
		||||
		} else {
 | 
			
		||||
			refName = getRefName(ctx, refType)
 | 
			
		||||
			ctx.Repo.BranchName = refName
 | 
			
		||||
			if refType.RefTypeIncludesBranches() && ctx.Repo.GitRepo.IsBranchExist(refName) {
 | 
			
		||||
				ctx.Repo.IsViewBranch = true
 | 
			
		||||
 | 
			
		||||
			// Get default branch.
 | 
			
		||||
			if len(ctx.Params("*")) == 0 {
 | 
			
		||||
				refName = ctx.Repo.Repository.DefaultBranch
 | 
			
		||||
				ctx.Repo.BranchName = refName
 | 
			
		||||
				if !ctx.Repo.GitRepo.IsBranchExist(refName) {
 | 
			
		||||
					brs, _, err := ctx.Repo.GitRepo.GetBranches(0, 0)
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						ctx.ServerError("GetBranches", err)
 | 
			
		||||
						return
 | 
			
		||||
					} else if len(brs) == 0 {
 | 
			
		||||
						err = fmt.Errorf("No branches in non-empty repository %s",
 | 
			
		||||
							ctx.Repo.GitRepo.Path)
 | 
			
		||||
						ctx.ServerError("GetBranches", err)
 | 
			
		||||
						return
 | 
			
		||||
					}
 | 
			
		||||
					refName = brs[0]
 | 
			
		||||
				}
 | 
			
		||||
				ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					ctx.ServerError("GetBranchCommit", err)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
 | 
			
		||||
				ctx.Repo.IsViewBranch = true
 | 
			
		||||
 | 
			
		||||
			} else if refType.RefTypeIncludesTags() && ctx.Repo.GitRepo.IsTagExist(refName) {
 | 
			
		||||
				ctx.Repo.IsViewTag = true
 | 
			
		||||
				ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetTagCommit(refName)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					ctx.ServerError("GetTagCommit", err)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
 | 
			
		||||
			} else if len(refName) >= 7 && len(refName) <= 40 {
 | 
			
		||||
				ctx.Repo.IsViewCommit = true
 | 
			
		||||
				ctx.Repo.CommitID = refName
 | 
			
		||||
 | 
			
		||||
				ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					ctx.NotFound("GetCommit", err)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				// If short commit ID add canonical link header
 | 
			
		||||
				if len(refName) < 40 {
 | 
			
		||||
					ctx.Header().Set("Link", fmt.Sprintf("<%s>; rel=\"canonical\"",
 | 
			
		||||
						util.URLJoin(setting.AppURL, strings.Replace(ctx.Req.URL.RequestURI(), refName, ctx.Repo.Commit.ID.String(), 1))))
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				refName = getRefName(ctx, refType)
 | 
			
		||||
				ctx.Repo.BranchName = refName
 | 
			
		||||
				if refType.RefTypeIncludesBranches() && ctx.Repo.GitRepo.IsBranchExist(refName) {
 | 
			
		||||
					ctx.Repo.IsViewBranch = true
 | 
			
		||||
 | 
			
		||||
					ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						ctx.ServerError("GetBranchCommit", err)
 | 
			
		||||
						return
 | 
			
		||||
					}
 | 
			
		||||
					ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
 | 
			
		||||
 | 
			
		||||
				} else if refType.RefTypeIncludesTags() && ctx.Repo.GitRepo.IsTagExist(refName) {
 | 
			
		||||
					ctx.Repo.IsViewTag = true
 | 
			
		||||
					ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetTagCommit(refName)
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						ctx.ServerError("GetTagCommit", err)
 | 
			
		||||
						return
 | 
			
		||||
					}
 | 
			
		||||
					ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
 | 
			
		||||
				} else if len(refName) >= 7 && len(refName) <= 40 {
 | 
			
		||||
					ctx.Repo.IsViewCommit = true
 | 
			
		||||
					ctx.Repo.CommitID = refName
 | 
			
		||||
 | 
			
		||||
					ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName)
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						ctx.NotFound("GetCommit", err)
 | 
			
		||||
						return
 | 
			
		||||
					}
 | 
			
		||||
					// If short commit ID add canonical link header
 | 
			
		||||
					if len(refName) < 40 {
 | 
			
		||||
						ctx.Header().Set("Link", fmt.Sprintf("<%s>; rel=\"canonical\"",
 | 
			
		||||
							util.URLJoin(setting.AppURL, strings.Replace(ctx.Req.URL.RequestURI(), refName, ctx.Repo.Commit.ID.String(), 1))))
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					ctx.NotFound("RepoRef invalid repo", fmt.Errorf("branch or tag not exist: %s", refName))
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if refType == RepoRefLegacy {
 | 
			
		||||
					// redirect from old URL scheme to new URL scheme
 | 
			
		||||
					ctx.Redirect(path.Join(
 | 
			
		||||
						setting.AppSubURL,
 | 
			
		||||
						strings.TrimSuffix(ctx.Req.URL.Path, ctx.Params("*")),
 | 
			
		||||
						ctx.Repo.BranchNameSubURL(),
 | 
			
		||||
						ctx.Repo.TreePath))
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			ctx.Data["BranchName"] = ctx.Repo.BranchName
 | 
			
		||||
			ctx.Data["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL()
 | 
			
		||||
			ctx.Data["CommitID"] = ctx.Repo.CommitID
 | 
			
		||||
			ctx.Data["TreePath"] = ctx.Repo.TreePath
 | 
			
		||||
			ctx.Data["IsViewBranch"] = ctx.Repo.IsViewBranch
 | 
			
		||||
			ctx.Data["IsViewTag"] = ctx.Repo.IsViewTag
 | 
			
		||||
			ctx.Data["IsViewCommit"] = ctx.Repo.IsViewCommit
 | 
			
		||||
			ctx.Data["CanCreateBranch"] = ctx.Repo.CanCreateBranch()
 | 
			
		||||
 | 
			
		||||
			ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				ctx.ServerError("GetCommitsCount", err)
 | 
			
		||||
				ctx.NotFound("RepoRef invalid repo", fmt.Errorf("branch or tag not exist: %s", refName))
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
 | 
			
		||||
 | 
			
		||||
			next.ServeHTTP(w, req)
 | 
			
		||||
		})
 | 
			
		||||
			if refType == RepoRefLegacy {
 | 
			
		||||
				// redirect from old URL scheme to new URL scheme
 | 
			
		||||
				ctx.Redirect(path.Join(
 | 
			
		||||
					setting.AppSubURL,
 | 
			
		||||
					strings.TrimSuffix(ctx.Req.URL.Path, ctx.Params("*")),
 | 
			
		||||
					ctx.Repo.BranchNameSubURL(),
 | 
			
		||||
					ctx.Repo.TreePath))
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ctx.Data["BranchName"] = ctx.Repo.BranchName
 | 
			
		||||
		ctx.Data["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL()
 | 
			
		||||
		ctx.Data["CommitID"] = ctx.Repo.CommitID
 | 
			
		||||
		ctx.Data["TreePath"] = ctx.Repo.TreePath
 | 
			
		||||
		ctx.Data["IsViewBranch"] = ctx.Repo.IsViewBranch
 | 
			
		||||
		ctx.Data["IsViewTag"] = ctx.Repo.IsViewTag
 | 
			
		||||
		ctx.Data["IsViewCommit"] = ctx.Repo.IsViewCommit
 | 
			
		||||
		ctx.Data["CanCreateBranch"] = ctx.Repo.CanCreateBranch()
 | 
			
		||||
 | 
			
		||||
		ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.ServerError("GetCommitsCount", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,9 @@
 | 
			
		||||
 | 
			
		||||
package context
 | 
			
		||||
 | 
			
		||||
import "net/http"
 | 
			
		||||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ResponseWriter represents a response writer for HTTP
 | 
			
		||||
type ResponseWriter interface {
 | 
			
		||||
@@ -60,8 +62,10 @@ func (r *Response) WriteHeader(statusCode int) {
 | 
			
		||||
		}
 | 
			
		||||
		r.beforeExecuted = true
 | 
			
		||||
	}
 | 
			
		||||
	r.status = statusCode
 | 
			
		||||
	r.ResponseWriter.WriteHeader(statusCode)
 | 
			
		||||
	if r.status == 0 {
 | 
			
		||||
		r.status = statusCode
 | 
			
		||||
		r.ResponseWriter.WriteHeader(statusCode)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Flush flush cached data
 | 
			
		||||
 
 | 
			
		||||
@@ -85,18 +85,17 @@ func ToPullReviewCommentList(review *models.Review, doer *models.User) ([]*api.P
 | 
			
		||||
 | 
			
		||||
	apiComments := make([]*api.PullReviewComment, 0, len(review.CodeComments))
 | 
			
		||||
 | 
			
		||||
	auth := false
 | 
			
		||||
	if doer != nil {
 | 
			
		||||
		auth = doer.IsAdmin || doer.ID == review.ReviewerID
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, lines := range review.CodeComments {
 | 
			
		||||
		for _, comments := range lines {
 | 
			
		||||
			for _, comment := range comments {
 | 
			
		||||
				auth := false
 | 
			
		||||
				if doer != nil {
 | 
			
		||||
					auth = doer.IsAdmin || doer.ID == comment.Poster.ID
 | 
			
		||||
				}
 | 
			
		||||
				apiComment := &api.PullReviewComment{
 | 
			
		||||
					ID:           comment.ID,
 | 
			
		||||
					Body:         comment.Content,
 | 
			
		||||
					Reviewer:     ToUser(review.Reviewer, doer != nil, auth),
 | 
			
		||||
					Reviewer:     ToUser(comment.Poster, doer != nil, auth),
 | 
			
		||||
					ReviewID:     review.ID,
 | 
			
		||||
					Created:      comment.CreatedUnix.AsTime(),
 | 
			
		||||
					Updated:      comment.UpdatedUnix.AsTime(),
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@ package git
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
@@ -14,32 +15,15 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestBlob_Data(t *testing.T) {
 | 
			
		||||
	output := `Copyright (c) 2016 The Gitea Authors
 | 
			
		||||
Copyright (c) 2015 The Gogs Authors
 | 
			
		||||
 | 
			
		||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
in the Software without restriction, including without limitation the rights
 | 
			
		||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
furnished to do so, subject to the following conditions:
 | 
			
		||||
 | 
			
		||||
The above copyright notice and this permission notice shall be included in
 | 
			
		||||
all copies or substantial portions of the Software.
 | 
			
		||||
 | 
			
		||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | 
			
		||||
THE SOFTWARE.
 | 
			
		||||
`
 | 
			
		||||
	repo, err := OpenRepository("../../.git")
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	output := "file2\n"
 | 
			
		||||
	bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
 | 
			
		||||
	repo, err := OpenRepository(bareRepo1Path)
 | 
			
		||||
	if !assert.NoError(t, err) {
 | 
			
		||||
		t.Fatal()
 | 
			
		||||
	}
 | 
			
		||||
	defer repo.Close()
 | 
			
		||||
 | 
			
		||||
	testBlob, err := repo.GetBlob("a8d4b49dd073a4a38a7e58385eeff7cc52568697")
 | 
			
		||||
	testBlob, err := repo.GetBlob("6c493ff740f9380390d5c9ddef4af18697ac9375")
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	r, err := testBlob.DataAsync()
 | 
			
		||||
@@ -53,13 +37,14 @@ THE SOFTWARE.
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Benchmark_Blob_Data(b *testing.B) {
 | 
			
		||||
	repo, err := OpenRepository("../../.git")
 | 
			
		||||
	bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
 | 
			
		||||
	repo, err := OpenRepository(bareRepo1Path)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		b.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	defer repo.Close()
 | 
			
		||||
 | 
			
		||||
	testBlob, err := repo.GetBlob("a8d4b49dd073a4a38a7e58385eeff7cc52568697")
 | 
			
		||||
	testBlob, err := repo.GetBlob("6c493ff740f9380390d5c9ddef4af18697ac9375")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		b.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -47,7 +47,7 @@ func GetRawDiffForFile(repoPath, startCommit, endCommit string, diffType RawDiff
 | 
			
		||||
func GetRepoRawDiffForFile(repo *Repository, startCommit, endCommit string, diffType RawDiffType, file string, writer io.Writer) error {
 | 
			
		||||
	commit, err := repo.GetCommit(endCommit)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("GetCommit: %v", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	fileArgs := make([]string, 0)
 | 
			
		||||
	if len(file) > 0 {
 | 
			
		||||
 
 | 
			
		||||
@@ -21,14 +21,7 @@ func (repo *Repository) GetBranchCommitID(name string) (string, error) {
 | 
			
		||||
 | 
			
		||||
// GetTagCommitID returns last commit ID string of given tag.
 | 
			
		||||
func (repo *Repository) GetTagCommitID(name string) (string, error) {
 | 
			
		||||
	stdout, err := NewCommand("rev-list", "-n", "1", TagPrefix+name).RunInDir(repo.Path)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if strings.Contains(err.Error(), "unknown revision or path") {
 | 
			
		||||
			return "", ErrNotExist{name, ""}
 | 
			
		||||
		}
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	return strings.TrimSpace(stdout), nil
 | 
			
		||||
	return repo.GetRefCommitID(TagPrefix + name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ConvertToSHA1 returns a Hash object from a potential ID string
 | 
			
		||||
 
 | 
			
		||||
@@ -43,7 +43,7 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
 | 
			
		||||
 | 
			
		||||
	sizes := make(map[string]int64)
 | 
			
		||||
	err = tree.Files().ForEach(func(f *object.File) error {
 | 
			
		||||
		if f.Size == 0 || enry.IsVendor(f.Name) || enry.IsDotFile(f.Name) ||
 | 
			
		||||
		if f.Size == 0 || analyze.IsVendor(f.Name) || enry.IsDotFile(f.Name) ||
 | 
			
		||||
			enry.IsDocumentation(f.Name) || enry.IsConfiguration(f.Name) {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -67,7 +67,7 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
 | 
			
		||||
	for _, f := range entries {
 | 
			
		||||
		contentBuf.Reset()
 | 
			
		||||
		content = contentBuf.Bytes()
 | 
			
		||||
		if f.Size() == 0 || enry.IsVendor(f.Name()) || enry.IsDotFile(f.Name()) ||
 | 
			
		||||
		if f.Size() == 0 || analyze.IsVendor(f.Name()) || enry.IsDotFile(f.Name()) ||
 | 
			
		||||
			enry.IsDocumentation(f.Name()) || enry.IsConfiguration(f.Name()) {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -68,17 +68,19 @@ func (g *Manager) start() {
 | 
			
		||||
	// Set the running state
 | 
			
		||||
	g.setState(stateRunning)
 | 
			
		||||
	if skip, _ := strconv.ParseBool(os.Getenv("SKIP_MINWINSVC")); skip {
 | 
			
		||||
		log.Trace("Skipping SVC check as SKIP_MINWINSVC is set")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Make SVC process
 | 
			
		||||
	run := svc.Run
 | 
			
		||||
	isInteractive, err := svc.IsWindowsService()
 | 
			
		||||
	isWindowsService, err := svc.IsWindowsService()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error("Unable to ascertain if running as an Interactive Session: %v", err)
 | 
			
		||||
		log.Error("Unable to ascertain if running as an Windows Service: %v", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if isInteractive {
 | 
			
		||||
	if !isWindowsService {
 | 
			
		||||
		log.Trace("Not running a service ... using the debug SVC manager")
 | 
			
		||||
		run = debug.Run
 | 
			
		||||
	}
 | 
			
		||||
	go func() {
 | 
			
		||||
@@ -94,38 +96,49 @@ func (g *Manager) Execute(args []string, changes <-chan svc.ChangeRequest, statu
 | 
			
		||||
		status <- svc.Status{State: svc.StartPending, WaitHint: uint32(setting.StartupTimeout / time.Millisecond)}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log.Trace("Awaiting server start-up")
 | 
			
		||||
	// Now need to wait for everything to start...
 | 
			
		||||
	if !g.awaitServer(setting.StartupTimeout) {
 | 
			
		||||
		log.Trace("... start-up failed ... Stopped")
 | 
			
		||||
		return false, 1
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log.Trace("Sending Running state to SVC")
 | 
			
		||||
 | 
			
		||||
	// We need to implement some way of svc.AcceptParamChange/svc.ParamChange
 | 
			
		||||
	status <- svc.Status{
 | 
			
		||||
		State:   svc.Running,
 | 
			
		||||
		Accepts: svc.AcceptStop | svc.AcceptShutdown | acceptHammerCode,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log.Trace("Started")
 | 
			
		||||
 | 
			
		||||
	waitTime := 30 * time.Second
 | 
			
		||||
 | 
			
		||||
loop:
 | 
			
		||||
	for {
 | 
			
		||||
		select {
 | 
			
		||||
		case <-g.ctx.Done():
 | 
			
		||||
			log.Trace("Shutting down")
 | 
			
		||||
			g.DoGracefulShutdown()
 | 
			
		||||
			waitTime += setting.GracefulHammerTime
 | 
			
		||||
			break loop
 | 
			
		||||
		case <-g.shutdownRequested:
 | 
			
		||||
			log.Trace("Shutting down")
 | 
			
		||||
			waitTime += setting.GracefulHammerTime
 | 
			
		||||
			break loop
 | 
			
		||||
		case change := <-changes:
 | 
			
		||||
			switch change.Cmd {
 | 
			
		||||
			case svc.Interrogate:
 | 
			
		||||
				log.Trace("SVC sent interrogate")
 | 
			
		||||
				status <- change.CurrentStatus
 | 
			
		||||
			case svc.Stop, svc.Shutdown:
 | 
			
		||||
				log.Trace("SVC requested shutdown - shutting down")
 | 
			
		||||
				g.DoGracefulShutdown()
 | 
			
		||||
				waitTime += setting.GracefulHammerTime
 | 
			
		||||
				break loop
 | 
			
		||||
			case hammerCode:
 | 
			
		||||
				log.Trace("SVC requested hammer - shutting down and hammering immediately")
 | 
			
		||||
				g.DoGracefulShutdown()
 | 
			
		||||
				g.DoImmediateHammer()
 | 
			
		||||
				break loop
 | 
			
		||||
@@ -134,6 +147,8 @@ loop:
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log.Trace("Sending StopPending state to SVC")
 | 
			
		||||
	status <- svc.Status{
 | 
			
		||||
		State:    svc.StopPending,
 | 
			
		||||
		WaitHint: uint32(waitTime / time.Millisecond),
 | 
			
		||||
@@ -145,8 +160,10 @@ hammerLoop:
 | 
			
		||||
		case change := <-changes:
 | 
			
		||||
			switch change.Cmd {
 | 
			
		||||
			case svc.Interrogate:
 | 
			
		||||
				log.Trace("SVC sent interrogate")
 | 
			
		||||
				status <- change.CurrentStatus
 | 
			
		||||
			case svc.Stop, svc.Shutdown, hammerCmd:
 | 
			
		||||
				log.Trace("SVC requested hammer - hammering immediately")
 | 
			
		||||
				g.DoImmediateHammer()
 | 
			
		||||
				break hammerLoop
 | 
			
		||||
			default:
 | 
			
		||||
@@ -156,6 +173,8 @@ hammerLoop:
 | 
			
		||||
			break hammerLoop
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log.Trace("Stopped")
 | 
			
		||||
	return false, 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -178,7 +178,7 @@ func NewBleveIndexer(indexDir string) (*BleveIndexer, bool, error) {
 | 
			
		||||
 | 
			
		||||
func (b *BleveIndexer) addUpdate(batchWriter *io.PipeWriter, batchReader *bufio.Reader, commitSha string, update fileUpdate, repo *models.Repository, batch rupture.FlushingBatch) error {
 | 
			
		||||
	// Ignore vendored files in code search
 | 
			
		||||
	if setting.Indexer.ExcludeVendored && enry.IsVendor(update.Filename) {
 | 
			
		||||
	if setting.Indexer.ExcludeVendored && analyze.IsVendor(update.Filename) {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -177,7 +177,7 @@ func (b *ElasticSearchIndexer) init() (bool, error) {
 | 
			
		||||
 | 
			
		||||
func (b *ElasticSearchIndexer) addUpdate(batchWriter *io.PipeWriter, batchReader *bufio.Reader, sha string, update fileUpdate, repo *models.Repository) ([]elastic.BulkableRequest, error) {
 | 
			
		||||
	// Ignore vendored files in code search
 | 
			
		||||
	if setting.Indexer.ExcludeVendored && enry.IsVendor(update.Filename) {
 | 
			
		||||
	if setting.Indexer.ExcludeVendored && analyze.IsVendor(update.Filename) {
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -44,24 +44,13 @@ type ContentStore struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Get takes a Meta object and retrieves the content from the store, returning
 | 
			
		||||
// it as an io.Reader. If fromByte > 0, the reader starts from that byte
 | 
			
		||||
func (s *ContentStore) Get(meta *models.LFSMetaObject, fromByte int64) (io.ReadCloser, error) {
 | 
			
		||||
// it as an io.ReadSeekCloser.
 | 
			
		||||
func (s *ContentStore) Get(meta *models.LFSMetaObject) (storage.Object, error) {
 | 
			
		||||
	f, err := s.Open(meta.RelativePath())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error("Whilst trying to read LFS OID[%s]: Unable to open Error: %v", meta.Oid, err)
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if fromByte > 0 {
 | 
			
		||||
		if fromByte >= meta.Size {
 | 
			
		||||
			return nil, ErrRangeNotSatisfiable{
 | 
			
		||||
				FromByte: fromByte,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		_, err = f.Seek(fromByte, io.SeekStart)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Error("Whilst trying to read LFS OID[%s]: Unable to seek to %d Error: %v", meta.Oid, fromByte, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return f, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -74,7 +63,7 @@ func (s *ContentStore) Put(meta *models.LFSMetaObject, r io.Reader) error {
 | 
			
		||||
 | 
			
		||||
	// now pass the wrapped reader to Save - if there is a size mismatch or hash mismatch then
 | 
			
		||||
	// the errors returned by the newHashingReader should percolate up to here
 | 
			
		||||
	written, err := s.Save(p, wrappedRd)
 | 
			
		||||
	written, err := s.Save(p, wrappedRd, meta.Size)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error("Whilst putting LFS OID[%s]: Failed to copy to tmpPath: %s Error: %v", meta.Oid, p, err)
 | 
			
		||||
		return err
 | 
			
		||||
 
 | 
			
		||||
@@ -67,5 +67,5 @@ func IsPointerFile(buf *[]byte) *models.LFSMetaObject {
 | 
			
		||||
// ReadMetaObject will read a models.LFSMetaObject and return a reader
 | 
			
		||||
func ReadMetaObject(meta *models.LFSMetaObject) (io.ReadCloser, error) {
 | 
			
		||||
	contentStore := &ContentStore{ObjectStorage: storage.LFS}
 | 
			
		||||
	return contentStore.Get(meta, 0)
 | 
			
		||||
	return contentStore.Get(meta)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -175,6 +175,11 @@ func getContentHandler(ctx *context.Context) {
 | 
			
		||||
			statusCode = 206
 | 
			
		||||
			fromByte, _ = strconv.ParseInt(match[1], 10, 32)
 | 
			
		||||
 | 
			
		||||
			if fromByte >= meta.Size {
 | 
			
		||||
				writeStatus(ctx, http.StatusRequestedRangeNotSatisfiable)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if match[2] != "" {
 | 
			
		||||
				_toByte, _ := strconv.ParseInt(match[2], 10, 32)
 | 
			
		||||
				if _toByte >= fromByte && _toByte < toByte {
 | 
			
		||||
@@ -188,18 +193,24 @@ func getContentHandler(ctx *context.Context) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	contentStore := &ContentStore{ObjectStorage: storage.LFS}
 | 
			
		||||
	content, err := contentStore.Get(meta, fromByte)
 | 
			
		||||
	content, err := contentStore.Get(meta)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if IsErrRangeNotSatisfiable(err) {
 | 
			
		||||
			writeStatus(ctx, http.StatusRequestedRangeNotSatisfiable)
 | 
			
		||||
		} else {
 | 
			
		||||
			// Errors are logged in contentStore.Get
 | 
			
		||||
			writeStatus(ctx, 404)
 | 
			
		||||
		}
 | 
			
		||||
		// Errors are logged in contentStore.Get
 | 
			
		||||
		writeStatus(ctx, http.StatusNotFound)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	defer content.Close()
 | 
			
		||||
 | 
			
		||||
	if fromByte > 0 {
 | 
			
		||||
		_, err = content.Seek(fromByte, io.SeekStart)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Error("Whilst trying to read LFS OID[%s]: Unable to seek to %d Error: %v", meta.Oid, fromByte, err)
 | 
			
		||||
 | 
			
		||||
			writeStatus(ctx, http.StatusInternalServerError)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	contentLength := toByte + 1 - fromByte
 | 
			
		||||
	ctx.Resp.Header().Set("Content-Length", strconv.FormatInt(contentLength, 10))
 | 
			
		||||
	ctx.Resp.Header().Set("Content-Type", "application/octet-stream")
 | 
			
		||||
 
 | 
			
		||||
@@ -313,7 +313,7 @@ func RenderEmoji(
 | 
			
		||||
	return ctx.postProcess(rawHTML)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var tagCleaner = regexp.MustCompile(`<((?:/?\w+/\w+)|(?:/[\w ]+/)|(/?[hH][tT][mM][lL][ />])|(/?[hH][eE][aA][dD][ />]))`)
 | 
			
		||||
var tagCleaner = regexp.MustCompile(`<((?:/?\w+/\w+)|(?:/[\w ]+/)|(/?[hH][tT][mM][lL]\b)|(/?[hH][eE][aA][dD]\b))`)
 | 
			
		||||
var nulCleaner = strings.NewReplacer("\000", "")
 | 
			
		||||
 | 
			
		||||
func (ctx *postProcessCtx) postProcess(rawHTML []byte) ([]byte, error) {
 | 
			
		||||
@@ -327,7 +327,7 @@ func (ctx *postProcessCtx) postProcess(rawHTML []byte) ([]byte, error) {
 | 
			
		||||
	_, _ = res.WriteString("<html><body>")
 | 
			
		||||
 | 
			
		||||
	// Strip out nuls - they're always invalid
 | 
			
		||||
	_, _ = nulCleaner.WriteString(res, string(tagCleaner.ReplaceAll(rawHTML, []byte("<$1"))))
 | 
			
		||||
	_, _ = res.Write(tagCleaner.ReplaceAll([]byte(nulCleaner.Replace(string(rawHTML))), []byte("<$1")))
 | 
			
		||||
 | 
			
		||||
	// close the tags
 | 
			
		||||
	_, _ = res.WriteString("</body></html>")
 | 
			
		||||
 
 | 
			
		||||
@@ -124,7 +124,7 @@ func TestRender_links(t *testing.T) {
 | 
			
		||||
		`<p><a href="http://www.example.com/wpstyle/?p=364" rel="nofollow">http://www.example.com/wpstyle/?p=364</a></p>`)
 | 
			
		||||
	test(
 | 
			
		||||
		"https://www.example.com/foo/?bar=baz&inga=42&quux",
 | 
			
		||||
		`<p><a href="https://www.example.com/foo/?bar=baz&inga=42&quux=" rel="nofollow">https://www.example.com/foo/?bar=baz&inga=42&quux</a></p>`)
 | 
			
		||||
		`<p><a href="https://www.example.com/foo/?bar=baz&inga=42&quux" rel="nofollow">https://www.example.com/foo/?bar=baz&inga=42&quux</a></p>`)
 | 
			
		||||
	test(
 | 
			
		||||
		"http://142.42.1.1/",
 | 
			
		||||
		`<p><a href="http://142.42.1.1/" rel="nofollow">http://142.42.1.1/</a></p>`)
 | 
			
		||||
 
 | 
			
		||||
@@ -46,7 +46,9 @@ func ReplaceSanitizer() {
 | 
			
		||||
	sanitizer.policy.AllowAttrs("checked", "disabled").OnElements("input")
 | 
			
		||||
 | 
			
		||||
	// Custom URL-Schemes
 | 
			
		||||
	sanitizer.policy.AllowURLSchemes(setting.Markdown.CustomURLSchemes...)
 | 
			
		||||
	if len(setting.Markdown.CustomURLSchemes) > 0 {
 | 
			
		||||
		sanitizer.policy.AllowURLSchemes(setting.Markdown.CustomURLSchemes...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Allow keyword markup
 | 
			
		||||
	sanitizer.policy.AllowAttrs("class").Matching(regexp.MustCompile(`^` + keywordClass + `$`)).OnElements("span")
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,8 @@
 | 
			
		||||
package markup
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"html/template"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
@@ -50,3 +52,13 @@ func Test_Sanitizer(t *testing.T) {
 | 
			
		||||
		assert.Equal(t, testCases[i+1], string(SanitizeBytes([]byte(testCases[i]))))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSanitizeNonEscape(t *testing.T) {
 | 
			
		||||
	descStr := "<scrİpt><script>alert(document.domain)</script></scrİpt>"
 | 
			
		||||
 | 
			
		||||
	output := template.HTML(Sanitize(string(descStr)))
 | 
			
		||||
	if strings.Contains(string(output), "<script>") {
 | 
			
		||||
		t.Errorf("un-escaped <script> in output: %q", output)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -525,9 +525,6 @@ func (g *GiteaDownloader) GetPullRequests(page, perPage int) ([]*base.PullReques
 | 
			
		||||
				headRepoName = pr.Head.Repository.Name
 | 
			
		||||
				headCloneURL = pr.Head.Repository.CloneURL
 | 
			
		||||
			}
 | 
			
		||||
			if err := fixPullHeadSha(g.client, pr); err != nil {
 | 
			
		||||
				return nil, false, fmt.Errorf("error while resolving head git ref: %s for pull #%d. Error: %v", pr.Head.Ref, pr.Index, err)
 | 
			
		||||
			}
 | 
			
		||||
			headSHA = pr.Head.Sha
 | 
			
		||||
			headRef = pr.Head.Ref
 | 
			
		||||
		}
 | 
			
		||||
@@ -679,22 +676,3 @@ func (g *GiteaDownloader) GetReviews(index int64) ([]*base.Review, error) {
 | 
			
		||||
	}
 | 
			
		||||
	return allReviews, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// fixPullHeadSha is a workaround for https://github.com/go-gitea/gitea/issues/12675
 | 
			
		||||
// When no head sha is available, this is because the branch got deleted in the base repo.
 | 
			
		||||
// pr.Head.Ref points in this case not to the head repo branch name, but the base repo ref,
 | 
			
		||||
// which stays available to resolve the commit sha.
 | 
			
		||||
func fixPullHeadSha(client *gitea_sdk.Client, pr *gitea_sdk.PullRequest) error {
 | 
			
		||||
	owner := pr.Base.Repository.Owner.UserName
 | 
			
		||||
	repo := pr.Base.Repository.Name
 | 
			
		||||
	if pr.Head != nil && pr.Head.Sha == "" {
 | 
			
		||||
		refs, _, err := client.GetRepoRefs(owner, repo, pr.Head.Ref)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		} else if len(refs) == 0 {
 | 
			
		||||
			return fmt.Errorf("unable to resolve PR ref '%s'", pr.Head.Ref)
 | 
			
		||||
		}
 | 
			
		||||
		pr.Head.Sha = refs[0].Object.SHA
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -283,7 +283,7 @@ func (g *GiteaLocalUploader) CreateReleases(releases ...*base.Release) error {
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				defer rc.Close()
 | 
			
		||||
				_, err = storage.Attachments.Save(attach.RelativePath(), rc)
 | 
			
		||||
				_, err = storage.Attachments.Save(attach.RelativePath(), rc, int64(*asset.Size))
 | 
			
		||||
				return err
 | 
			
		||||
			}()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
 
 | 
			
		||||
@@ -132,6 +132,11 @@ func (g *GithubDownloaderV3) sleep() {
 | 
			
		||||
func (g *GithubDownloaderV3) RefreshRate() error {
 | 
			
		||||
	rates, _, err := g.client.RateLimits(g.ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		// if rate limit is not enabled, ignore it
 | 
			
		||||
		if strings.Contains(err.Error(), "404") {
 | 
			
		||||
			g.rate = nil
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -332,7 +332,8 @@ func (a *actionNotifier) NotifyPushCommits(pusher *models.User, repo *models.Rep
 | 
			
		||||
func (a *actionNotifier) NotifyCreateRef(doer *models.User, repo *models.Repository, refType, refFullName string) {
 | 
			
		||||
	opType := models.ActionCommitRepo
 | 
			
		||||
	if refType == "tag" {
 | 
			
		||||
		opType = models.ActionPushTag
 | 
			
		||||
		// has sent same action in `NotifyPushCommits`, so skip it.
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if err := models.NotifyWatchers(&models.Action{
 | 
			
		||||
		ActUserID: doer.ID,
 | 
			
		||||
@@ -350,7 +351,8 @@ func (a *actionNotifier) NotifyCreateRef(doer *models.User, repo *models.Reposit
 | 
			
		||||
func (a *actionNotifier) NotifyDeleteRef(doer *models.User, repo *models.Repository, refType, refFullName string) {
 | 
			
		||||
	opType := models.ActionDeleteBranch
 | 
			
		||||
	if refType == "tag" {
 | 
			
		||||
		opType = models.ActionDeleteTag
 | 
			
		||||
		// has sent same action in `NotifyPushCommits`, so skip it.
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if err := models.NotifyWatchers(&models.Action{
 | 
			
		||||
		ActUserID: doer.ID,
 | 
			
		||||
 
 | 
			
		||||
@@ -27,6 +27,7 @@ type Options struct {
 | 
			
		||||
// KnownPublicEntries list all direct children in the `public` directory
 | 
			
		||||
var KnownPublicEntries = []string{
 | 
			
		||||
	"css",
 | 
			
		||||
	"fonts",
 | 
			
		||||
	"img",
 | 
			
		||||
	"js",
 | 
			
		||||
	"serviceworker.js",
 | 
			
		||||
 
 | 
			
		||||
@@ -174,6 +174,7 @@ func (m *Manager) FlushAll(baseCtx context.Context, timeout time.Duration) error
 | 
			
		||||
		default:
 | 
			
		||||
		}
 | 
			
		||||
		mqs := m.ManagedQueues()
 | 
			
		||||
		log.Debug("Found %d Managed Queues", len(mqs))
 | 
			
		||||
		wg := sync.WaitGroup{}
 | 
			
		||||
		wg.Add(len(mqs))
 | 
			
		||||
		allEmpty := true
 | 
			
		||||
@@ -184,6 +185,7 @@ func (m *Manager) FlushAll(baseCtx context.Context, timeout time.Duration) error
 | 
			
		||||
			}
 | 
			
		||||
			allEmpty = false
 | 
			
		||||
			if flushable, ok := mq.Managed.(Flushable); ok {
 | 
			
		||||
				log.Debug("Flushing (flushable) queue: %s", mq.Name)
 | 
			
		||||
				go func(q *ManagedQueue) {
 | 
			
		||||
					localCtx, localCancel := context.WithCancel(ctx)
 | 
			
		||||
					pid := q.RegisterWorkers(1, start, hasTimeout, end, localCancel, true)
 | 
			
		||||
@@ -196,7 +198,11 @@ func (m *Manager) FlushAll(baseCtx context.Context, timeout time.Duration) error
 | 
			
		||||
					wg.Done()
 | 
			
		||||
				}(mq)
 | 
			
		||||
			} else {
 | 
			
		||||
				wg.Done()
 | 
			
		||||
				log.Debug("Queue: %s is non-empty but is not flushable - adding 100 millisecond wait", mq.Name)
 | 
			
		||||
				go func() {
 | 
			
		||||
					<-time.After(100 * time.Millisecond)
 | 
			
		||||
					wg.Done()
 | 
			
		||||
				}()
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -99,42 +99,10 @@ func UploadRepoFiles(repo *models.Repository, doer *models.User, opts *UploadRep
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Copy uploaded files into repository.
 | 
			
		||||
	for i, uploadInfo := range infos {
 | 
			
		||||
		file, err := os.Open(uploadInfo.upload.LocalPath())
 | 
			
		||||
		if err != nil {
 | 
			
		||||
	for i := range infos {
 | 
			
		||||
		if err := copyUploadedLFSFileIntoRepository(&infos[i], filename2attribute2info, t, opts.TreePath); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		defer file.Close()
 | 
			
		||||
 | 
			
		||||
		var objectHash string
 | 
			
		||||
		if setting.LFS.StartServer && filename2attribute2info[uploadInfo.upload.Name] != nil && filename2attribute2info[uploadInfo.upload.Name]["filter"] == "lfs" {
 | 
			
		||||
			// Handle LFS
 | 
			
		||||
			// FIXME: Inefficient! this should probably happen in models.Upload
 | 
			
		||||
			oid, err := models.GenerateLFSOid(file)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			fileInfo, err := file.Stat()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			uploadInfo.lfsMetaObject = &models.LFSMetaObject{Oid: oid, Size: fileInfo.Size(), RepositoryID: t.repo.ID}
 | 
			
		||||
 | 
			
		||||
			if objectHash, err = t.HashObject(strings.NewReader(uploadInfo.lfsMetaObject.Pointer())); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			infos[i] = uploadInfo
 | 
			
		||||
 | 
			
		||||
		} else if objectHash, err = t.HashObject(file); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Add the object to the index
 | 
			
		||||
		if err := t.AddObjectToIndex("100644", objectHash, path.Join(opts.TreePath, uploadInfo.upload.Name)); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Now write the tree
 | 
			
		||||
@@ -154,11 +122,11 @@ func UploadRepoFiles(repo *models.Repository, doer *models.User, opts *UploadRep
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Now deal with LFS objects
 | 
			
		||||
	for _, uploadInfo := range infos {
 | 
			
		||||
		if uploadInfo.lfsMetaObject == nil {
 | 
			
		||||
	for i := range infos {
 | 
			
		||||
		if infos[i].lfsMetaObject == nil {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		uploadInfo.lfsMetaObject, err = models.NewLFSMetaObject(uploadInfo.lfsMetaObject)
 | 
			
		||||
		infos[i].lfsMetaObject, err = models.NewLFSMetaObject(infos[i].lfsMetaObject)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			// OK Now we need to cleanup
 | 
			
		||||
			return cleanUpAfterFailure(&infos, t, err)
 | 
			
		||||
@@ -170,28 +138,10 @@ func UploadRepoFiles(repo *models.Repository, doer *models.User, opts *UploadRep
 | 
			
		||||
	// OK now we can insert the data into the store - there's no way to clean up the store
 | 
			
		||||
	// once it's in there, it's in there.
 | 
			
		||||
	contentStore := &lfs.ContentStore{ObjectStorage: storage.LFS}
 | 
			
		||||
	for _, uploadInfo := range infos {
 | 
			
		||||
		if uploadInfo.lfsMetaObject == nil {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		exist, err := contentStore.Exists(uploadInfo.lfsMetaObject)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
	for _, info := range infos {
 | 
			
		||||
		if err := uploadToLFSContentStore(info, contentStore); err != nil {
 | 
			
		||||
			return cleanUpAfterFailure(&infos, t, err)
 | 
			
		||||
		}
 | 
			
		||||
		if !exist {
 | 
			
		||||
			file, err := os.Open(uploadInfo.upload.LocalPath())
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return cleanUpAfterFailure(&infos, t, err)
 | 
			
		||||
			}
 | 
			
		||||
			defer file.Close()
 | 
			
		||||
			// FIXME: Put regenerates the hash and copies the file over.
 | 
			
		||||
			// I guess this strictly ensures the soundness of the store but this is inefficient.
 | 
			
		||||
			if err := contentStore.Put(uploadInfo.lfsMetaObject, file); err != nil {
 | 
			
		||||
				// OK Now we need to cleanup
 | 
			
		||||
				// Can't clean up the store, once uploaded there they're there.
 | 
			
		||||
				return cleanUpAfterFailure(&infos, t, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Then push this tree to NewBranch
 | 
			
		||||
@@ -201,3 +151,62 @@ func UploadRepoFiles(repo *models.Repository, doer *models.User, opts *UploadRep
 | 
			
		||||
 | 
			
		||||
	return models.DeleteUploads(uploads...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func copyUploadedLFSFileIntoRepository(info *uploadInfo, filename2attribute2info map[string]map[string]string, t *TemporaryUploadRepository, treePath string) error {
 | 
			
		||||
	file, err := os.Open(info.upload.LocalPath())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer file.Close()
 | 
			
		||||
 | 
			
		||||
	var objectHash string
 | 
			
		||||
	if setting.LFS.StartServer && filename2attribute2info[info.upload.Name] != nil && filename2attribute2info[info.upload.Name]["filter"] == "lfs" {
 | 
			
		||||
		// Handle LFS
 | 
			
		||||
		// FIXME: Inefficient! this should probably happen in models.Upload
 | 
			
		||||
		oid, err := models.GenerateLFSOid(file)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		fileInfo, err := file.Stat()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		info.lfsMetaObject = &models.LFSMetaObject{Oid: oid, Size: fileInfo.Size(), RepositoryID: t.repo.ID}
 | 
			
		||||
 | 
			
		||||
		if objectHash, err = t.HashObject(strings.NewReader(info.lfsMetaObject.Pointer())); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	} else if objectHash, err = t.HashObject(file); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Add the object to the index
 | 
			
		||||
	return t.AddObjectToIndex("100644", objectHash, path.Join(treePath, info.upload.Name))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func uploadToLFSContentStore(info uploadInfo, contentStore *lfs.ContentStore) error {
 | 
			
		||||
	if info.lfsMetaObject == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	exist, err := contentStore.Exists(info.lfsMetaObject)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if !exist {
 | 
			
		||||
		file, err := os.Open(info.upload.LocalPath())
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		defer file.Close()
 | 
			
		||||
		// FIXME: Put regenerates the hash and copies the file over.
 | 
			
		||||
		// I guess this strictly ensures the soundness of the store but this is inefficient.
 | 
			
		||||
		if err := contentStore.Put(info.lfsMetaObject, file); err != nil {
 | 
			
		||||
			// OK Now we need to cleanup
 | 
			
		||||
			// Can't clean up the store, once uploaded there they're there.
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -64,6 +64,12 @@ func ForkRepository(doer, owner *models.User, oldRepo *models.Repository, name,
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// copy lfs files failure should not be ignored
 | 
			
		||||
		if err := models.CopyLFS(ctx, repo, oldRepo); err != nil {
 | 
			
		||||
			rollbackRemoveFn()
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		repoPath := models.RepoPath(owner.Name, repo.Name)
 | 
			
		||||
		if stdout, err := git.NewCommand(
 | 
			
		||||
			"clone", "--bare", oldRepoPath, repoPath).
 | 
			
		||||
@@ -92,6 +98,7 @@ func ForkRepository(doer, owner *models.User, oldRepo *models.Repository, name,
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// even if below operations failed, it could be ignored. And they will be retried
 | 
			
		||||
	ctx := models.DefaultDBContext()
 | 
			
		||||
	if err = repo.UpdateSize(ctx); err != nil {
 | 
			
		||||
		log.Error("Failed to update size for repository: %v", err)
 | 
			
		||||
@@ -100,11 +107,5 @@ func ForkRepository(doer, owner *models.User, oldRepo *models.Repository, name,
 | 
			
		||||
		log.Error("Copy language stat from oldRepo failed")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := models.CopyLFS(ctx, repo, oldRepo); err != nil {
 | 
			
		||||
		if errDelete := models.DeleteRepository(doer, owner.ID, repo.ID); errDelete != nil {
 | 
			
		||||
			log.Error("Rollback deleteRepository: %v", errDelete)
 | 
			
		||||
		}
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return repo, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -66,7 +66,7 @@ func (l *LocalStorage) Open(path string) (Object, error) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Save a file
 | 
			
		||||
func (l *LocalStorage) Save(path string, r io.Reader) (int64, error) {
 | 
			
		||||
func (l *LocalStorage) Save(path string, r io.Reader, size int64) (int64, error) {
 | 
			
		||||
	p := filepath.Join(l.dir, path)
 | 
			
		||||
	if err := os.MkdirAll(filepath.Dir(p), os.ModePerm); err != nil {
 | 
			
		||||
		return 0, err
 | 
			
		||||
 
 | 
			
		||||
@@ -131,13 +131,13 @@ func (m *MinioStorage) Open(path string) (Object, error) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Save save a file to minio
 | 
			
		||||
func (m *MinioStorage) Save(path string, r io.Reader) (int64, error) {
 | 
			
		||||
func (m *MinioStorage) Save(path string, r io.Reader, size int64) (int64, error) {
 | 
			
		||||
	uploadInfo, err := m.client.PutObject(
 | 
			
		||||
		m.ctx,
 | 
			
		||||
		m.bucket,
 | 
			
		||||
		m.buildMinioPath(path),
 | 
			
		||||
		r,
 | 
			
		||||
		-1,
 | 
			
		||||
		size,
 | 
			
		||||
		minio.PutObjectOptions{ContentType: "application/octet-stream"},
 | 
			
		||||
	)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
 
 | 
			
		||||
@@ -65,7 +65,8 @@ type Object interface {
 | 
			
		||||
// ObjectStorage represents an object storage to handle a bucket and files
 | 
			
		||||
type ObjectStorage interface {
 | 
			
		||||
	Open(path string) (Object, error)
 | 
			
		||||
	Save(path string, r io.Reader) (int64, error)
 | 
			
		||||
	// Save store a object, if size is unknown set -1
 | 
			
		||||
	Save(path string, r io.Reader, size int64) (int64, error)
 | 
			
		||||
	Stat(path string) (os.FileInfo, error)
 | 
			
		||||
	Delete(path string) error
 | 
			
		||||
	URL(path, name string) (*url.URL, error)
 | 
			
		||||
@@ -80,7 +81,13 @@ func Copy(dstStorage ObjectStorage, dstPath string, srcStorage ObjectStorage, sr
 | 
			
		||||
	}
 | 
			
		||||
	defer f.Close()
 | 
			
		||||
 | 
			
		||||
	return dstStorage.Save(dstPath, f)
 | 
			
		||||
	size := int64(-1)
 | 
			
		||||
	fsinfo, err := f.Stat()
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		size = fsinfo.Size()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return dstStorage.Save(dstPath, f, size)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SaveFrom saves data to the ObjectStorage with path p from the callback
 | 
			
		||||
@@ -94,7 +101,7 @@ func SaveFrom(objStorage ObjectStorage, p string, callback func(w io.Writer) err
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	_, err := objStorage.Save(p, pr)
 | 
			
		||||
	_, err := objStorage.Save(p, pr, -1)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -27,8 +27,9 @@ type LangType struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	matcher  language.Matcher
 | 
			
		||||
	allLangs []LangType
 | 
			
		||||
	matcher       language.Matcher
 | 
			
		||||
	allLangs      []LangType
 | 
			
		||||
	supportedTags []language.Tag
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// AllLangs returns all supported langauages
 | 
			
		||||
@@ -51,12 +52,12 @@ func InitLocales() {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tags := make([]language.Tag, len(setting.Langs))
 | 
			
		||||
	supportedTags = make([]language.Tag, len(setting.Langs))
 | 
			
		||||
	for i, lang := range setting.Langs {
 | 
			
		||||
		tags[i] = language.Raw.Make(lang)
 | 
			
		||||
		supportedTags[i] = language.Raw.Make(lang)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	matcher = language.NewMatcher(tags)
 | 
			
		||||
	matcher = language.NewMatcher(supportedTags)
 | 
			
		||||
	for i := range setting.Names {
 | 
			
		||||
		key := "locale_" + setting.Langs[i] + ".ini"
 | 
			
		||||
		if err = i18n.SetMessageWithDesc(setting.Langs[i], setting.Names[i], localFiles[key]); err != nil {
 | 
			
		||||
@@ -79,8 +80,9 @@ func InitLocales() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Match matches accept languages
 | 
			
		||||
func Match(tags ...language.Tag) (tag language.Tag, index int, c language.Confidence) {
 | 
			
		||||
	return matcher.Match(tags...)
 | 
			
		||||
func Match(tags ...language.Tag) language.Tag {
 | 
			
		||||
	_, i, _ := matcher.Match(tags...)
 | 
			
		||||
	return supportedTags[i]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// locale represents the information of localization.
 | 
			
		||||
 
 | 
			
		||||
@@ -38,7 +38,7 @@ func Locale(resp http.ResponseWriter, req *http.Request) translation.Locale {
 | 
			
		||||
	// The first element in the list is chosen to be the default language automatically.
 | 
			
		||||
	if len(lang) == 0 {
 | 
			
		||||
		tags, _, _ := language.ParseAcceptLanguage(req.Header.Get("Accept-Language"))
 | 
			
		||||
		tag, _, _ := translation.Match(tags...)
 | 
			
		||||
		tag := translation.Match(tags...)
 | 
			
		||||
		lang = tag.String()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -68,10 +68,11 @@ func Wrap(handlers ...interface{}) http.HandlerFunc {
 | 
			
		||||
				}
 | 
			
		||||
			case func(http.Handler) http.Handler:
 | 
			
		||||
				var next = http.HandlerFunc(func(http.ResponseWriter, *http.Request) {})
 | 
			
		||||
				t(next).ServeHTTP(resp, req)
 | 
			
		||||
				if r, ok := resp.(context.ResponseWriter); ok && r.Status() > 0 {
 | 
			
		||||
					return
 | 
			
		||||
				if len(handlers) > i+1 {
 | 
			
		||||
					next = Wrap(handlers[i+1:]...)
 | 
			
		||||
				}
 | 
			
		||||
				t(next).ServeHTTP(resp, req)
 | 
			
		||||
				return
 | 
			
		||||
			default:
 | 
			
		||||
				panic(fmt.Sprintf("Unsupported handler type: %#v", t))
 | 
			
		||||
			}
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,6 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/modules/context"
 | 
			
		||||
	"code.gitea.io/gitea/modules/convert"
 | 
			
		||||
	issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/notification"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	api "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
@@ -113,11 +112,7 @@ func SearchIssues(ctx *context.APIContext) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// find repos user can access (for issue search)
 | 
			
		||||
	repoIDs := make([]int64, 0)
 | 
			
		||||
	opts := &models.SearchRepoOptions{
 | 
			
		||||
		ListOptions: models.ListOptions{
 | 
			
		||||
			PageSize: 15,
 | 
			
		||||
		},
 | 
			
		||||
		Private:     false,
 | 
			
		||||
		AllPublic:   true,
 | 
			
		||||
		TopicOnly:   false,
 | 
			
		||||
@@ -132,21 +127,10 @@ func SearchIssues(ctx *context.APIContext) {
 | 
			
		||||
		opts.AllLimited = true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for page := 1; ; page++ {
 | 
			
		||||
		opts.Page = page
 | 
			
		||||
		repos, count, err := models.SearchRepositoryByName(opts)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.Error(http.StatusInternalServerError, "SearchRepositoryByName", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(repos) == 0 {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		log.Trace("Processing next %d repos of %d", len(repos), count)
 | 
			
		||||
		for _, repo := range repos {
 | 
			
		||||
			repoIDs = append(repoIDs, repo.ID)
 | 
			
		||||
		}
 | 
			
		||||
	repoIDs, _, err := models.SearchRepositoryIDs(opts)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.Error(http.StatusInternalServerError, "SearchRepositoryByName", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var issues []*models.Issue
 | 
			
		||||
@@ -157,7 +141,6 @@ func SearchIssues(ctx *context.APIContext) {
 | 
			
		||||
		keyword = ""
 | 
			
		||||
	}
 | 
			
		||||
	var issueIDs []int64
 | 
			
		||||
	var labelIDs []int64
 | 
			
		||||
	if len(keyword) > 0 && len(repoIDs) > 0 {
 | 
			
		||||
		if issueIDs, err = issue_indexer.SearchIssuesByKeyword(repoIDs, keyword); err != nil {
 | 
			
		||||
			ctx.Error(http.StatusInternalServerError, "SearchIssuesByKeyword", err)
 | 
			
		||||
@@ -192,7 +175,7 @@ func SearchIssues(ctx *context.APIContext) {
 | 
			
		||||
 | 
			
		||||
	// Only fetch the issues if we either don't have a keyword or the search returned issues
 | 
			
		||||
	// This would otherwise return all issues if no issues were found by the search.
 | 
			
		||||
	if len(keyword) == 0 || len(issueIDs) > 0 || len(labelIDs) > 0 {
 | 
			
		||||
	if len(keyword) == 0 || len(issueIDs) > 0 || len(includedLabelNames) > 0 {
 | 
			
		||||
		issuesOpt := &models.IssuesOptions{
 | 
			
		||||
			ListOptions: models.ListOptions{
 | 
			
		||||
				Page:     ctx.QueryInt("page"),
 | 
			
		||||
 
 | 
			
		||||
@@ -274,7 +274,11 @@ func DeleteOauth2Application(ctx *context.APIContext) {
 | 
			
		||||
	//     "$ref": "#/responses/empty"
 | 
			
		||||
	appID := ctx.ParamsInt64(":id")
 | 
			
		||||
	if err := models.DeleteOAuth2Application(appID, ctx.User.ID); err != nil {
 | 
			
		||||
		ctx.Error(http.StatusInternalServerError, "DeleteOauth2ApplicationByID", err)
 | 
			
		||||
		if models.IsErrOAuthApplicationNotFound(err) {
 | 
			
		||||
			ctx.NotFound()
 | 
			
		||||
		} else {
 | 
			
		||||
			ctx.Error(http.StatusInternalServerError, "DeleteOauth2ApplicationByID", err)
 | 
			
		||||
		}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -30,6 +30,17 @@ func Events(ctx *context.Context) {
 | 
			
		||||
	ctx.Resp.Header().Set("X-Accel-Buffering", "no")
 | 
			
		||||
	ctx.Resp.WriteHeader(http.StatusOK)
 | 
			
		||||
 | 
			
		||||
	if !ctx.IsSigned {
 | 
			
		||||
		// Return unauthorized status event
 | 
			
		||||
		event := (&eventsource.Event{
 | 
			
		||||
			Name: "close",
 | 
			
		||||
			Data: "unauthorized",
 | 
			
		||||
		})
 | 
			
		||||
		_, _ = event.WriteTo(ctx)
 | 
			
		||||
		ctx.Resp.Flush()
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Listen to connection close and un-register messageChan
 | 
			
		||||
	notify := ctx.Req.Context().Done()
 | 
			
		||||
	ctx.Resp.Flush()
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@
 | 
			
		||||
package repo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"path"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
@@ -389,6 +390,11 @@ func RawDiff(ctx *context.Context) {
 | 
			
		||||
		git.RawDiffType(ctx.Params(":ext")),
 | 
			
		||||
		ctx.Resp,
 | 
			
		||||
	); err != nil {
 | 
			
		||||
		if git.IsErrNotExist(err) {
 | 
			
		||||
			ctx.NotFound("GetRawDiff",
 | 
			
		||||
				errors.New("commit "+ctx.Params(":sha")+" does not exist."))
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		ctx.ServerError("GetRawDiff", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -431,11 +431,15 @@ func PrepareCompareDiff(
 | 
			
		||||
		ctx.Data["IsNothingToCompare"] = true
 | 
			
		||||
		if unit, err := repo.GetUnit(models.UnitTypePullRequests); err == nil {
 | 
			
		||||
			config := unit.PullRequestsConfig()
 | 
			
		||||
 | 
			
		||||
			if !config.AutodetectManualMerge {
 | 
			
		||||
				ctx.Data["AllowEmptyPr"] = !(baseBranch == headBranch && ctx.Repo.Repository.Name == headRepo.Name)
 | 
			
		||||
			} else {
 | 
			
		||||
				ctx.Data["AllowEmptyPr"] = false
 | 
			
		||||
				allowEmptyPr := !(baseBranch == headBranch && ctx.Repo.Repository.Name == headRepo.Name)
 | 
			
		||||
				ctx.Data["AllowEmptyPr"] = allowEmptyPr
 | 
			
		||||
 | 
			
		||||
				return !allowEmptyPr
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			ctx.Data["AllowEmptyPr"] = false
 | 
			
		||||
		}
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -103,9 +103,11 @@ func httpBase(ctx *context.Context) (h *serviceHandler) {
 | 
			
		||||
 | 
			
		||||
	isWiki := false
 | 
			
		||||
	var unitType = models.UnitTypeCode
 | 
			
		||||
	var wikiRepoName string
 | 
			
		||||
	if strings.HasSuffix(reponame, ".wiki") {
 | 
			
		||||
		isWiki = true
 | 
			
		||||
		unitType = models.UnitTypeWiki
 | 
			
		||||
		wikiRepoName = reponame
 | 
			
		||||
		reponame = reponame[:len(reponame)-5]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -314,6 +316,11 @@ func httpBase(ctx *context.Context) (h *serviceHandler) {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if isWiki { // you cannot send wiki operation before create the repository
 | 
			
		||||
			ctx.HandleText(http.StatusNotFound, "Repository not found")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if owner.IsOrganization() && !setting.Repository.EnablePushCreateOrg {
 | 
			
		||||
			ctx.HandleText(http.StatusForbidden, "Push to create is not enabled for organizations.")
 | 
			
		||||
			return
 | 
			
		||||
@@ -363,6 +370,9 @@ func httpBase(ctx *context.Context) (h *serviceHandler) {
 | 
			
		||||
	r.URL.Path = strings.ToLower(r.URL.Path) // blue: In case some repo name has upper case name
 | 
			
		||||
 | 
			
		||||
	dir := models.RepoPath(username, reponame)
 | 
			
		||||
	if isWiki {
 | 
			
		||||
		dir = models.RepoPath(username, wikiRepoName)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &serviceHandler{cfg, w, r, dir, cfg.Env}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -241,14 +241,13 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	approvalCounts, err := models.IssueList(issues).GetApprovalCounts()
 | 
			
		||||
	var issueList = models.IssueList(issues)
 | 
			
		||||
	approvalCounts, err := issueList.GetApprovalCounts()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("ApprovalCounts", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var commitStatus = make(map[int64]*models.CommitStatus, len(issues))
 | 
			
		||||
 | 
			
		||||
	// Get posters.
 | 
			
		||||
	for i := range issues {
 | 
			
		||||
		// Check read status
 | 
			
		||||
@@ -258,16 +257,12 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
 | 
			
		||||
			ctx.ServerError("GetIsRead", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		if issues[i].IsPull {
 | 
			
		||||
			if err := issues[i].LoadPullRequest(); err != nil {
 | 
			
		||||
				ctx.ServerError("LoadPullRequest", err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			var statuses, _ = pull_service.GetLastCommitStatus(issues[i].PullRequest)
 | 
			
		||||
			commitStatus[issues[i].PullRequest.ID] = models.CalcCommitStatus(statuses)
 | 
			
		||||
		}
 | 
			
		||||
	commitStatus, err := pull_service.GetIssuesLastCommitStatus(issues)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("GetIssuesLastCommitStatus", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Data["Issues"] = issues
 | 
			
		||||
 
 | 
			
		||||
@@ -192,6 +192,9 @@ func CreatePost(ctx *context.Context) {
 | 
			
		||||
	ctx.Data["Licenses"] = models.Licenses
 | 
			
		||||
	ctx.Data["Readmes"] = models.Readmes
 | 
			
		||||
 | 
			
		||||
	ctx.Data["CanCreateRepo"] = ctx.User.CanCreateRepo()
 | 
			
		||||
	ctx.Data["MaxCreationLimit"] = ctx.User.MaxCreationLimit()
 | 
			
		||||
 | 
			
		||||
	ctxUser := checkContextUser(ctx, form.UID)
 | 
			
		||||
	if ctx.Written() {
 | 
			
		||||
		return
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,6 @@
 | 
			
		||||
package repo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"path"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/base"
 | 
			
		||||
@@ -40,7 +39,7 @@ func Search(ctx *context.Context) {
 | 
			
		||||
	ctx.Data["Keyword"] = keyword
 | 
			
		||||
	ctx.Data["Language"] = language
 | 
			
		||||
	ctx.Data["queryType"] = queryType
 | 
			
		||||
	ctx.Data["SourcePath"] = path.Join(setting.AppSubURL, ctx.Repo.Repository.Owner.Name, ctx.Repo.Repository.Name)
 | 
			
		||||
	ctx.Data["SourcePath"] = ctx.Repo.Repository.HTMLURL()
 | 
			
		||||
	ctx.Data["SearchResults"] = searchResults
 | 
			
		||||
	ctx.Data["SearchResultLanguages"] = searchResultLanguages
 | 
			
		||||
	ctx.Data["RequireHighlightJS"] = true
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
@@ -87,13 +88,21 @@ func storageHandler(storageSetting setting.Storage, prefix string, objStore stor
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if !strings.HasPrefix(req.URL.RequestURI(), "/"+prefix) {
 | 
			
		||||
			prefix := strings.Trim(prefix, "/")
 | 
			
		||||
 | 
			
		||||
			if !strings.HasPrefix(req.URL.EscapedPath(), "/"+prefix+"/") {
 | 
			
		||||
				next.ServeHTTP(w, req)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			rPath := strings.TrimPrefix(req.URL.RequestURI(), "/"+prefix)
 | 
			
		||||
			rPath := strings.TrimPrefix(req.URL.EscapedPath(), "/"+prefix+"/")
 | 
			
		||||
			rPath = strings.TrimPrefix(rPath, "/")
 | 
			
		||||
			if rPath == "" {
 | 
			
		||||
				http.Error(w, "file not found", 404)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			rPath = path.Clean("/" + filepath.ToSlash(rPath))
 | 
			
		||||
			rPath = rPath[1:]
 | 
			
		||||
 | 
			
		||||
			fi, err := objStore.Stat(rPath)
 | 
			
		||||
			if err == nil && httpcache.HandleTimeCache(req, w, fi) {
 | 
			
		||||
 
 | 
			
		||||
@@ -168,15 +168,6 @@ func WebRoutes() *web.Route {
 | 
			
		||||
		r.Use(h)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (setting.Protocol == setting.FCGI || setting.Protocol == setting.FCGIUnix) && setting.AppSubURL != "" {
 | 
			
		||||
		r.Use(func(next http.Handler) http.Handler {
 | 
			
		||||
			return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
 | 
			
		||||
				req.URL.Path = strings.TrimPrefix(req.URL.Path, setting.AppSubURL)
 | 
			
		||||
				next.ServeHTTP(resp, req)
 | 
			
		||||
			})
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mailer.InitMailRender(templates.Mailer())
 | 
			
		||||
 | 
			
		||||
	if setting.Service.EnableCaptcha {
 | 
			
		||||
@@ -400,7 +391,7 @@ func RegisterRoutes(m *web.Route) {
 | 
			
		||||
		})
 | 
			
		||||
	}, reqSignOut)
 | 
			
		||||
 | 
			
		||||
	m.Any("/user/events", reqSignIn, events.Events)
 | 
			
		||||
	m.Any("/user/events", events.Events)
 | 
			
		||||
 | 
			
		||||
	m.Group("/login/oauth", func() {
 | 
			
		||||
		m.Get("/authorize", bindIgnErr(auth.AuthorizationForm{}), user.AuthorizeOAuth)
 | 
			
		||||
@@ -700,7 +691,7 @@ func RegisterRoutes(m *web.Route) {
 | 
			
		||||
	}, reqSignIn)
 | 
			
		||||
 | 
			
		||||
	// ***** Release Attachment Download without Signin
 | 
			
		||||
	m.Get("/{username}/{reponame}/releases/download/{vTag}/{fileName}", ignSignIn, context.RepoAssignment(), repo.MustBeNotEmpty, repo.RedirectDownload)
 | 
			
		||||
	m.Get("/{username}/{reponame}/releases/download/{vTag}/{fileName}", ignSignIn, context.RepoAssignment, repo.MustBeNotEmpty, repo.RedirectDownload)
 | 
			
		||||
 | 
			
		||||
	m.Group("/{username}/{reponame}", func() {
 | 
			
		||||
		m.Group("/settings", func() {
 | 
			
		||||
@@ -780,9 +771,9 @@ func RegisterRoutes(m *web.Route) {
 | 
			
		||||
			ctx.Data["PageIsSettings"] = true
 | 
			
		||||
			ctx.Data["LFSStartServer"] = setting.LFS.StartServer
 | 
			
		||||
		})
 | 
			
		||||
	}, reqSignIn, context.RepoAssignment(), context.UnitTypes(), reqRepoAdmin, context.RepoRef())
 | 
			
		||||
	}, reqSignIn, context.RepoAssignment, context.UnitTypes(), reqRepoAdmin, context.RepoRef())
 | 
			
		||||
 | 
			
		||||
	m.Post("/{username}/{reponame}/action/{action}", reqSignIn, context.RepoAssignment(), context.UnitTypes(), repo.Action)
 | 
			
		||||
	m.Post("/{username}/{reponame}/action/{action}", reqSignIn, context.RepoAssignment, context.UnitTypes(), repo.Action)
 | 
			
		||||
 | 
			
		||||
	// Grouping for those endpoints not requiring authentication
 | 
			
		||||
	m.Group("/{username}/{reponame}", func() {
 | 
			
		||||
@@ -792,7 +783,7 @@ func RegisterRoutes(m *web.Route) {
 | 
			
		||||
		m.Combo("/compare/*", repo.MustBeNotEmpty, reqRepoCodeReader, repo.SetEditorconfigIfExists).
 | 
			
		||||
			Get(ignSignIn, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.CompareDiff).
 | 
			
		||||
			Post(reqSignIn, context.RepoMustNotBeArchived(), reqRepoPullsReader, repo.MustAllowPulls, bindIgnErr(auth.CreateIssueForm{}), repo.SetWhitespaceBehavior, repo.CompareAndPullRequestPost)
 | 
			
		||||
	}, context.RepoAssignment(), context.UnitTypes())
 | 
			
		||||
	}, context.RepoAssignment, context.UnitTypes())
 | 
			
		||||
 | 
			
		||||
	// Grouping for those endpoints that do require authentication
 | 
			
		||||
	m.Group("/{username}/{reponame}", func() {
 | 
			
		||||
@@ -899,7 +890,7 @@ func RegisterRoutes(m *web.Route) {
 | 
			
		||||
			m.Post("/restore", repo.RestoreBranchPost)
 | 
			
		||||
		}, context.RepoMustNotBeArchived(), reqRepoCodeWriter, repo.MustBeNotEmpty)
 | 
			
		||||
 | 
			
		||||
	}, reqSignIn, context.RepoAssignment(), context.UnitTypes())
 | 
			
		||||
	}, reqSignIn, context.RepoAssignment, context.UnitTypes())
 | 
			
		||||
 | 
			
		||||
	// Releases
 | 
			
		||||
	m.Group("/{username}/{reponame}", func() {
 | 
			
		||||
@@ -937,11 +928,11 @@ func RegisterRoutes(m *web.Route) {
 | 
			
		||||
			}
 | 
			
		||||
			ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
 | 
			
		||||
		})
 | 
			
		||||
	}, ignSignIn, context.RepoAssignment(), context.UnitTypes(), reqRepoReleaseReader)
 | 
			
		||||
	}, ignSignIn, context.RepoAssignment, context.UnitTypes(), reqRepoReleaseReader)
 | 
			
		||||
 | 
			
		||||
	m.Group("/{username}/{reponame}", func() {
 | 
			
		||||
		m.Post("/topics", repo.TopicsPost)
 | 
			
		||||
	}, context.RepoAssignment(), context.RepoMustNotBeArchived(), reqRepoAdmin)
 | 
			
		||||
	}, context.RepoAssignment, context.RepoMustNotBeArchived(), reqRepoAdmin)
 | 
			
		||||
 | 
			
		||||
	m.Group("/{username}/{reponame}", func() {
 | 
			
		||||
		m.Group("", func() {
 | 
			
		||||
@@ -1089,17 +1080,17 @@ func RegisterRoutes(m *web.Route) {
 | 
			
		||||
		}, context.RepoRef(), reqRepoCodeReader)
 | 
			
		||||
		m.Get("/commit/{sha:([a-f0-9]{7,40})}.{ext:patch|diff}",
 | 
			
		||||
			repo.MustBeNotEmpty, reqRepoCodeReader, repo.RawDiff)
 | 
			
		||||
	}, ignSignIn, context.RepoAssignment(), context.UnitTypes())
 | 
			
		||||
	}, ignSignIn, context.RepoAssignment, context.UnitTypes())
 | 
			
		||||
	m.Group("/{username}/{reponame}", func() {
 | 
			
		||||
		m.Get("/stars", repo.Stars)
 | 
			
		||||
		m.Get("/watchers", repo.Watchers)
 | 
			
		||||
		m.Get("/search", reqRepoCodeReader, repo.Search)
 | 
			
		||||
	}, ignSignIn, context.RepoAssignment(), context.RepoRef(), context.UnitTypes())
 | 
			
		||||
	}, ignSignIn, context.RepoAssignment, context.RepoRef(), context.UnitTypes())
 | 
			
		||||
 | 
			
		||||
	m.Group("/{username}", func() {
 | 
			
		||||
		m.Group("/{reponame}", func() {
 | 
			
		||||
			m.Get("", repo.SetEditorconfigIfExists, repo.Home)
 | 
			
		||||
		}, goGet, ignSignIn, context.RepoAssignment(), context.RepoRef(), context.UnitTypes())
 | 
			
		||||
		}, goGet, ignSignIn, context.RepoAssignment, context.RepoRef(), context.UnitTypes())
 | 
			
		||||
 | 
			
		||||
		m.Group("/{reponame}", func() {
 | 
			
		||||
			m.Group("/info/lfs", func() {
 | 
			
		||||
 
 | 
			
		||||
@@ -7,12 +7,14 @@ package user
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"path"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models"
 | 
			
		||||
	"code.gitea.io/gitea/modules/context"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Avatar redirect browser to user avatar of requested size
 | 
			
		||||
@@ -70,8 +72,21 @@ func AvatarByEmailHash(ctx *context.Context) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var avatarURL *url.URL
 | 
			
		||||
	avatarURL, err = models.LibravatarURL(email)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
 | 
			
		||||
	if setting.EnableFederatedAvatar && setting.LibravatarService != nil {
 | 
			
		||||
		avatarURL, err = models.LibravatarURL(email)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			avatarURL, err = url.Parse(models.DefaultAvatarLink())
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				ctx.ServerError("invalid default avatar url", err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	} else if !setting.DisableGravatar {
 | 
			
		||||
		copyOfGravatarSourceURL := *setting.GravatarSourceURL
 | 
			
		||||
		avatarURL = ©OfGravatarSourceURL
 | 
			
		||||
		avatarURL.Path = path.Join(avatarURL.Path, hash)
 | 
			
		||||
	} else {
 | 
			
		||||
		avatarURL, err = url.Parse(models.DefaultAvatarLink())
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.ServerError("invalid default avatar url", err)
 | 
			
		||||
 
 | 
			
		||||
@@ -546,14 +546,14 @@ func buildIssueOverview(ctx *context.Context, unitType models.UnitType) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// maps pull request IDs to their CommitStatus. Will be posted to ctx.Data.
 | 
			
		||||
	var commitStatus = make(map[int64]*models.CommitStatus, len(issues))
 | 
			
		||||
	for _, issue := range issues {
 | 
			
		||||
		issue.Repo = showReposMap[issue.RepoID]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		if isPullList {
 | 
			
		||||
			var statuses, _ = pull_service.GetLastCommitStatus(issue.PullRequest)
 | 
			
		||||
			commitStatus[issue.PullRequest.ID] = models.CalcCommitStatus(statuses)
 | 
			
		||||
		}
 | 
			
		||||
	commitStatus, err := pull_service.GetIssuesLastCommitStatus(issues)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("GetIssuesLastCommitStatus", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// -------------------------------
 | 
			
		||||
 
 | 
			
		||||
@@ -1014,6 +1014,11 @@ func parseHunks(curFile *DiffFile, maxLines, maxLineCharacters int, input *bufio
 | 
			
		||||
			}
 | 
			
		||||
			diffLine := &DiffLine{Type: DiffLineAdd, RightIdx: rightLine}
 | 
			
		||||
			rightLine++
 | 
			
		||||
			if curSection == nil {
 | 
			
		||||
				// Create a new section to represent this hunk
 | 
			
		||||
				curSection = &DiffSection{}
 | 
			
		||||
				curFile.Sections = append(curFile.Sections, curSection)
 | 
			
		||||
			}
 | 
			
		||||
			curSection.Lines = append(curSection.Lines, diffLine)
 | 
			
		||||
		case '-':
 | 
			
		||||
			curFileLinesCount++
 | 
			
		||||
@@ -1026,6 +1031,11 @@ func parseHunks(curFile *DiffFile, maxLines, maxLineCharacters int, input *bufio
 | 
			
		||||
			if leftLine > 0 {
 | 
			
		||||
				leftLine++
 | 
			
		||||
			}
 | 
			
		||||
			if curSection == nil {
 | 
			
		||||
				// Create a new section to represent this hunk
 | 
			
		||||
				curSection = &DiffSection{}
 | 
			
		||||
				curFile.Sections = append(curFile.Sections, curSection)
 | 
			
		||||
			}
 | 
			
		||||
			curSection.Lines = append(curSection.Lines, diffLine)
 | 
			
		||||
		case ' ':
 | 
			
		||||
			curFileLinesCount++
 | 
			
		||||
@@ -1036,6 +1046,11 @@ func parseHunks(curFile *DiffFile, maxLines, maxLineCharacters int, input *bufio
 | 
			
		||||
			diffLine := &DiffLine{Type: DiffLinePlain, LeftIdx: leftLine, RightIdx: rightLine}
 | 
			
		||||
			leftLine++
 | 
			
		||||
			rightLine++
 | 
			
		||||
			if curSection == nil {
 | 
			
		||||
				// Create a new section to represent this hunk
 | 
			
		||||
				curSection = &DiffSection{}
 | 
			
		||||
				curFile.Sections = append(curFile.Sections, curSection)
 | 
			
		||||
			}
 | 
			
		||||
			curSection.Lines = append(curSection.Lines, diffLine)
 | 
			
		||||
		default:
 | 
			
		||||
			// This is unexpected
 | 
			
		||||
@@ -1288,6 +1303,14 @@ func CommentAsDiff(c *models.Comment) (*Diff, error) {
 | 
			
		||||
 | 
			
		||||
// CommentMustAsDiff executes AsDiff and logs the error instead of returning
 | 
			
		||||
func CommentMustAsDiff(c *models.Comment) *Diff {
 | 
			
		||||
	if c == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if err := recover(); err != nil {
 | 
			
		||||
			log.Error("PANIC whilst retrieving diff for comment[%d] Error: %v\nStack: %s", c.ID, err, log.Stack(2))
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	diff, err := CommentAsDiff(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Warn("CommentMustAsDiff: %v", err)
 | 
			
		||||
 
 | 
			
		||||
@@ -64,7 +64,7 @@ func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repositor
 | 
			
		||||
	pr.Merger = doer
 | 
			
		||||
	pr.MergerID = doer.ID
 | 
			
		||||
 | 
			
		||||
	if _, err = pr.SetMerged(); err != nil {
 | 
			
		||||
	if _, err := pr.SetMerged(); err != nil {
 | 
			
		||||
		log.Error("setMerged [%d]: %v", pr.ID, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,6 @@ import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"context"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
@@ -643,33 +642,74 @@ func GetSquashMergeCommitMessages(pr *models.PullRequest) string {
 | 
			
		||||
	return stringBuilder.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetLastCommitStatus returns list of commit statuses for latest commit on this pull request.
 | 
			
		||||
func GetLastCommitStatus(pr *models.PullRequest) (status []*models.CommitStatus, err error) {
 | 
			
		||||
	if err = pr.LoadBaseRepo(); err != nil {
 | 
			
		||||
// GetIssuesLastCommitStatus returns a map
 | 
			
		||||
func GetIssuesLastCommitStatus(issues models.IssueList) (map[int64]*models.CommitStatus, error) {
 | 
			
		||||
	if err := issues.LoadPullRequests(); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := issues.LoadRepositories(); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var (
 | 
			
		||||
		gitRepos = make(map[int64]*git.Repository)
 | 
			
		||||
		res      = make(map[int64]*models.CommitStatus)
 | 
			
		||||
		err      error
 | 
			
		||||
	)
 | 
			
		||||
	defer func() {
 | 
			
		||||
		for _, gitRepo := range gitRepos {
 | 
			
		||||
			gitRepo.Close()
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	for _, issue := range issues {
 | 
			
		||||
		if !issue.IsPull {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		gitRepo, ok := gitRepos[issue.RepoID]
 | 
			
		||||
		if !ok {
 | 
			
		||||
			gitRepo, err = git.OpenRepository(issue.Repo.RepoPath())
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			gitRepos[issue.RepoID] = gitRepo
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		status, err := getLastCommitStatus(gitRepo, issue.PullRequest)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		res[issue.PullRequest.ID] = status
 | 
			
		||||
	}
 | 
			
		||||
	return res, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetLastCommitStatus returns list of commit statuses for latest commit on this pull request.
 | 
			
		||||
func GetLastCommitStatus(pr *models.PullRequest) (status *models.CommitStatus, err error) {
 | 
			
		||||
	if err = pr.LoadBaseRepo(); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	gitRepo, err := git.OpenRepository(pr.BaseRepo.RepoPath())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	defer gitRepo.Close()
 | 
			
		||||
 | 
			
		||||
	compareInfo, err := gitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(), pr.MergeBase, pr.GetGitRefName())
 | 
			
		||||
	return getLastCommitStatus(gitRepo, pr)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getLastCommitStatus get pr's last commit status. PR's last commit status is the head commit id's last commit status
 | 
			
		||||
func getLastCommitStatus(gitRepo *git.Repository, pr *models.PullRequest) (status *models.CommitStatus, err error) {
 | 
			
		||||
	sha, err := gitRepo.GetRefCommitID(pr.GetGitRefName())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if compareInfo.Commits.Len() == 0 {
 | 
			
		||||
		return nil, errors.New("pull request has no commits")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sha := compareInfo.Commits.Front().Value.(*git.Commit).ID.String()
 | 
			
		||||
	statusList, err := models.GetLatestCommitStatus(pr.BaseRepo.ID, sha, models.ListOptions{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return statusList, nil
 | 
			
		||||
	return models.CalcCommitStatus(statusList), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsHeadEqualWithBranch returns if the commits of branchName are available in pull request head
 | 
			
		||||
 
 | 
			
		||||
@@ -70,7 +70,7 @@
 | 
			
		||||
				<dd>{{if not .SSH.Disabled}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
 | 
			
		||||
				{{if not .SSH.Disabled}}
 | 
			
		||||
					<dt>{{.i18n.Tr "admin.config.ssh_start_builtin_server"}}</dt>
 | 
			
		||||
					<dd>{{if not .SSH.StartBuiltinServer}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
 | 
			
		||||
					<dd>{{if .SSH.StartBuiltinServer}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
 | 
			
		||||
					<dt>{{.i18n.Tr "admin.config.ssh_domain"}}</dt>
 | 
			
		||||
					<dd>{{.SSH.Domain}}</dd>
 | 
			
		||||
					<dt>{{.i18n.Tr "admin.config.ssh_port"}}</dt>
 | 
			
		||||
 
 | 
			
		||||
@@ -3,29 +3,27 @@
 | 
			
		||||
	{{template "explore/navbar" .}}
 | 
			
		||||
	<div class="ui container">
 | 
			
		||||
		<form class="ui form ignore-dirty" style="max-width: 100%">
 | 
			
		||||
            <input type="hidden" name="tab" value="{{$.TabName}}">
 | 
			
		||||
            <div class="ui fluid action input">
 | 
			
		||||
            <div class="twelve wide field">
 | 
			
		||||
                <input name="q" value="{{.Keyword}}" placeholder="{{.i18n.Tr "explore.search"}}..." autofocus>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="two wide field">
 | 
			
		||||
                <select name="t">
 | 
			
		||||
                    <option value="">{{.i18n.Tr "explore.search.fuzzy"}}</option>
 | 
			
		||||
                    <option value="match" {{if eq .queryType "match"}}selected{{end}}>{{.i18n.Tr "explore.search.match"}}</option>
 | 
			
		||||
                </select>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="three field">
 | 
			
		||||
                <button class="ui blue button">{{.i18n.Tr "explore.search"}}</button>
 | 
			
		||||
            </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </form>
 | 
			
		||||
        <div class="ui divider"></div>
 | 
			
		||||
			<input type="hidden" name="tab" value="{{$.TabName}}">
 | 
			
		||||
			<div class="ui fluid action input">
 | 
			
		||||
				<input name="q" value="{{.Keyword}}" placeholder="{{.i18n.Tr "explore.search"}}..." autofocus>
 | 
			
		||||
				<div class="ui dropdown selection">
 | 
			
		||||
					<input name="t" type="hidden" value="{{.queryType}}">{{svg "octicon-triangle-down" 14 "dropdown icon"}}
 | 
			
		||||
					<div class="text">{{.i18n.Tr (printf "explore.search.%s" (or .queryType "fuzzy"))}}</div>
 | 
			
		||||
					<div class="menu transition hidden" tabindex="-1" style="display: block !important;">
 | 
			
		||||
						<div class="item" data-value="">{{.i18n.Tr "explore.search.fuzzy"}}</div>
 | 
			
		||||
						<div class="item" data-value="match">{{.i18n.Tr "explore.search.match"}}</div>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
				<button class="ui blue button">{{.i18n.Tr "explore.search"}}</button>
 | 
			
		||||
			</div>
 | 
			
		||||
		</form>
 | 
			
		||||
		<div class="ui divider"></div>
 | 
			
		||||
 | 
			
		||||
		<div class="ui user list">
 | 
			
		||||
			{{if .SearchResults}}
 | 
			
		||||
                <h3>
 | 
			
		||||
                    {{.i18n.Tr "explore.code_search_results" (.Keyword|Escape) | Str2html }}
 | 
			
		||||
                </h3>
 | 
			
		||||
				<h3>
 | 
			
		||||
					{{.i18n.Tr "explore.code_search_results" (.Keyword|Escape) | Str2html }}
 | 
			
		||||
				</h3>
 | 
			
		||||
				<div class="df ac fw">
 | 
			
		||||
					{{range $term := .SearchResultLanguages}}
 | 
			
		||||
					<a class="ui text-label df ac mr-1 my-1 {{if eq $.Language $term.Language}}primary {{end}}basic label" href="{{AppSubUrl}}/explore/code?q={{$.Keyword}}{{if ne $.Language $term.Language}}&l={{$term.Language}}{{end}}{{if ne $.queryType ""}}&t={{$.queryType}}{{end}}">
 | 
			
		||||
@@ -35,34 +33,34 @@
 | 
			
		||||
					</a>
 | 
			
		||||
					{{end}}
 | 
			
		||||
				</div>
 | 
			
		||||
                <div class="repository search">
 | 
			
		||||
                    {{range $result := .SearchResults}}
 | 
			
		||||
                        {{$repo := (index $.RepoMaps .RepoID)}}
 | 
			
		||||
                        <div class="diff-file-box diff-box file-content non-diff-file-content repo-search-result">
 | 
			
		||||
                            <h4 class="ui top attached normal header">
 | 
			
		||||
                                <span class="file"><a rel="nofollow" href="{{EscapePound $repo.HTMLURL}}">{{$repo.FullName}}</a> - {{.Filename}}</span>
 | 
			
		||||
                                <a class="ui basic tiny button" rel="nofollow" href="{{EscapePound $repo.HTMLURL}}/src/commit/{{$result.CommitID}}/{{EscapePound .Filename}}">{{$.i18n.Tr "repo.diff.view_file"}}</a>
 | 
			
		||||
                            </h4>
 | 
			
		||||
                            <div class="ui attached table segment">
 | 
			
		||||
                                <div class="file-body file-code code-view">
 | 
			
		||||
                                    <table>
 | 
			
		||||
                                        <tbody>
 | 
			
		||||
                                            <tr>
 | 
			
		||||
                                                <td class="lines-num">
 | 
			
		||||
                                                    {{range .LineNumbers}}
 | 
			
		||||
                                                        <a href="{{EscapePound $repo.HTMLURL}}/src/commit/{{$result.CommitID}}/{{EscapePound $result.Filename}}#L{{.}}"><span>{{.}}</span></a>
 | 
			
		||||
                                                    {{end}}
 | 
			
		||||
                                                </td>
 | 
			
		||||
                                                <td class="lines-code"><pre><code class="chroma"><ol class="linenums">{{.FormattedLines | Safe}}</ol></code></pre></td>
 | 
			
		||||
                                            </tr>
 | 
			
		||||
                                        </tbody>
 | 
			
		||||
                                    </table>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            {{template "shared/searchbottom" dict "root" $ "result" .}}
 | 
			
		||||
                        </div>
 | 
			
		||||
                    {{end}}
 | 
			
		||||
                </div>
 | 
			
		||||
				<div class="repository search">
 | 
			
		||||
					{{range $result := .SearchResults}}
 | 
			
		||||
						{{$repo := (index $.RepoMaps .RepoID)}}
 | 
			
		||||
						<div class="diff-file-box diff-box file-content non-diff-file-content repo-search-result">
 | 
			
		||||
							<h4 class="ui top attached normal header">
 | 
			
		||||
								<span class="file"><a rel="nofollow" href="{{EscapePound $repo.HTMLURL}}">{{$repo.FullName}}</a> - {{.Filename}}</span>
 | 
			
		||||
								<a class="ui basic tiny button" rel="nofollow" href="{{EscapePound $repo.HTMLURL}}/src/commit/{{$result.CommitID}}/{{EscapePound .Filename}}">{{$.i18n.Tr "repo.diff.view_file"}}</a>
 | 
			
		||||
							</h4>
 | 
			
		||||
							<div class="ui attached table segment">
 | 
			
		||||
								<div class="file-body file-code code-view">
 | 
			
		||||
									<table>
 | 
			
		||||
										<tbody>
 | 
			
		||||
											<tr>
 | 
			
		||||
												<td class="lines-num">
 | 
			
		||||
													{{range .LineNumbers}}
 | 
			
		||||
														<a href="{{EscapePound $repo.HTMLURL}}/src/commit/{{$result.CommitID}}/{{EscapePound $result.Filename}}#L{{.}}"><span>{{.}}</span></a>
 | 
			
		||||
													{{end}}
 | 
			
		||||
												</td>
 | 
			
		||||
												<td class="lines-code"><pre><code class="chroma"><ol class="linenums">{{.FormattedLines | Safe}}</ol></code></pre></td>
 | 
			
		||||
											</tr>
 | 
			
		||||
										</tbody>
 | 
			
		||||
									</table>
 | 
			
		||||
								</div>
 | 
			
		||||
							</div>
 | 
			
		||||
							{{template "shared/searchbottom" dict "root" $ "result" .}}
 | 
			
		||||
						</div>
 | 
			
		||||
					{{end}}
 | 
			
		||||
				</div>
 | 
			
		||||
			{{else}}
 | 
			
		||||
				<div>{{$.i18n.Tr "explore.code_no_results"}}</div>
 | 
			
		||||
			{{end}}
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,7 @@
 | 
			
		||||
						{{else if eq .HookType "discord"}}
 | 
			
		||||
							<img width="26" height="26" src="{{StaticUrlPrefix}}/img/discord.png">
 | 
			
		||||
						{{else if eq .HookType "dingtalk"}}
 | 
			
		||||
							<img width="26" height="26" src="{{StaticUrlPrefix}}/img/dingtalk.png">
 | 
			
		||||
							<img width="26" height="26" src="{{StaticUrlPrefix}}/img/dingtalk.ico">
 | 
			
		||||
						{{else if eq .HookType "telegram"}}
 | 
			
		||||
							<img width="26" height="26" src="{{StaticUrlPrefix}}/img/telegram.png">
 | 
			
		||||
						{{else if eq .HookType "msteams"}}
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@
 | 
			
		||||
						</td>
 | 
			
		||||
						<td class="right aligned overflow-visible">
 | 
			
		||||
							<div class="ui basic jump dropdown icon button poping up" data-content="{{$.i18n.Tr "repo.branch.download" ($.DefaultBranch)}}" data-variation="tiny inverted" data-position="top right">
 | 
			
		||||
							  <i class="download icon"></i>
 | 
			
		||||
							  {{svg "octicon-download"}}
 | 
			
		||||
							  <div class="menu">
 | 
			
		||||
							    <a class="item archive-link" data-url="{{$.RepoLink}}/archive/{{EscapePound $.DefaultBranch}}.zip">{{svg "octicon-file-zip"}} ZIP</a>
 | 
			
		||||
							    <a class="item archive-link" data-url="{{$.RepoLink}}/archive/{{EscapePound $.DefaultBranch}}.tar.gz">{{svg "octicon-file-zip"}} TAR.GZ</a>
 | 
			
		||||
@@ -91,20 +91,20 @@
 | 
			
		||||
											</a>
 | 
			
		||||
											{{end}}
 | 
			
		||||
										{{else}}
 | 
			
		||||
											<a href="{{.LatestPullRequest.Issue.HTMLURL}}">{{if not .LatestPullRequest.IsSameRepo}}{{.LatestPullRequest.BaseRepo.FullName}}{{end}}#{{.LatestPullRequest.Issue.Index}}</a>
 | 
			
		||||
											<a href="{{.LatestPullRequest.Issue.HTMLURL}}" class="vm">{{if not .LatestPullRequest.IsSameRepo}}{{.LatestPullRequest.BaseRepo.FullName}}{{end}}#{{.LatestPullRequest.Issue.Index}}</a>
 | 
			
		||||
											{{if .LatestPullRequest.HasMerged}}
 | 
			
		||||
												<a href="{{.LatestPullRequest.Issue.HTMLURL}}" class="ui text-label purple mini label">{{svg "octicon-git-merge"}} {{$.i18n.Tr "repo.pulls.merged"}}</a>
 | 
			
		||||
												<a href="{{.LatestPullRequest.Issue.HTMLURL}}" class="ui text-label purple large label vm">{{svg "octicon-git-merge" 16 "mr-2"}}{{$.i18n.Tr "repo.pulls.merged"}}</a>
 | 
			
		||||
											{{else if .LatestPullRequest.Issue.IsClosed}}
 | 
			
		||||
												<a href="{{.LatestPullRequest.Issue.HTMLURL}}" class="ui text-label red mini label">{{svg "octicon-git-pull-request"}} {{$.i18n.Tr "repo.issues.closed_title"}}</a>
 | 
			
		||||
												<a href="{{.LatestPullRequest.Issue.HTMLURL}}" class="ui text-label red large label vm">{{svg "octicon-git-pull-request" 16 "mr-2"}}{{$.i18n.Tr "repo.issues.closed_title"}}</a>
 | 
			
		||||
											{{else}}
 | 
			
		||||
												<a href="{{.LatestPullRequest.Issue.HTMLURL}}" class="ui text-label green mini label">{{svg "octicon-git-pull-request"}} {{$.i18n.Tr "repo.issues.open_title"}}</a>
 | 
			
		||||
												<a href="{{.LatestPullRequest.Issue.HTMLURL}}" class="ui text-label green large label vm">{{svg "octicon-git-pull-request" 16 "mr-2"}}{{$.i18n.Tr "repo.issues.open_title"}}</a>
 | 
			
		||||
											{{end}}
 | 
			
		||||
										{{end}}
 | 
			
		||||
									</td>
 | 
			
		||||
									<td class="two wide right aligned overflow-visible">
 | 
			
		||||
										{{if (not .IsDeleted)}}
 | 
			
		||||
											<div class="ui basic jump dropdown icon button poping up" data-content="{{$.i18n.Tr "repo.branch.download" (.Name)}}" data-variation="tiny inverted" data-position="top right">
 | 
			
		||||
												<i class="download icon"></i>
 | 
			
		||||
												{{svg "octicon-download"}}
 | 
			
		||||
												<div class="menu">
 | 
			
		||||
													<a class="item archive-link" data-url="{{$.RepoLink}}/archive/{{EscapePound .Name}}.zip">{{svg "octicon-file-zip"}} ZIP</a>
 | 
			
		||||
													<a class="item archive-link" data-url="{{$.RepoLink}}/archive/{{EscapePound .Name}}.tar.gz">{{svg "octicon-file-zip"}} TAR.GZ</a>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,17 @@
 | 
			
		||||
{{if not $.DisableHTTP}}
 | 
			
		||||
	<button class="ui basic clone button no-transition" id="repo-clone-https" data-link="{{.CloneLink.HTTPS}}">
 | 
			
		||||
	<button class="ui basic clone button no-transition" id="repo-clone-https" data-link="{{if $.PageIsWiki}}{{$.WikiCloneLink.HTTPS}}{{else}}{{$.CloneLink.HTTPS}}{{end}}">
 | 
			
		||||
		{{if UseHTTPS}}HTTPS{{else}}HTTP{{end}}
 | 
			
		||||
	</button>
 | 
			
		||||
{{end}}
 | 
			
		||||
{{if and (not $.DisableSSH) (or $.IsSigned $.ExposeAnonSSH)}}
 | 
			
		||||
	<button class="ui basic clone button no-transition" id="repo-clone-ssh" data-link="{{.CloneLink.SSH}}">
 | 
			
		||||
	<button class="ui basic clone button no-transition" id="repo-clone-ssh" data-link="{{if $.PageIsWiki}}{{$.WikiCloneLink.SSH}}{{else}}{{$.CloneLink.SSH}}{{end}}">
 | 
			
		||||
		SSH
 | 
			
		||||
	</button>
 | 
			
		||||
{{end}}
 | 
			
		||||
{{if not $.DisableHTTP}}
 | 
			
		||||
	<input id="repo-clone-url" value="{{$.CloneLink.HTTPS}}" readonly>
 | 
			
		||||
	<input id="repo-clone-url" value="{{if $.PageIsWiki}}{{$.WikiCloneLink.HTTPS}}{{else}}{{$.CloneLink.HTTPS}}{{end}}" readonly>
 | 
			
		||||
{{else if and (not .DisableSSH) (or $.IsSigned $.ExposeAnonSSH)}}
 | 
			
		||||
	<input id="repo-clone-url" value="{{$.CloneLink.SSH}}" readonly>
 | 
			
		||||
	<input id="repo-clone-url" value="{{if $.PageIsWiki}}{{$.WikiCloneLink.SSH}}{{else}}{{$.CloneLink.SSH}}{{end}}" readonly>
 | 
			
		||||
{{end}}
 | 
			
		||||
{{if or (not $.DisableHTTP) (and (not $.DisableSSH) (or $.IsSigned $.ExposeAnonSSH))}}
 | 
			
		||||
	<button class="ui basic icon button poping up clipboard" id="clipboard-btn" data-original="{{.i18n.Tr "repo.copy_link"}}" data-success="{{.i18n.Tr "repo.copy_link_success"}}" data-error="{{.i18n.Tr "repo.copy_link_error"}}" data-content="{{.i18n.Tr "repo.copy_link"}}" data-variation="inverted tiny" data-clipboard-target="#repo-clone-url">
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@
 | 
			
		||||
				<div class="ui attached segment">
 | 
			
		||||
					{{template "base/alert" .}}
 | 
			
		||||
 | 
			
		||||
					<p class="ui center">{{.i18n.Tr "repo.new_repo_helper" "/repo/migrate" | Safe}}</p>
 | 
			
		||||
					<p class="ui center">{{.i18n.Tr "repo.new_repo_helper" (printf "%s%s" AppSubUrl "/repo/migrate") | Safe}}</p>
 | 
			
		||||
 | 
			
		||||
					{{if not .CanCreateRepo}}
 | 
			
		||||
						<div class="ui negative message">
 | 
			
		||||
 
 | 
			
		||||
@@ -43,15 +43,13 @@
 | 
			
		||||
		</ol>
 | 
			
		||||
		{{range $i, $file := .Diff.Files}}
 | 
			
		||||
			{{if $file.IsIncomplete}}
 | 
			
		||||
				<div class="diff-file-box diff-box file-content">
 | 
			
		||||
				<div class="diff-file-box diff-box file-content mt-3">
 | 
			
		||||
					<h4 class="ui top attached normal header rounded">
 | 
			
		||||
						<a role="button" class="fold-file muted mr-2">
 | 
			
		||||
							{{svg "octicon-chevron-down" 18}}
 | 
			
		||||
						</a>
 | 
			
		||||
						<div class="bold ui left df ac">
 | 
			
		||||
							{{if not $file.IsRenamed}}
 | 
			
		||||
								{{template "repo/diff/stats" dict "file" . "root" $}}
 | 
			
		||||
							{{end}}
 | 
			
		||||
							{{template "repo/diff/stats" dict "file" . "root" $}}
 | 
			
		||||
						</div>
 | 
			
		||||
						<span class="file mono">{{$file.Name}}</span>
 | 
			
		||||
						<div class="diff-file-header-actions df ac">
 | 
			
		||||
@@ -70,7 +68,7 @@
 | 
			
		||||
					</h4>
 | 
			
		||||
				</div>
 | 
			
		||||
			{{else}}
 | 
			
		||||
				<div class="diff-file-box diff-box file-content {{TabSizeClass $.Editorconfig $file.Name}}" id="diff-{{.Index}}">
 | 
			
		||||
				<div class="diff-file-box diff-box file-content {{TabSizeClass $.Editorconfig $file.Name}} mt-3" id="diff-{{.Index}}">
 | 
			
		||||
					<h4 class="diff-file-header sticky-2nd-row ui top attached normal header df ac sb">
 | 
			
		||||
						<div class="df ac">
 | 
			
		||||
							{{$isImage := false}}
 | 
			
		||||
@@ -85,7 +83,7 @@
 | 
			
		||||
							<div class="bold df ac">
 | 
			
		||||
								{{if $file.IsBin}}
 | 
			
		||||
									{{$.i18n.Tr "repo.diff.bin"}}
 | 
			
		||||
								{{else if not $file.IsRenamed}}
 | 
			
		||||
								{{else}}
 | 
			
		||||
									{{template "repo/diff/stats" dict "file" . "root" $}}
 | 
			
		||||
								{{end}}
 | 
			
		||||
							</div>
 | 
			
		||||
@@ -105,30 +103,28 @@
 | 
			
		||||
						</div>
 | 
			
		||||
					</h4>
 | 
			
		||||
					<div class="diff-file-body ui attached unstackable table segment">
 | 
			
		||||
						{{if ne $file.Type 4}}
 | 
			
		||||
							<div class="file-body file-code has-context-menu{{if not $isImage}} code-diff{{end}}{{if $.IsSplitStyle}} code-diff-split{{else}} code-diff-unified{{end}}{{if $isImage}} py-4{{end}}">
 | 
			
		||||
								<table class="chroma{{if $isImage}} w-100{{end}}">
 | 
			
		||||
									<tbody>
 | 
			
		||||
										{{if $isImage}}
 | 
			
		||||
											{{template "repo/diff/image_diff" dict "file" . "root" $}}
 | 
			
		||||
						<div class="file-body file-code has-context-menu{{if not $isImage}} code-diff{{end}}{{if $.IsSplitStyle}} code-diff-split{{else}} code-diff-unified{{end}}{{if $isImage}} py-4{{end}}">
 | 
			
		||||
							<table class="chroma{{if $isImage}} w-100{{end}}">
 | 
			
		||||
								<tbody>
 | 
			
		||||
									{{if $isImage}}
 | 
			
		||||
										{{template "repo/diff/image_diff" dict "file" . "root" $}}
 | 
			
		||||
									{{else}}
 | 
			
		||||
										{{if $.IsSplitStyle}}
 | 
			
		||||
											{{template "repo/diff/section_split" dict "file" . "root" $}}
 | 
			
		||||
										{{else}}
 | 
			
		||||
											{{if $.IsSplitStyle}}
 | 
			
		||||
												{{template "repo/diff/section_split" dict "file" . "root" $}}
 | 
			
		||||
											{{else}}
 | 
			
		||||
												{{template "repo/diff/section_unified" dict "file" . "root" $}}
 | 
			
		||||
											{{end}}
 | 
			
		||||
											{{template "repo/diff/section_unified" dict "file" . "root" $}}
 | 
			
		||||
										{{end}}
 | 
			
		||||
									</tbody>
 | 
			
		||||
								</table>
 | 
			
		||||
							</div>
 | 
			
		||||
						{{end}}
 | 
			
		||||
									{{end}}
 | 
			
		||||
								</tbody>
 | 
			
		||||
							</table>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			{{end}}
 | 
			
		||||
		{{end}}
 | 
			
		||||
 | 
			
		||||
		{{if .Diff.IsIncomplete}}
 | 
			
		||||
			<div class="diff-file-box diff-box file-content">
 | 
			
		||||
			<div class="diff-file-box diff-box file-content mt-3">
 | 
			
		||||
				<h4 class="ui top attached normal header">
 | 
			
		||||
					{{$.i18n.Tr "repo.diff.too_many_files"}}
 | 
			
		||||
				</h4>
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
{{ $createdStr:= TimeSinceUnix .CreatedUnix $.root.Lang }}
 | 
			
		||||
<div class="comment" id="{{.HashTag}}">
 | 
			
		||||
	{{if .OriginalAuthor }}
 | 
			
		||||
		<span class="avatar"><img src="/img/avatar_default.png"></span>
 | 
			
		||||
		<span class="avatar"><img src="{{AppSubUrl}}/img/avatar_default.png"></span>
 | 
			
		||||
	{{else}}
 | 
			
		||||
		<a class="avatar" {{if gt .Poster.ID 0}}href="{{.Poster.HomeLink}}"{{end}}>
 | 
			
		||||
			{{avatar .Poster}}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,16 +3,20 @@
 | 
			
		||||
{{$isNotPending := (not (eq (index .comments 0).Review.Type 0))}}
 | 
			
		||||
<div class="conversation-holder" data-path="{{(index .comments 0).TreePath}}" data-side="{{if lt (index .comments 0).Line 0}}left{{else}}right{{end}}" data-idx="{{(index .comments 0).UnsignedLine}}">
 | 
			
		||||
	{{if $resolved}}
 | 
			
		||||
		<div class="ui attached header resolved-placeholder">
 | 
			
		||||
			<span class="ui grey text left"><b>{{$resolveDoer.Name}}</b> {{$.i18n.Tr "repo.issues.review.resolved_by"}}</span>
 | 
			
		||||
			<button id="show-outdated-{{(index .comments 0).ID}}" data-comment="{{(index .comments 0).ID}}" class="ui tiny right labeled button show-outdated">
 | 
			
		||||
				{{svg "octicon-unfold"}}
 | 
			
		||||
				{{$.i18n.Tr "repo.issues.review.show_resolved"}}
 | 
			
		||||
			</button>
 | 
			
		||||
			<button id="hide-outdated-{{(index .comments 0).ID}}" data-comment="{{(index .comments 0).ID}}" class="hide ui tiny right labeled button hide-outdated">
 | 
			
		||||
				{{svg "octicon-fold"}}
 | 
			
		||||
				{{$.i18n.Tr "repo.issues.review.hide_resolved"}}
 | 
			
		||||
			</button>
 | 
			
		||||
		<div class="ui attached header resolved-placeholder df ac sb">
 | 
			
		||||
			<div class="ui grey text">
 | 
			
		||||
				<b>{{$resolveDoer.Name}}</b> {{$.i18n.Tr "repo.issues.review.resolved_by"}}
 | 
			
		||||
			</div>
 | 
			
		||||
			<div>
 | 
			
		||||
				<button id="show-outdated-{{(index .comments 0).ID}}" data-comment="{{(index .comments 0).ID}}" class="ui tiny right labeled button show-outdated df ac">
 | 
			
		||||
					{{svg "octicon-unfold" 16 "mr-3"}}
 | 
			
		||||
					{{$.i18n.Tr "repo.issues.review.show_resolved"}}
 | 
			
		||||
				</button>
 | 
			
		||||
				<button id="hide-outdated-{{(index .comments 0).ID}}" data-comment="{{(index .comments 0).ID}}" class="hide ui tiny right labeled button hide-outdated df ac">
 | 
			
		||||
					{{svg "octicon-fold" 16 "mr-3"}}
 | 
			
		||||
					{{$.i18n.Tr "repo.issues.review.hide_resolved"}}
 | 
			
		||||
				</button>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	{{end}}
 | 
			
		||||
	<div id="code-comments-{{(index  .comments 0).ID}}" class="field comment-code-cloud {{if $resolved}}hide{{end}}">
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
<div id="rev-container">
 | 
			
		||||
	<ul id="rev-list">
 | 
			
		||||
		{{ range $commitI, $commit := .Graph.Commits }}
 | 
			
		||||
			<li id="commit-{{$commit.Rev}}" data-flow="{{$commit.Flow}}">
 | 
			
		||||
			<li {{if $commit.Rev}}id="commit-{{$commit.Rev}}"{{end}} data-flow="{{$commit.Flow}}">
 | 
			
		||||
				{{ if $commit.OnlyRelation }}
 | 
			
		||||
					<span />
 | 
			
		||||
					<span></span>
 | 
			
		||||
				{{ else }}
 | 
			
		||||
					<span class="sha" id="{{$commit.ShortRev}}">
 | 
			
		||||
						{{$class := "ui sha label"}}
 | 
			
		||||
 
 | 
			
		||||
@@ -110,13 +110,13 @@
 | 
			
		||||
				{{if eq $n 0}}
 | 
			
		||||
					<div class="ui action tiny input" id="clone-panel">
 | 
			
		||||
						{{template "repo/clone_buttons" .}}
 | 
			
		||||
						<div class="ui basic jump dropdown icon button poping up" data-content="{{.i18n.Tr "repo.download_archive"}}" data-variation="tiny inverted" data-position="top right">
 | 
			
		||||
						<button id="download-btn" class="ui basic jump dropdown icon button poping up" data-content="{{.i18n.Tr "repo.download_archive"}}" data-variation="tiny inverted" data-position="top right">
 | 
			
		||||
							{{svg "octicon-download"}}
 | 
			
		||||
							<div class="menu">
 | 
			
		||||
								<a class="item archive-link" data-url="{{$.RepoLink}}/archive/{{EscapePound $.BranchName}}.zip">{{svg "octicon-file-zip"}} ZIP</a>
 | 
			
		||||
								<a class="item archive-link" data-url="{{$.RepoLink}}/archive/{{EscapePound $.BranchName}}.tar.gz">{{svg "octicon-file-zip"}} TAR.GZ</a>
 | 
			
		||||
							</div>
 | 
			
		||||
						</div>
 | 
			
		||||
						</button>
 | 
			
		||||
					</div>
 | 
			
		||||
				{{end}}
 | 
			
		||||
			</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -40,7 +40,7 @@
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="three wide column">
 | 
			
		||||
					{{if $.PageIsOrgSettingsLabels}}
 | 
			
		||||
						<a class="ui right open-issues" href="/issues?labels={{.ID}}">{{svg "octicon-issue-opened"}} {{$.i18n.Tr "repo.issues.label_open_issues" .NumOpenIssues}}</a>
 | 
			
		||||
						<a class="ui right open-issues" href="{{AppSubUrl}}/issues?labels={{.ID}}">{{svg "octicon-issue-opened"}} {{$.i18n.Tr "repo.issues.label_open_issues" .NumOpenIssues}}</a>
 | 
			
		||||
					{{else}}
 | 
			
		||||
						<a class="ui right open-issues" href="{{$.RepoLink}}/issues?labels={{.ID}}">{{svg "octicon-issue-opened"}} {{$.i18n.Tr "repo.issues.label_open_issues" .NumOpenIssues}}</a>
 | 
			
		||||
					{{end}}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,8 @@
 | 
			
		||||
	<div class="ui container">
 | 
			
		||||
		<div class="ui three column stackable grid">
 | 
			
		||||
			<div class="column">
 | 
			
		||||
				<h3>{{.Milestone.Name}}</h3>
 | 
			
		||||
				<div class="content">
 | 
			
		||||
				<h1>{{.Milestone.Name}}</h1>
 | 
			
		||||
				<div class="markdown content">
 | 
			
		||||
					{{.Milestone.RenderedContent|Str2html}}
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -43,7 +43,7 @@
 | 
			
		||||
		<div class="milestone list">
 | 
			
		||||
			{{range .Milestones}}
 | 
			
		||||
				<li class="item">
 | 
			
		||||
					{{svg "octicon-milestone"}} <a href="{{$.RepoLink}}/milestone/{{.ID}}">{{.Name}}</a>
 | 
			
		||||
					{{svg "octicon-milestone" 16 "mr-2"}} <a href="{{$.RepoLink}}/milestone/{{.ID}}">{{.Name}}</a>
 | 
			
		||||
					<div class="ui right green progress" data-percent="{{.Completeness}}">
 | 
			
		||||
						<div class="bar" {{if not .Completeness}}style="background-color: transparent"{{end}}>
 | 
			
		||||
							<div class="progress"></div>
 | 
			
		||||
@@ -80,7 +80,7 @@
 | 
			
		||||
						</div>
 | 
			
		||||
					{{end}}
 | 
			
		||||
					{{if .Content}}
 | 
			
		||||
						<div class="content">
 | 
			
		||||
						<div class="markdown content">
 | 
			
		||||
							{{.RenderedContent|Str2html}}
 | 
			
		||||
						</div>
 | 
			
		||||
					{{end}}
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@
 | 
			
		||||
		<ui class="ui timeline">
 | 
			
		||||
			<div id="{{.Issue.HashTag}}" class="timeline-item comment first">
 | 
			
		||||
			{{if .Issue.OriginalAuthor }}
 | 
			
		||||
				<span class="timeline-avatar"><img src="/img/avatar_default.png"></span>
 | 
			
		||||
				<span class="timeline-avatar"><img src="{{AppSubUrl}}/img/avatar_default.png"></span>
 | 
			
		||||
			{{else}}
 | 
			
		||||
				<a class="timeline-avatar" {{if gt .Issue.Poster.ID 0}}href="{{.Issue.Poster.HomeLink}}"{{end}}>
 | 
			
		||||
					{{avatar .Issue.Poster}}
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@
 | 
			
		||||
	{{if eq .Type 0}}
 | 
			
		||||
		<div class="timeline-item comment" id="{{.HashTag}}">
 | 
			
		||||
		{{if .OriginalAuthor }}
 | 
			
		||||
			<span class="timeline-avatar"><img src="/img/avatar_default.png"></span>
 | 
			
		||||
			<span class="timeline-avatar"><img src="{{AppSubUrl}}/img/avatar_default.png"></span>
 | 
			
		||||
		{{else}}
 | 
			
		||||
			<a class="timeline-avatar" {{if gt .Poster.ID 0}}href="{{.Poster.HomeLink}}"{{end}}>
 | 
			
		||||
				{{avatar .Poster}}
 | 
			
		||||
@@ -459,35 +459,39 @@
 | 
			
		||||
				{{ range $filename, $lines := .Review.CodeComments}}
 | 
			
		||||
					{{range $line, $comms := $lines}}
 | 
			
		||||
							<div class="ui segments">
 | 
			
		||||
								<div class="ui segment py-3">
 | 
			
		||||
								<div class="ui segment py-3 df ac sb">
 | 
			
		||||
									{{$invalid := (index $comms 0).Invalidated}}
 | 
			
		||||
									{{$resolved := (index $comms 0).IsResolved}}
 | 
			
		||||
									{{$resolveDoer := (index $comms 0).ResolveDoer}}
 | 
			
		||||
									{{$isNotPending := (not (eq (index $comms 0).Review.Type 0))}}
 | 
			
		||||
								{{if or $invalid $resolved}}
 | 
			
		||||
									<button id="show-outdated-{{(index $comms 0).ID}}" data-comment="{{(index $comms 0).ID}}" class="{{if not $resolved}}hide {{end}}ui compact right labeled button show-outdated">
 | 
			
		||||
										{{svg "octicon-unfold"}}
 | 
			
		||||
										{{if $resolved}}
 | 
			
		||||
											{{$.i18n.Tr "repo.issues.review.show_resolved"}}
 | 
			
		||||
										{{else}}
 | 
			
		||||
											{{$.i18n.Tr "repo.issues.review.show_outdated"}}
 | 
			
		||||
									<div class="df ac">
 | 
			
		||||
										<a href="{{(index $comms 0).CodeCommentURL}}" class="file-comment ml-3">{{$filename}}</a>
 | 
			
		||||
										{{if $invalid }}
 | 
			
		||||
											<span class="ui label basic small ml-3">
 | 
			
		||||
												{{$.i18n.Tr "repo.issues.review.outdated"}}
 | 
			
		||||
											</span>
 | 
			
		||||
										{{end}}
 | 
			
		||||
									</button>
 | 
			
		||||
									<button id="hide-outdated-{{(index $comms 0).ID}}" data-comment="{{(index $comms 0).ID}}" class="{{if $resolved}}hide {{end}}ui compact right labeled button hide-outdated">
 | 
			
		||||
										{{svg "octicon-fold"}}
 | 
			
		||||
										{{if $resolved}}
 | 
			
		||||
											{{$.i18n.Tr "repo.issues.review.hide_resolved"}}
 | 
			
		||||
										{{else}}
 | 
			
		||||
											{{$.i18n.Tr "repo.issues.review.hide_outdated"}}
 | 
			
		||||
									</div>
 | 
			
		||||
									<div>
 | 
			
		||||
										{{if or $invalid $resolved}}
 | 
			
		||||
											<button id="show-outdated-{{(index $comms 0).ID}}" data-comment="{{(index $comms 0).ID}}" class="{{if not $resolved}}hide {{end}}ui compact right labeled button show-outdated df ac">
 | 
			
		||||
												{{svg "octicon-unfold" 16 "mr-3"}}
 | 
			
		||||
												{{if $resolved}}
 | 
			
		||||
													{{$.i18n.Tr "repo.issues.review.show_resolved"}}
 | 
			
		||||
												{{else}}
 | 
			
		||||
													{{$.i18n.Tr "repo.issues.review.show_outdated"}}
 | 
			
		||||
												{{end}}
 | 
			
		||||
											</button>
 | 
			
		||||
											<button id="hide-outdated-{{(index $comms 0).ID}}" data-comment="{{(index $comms 0).ID}}" class="{{if $resolved}}hide {{end}}ui compact right labeled button hide-outdated df ac">
 | 
			
		||||
												{{svg "octicon-fold" 16 "mr-3"}}
 | 
			
		||||
												{{if $resolved}}
 | 
			
		||||
													{{$.i18n.Tr "repo.issues.review.hide_resolved"}}
 | 
			
		||||
												{{else}}
 | 
			
		||||
													{{$.i18n.Tr "repo.issues.review.hide_outdated"}}
 | 
			
		||||
												{{end}}
 | 
			
		||||
											</button>
 | 
			
		||||
										{{end}}
 | 
			
		||||
									</button>
 | 
			
		||||
								{{end}}
 | 
			
		||||
								<a href="{{(index $comms 0).CodeCommentURL}}" class="file-comment">{{$filename}}</a>
 | 
			
		||||
								{{if $invalid }}
 | 
			
		||||
									<span class="ui label basic small yellow">
 | 
			
		||||
										{{$.i18n.Tr "repo.issues.review.outdated"}}
 | 
			
		||||
									</span>
 | 
			
		||||
								{{end}}
 | 
			
		||||
									</div>
 | 
			
		||||
								</div>
 | 
			
		||||
								{{$diff := (CommentMustAsDiff (index $comms 0))}}
 | 
			
		||||
								{{if $diff}}
 | 
			
		||||
 
 | 
			
		||||
@@ -578,8 +578,6 @@
 | 
			
		||||
					{{end}}
 | 
			
		||||
				</button>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
			<div class="ui tiny modal" id="lock">
 | 
			
		||||
				<div class="header">
 | 
			
		||||
					{{ if .Issue.IsLocked }}
 | 
			
		||||
@@ -588,62 +586,61 @@
 | 
			
		||||
						{{.i18n.Tr "repo.issues.lock.title"}}
 | 
			
		||||
					{{end}}
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="content">
 | 
			
		||||
				<div class="ui warning message text left">
 | 
			
		||||
					{{ if .Issue.IsLocked }}
 | 
			
		||||
						{{.i18n.Tr "repo.issues.unlock.notice_1"}}<br>
 | 
			
		||||
						{{.i18n.Tr "repo.issues.unlock.notice_2"}}<br>
 | 
			
		||||
					{{else}}
 | 
			
		||||
						{{.i18n.Tr "repo.issues.lock.notice_1"}}<br>
 | 
			
		||||
						{{.i18n.Tr "repo.issues.lock.notice_2"}}<br>
 | 
			
		||||
						{{.i18n.Tr "repo.issues.lock.notice_3"}}<br>
 | 
			
		||||
					{{end}}
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="content">
 | 
			
		||||
					<div class="ui warning message text left">
 | 
			
		||||
						{{ if .Issue.IsLocked }}
 | 
			
		||||
							{{.i18n.Tr "repo.issues.unlock.notice_1"}}<br>
 | 
			
		||||
							{{.i18n.Tr "repo.issues.unlock.notice_2"}}<br>
 | 
			
		||||
						{{else}}
 | 
			
		||||
							{{.i18n.Tr "repo.issues.lock.notice_1"}}<br>
 | 
			
		||||
							{{.i18n.Tr "repo.issues.lock.notice_2"}}<br>
 | 
			
		||||
							{{.i18n.Tr "repo.issues.lock.notice_3"}}<br>
 | 
			
		||||
						{{end}}
 | 
			
		||||
					</div>
 | 
			
		||||
 | 
			
		||||
				<form class="ui form" action="{{$.RepoLink}}/issues/{{.Issue.Index}}{{ if .Issue.IsLocked }}/unlock{{else}}/lock{{end}}"
 | 
			
		||||
					method="post">
 | 
			
		||||
					{{.CsrfTokenHtml}}
 | 
			
		||||
					<form class="ui form" action="{{$.RepoLink}}/issues/{{.Issue.Index}}{{ if .Issue.IsLocked }}/unlock{{else}}/lock{{end}}"
 | 
			
		||||
						method="post">
 | 
			
		||||
						{{.CsrfTokenHtml}}
 | 
			
		||||
 | 
			
		||||
					{{ if not .Issue.IsLocked }}
 | 
			
		||||
						<div class="field">
 | 
			
		||||
							<strong> {{ .i18n.Tr "repo.issues.lock.reason" }} </strong>
 | 
			
		||||
						</div>
 | 
			
		||||
						{{ if not .Issue.IsLocked }}
 | 
			
		||||
							<div class="field">
 | 
			
		||||
								<strong> {{ .i18n.Tr "repo.issues.lock.reason" }} </strong>
 | 
			
		||||
							</div>
 | 
			
		||||
 | 
			
		||||
						<div class="field">
 | 
			
		||||
							<div class="ui fluid dropdown selection" tabindex="0">
 | 
			
		||||
							<div class="field">
 | 
			
		||||
								<div class="ui fluid dropdown selection" tabindex="0">
 | 
			
		||||
 | 
			
		||||
								<select name="reason">
 | 
			
		||||
									<option value=""> </option>
 | 
			
		||||
									{{range .LockReasons}}
 | 
			
		||||
										<option value="{{.}}">{{.}}</option>
 | 
			
		||||
									{{end}}
 | 
			
		||||
								</select>
 | 
			
		||||
								{{svg "octicon-triangle-down" 14 "dropdown icon"}}
 | 
			
		||||
									<select name="reason">
 | 
			
		||||
										<option value=""> </option>
 | 
			
		||||
										{{range .LockReasons}}
 | 
			
		||||
											<option value="{{.}}">{{.}}</option>
 | 
			
		||||
										{{end}}
 | 
			
		||||
									</select>
 | 
			
		||||
									{{svg "octicon-triangle-down" 14 "dropdown icon"}}
 | 
			
		||||
 | 
			
		||||
								<div class="default text"> </div>
 | 
			
		||||
									<div class="default text"> </div>
 | 
			
		||||
 | 
			
		||||
								<div class="menu transition hidden" tabindex="-1" style="display: block !important;">
 | 
			
		||||
									{{range .LockReasons}}
 | 
			
		||||
										<div class="item" data-value="{{.}}">{{.}}</div>
 | 
			
		||||
									{{end}}
 | 
			
		||||
									<div class="menu transition hidden" tabindex="-1" style="display: block !important;">
 | 
			
		||||
										{{range .LockReasons}}
 | 
			
		||||
											<div class="item" data-value="{{.}}">{{.}}</div>
 | 
			
		||||
										{{end}}
 | 
			
		||||
									</div>
 | 
			
		||||
								</div>
 | 
			
		||||
							</div>
 | 
			
		||||
						</div>
 | 
			
		||||
					{{end}}
 | 
			
		||||
						{{end}}
 | 
			
		||||
 | 
			
		||||
					<div class="text right actions">
 | 
			
		||||
						<div class="ui cancel button">{{.i18n.Tr "settings.cancel"}}</div>
 | 
			
		||||
						<button class="ui red button">
 | 
			
		||||
							{{ if .Issue.IsLocked }}
 | 
			
		||||
								{{.i18n.Tr "repo.issues.unlock_confirm"}}
 | 
			
		||||
							{{else}}
 | 
			
		||||
								{{.i18n.Tr "repo.issues.lock_confirm"}}
 | 
			
		||||
							{{end}}
 | 
			
		||||
						</button>
 | 
			
		||||
					</div>
 | 
			
		||||
				</form>
 | 
			
		||||
			</div>
 | 
			
		||||
						<div class="text right actions">
 | 
			
		||||
							<div class="ui cancel button">{{.i18n.Tr "settings.cancel"}}</div>
 | 
			
		||||
							<button class="ui red button">
 | 
			
		||||
								{{ if .Issue.IsLocked }}
 | 
			
		||||
									{{.i18n.Tr "repo.issues.unlock_confirm"}}
 | 
			
		||||
								{{else}}
 | 
			
		||||
									{{.i18n.Tr "repo.issues.lock_confirm"}}
 | 
			
		||||
								{{end}}
 | 
			
		||||
							</button>
 | 
			
		||||
						</div>
 | 
			
		||||
					</form>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		{{end}}
 | 
			
		||||
	</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -158,10 +158,23 @@
 | 
			
		||||
					<div class="card board-card" data-issue="{{.ID}}">
 | 
			
		||||
						<div class="content">
 | 
			
		||||
							<div class="header">
 | 
			
		||||
								<span class="{{if .IsClosed}}red{{else}}green{{end}}">
 | 
			
		||||
									{{if .IsPull}}{{svg "octicon-git-merge"}}
 | 
			
		||||
									{{else if .IsClosed}}{{svg "octicon-issue-closed"}}
 | 
			
		||||
									{{else}}{{svg "octicon-issue-opened"}}
 | 
			
		||||
								<span>
 | 
			
		||||
									{{if .IsPull}}
 | 
			
		||||
										{{if .PullRequest.HasMerged}}
 | 
			
		||||
											{{svg "octicon-git-merge" 16 "text purple"}}
 | 
			
		||||
										{{else}}
 | 
			
		||||
											{{if .IsClosed}}
 | 
			
		||||
												{{svg "octicon-git-pull-request" 16 "text red"}}
 | 
			
		||||
											{{else}}
 | 
			
		||||
												{{svg "octicon-git-pull-request" 16 "text green"}}
 | 
			
		||||
											{{end}}
 | 
			
		||||
										{{end}}
 | 
			
		||||
									{{else}}
 | 
			
		||||
										{{if .IsClosed}}
 | 
			
		||||
											{{svg "octicon-issue-closed" 16 "text red"}}
 | 
			
		||||
										{{else}}
 | 
			
		||||
											{{svg "octicon-issue-opened" 16 "text green"}}
 | 
			
		||||
										{{end}}
 | 
			
		||||
									{{end}}
 | 
			
		||||
								</span>
 | 
			
		||||
								<a class="project-board-title" href="{{$.RepoLink}}/issues/{{.Index}}">#{{.Index}} {{.Title}}</a>
 | 
			
		||||
 
 | 
			
		||||
@@ -5,20 +5,16 @@
 | 
			
		||||
		<div class="ui repo-search">
 | 
			
		||||
			<form class="ui form ignore-dirty" method="get">
 | 
			
		||||
				<div class="ui fluid action input">
 | 
			
		||||
					<div class="twelve wide field">
 | 
			
		||||
						<input name="q" value="{{.Keyword}}" placeholder="{{.i18n.Tr "repo.search.search_repo"}}">
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class="two wide field">
 | 
			
		||||
						<select name="t">
 | 
			
		||||
							<option value="">{{.i18n.Tr "repo.search.fuzzy"}}</option>
 | 
			
		||||
							<option value="match" {{if eq .queryType "match"}}selected{{end}}>{{.i18n.Tr "repo.search.match"}}</option>
 | 
			
		||||
						</select>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class="three field">
 | 
			
		||||
					  <button class="ui button" type="submit">
 | 
			
		||||
						  <i class="icon df ac jc">{{svg "octicon-search" 16}}</i>
 | 
			
		||||
					  </button>
 | 
			
		||||
					<input name="q" value="{{.Keyword}}" placeholder="{{.i18n.Tr "repo.search.search_repo"}}">
 | 
			
		||||
					<div class="ui dropdown selection">
 | 
			
		||||
						<input name="t" type="hidden" value="{{.queryType}}">{{svg "octicon-triangle-down" 14 "dropdown icon"}}
 | 
			
		||||
						<div class="text">{{.i18n.Tr (printf "repo.search.%s" (or .queryType "fuzzy"))}}</div>
 | 
			
		||||
						<div class="menu transition hidden" tabindex="-1" style="display: block !important;">
 | 
			
		||||
							<div class="item" data-value="">{{.i18n.Tr "repo.search.fuzzy"}}</div>
 | 
			
		||||
							<div class="item" data-value="match">{{.i18n.Tr "repo.search.match"}}</div>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
					<button class="ui icon button" type="submit">{{svg "octicon-search" 16}}</button>
 | 
			
		||||
				</div>
 | 
			
		||||
			</form>
 | 
			
		||||
		</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -62,12 +62,12 @@
 | 
			
		||||
						{{$.i18n.Tr .GetLastEventLabelFake $timeStr (.Poster.GetDisplayName | Escape) | Safe}}
 | 
			
		||||
					{{end}}
 | 
			
		||||
					{{if and .Milestone (ne $.listType "milestone")}}
 | 
			
		||||
						<a class="milestone" {{if $.RepoLink}}href="{{$.RepoLink}}/milestone/{{.Milestone.ID}}"{{else}}href="{{AppSubUrl}}/{{.Repo.Owner.Name}}/{{.Repo.Name}}/milestone/{{.Milestone.ID}}"{{end}}>
 | 
			
		||||
						<a class="milestone" {{if $.RepoLink}}href="{{$.RepoLink}}/milestone/{{.Milestone.ID}}"{{else}}href="{{AppSubUrl}}/{{.Repo.OwnerName}}/{{.Repo.Name}}/milestone/{{.Milestone.ID}}"{{end}}>
 | 
			
		||||
							{{svg "octicon-milestone" 14 "mr-2"}}{{.Milestone.Name}}
 | 
			
		||||
						</a>
 | 
			
		||||
					{{end}}
 | 
			
		||||
					{{if .Ref}}
 | 
			
		||||
						<a class="ref" {{if $.RepoLink}}href="{{$.RepoLink}}{{index $.IssueRefURLs .ID}}"{{else}}href="{{AppSubUrl}}/{{.Repo.Owner.Name}}/{{.Repo.Name}}{{index $.IssueRefURLs .ID}}"{{end}}>
 | 
			
		||||
						<a class="ref" {{if $.RepoLink}}href="{{$.RepoLink}}{{index $.IssueRefURLs .ID}}"{{else}}href="{{AppSubUrl}}/{{.Repo.OwnerName}}/{{.Repo.Name}}{{index $.IssueRefURLs .ID}}"{{end}}>
 | 
			
		||||
							{{svg "octicon-git-branch" 14 "mr-2"}}{{index $.IssueRefEndNames .ID}}
 | 
			
		||||
						</a>
 | 
			
		||||
					{{end}}
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@
 | 
			
		||||
						{{end}}
 | 
			
		||||
					{{else}}
 | 
			
		||||
						{{if .NeedsPassword}}
 | 
			
		||||
							<form class="ui form" action="/user/activate" method="post">
 | 
			
		||||
							<form class="ui form" action="{{AppSubUrl}}/user/activate" method="post">
 | 
			
		||||
								<div class="required inline field">
 | 
			
		||||
									<label for="password">{{.i18n.Tr "password"}}</label>
 | 
			
		||||
									<input id="password" name="password" type="password" autocomplete="off" required>
 | 
			
		||||
 
 | 
			
		||||
@@ -71,8 +71,8 @@
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		{{end}}
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	{{if .ContextUser.IsOrganization}}
 | 
			
		||||
		<div class="right stackable menu">
 | 
			
		||||
@@ -101,5 +101,6 @@
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	{{end}}
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
<div class="ui divider"></div>
 | 
			
		||||
 
 | 
			
		||||
@@ -38,7 +38,7 @@
 | 
			
		||||
			</h4>
 | 
			
		||||
			<div class="ui attached segment repos-search">
 | 
			
		||||
				<div class="ui fluid right action left icon input" :class="{loading: isLoading}">
 | 
			
		||||
					<input @input="searchRepos(reposFilter)" v-model="searchQuery" ref="search" placeholder="{{.i18n.Tr "home.search_repos"}}">
 | 
			
		||||
					<input @input="changeReposFilter(reposFilter)" v-model="searchQuery" ref="search" placeholder="{{.i18n.Tr "home.search_repos"}}">
 | 
			
		||||
					<i class="icon df ac jc">{{svg "octicon-search" 16}}</i>
 | 
			
		||||
					<div class="ui dropdown icon button" title="{{.i18n.Tr "home.filter"}}">
 | 
			
		||||
						<i class="icon df ac jc m-0">{{svg "octicon-filter" 16}}</i>
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user