mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 02:46:04 +01:00 
			
		
		
		
	add submodule diff links (#33097)
This adds links to submodules in diffs, similar to the existing link when viewing a repo at a specific commit. It does this by expanding diff parsing to recognize changes to submodules, and find the specific refs that are added, deleted or changed. Related #25888 --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
		
							
								
								
									
										12
									
								
								assets/go-licenses.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										12
									
								
								assets/go-licenses.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										25
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								go.mod
									
									
									
									
									
								
							| @@ -24,7 +24,7 @@ require ( | ||||
| 	github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 | ||||
| 	github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1 | ||||
| 	github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 | ||||
| 	github.com/ProtonMail/go-crypto v1.0.0 | ||||
| 	github.com/ProtonMail/go-crypto v1.1.4 | ||||
| 	github.com/PuerkitoBio/goquery v1.10.0 | ||||
| 	github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.3 | ||||
| 	github.com/alecthomas/chroma/v2 v2.14.0 | ||||
| @@ -54,8 +54,8 @@ require ( | ||||
| 	github.com/go-chi/cors v1.2.1 | ||||
| 	github.com/go-co-op/gocron v1.37.0 | ||||
| 	github.com/go-enry/go-enry/v2 v2.9.1 | ||||
| 	github.com/go-git/go-billy/v5 v5.6.0 | ||||
| 	github.com/go-git/go-git/v5 v5.12.0 | ||||
| 	github.com/go-git/go-billy/v5 v5.6.1 | ||||
| 	github.com/go-git/go-git/v5 v5.13.1 | ||||
| 	github.com/go-ldap/ldap/v3 v3.4.8 | ||||
| 	github.com/go-redsync/redsync/v4 v4.13.0 | ||||
| 	github.com/go-sql-driver/mysql v1.8.1 | ||||
| @@ -106,7 +106,7 @@ require ( | ||||
| 	github.com/sassoftware/go-rpmutils v0.4.0 | ||||
| 	github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 | ||||
| 	github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92 | ||||
| 	github.com/stretchr/testify v1.9.0 | ||||
| 	github.com/stretchr/testify v1.10.0 | ||||
| 	github.com/syndtr/goleveldb v1.0.0 | ||||
| 	github.com/tstranex/u2f v1.0.0 | ||||
| 	github.com/ulikunitz/xz v0.5.12 | ||||
| @@ -118,14 +118,14 @@ require ( | ||||
| 	github.com/yuin/goldmark v1.7.8 | ||||
| 	github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc | ||||
| 	github.com/yuin/goldmark-meta v1.1.0 | ||||
| 	golang.org/x/crypto v0.31.0 | ||||
| 	golang.org/x/crypto v0.32.0 | ||||
| 	golang.org/x/image v0.21.0 | ||||
| 	golang.org/x/net v0.33.0 | ||||
| 	golang.org/x/net v0.34.0 | ||||
| 	golang.org/x/oauth2 v0.23.0 | ||||
| 	golang.org/x/sync v0.10.0 | ||||
| 	golang.org/x/sys v0.28.0 | ||||
| 	golang.org/x/sys v0.29.0 | ||||
| 	golang.org/x/text v0.21.0 | ||||
| 	golang.org/x/tools v0.26.0 | ||||
| 	golang.org/x/tools v0.29.0 | ||||
| 	google.golang.org/grpc v1.67.1 | ||||
| 	google.golang.org/protobuf v1.35.1 | ||||
| 	gopkg.in/ini.v1 v1.67.0 | ||||
| @@ -186,7 +186,7 @@ require ( | ||||
| 	github.com/couchbase/gomemcached v0.3.2 // indirect | ||||
| 	github.com/couchbase/goutils v0.1.2 // indirect | ||||
| 	github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect | ||||
| 	github.com/cyphar/filepath-securejoin v0.3.4 // indirect | ||||
| 	github.com/cyphar/filepath-securejoin v0.3.6 // indirect | ||||
| 	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect | ||||
| 	github.com/davidmz/go-pageant v1.0.2 // indirect | ||||
| 	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect | ||||
| @@ -219,7 +219,7 @@ require ( | ||||
| 	github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect | ||||
| 	github.com/golang-sql/sqlexp v0.1.0 // indirect | ||||
| 	github.com/golang/geo v0.0.0-20230421003525-6adc56603217 // indirect | ||||
| 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect | ||||
| 	github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect | ||||
| 	github.com/golang/protobuf v1.5.4 // indirect | ||||
| 	github.com/golang/snappy v0.0.4 // indirect | ||||
| 	github.com/google/btree v1.1.3 // indirect | ||||
| @@ -253,6 +253,7 @@ require ( | ||||
| 	github.com/mitchellh/copystructure v1.2.0 // indirect | ||||
| 	github.com/mitchellh/mapstructure v1.5.0 // indirect | ||||
| 	github.com/mitchellh/reflectwalk v1.0.2 // indirect | ||||
| 	github.com/mmcloughlin/avo v0.6.0 // indirect | ||||
| 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect | ||||
| 	github.com/modern-go/reflect2 v1.0.2 // indirect | ||||
| 	github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect | ||||
| @@ -264,7 +265,7 @@ require ( | ||||
| 	github.com/onsi/ginkgo v1.16.5 // indirect | ||||
| 	github.com/pelletier/go-toml/v2 v2.2.3 // indirect | ||||
| 	github.com/pierrec/lz4/v4 v4.1.21 // indirect | ||||
| 	github.com/pjbgf/sha1cd v0.3.0 // indirect | ||||
| 	github.com/pjbgf/sha1cd v0.3.1 // indirect | ||||
| 	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect | ||||
| 	github.com/prometheus/client_model v0.6.1 // indirect | ||||
| 	github.com/prometheus/common v0.60.1 // indirect | ||||
| @@ -304,7 +305,7 @@ require ( | ||||
| 	go.uber.org/multierr v1.11.0 // indirect | ||||
| 	go.uber.org/zap v1.27.0 // indirect | ||||
| 	golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect | ||||
| 	golang.org/x/mod v0.21.0 // indirect | ||||
| 	golang.org/x/mod v0.22.0 // indirect | ||||
| 	golang.org/x/time v0.7.0 // indirect | ||||
| 	google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect | ||||
| 	gopkg.in/warnings.v0 v0.1.2 // indirect | ||||
|   | ||||
							
								
								
									
										70
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										70
									
								
								go.sum
									
									
									
									
									
								
							| @@ -71,8 +71,8 @@ github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSC | ||||
| github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= | ||||
| github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= | ||||
| github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= | ||||
| github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= | ||||
| github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= | ||||
| github.com/ProtonMail/go-crypto v1.1.4 h1:G5U5asvD5N/6/36oIw3k2bOfBn5XVcZrb7PBjzzKKoE= | ||||
| github.com/ProtonMail/go-crypto v1.1.4/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= | ||||
| github.com/PuerkitoBio/goquery v1.10.0 h1:6fiXdLuUvYs2OJSvNRqlNPoBm6YABE226xrbavY5Wv4= | ||||
| github.com/PuerkitoBio/goquery v1.10.0/go.mod h1:TjZZl68Q3eGHNBA8CWaxAN7rOU1EbDz3CWuolcO5Yu4= | ||||
| github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo= | ||||
| @@ -188,7 +188,6 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= | ||||
| github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= | ||||
| github.com/buildkite/terminal-to-html/v3 v3.16.3 h1:IGuJjboHjuMLWOGsKZKNxbbn41emOLiHzXPmQZk31fk= | ||||
| github.com/buildkite/terminal-to-html/v3 v3.16.3/go.mod h1:r/J7cC9c3EzBzP3/wDz0RJLPwv5PUAMp+KF2w+ntMc0= | ||||
| github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= | ||||
| github.com/caddyserver/certmagic v0.21.4 h1:e7VobB8rffHv8ZZpSiZtEwnLDHUwLVYLWzWSa1FfKI0= | ||||
| github.com/caddyserver/certmagic v0.21.4/go.mod h1:swUXjQ1T9ZtMv95qj7/InJvWLXURU85r+CfG0T+ZbDE= | ||||
| github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= | ||||
| @@ -205,7 +204,6 @@ github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moA | ||||
| github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= | ||||
| github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= | ||||
| github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= | ||||
| github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= | ||||
| github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys= | ||||
| github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= | ||||
| github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= | ||||
| @@ -223,8 +221,8 @@ github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwc | ||||
| github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= | ||||
| github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= | ||||
| github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= | ||||
| github.com/cyphar/filepath-securejoin v0.3.4 h1:VBWugsJh2ZxJmLFSM06/0qzQyiQX2Qs0ViKrUAcqdZ8= | ||||
| github.com/cyphar/filepath-securejoin v0.3.4/go.mod h1:8s/MCNJREmFK0H02MF6Ihv1nakJe4L/w3WZLHNkvlYM= | ||||
| github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM= | ||||
| github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= | ||||
| github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
| github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
| github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= | ||||
| @@ -254,8 +252,8 @@ github.com/dvyukov/go-fuzz v0.0.0-20210429054444-fca39067bc72/go.mod h1:11Gm+ccJ | ||||
| github.com/editorconfig/editorconfig-core-go/v2 v2.6.2 h1:dKG8sc7n321deIVRcQtwlMNoBEra7j0qQ8RwxO8RN0w= | ||||
| github.com/editorconfig/editorconfig-core-go/v2 v2.6.2/go.mod h1:7dvD3GCm7eBw53xZ/lsiq72LqobdMg3ITbMBxnmJmqY= | ||||
| github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= | ||||
| github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= | ||||
| github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= | ||||
| github.com/elazarl/goproxy v1.2.3 h1:xwIyKHbaP5yfT6O9KIeYJR5549MXRQkoQMRXGztz8YQ= | ||||
| github.com/elazarl/goproxy v1.2.3/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64= | ||||
| github.com/emersion/go-imap v1.2.1 h1:+s9ZjMEjOB8NzZMVTM3cCenz2JrQIGGo5j1df19WjTA= | ||||
| github.com/emersion/go-imap v1.2.1/go.mod h1:Qlx1FSx2FTxjnjWpIlVNEuX+ylerZQNFE5NsmKFSejY= | ||||
| github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4= | ||||
| @@ -313,12 +311,12 @@ github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e h1:oRq/fiirun5Hql | ||||
| github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM= | ||||
| github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= | ||||
| github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= | ||||
| github.com/go-git/go-billy/v5 v5.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8= | ||||
| github.com/go-git/go-billy/v5 v5.6.0/go.mod h1:sFDq7xD3fn3E0GOwUSZqHo9lrkmx8xJhA0ZrfvjBRGM= | ||||
| github.com/go-git/go-billy/v5 v5.6.1 h1:u+dcrgaguSSkbjzHwelEjc0Yj300NUevrrPphk/SoRA= | ||||
| github.com/go-git/go-billy/v5 v5.6.1/go.mod h1:0AsLr1z2+Uksi4NlElmMblP5rPcDZNRCD8ujZCRR2BE= | ||||
| github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= | ||||
| github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= | ||||
| github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= | ||||
| github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= | ||||
| github.com/go-git/go-git/v5 v5.13.1 h1:DAQ9APonnlvSWpvolXWIuV6Q6zXy2wHbN4cVlNR5Q+M= | ||||
| github.com/go-git/go-git/v5 v5.13.1/go.mod h1:qryJB4cSBoq3FRoBRf5A77joojuBcmPJ0qu3XXXVixc= | ||||
| github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= | ||||
| github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= | ||||
| github.com/go-ldap/ldap/v3 v3.4.8 h1:loKJyspcRezt2Q3ZRMq2p/0v8iOurlmeXDPw6fikSvQ= | ||||
| @@ -385,8 +383,8 @@ github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei | ||||
| github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= | ||||
| github.com/golang/geo v0.0.0-20230421003525-6adc56603217 h1:HKlyj6in2JV6wVkmQ4XmG/EIm+SCYlPZ+V4GWit7Z+I= | ||||
| github.com/golang/geo v0.0.0-20230421003525-6adc56603217/go.mod h1:8wI0hitZ3a1IxZfeH3/5I97CI8i5cLGsYe7xNhQGs9U= | ||||
| github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= | ||||
| github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= | ||||
| github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= | ||||
| github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= | ||||
| github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= | ||||
| github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= | ||||
| github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= | ||||
| @@ -581,6 +579,8 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua | ||||
| github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= | ||||
| github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= | ||||
| github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= | ||||
| github.com/mmcloughlin/avo v0.6.0 h1:QH6FU8SKoTLaVs80GA8TJuLNkUYl4VokHKlPhVDg4YY= | ||||
| github.com/mmcloughlin/avo v0.6.0/go.mod h1:8CoAGaCSYXtCPR+8y18Y9aB/kxb8JSS6FRI7mSkvD+8= | ||||
| github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= | ||||
| github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= | ||||
| github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= | ||||
| @@ -631,8 +631,8 @@ github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG | ||||
| github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= | ||||
| github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= | ||||
| github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= | ||||
| github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= | ||||
| github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= | ||||
| github.com/pjbgf/sha1cd v0.3.1 h1:Dh2GYdpJnO84lIw0LJwTFXjcNbasP/bklicSznyAaPI= | ||||
| github.com/pjbgf/sha1cd v0.3.1/go.mod h1:Y8t7jSB/dEI/lQE04A1HVKteqjj9bX5O4+Cex0TCu8s= | ||||
| github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= | ||||
| github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= | ||||
| github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= | ||||
| @@ -737,8 +737,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ | ||||
| github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= | ||||
| github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= | ||||
| github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= | ||||
| github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= | ||||
| github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | ||||
| github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= | ||||
| github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | ||||
| github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203 h1:QVqDTf3h2WHt08YuiTGPZLls0Wq99X9bWd0Q5ZSBesM= | ||||
| github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKNihBbwlX8dLpwxCl3+HnXKV/R0e+sRLd9C8= | ||||
| github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= | ||||
| @@ -823,16 +823,14 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh | ||||
| golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= | ||||
| golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | ||||
| golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= | ||||
| golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= | ||||
| golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= | ||||
| golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= | ||||
| golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= | ||||
| golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= | ||||
| golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= | ||||
| golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= | ||||
| golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= | ||||
| golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= | ||||
| golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= | ||||
| golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= | ||||
| golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= | ||||
| golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= | ||||
| golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= | ||||
| golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s= | ||||
| @@ -846,8 +844,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= | ||||
| golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= | ||||
| golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= | ||||
| golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= | ||||
| golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= | ||||
| golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= | ||||
| golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= | ||||
| golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= | ||||
| golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||
| golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||||
| golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||
| @@ -859,18 +857,16 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY | ||||
| golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | ||||
| golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | ||||
| golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= | ||||
| golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= | ||||
| golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= | ||||
| golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= | ||||
| golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= | ||||
| golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= | ||||
| golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= | ||||
| golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= | ||||
| golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= | ||||
| golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= | ||||
| golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= | ||||
| golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= | ||||
| golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= | ||||
| golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= | ||||
| golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= | ||||
| golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= | ||||
| golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= | ||||
| golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| @@ -910,8 +906,6 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc | ||||
| golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| @@ -921,14 +915,12 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||||
| golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||||
| golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||||
| golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||||
| golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= | ||||
| golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||||
| golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= | ||||
| golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||||
| golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= | ||||
| golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | ||||
| golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= | ||||
| golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= | ||||
| golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= | ||||
| golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= | ||||
| golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= | ||||
| golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= | ||||
| golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= | ||||
| @@ -936,15 +928,13 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= | ||||
| golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= | ||||
| golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= | ||||
| golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= | ||||
| golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= | ||||
| golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= | ||||
| golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= | ||||
| golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= | ||||
| golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||
| golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||
| golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||
| golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= | ||||
| golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= | ||||
| golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= | ||||
| golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= | ||||
| golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= | ||||
| golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= | ||||
| golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= | ||||
| @@ -964,8 +954,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc | ||||
| golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= | ||||
| golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= | ||||
| golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= | ||||
| golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= | ||||
| golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= | ||||
| golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= | ||||
| golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= | ||||
| golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
| golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
| golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
|   | ||||
| @@ -784,60 +784,10 @@ func GetRepositoryByName(ctx context.Context, ownerID int64, name string) (*Repo | ||||
| 	return &repo, err | ||||
| } | ||||
|  | ||||
| func parseRepositoryURL(ctx context.Context, repoURL string) (ret struct { | ||||
| 	OwnerName, RepoName, RemainingPath string | ||||
| }, | ||||
| ) { | ||||
| 	// possible urls for git: | ||||
| 	//  https://my.domain/sub-path/<owner>/<repo>[.git] | ||||
| 	//  git+ssh://user@my.domain/<owner>/<repo>[.git] | ||||
| 	//  ssh://user@my.domain/<owner>/<repo>[.git] | ||||
| 	//  user@my.domain:<owner>/<repo>[.git] | ||||
|  | ||||
| 	fillPathParts := func(s string) { | ||||
| 		s = strings.TrimPrefix(s, "/") | ||||
| 		fields := strings.SplitN(s, "/", 3) | ||||
| 		if len(fields) >= 2 { | ||||
| 			ret.OwnerName = fields[0] | ||||
| 			ret.RepoName = strings.TrimSuffix(fields[1], ".git") | ||||
| 			if len(fields) == 3 { | ||||
| 				ret.RemainingPath = "/" + fields[2] | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	parsed, err := giturl.ParseGitURL(repoURL) | ||||
| 	if err != nil { | ||||
| 		return ret | ||||
| 	} | ||||
| 	if parsed.URL.Scheme == "http" || parsed.URL.Scheme == "https" { | ||||
| 		if !httplib.IsCurrentGiteaSiteURL(ctx, repoURL) { | ||||
| 			return ret | ||||
| 		} | ||||
| 		fillPathParts(strings.TrimPrefix(parsed.URL.Path, setting.AppSubURL)) | ||||
| 	} else if parsed.URL.Scheme == "ssh" || parsed.URL.Scheme == "git+ssh" { | ||||
| 		domainSSH := setting.SSH.Domain | ||||
| 		domainCur := httplib.GuessCurrentHostDomain(ctx) | ||||
| 		urlDomain, _, _ := net.SplitHostPort(parsed.URL.Host) | ||||
| 		urlDomain = util.IfZero(urlDomain, parsed.URL.Host) | ||||
| 		if urlDomain == "" { | ||||
| 			return ret | ||||
| 		} | ||||
| 		// check whether URL domain is the App domain | ||||
| 		domainMatches := domainSSH == urlDomain | ||||
| 		// check whether URL domain is current domain from context | ||||
| 		domainMatches = domainMatches || (domainCur != "" && domainCur == urlDomain) | ||||
| 		if domainMatches { | ||||
| 			fillPathParts(parsed.URL.Path) | ||||
| 		} | ||||
| 	} | ||||
| 	return ret | ||||
| } | ||||
|  | ||||
| // GetRepositoryByURL returns the repository by given url | ||||
| func GetRepositoryByURL(ctx context.Context, repoURL string) (*Repository, error) { | ||||
| 	ret := parseRepositoryURL(ctx, repoURL) | ||||
| 	if ret.OwnerName == "" { | ||||
| 	ret, err := giturl.ParseRepositoryURL(ctx, repoURL) | ||||
| 	if err != nil || ret.OwnerName == "" { | ||||
| 		return nil, fmt.Errorf("unknown or malformed repository URL") | ||||
| 	} | ||||
| 	return GetRepositoryByOwnerAndName(ctx, ret.OwnerName, ret.RepoName) | ||||
|   | ||||
| @@ -4,16 +4,12 @@ | ||||
| package repo | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"testing" | ||||
|  | ||||
| 	"code.gitea.io/gitea/models/db" | ||||
| 	"code.gitea.io/gitea/models/unit" | ||||
| 	"code.gitea.io/gitea/models/unittest" | ||||
| 	user_model "code.gitea.io/gitea/models/user" | ||||
| 	"code.gitea.io/gitea/modules/httplib" | ||||
| 	"code.gitea.io/gitea/modules/markup" | ||||
| 	"code.gitea.io/gitea/modules/optional" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| @@ -132,79 +128,6 @@ func TestMetas(t *testing.T) { | ||||
| 	assert.Equal(t, ",owners,team1,", metas["teams"]) | ||||
| } | ||||
|  | ||||
| func TestParseRepositoryURLPathSegments(t *testing.T) { | ||||
| 	defer test.MockVariableValue(&setting.AppURL, "https://localhost:3000")() | ||||
|  | ||||
| 	ctxURL, _ := url.Parse("https://gitea") | ||||
| 	ctxReq := &http.Request{URL: ctxURL, Header: http.Header{}} | ||||
| 	ctxReq.Host = ctxURL.Host | ||||
| 	ctxReq.Header.Add("X-Forwarded-Proto", ctxURL.Scheme) | ||||
| 	ctx := context.WithValue(context.Background(), httplib.RequestContextKey, ctxReq) | ||||
| 	cases := []struct { | ||||
| 		input                          string | ||||
| 		ownerName, repoName, remaining string | ||||
| 	}{ | ||||
| 		{input: "/user/repo"}, | ||||
|  | ||||
| 		{input: "https://localhost:3000/user/repo", ownerName: "user", repoName: "repo"}, | ||||
| 		{input: "https://external:3000/user/repo"}, | ||||
|  | ||||
| 		{input: "https://localhost:3000/user/repo.git/other", ownerName: "user", repoName: "repo", remaining: "/other"}, | ||||
|  | ||||
| 		{input: "https://gitea/user/repo", ownerName: "user", repoName: "repo"}, | ||||
| 		{input: "https://gitea:3333/user/repo"}, | ||||
|  | ||||
| 		{input: "ssh://try.gitea.io:2222/user/repo", ownerName: "user", repoName: "repo"}, | ||||
| 		{input: "ssh://external:2222/user/repo"}, | ||||
|  | ||||
| 		{input: "git+ssh://user@try.gitea.io/user/repo.git", ownerName: "user", repoName: "repo"}, | ||||
| 		{input: "git+ssh://user@external/user/repo.git"}, | ||||
|  | ||||
| 		{input: "root@try.gitea.io:user/repo.git", ownerName: "user", repoName: "repo"}, | ||||
| 		{input: "root@gitea:user/repo.git", ownerName: "user", repoName: "repo"}, | ||||
| 		{input: "root@external:user/repo.git"}, | ||||
| 	} | ||||
|  | ||||
| 	for _, c := range cases { | ||||
| 		t.Run(c.input, func(t *testing.T) { | ||||
| 			ret := parseRepositoryURL(ctx, c.input) | ||||
| 			assert.Equal(t, c.ownerName, ret.OwnerName) | ||||
| 			assert.Equal(t, c.repoName, ret.RepoName) | ||||
| 			assert.Equal(t, c.remaining, ret.RemainingPath) | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	t.Run("WithSubpath", func(t *testing.T) { | ||||
| 		defer test.MockVariableValue(&setting.AppURL, "https://localhost:3000/subpath")() | ||||
| 		defer test.MockVariableValue(&setting.AppSubURL, "/subpath")() | ||||
| 		cases = []struct { | ||||
| 			input                          string | ||||
| 			ownerName, repoName, remaining string | ||||
| 		}{ | ||||
| 			{input: "https://localhost:3000/user/repo"}, | ||||
| 			{input: "https://localhost:3000/subpath/user/repo.git/other", ownerName: "user", repoName: "repo", remaining: "/other"}, | ||||
|  | ||||
| 			{input: "ssh://try.gitea.io:2222/user/repo", ownerName: "user", repoName: "repo"}, | ||||
| 			{input: "ssh://external:2222/user/repo"}, | ||||
|  | ||||
| 			{input: "git+ssh://user@try.gitea.io/user/repo.git", ownerName: "user", repoName: "repo"}, | ||||
| 			{input: "git+ssh://user@external/user/repo.git"}, | ||||
|  | ||||
| 			{input: "root@try.gitea.io:user/repo.git", ownerName: "user", repoName: "repo"}, | ||||
| 			{input: "root@external:user/repo.git"}, | ||||
| 		} | ||||
|  | ||||
| 		for _, c := range cases { | ||||
| 			t.Run(c.input, func(t *testing.T) { | ||||
| 				ret := parseRepositoryURL(ctx, c.input) | ||||
| 				assert.Equal(t, c.ownerName, ret.OwnerName) | ||||
| 				assert.Equal(t, c.repoName, ret.RepoName) | ||||
| 				assert.Equal(t, c.remaining, ret.RemainingPath) | ||||
| 			}) | ||||
| 		} | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func TestGetRepositoryByURL(t *testing.T) { | ||||
| 	assert.NoError(t, unittest.PrepareTestDatabase()) | ||||
|  | ||||
|   | ||||
| @@ -7,5 +7,5 @@ package git | ||||
| type CommitInfo struct { | ||||
| 	Entry         *TreeEntry | ||||
| 	Commit        *Commit | ||||
| 	SubModuleFile *CommitSubModuleFile | ||||
| 	SubmoduleFile *CommitSubmoduleFile | ||||
| } | ||||
|   | ||||
| @@ -85,8 +85,8 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath | ||||
| 			} else if subModule != nil { | ||||
| 				subModuleURL = subModule.URL | ||||
| 			} | ||||
| 			subModuleFile := NewCommitSubModuleFile(subModuleURL, entry.ID.String()) | ||||
| 			commitsInfo[i].SubModuleFile = subModuleFile | ||||
| 			subModuleFile := NewCommitSubmoduleFile(subModuleURL, entry.ID.String()) | ||||
| 			commitsInfo[i].SubmoduleFile = subModuleFile | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -79,8 +79,8 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath | ||||
| 			} else if subModule != nil { | ||||
| 				subModuleURL = subModule.URL | ||||
| 			} | ||||
| 			subModuleFile := NewCommitSubModuleFile(subModuleURL, entry.ID.String()) | ||||
| 			commitsInfo[i].SubModuleFile = subModuleFile | ||||
| 			subModuleFile := NewCommitSubmoduleFile(subModuleURL, entry.ID.String()) | ||||
| 			commitsInfo[i].SubmoduleFile = subModuleFile | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -3,6 +3,10 @@ | ||||
|  | ||||
| package git | ||||
|  | ||||
| type SubmoduleWebLink struct { | ||||
| 	RepoWebLink, CommitWebLink string | ||||
| } | ||||
|  | ||||
| // GetSubModules get all the submodules of current revision git tree | ||||
| func (c *Commit) GetSubModules() (*ObjectCache[*SubModule], error) { | ||||
| 	if c.submoduleCache != nil { | ||||
|   | ||||
| @@ -5,107 +5,50 @@ | ||||
| package git | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"net/url" | ||||
| 	"path" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| 	"context" | ||||
|  | ||||
| 	giturl "code.gitea.io/gitea/modules/git/url" | ||||
| ) | ||||
|  | ||||
| var scpSyntax = regexp.MustCompile(`^([a-zA-Z0-9_]+@)?([a-zA-Z0-9._-]+):(.*)$`) | ||||
|  | ||||
| // CommitSubModuleFile represents a file with submodule type. | ||||
| type CommitSubModuleFile struct { | ||||
| // CommitSubmoduleFile represents a file with submodule type. | ||||
| type CommitSubmoduleFile struct { | ||||
| 	refURL    string | ||||
| 	parsedURL *giturl.RepositoryURL | ||||
| 	parsed    bool | ||||
| 	refID     string | ||||
| 	repoLink  string | ||||
| } | ||||
|  | ||||
| // NewCommitSubModuleFile create a new submodule file | ||||
| func NewCommitSubModuleFile(refURL, refID string) *CommitSubModuleFile { | ||||
| 	return &CommitSubModuleFile{ | ||||
| 		refURL: refURL, | ||||
| 		refID:  refID, | ||||
| 	} | ||||
| // NewCommitSubmoduleFile create a new submodule file | ||||
| func NewCommitSubmoduleFile(refURL, refID string) *CommitSubmoduleFile { | ||||
| 	return &CommitSubmoduleFile{refURL: refURL, refID: refID} | ||||
| } | ||||
|  | ||||
| func getRefURL(refURL, urlPrefix, repoFullName, sshDomain string) string { | ||||
| 	if refURL == "" { | ||||
| 		return "" | ||||
| func (sf *CommitSubmoduleFile) RefID() string { | ||||
| 	return sf.refID // this function is only used in templates | ||||
| } | ||||
|  | ||||
| // SubmoduleWebLink tries to make some web links for a submodule, it also works on "nil" receiver | ||||
| func (sf *CommitSubmoduleFile) SubmoduleWebLink(ctx context.Context, optCommitID ...string) *SubmoduleWebLink { | ||||
| 	if sf == nil { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	refURI := strings.TrimSuffix(refURL, ".git") | ||||
|  | ||||
| 	prefixURL, _ := url.Parse(urlPrefix) | ||||
| 	urlPrefixHostname, _, err := net.SplitHostPort(prefixURL.Host) | ||||
| 	if !sf.parsed { | ||||
| 		sf.parsed = true | ||||
| 		parsedURL, err := giturl.ParseRepositoryURL(ctx, sf.refURL) | ||||
| 		if err != nil { | ||||
| 		urlPrefixHostname = prefixURL.Host | ||||
| 			return nil | ||||
| 		} | ||||
|  | ||||
| 	urlPrefix = strings.TrimSuffix(urlPrefix, "/") | ||||
|  | ||||
| 	// FIXME: Need to consider branch - which will require changes in modules/git/commit.go:GetSubModules | ||||
| 	// Relative url prefix check (according to git submodule documentation) | ||||
| 	if strings.HasPrefix(refURI, "./") || strings.HasPrefix(refURI, "../") { | ||||
| 		return urlPrefix + path.Clean(path.Join("/", repoFullName, refURI)) | ||||
| 		sf.parsedURL = parsedURL | ||||
| 		sf.repoLink = giturl.MakeRepositoryWebLink(sf.parsedURL) | ||||
| 	} | ||||
|  | ||||
| 	if !strings.Contains(refURI, "://") { | ||||
| 		// scp style syntax which contains *no* port number after the : (and is not parsed by net/url) | ||||
| 		// ex: git@try.gitea.io:go-gitea/gitea | ||||
| 		match := scpSyntax.FindAllStringSubmatch(refURI, -1) | ||||
| 		if len(match) > 0 { | ||||
| 			m := match[0] | ||||
| 			refHostname := m[2] | ||||
| 			pth := m[3] | ||||
|  | ||||
| 			if !strings.HasPrefix(pth, "/") { | ||||
| 				pth = "/" + pth | ||||
| 	var commitLink string | ||||
| 	if len(optCommitID) == 2 { | ||||
| 		commitLink = sf.repoLink + "/compare/" + optCommitID[0] + "..." + optCommitID[1] | ||||
| 	} else if len(optCommitID) == 1 { | ||||
| 		commitLink = sf.repoLink + "/commit/" + optCommitID[0] | ||||
| 	} else { | ||||
| 		commitLink = sf.repoLink + "/commit/" + sf.refID | ||||
| 	} | ||||
|  | ||||
| 			if urlPrefixHostname == refHostname || refHostname == sshDomain { | ||||
| 				return urlPrefix + path.Clean(path.Join("/", pth)) | ||||
| 			} | ||||
| 			return "http://" + refHostname + pth | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	ref, err := url.Parse(refURI) | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| 	} | ||||
|  | ||||
| 	refHostname, _, err := net.SplitHostPort(ref.Host) | ||||
| 	if err != nil { | ||||
| 		refHostname = ref.Host | ||||
| 	} | ||||
|  | ||||
| 	supportedSchemes := []string{"http", "https", "git", "ssh", "git+ssh"} | ||||
|  | ||||
| 	for _, scheme := range supportedSchemes { | ||||
| 		if ref.Scheme == scheme { | ||||
| 			if ref.Scheme == "http" || ref.Scheme == "https" { | ||||
| 				if len(ref.User.Username()) > 0 { | ||||
| 					return ref.Scheme + "://" + fmt.Sprintf("%v", ref.User) + "@" + ref.Host + ref.Path | ||||
| 				} | ||||
| 				return ref.Scheme + "://" + ref.Host + ref.Path | ||||
| 			} else if urlPrefixHostname == refHostname || refHostname == sshDomain { | ||||
| 				return urlPrefix + path.Clean(path.Join("/", ref.Path)) | ||||
| 			} | ||||
| 			return "http://" + refHostname + ref.Path | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| // RefURL guesses and returns reference URL. | ||||
| // FIXME: template passes AppURL as urlPrefix, it needs to figure out the correct approach (no hard-coded AppURL anymore) | ||||
| func (sf *CommitSubModuleFile) RefURL(urlPrefix, repoFullName, sshDomain string) string { | ||||
| 	return getRefURL(sf.refURL, urlPrefix, repoFullName, sshDomain) | ||||
| } | ||||
|  | ||||
| // RefID returns reference ID. | ||||
| func (sf *CommitSubModuleFile) RefID() string { | ||||
| 	return sf.refID | ||||
| 	return &SubmoduleWebLink{RepoWebLink: sf.repoLink, CommitWebLink: commitLink} | ||||
| } | ||||
|   | ||||
| @@ -1,42 +1,30 @@ | ||||
| // Copyright 2018 The Gitea Authors. All rights reserved. | ||||
| // Copyright 2024 The Gitea Authors. All rights reserved. | ||||
| // SPDX-License-Identifier: MIT | ||||
|  | ||||
| package git | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
|  | ||||
| func TestCommitSubModuleFileGetRefURL(t *testing.T) { | ||||
| 	kases := []struct { | ||||
| 		refURL     string | ||||
| 		prefixURL  string | ||||
| 		parentPath string | ||||
| 		SSHDomain  string | ||||
| 		expect     string | ||||
| 	}{ | ||||
| 		{"git://github.com/user1/repo1", "/", "user1/repo2", "", "http://github.com/user1/repo1"}, | ||||
| 		{"https://localhost/user1/repo1.git", "/", "user1/repo2", "", "https://localhost/user1/repo1"}, | ||||
| 		{"http://localhost/user1/repo1.git", "/", "owner/reponame", "", "http://localhost/user1/repo1"}, | ||||
| 		{"git@github.com:user1/repo1.git", "/", "owner/reponame", "", "http://github.com/user1/repo1"}, | ||||
| 		{"ssh://git@git.zefie.net:2222/zefie/lge_g6_kernel_scripts.git", "/", "zefie/lge_g6_kernel", "", "http://git.zefie.net/zefie/lge_g6_kernel_scripts"}, | ||||
| 		{"git@git.zefie.net:2222/zefie/lge_g6_kernel_scripts.git", "/", "zefie/lge_g6_kernel", "", "http://git.zefie.net/2222/zefie/lge_g6_kernel_scripts"}, | ||||
| 		{"git@try.gitea.io:go-gitea/gitea", "https://try.gitea.io/", "go-gitea/sdk", "", "https://try.gitea.io/go-gitea/gitea"}, | ||||
| 		{"ssh://git@try.gitea.io:9999/go-gitea/gitea", "https://try.gitea.io/", "go-gitea/sdk", "", "https://try.gitea.io/go-gitea/gitea"}, | ||||
| 		{"git://git@try.gitea.io:9999/go-gitea/gitea", "https://try.gitea.io/", "go-gitea/sdk", "", "https://try.gitea.io/go-gitea/gitea"}, | ||||
| 		{"ssh://git@127.0.0.1:9999/go-gitea/gitea", "https://127.0.0.1:3000/", "go-gitea/sdk", "", "https://127.0.0.1:3000/go-gitea/gitea"}, | ||||
| 		{"https://gitea.com:3000/user1/repo1.git", "https://127.0.0.1:3000/", "user/repo2", "", "https://gitea.com:3000/user1/repo1"}, | ||||
| 		{"https://example.gitea.com/gitea/user1/repo1.git", "https://example.gitea.com/gitea/", "", "user/repo2", "https://example.gitea.com/gitea/user1/repo1"}, | ||||
| 		{"https://username:password@github.com/username/repository.git", "/", "username/repository2", "", "https://username:password@github.com/username/repository"}, | ||||
| 		{"somethingbad", "https://127.0.0.1:3000/go-gitea/gitea", "/", "", ""}, | ||||
| 		{"git@localhost:user/repo", "https://localhost/", "user2/repo1", "", "https://localhost/user/repo"}, | ||||
| 		{"../path/to/repo.git/", "https://localhost/", "user/repo2", "", "https://localhost/user/path/to/repo.git"}, | ||||
| 		{"ssh://git@ssh.gitea.io:2222/go-gitea/gitea", "https://try.gitea.io/", "go-gitea/sdk", "ssh.gitea.io", "https://try.gitea.io/go-gitea/gitea"}, | ||||
| 	} | ||||
| func TestCommitSubmoduleLink(t *testing.T) { | ||||
| 	sf := NewCommitSubmoduleFile("git@github.com:user/repo.git", "aaaa") | ||||
|  | ||||
| 	for _, kase := range kases { | ||||
| 		assert.EqualValues(t, kase.expect, getRefURL(kase.refURL, kase.prefixURL, kase.parentPath, kase.SSHDomain)) | ||||
| 	} | ||||
| 	wl := sf.SubmoduleWebLink(context.Background()) | ||||
| 	assert.Equal(t, "https://github.com/user/repo", wl.RepoWebLink) | ||||
| 	assert.Equal(t, "https://github.com/user/repo/commit/aaaa", wl.CommitWebLink) | ||||
|  | ||||
| 	wl = sf.SubmoduleWebLink(context.Background(), "1111") | ||||
| 	assert.Equal(t, "https://github.com/user/repo", wl.RepoWebLink) | ||||
| 	assert.Equal(t, "https://github.com/user/repo/commit/1111", wl.CommitWebLink) | ||||
|  | ||||
| 	wl = sf.SubmoduleWebLink(context.Background(), "1111", "2222") | ||||
| 	assert.Equal(t, "https://github.com/user/repo", wl.RepoWebLink) | ||||
| 	assert.Equal(t, "https://github.com/user/repo/compare/1111...2222", wl.CommitWebLink) | ||||
|  | ||||
| 	wl = (*CommitSubmoduleFile)(nil).SubmoduleWebLink(context.Background()) | ||||
| 	assert.Nil(t, wl) | ||||
| } | ||||
|   | ||||
| @@ -4,9 +4,15 @@ | ||||
| package url | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	stdurl "net/url" | ||||
| 	"strings" | ||||
|  | ||||
| 	"code.gitea.io/gitea/modules/httplib" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| 	"code.gitea.io/gitea/modules/util" | ||||
| ) | ||||
|  | ||||
| // ErrWrongURLFormat represents an error with wrong url format | ||||
| @@ -90,3 +96,86 @@ func ParseGitURL(remote string) (*GitURL, error) { | ||||
| 		extraMark: 2, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| type RepositoryURL struct { | ||||
| 	GitURL *GitURL | ||||
|  | ||||
| 	// if the URL belongs to current Gitea instance, then the below fields have values | ||||
| 	OwnerName     string | ||||
| 	RepoName      string | ||||
| 	RemainingPath string | ||||
| } | ||||
|  | ||||
| // ParseRepositoryURL tries to parse a Git URL and extract the owner/repository name if it belongs to current Gitea instance. | ||||
| func ParseRepositoryURL(ctx context.Context, repoURL string) (*RepositoryURL, error) { | ||||
| 	// possible urls for git: | ||||
| 	//  https://my.domain/sub-path/<owner>/<repo>[.git] | ||||
| 	//  git+ssh://user@my.domain/<owner>/<repo>[.git] | ||||
| 	//  ssh://user@my.domain/<owner>/<repo>[.git] | ||||
| 	//  user@my.domain:<owner>/<repo>[.git] | ||||
| 	parsed, err := ParseGitURL(repoURL) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	ret := &RepositoryURL{} | ||||
| 	ret.GitURL = parsed | ||||
|  | ||||
| 	fillPathParts := func(s string) { | ||||
| 		s = strings.TrimPrefix(s, "/") | ||||
| 		fields := strings.SplitN(s, "/", 3) | ||||
| 		if len(fields) >= 2 { | ||||
| 			ret.OwnerName = fields[0] | ||||
| 			ret.RepoName = strings.TrimSuffix(fields[1], ".git") | ||||
| 			if len(fields) == 3 { | ||||
| 				ret.RemainingPath = "/" + fields[2] | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if parsed.URL.Scheme == "http" || parsed.URL.Scheme == "https" { | ||||
| 		if !httplib.IsCurrentGiteaSiteURL(ctx, repoURL) { | ||||
| 			return ret, nil | ||||
| 		} | ||||
| 		fillPathParts(strings.TrimPrefix(parsed.URL.Path, setting.AppSubURL)) | ||||
| 	} else if parsed.URL.Scheme == "ssh" || parsed.URL.Scheme == "git+ssh" { | ||||
| 		domainSSH := setting.SSH.Domain | ||||
| 		domainCur := httplib.GuessCurrentHostDomain(ctx) | ||||
| 		urlDomain, _, _ := net.SplitHostPort(parsed.URL.Host) | ||||
| 		urlDomain = util.IfZero(urlDomain, parsed.URL.Host) | ||||
| 		if urlDomain == "" { | ||||
| 			return ret, nil | ||||
| 		} | ||||
| 		// check whether URL domain is the App domain | ||||
| 		domainMatches := domainSSH == urlDomain | ||||
| 		// check whether URL domain is current domain from context | ||||
| 		domainMatches = domainMatches || (domainCur != "" && domainCur == urlDomain) | ||||
| 		if domainMatches { | ||||
| 			fillPathParts(parsed.URL.Path) | ||||
| 		} | ||||
| 	} | ||||
| 	return ret, nil | ||||
| } | ||||
|  | ||||
| // MakeRepositoryWebLink generates a web link (http/https) for a git repository (by guessing sometimes) | ||||
| func MakeRepositoryWebLink(repoURL *RepositoryURL) string { | ||||
| 	if repoURL.OwnerName != "" { | ||||
| 		return setting.AppSubURL + "/" + repoURL.OwnerName + "/" + repoURL.RepoName | ||||
| 	} | ||||
|  | ||||
| 	// now, let's guess, for example: | ||||
| 	// * git@github.com:owner/submodule.git | ||||
| 	// * https://github.com/example/submodule1.git | ||||
| 	if repoURL.GitURL.Scheme == "http" || repoURL.GitURL.Scheme == "https" { | ||||
| 		return strings.TrimSuffix(repoURL.GitURL.String(), ".git") | ||||
| 	} else if repoURL.GitURL.Scheme == "ssh" || repoURL.GitURL.Scheme == "git+ssh" { | ||||
| 		hostname, _, _ := net.SplitHostPort(repoURL.GitURL.Host) | ||||
| 		hostname = util.IfZero(hostname, repoURL.GitURL.Host) | ||||
| 		urlPath := strings.TrimSuffix(repoURL.GitURL.Path, ".git") | ||||
| 		urlPath = strings.TrimPrefix(urlPath, "/") | ||||
| 		urlFull := fmt.Sprintf("https://%s/%s", hostname, urlPath) | ||||
| 		urlFull = strings.TrimSuffix(urlFull, "/") | ||||
| 		return urlFull | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|   | ||||
| @@ -4,9 +4,15 @@ | ||||
| package url | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"testing" | ||||
|  | ||||
| 	"code.gitea.io/gitea/modules/httplib" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| 	"code.gitea.io/gitea/modules/test" | ||||
|  | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
|  | ||||
| @@ -164,3 +170,98 @@ func TestParseGitURLs(t *testing.T) { | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestParseRepositoryURL(t *testing.T) { | ||||
| 	defer test.MockVariableValue(&setting.AppURL, "https://localhost:3000")() | ||||
| 	defer test.MockVariableValue(&setting.SSH.Domain, "try.gitea.io")() | ||||
|  | ||||
| 	ctxURL, _ := url.Parse("https://gitea") | ||||
| 	ctxReq := &http.Request{URL: ctxURL, Header: http.Header{}} | ||||
| 	ctxReq.Host = ctxURL.Host | ||||
| 	ctxReq.Header.Add("X-Forwarded-Proto", ctxURL.Scheme) | ||||
| 	ctx := context.WithValue(context.Background(), httplib.RequestContextKey, ctxReq) | ||||
| 	cases := []struct { | ||||
| 		input                          string | ||||
| 		ownerName, repoName, remaining string | ||||
| 	}{ | ||||
| 		{input: "/user/repo"}, | ||||
|  | ||||
| 		{input: "https://localhost:3000/user/repo", ownerName: "user", repoName: "repo"}, | ||||
| 		{input: "https://external:3000/user/repo"}, | ||||
|  | ||||
| 		{input: "https://localhost:3000/user/repo.git/other", ownerName: "user", repoName: "repo", remaining: "/other"}, | ||||
|  | ||||
| 		{input: "https://gitea/user/repo", ownerName: "user", repoName: "repo"}, | ||||
| 		{input: "https://gitea:3333/user/repo"}, | ||||
|  | ||||
| 		{input: "ssh://try.gitea.io:2222/user/repo", ownerName: "user", repoName: "repo"}, | ||||
| 		{input: "ssh://external:2222/user/repo"}, | ||||
|  | ||||
| 		{input: "git+ssh://user@try.gitea.io/user/repo.git", ownerName: "user", repoName: "repo"}, | ||||
| 		{input: "git+ssh://user@external/user/repo.git"}, | ||||
|  | ||||
| 		{input: "root@try.gitea.io:user/repo.git", ownerName: "user", repoName: "repo"}, | ||||
| 		{input: "root@gitea:user/repo.git", ownerName: "user", repoName: "repo"}, | ||||
| 		{input: "root@external:user/repo.git"}, | ||||
| 	} | ||||
|  | ||||
| 	for _, c := range cases { | ||||
| 		t.Run(c.input, func(t *testing.T) { | ||||
| 			ret, _ := ParseRepositoryURL(ctx, c.input) | ||||
| 			assert.Equal(t, c.ownerName, ret.OwnerName) | ||||
| 			assert.Equal(t, c.repoName, ret.RepoName) | ||||
| 			assert.Equal(t, c.remaining, ret.RemainingPath) | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	t.Run("WithSubpath", func(t *testing.T) { | ||||
| 		defer test.MockVariableValue(&setting.AppURL, "https://localhost:3000/subpath")() | ||||
| 		defer test.MockVariableValue(&setting.AppSubURL, "/subpath")() | ||||
| 		cases = []struct { | ||||
| 			input                          string | ||||
| 			ownerName, repoName, remaining string | ||||
| 		}{ | ||||
| 			{input: "https://localhost:3000/user/repo"}, | ||||
| 			{input: "https://localhost:3000/subpath/user/repo.git/other", ownerName: "user", repoName: "repo", remaining: "/other"}, | ||||
|  | ||||
| 			{input: "ssh://try.gitea.io:2222/user/repo", ownerName: "user", repoName: "repo"}, | ||||
| 			{input: "ssh://external:2222/user/repo"}, | ||||
|  | ||||
| 			{input: "git+ssh://user@try.gitea.io/user/repo.git", ownerName: "user", repoName: "repo"}, | ||||
| 			{input: "git+ssh://user@external/user/repo.git"}, | ||||
|  | ||||
| 			{input: "root@try.gitea.io:user/repo.git", ownerName: "user", repoName: "repo"}, | ||||
| 			{input: "root@external:user/repo.git"}, | ||||
| 		} | ||||
|  | ||||
| 		for _, c := range cases { | ||||
| 			t.Run(c.input, func(t *testing.T) { | ||||
| 				ret, _ := ParseRepositoryURL(ctx, c.input) | ||||
| 				assert.Equal(t, c.ownerName, ret.OwnerName) | ||||
| 				assert.Equal(t, c.repoName, ret.RepoName) | ||||
| 				assert.Equal(t, c.remaining, ret.RemainingPath) | ||||
| 			}) | ||||
| 		} | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func TestMakeRepositoryBaseLink(t *testing.T) { | ||||
| 	defer test.MockVariableValue(&setting.AppURL, "https://localhost:3000/subpath")() | ||||
| 	defer test.MockVariableValue(&setting.AppSubURL, "/subpath")() | ||||
|  | ||||
| 	u, err := ParseRepositoryURL(context.Background(), "https://localhost:3000/subpath/user/repo.git") | ||||
| 	assert.NoError(t, err) | ||||
| 	assert.Equal(t, "/subpath/user/repo", MakeRepositoryWebLink(u)) | ||||
|  | ||||
| 	u, err = ParseRepositoryURL(context.Background(), "https://github.com/owner/repo.git") | ||||
| 	assert.NoError(t, err) | ||||
| 	assert.Equal(t, "https://github.com/owner/repo", MakeRepositoryWebLink(u)) | ||||
|  | ||||
| 	u, err = ParseRepositoryURL(context.Background(), "git@github.com:owner/repo.git") | ||||
| 	assert.NoError(t, err) | ||||
| 	assert.Equal(t, "https://github.com/owner/repo", MakeRepositoryWebLink(u)) | ||||
|  | ||||
| 	u, err = ParseRepositoryURL(context.Background(), "git+ssh://other:123/owner/repo.git") | ||||
| 	assert.NoError(t, err) | ||||
| 	assert.Equal(t, "https://other/owner/repo", MakeRepositoryWebLink(u)) | ||||
| } | ||||
|   | ||||
| @@ -6,6 +6,7 @@ package repository | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
|  | ||||
| 	"code.gitea.io/gitea/models/db" | ||||
| 	git_model "code.gitea.io/gitea/models/git" | ||||
| @@ -51,6 +52,9 @@ func SyncRepoBranchesWithRepo(ctx context.Context, repo *repo_model.Repository, | ||||
| 	{ | ||||
| 		branches, _, err := gitRepo.GetBranchNames(0, 0) | ||||
| 		if err != nil { | ||||
| 			if strings.Contains(err.Error(), "ref file is empty") { | ||||
| 				return 0, nil | ||||
| 			} | ||||
| 			return 0, err | ||||
| 		} | ||||
| 		log.Trace("SyncRepoBranches[%s]: branches[%d]: %v", repo.FullName(), len(branches), branches) | ||||
|   | ||||
| @@ -2628,6 +2628,9 @@ diff.image.overlay = Overlay | ||||
| diff.has_escaped = This line has hidden Unicode characters | ||||
| diff.show_file_tree = Show file tree | ||||
| diff.hide_file_tree = Hide file tree | ||||
| diff.submodule_added = Submodule %[1]s added at %[2]s | ||||
| diff.submodule_deleted = Submodule %[1]s deleted from %[2]s | ||||
| diff.submodule_updated = Submodule %[1]s updated: %[2]s | ||||
|  | ||||
| releases.desc = Track project versions and downloads. | ||||
| release.releases = Releases | ||||
|   | ||||
| @@ -309,7 +309,6 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri | ||||
| 	} | ||||
|  | ||||
| 	ctx.Data["TreeLink"] = treeLink | ||||
| 	ctx.Data["SSHDomain"] = setting.SSH.Domain | ||||
|  | ||||
| 	return allEntries | ||||
| } | ||||
|   | ||||
| @@ -360,7 +360,6 @@ type DiffFile struct { | ||||
| 	IsLFSFile                 bool | ||||
| 	IsRenamed                 bool | ||||
| 	IsAmbiguous               bool | ||||
| 	IsSubmodule               bool | ||||
| 	Sections                  []*DiffSection | ||||
| 	IsIncomplete              bool | ||||
| 	IsIncompleteLineTooLong   bool | ||||
| @@ -372,6 +371,9 @@ type DiffFile struct { | ||||
| 	Language                  string | ||||
| 	Mode                      string | ||||
| 	OldMode                   string | ||||
|  | ||||
| 	IsSubmodule       bool // if IsSubmodule==true, then there must be a SubmoduleDiffInfo | ||||
| 	SubmoduleDiffInfo *SubmoduleDiffInfo | ||||
| } | ||||
|  | ||||
| // GetType returns type of diff file. | ||||
| @@ -609,9 +611,8 @@ parsingLoop: | ||||
| 				if strings.HasPrefix(line, "new mode ") { | ||||
| 					curFile.Mode = prepareValue(line, "new mode ") | ||||
| 				} | ||||
|  | ||||
| 				if strings.HasSuffix(line, " 160000\n") { | ||||
| 					curFile.IsSubmodule = true | ||||
| 					curFile.IsSubmodule, curFile.SubmoduleDiffInfo = true, &SubmoduleDiffInfo{} | ||||
| 				} | ||||
| 			case strings.HasPrefix(line, "rename from "): | ||||
| 				curFile.IsRenamed = true | ||||
| @@ -646,17 +647,17 @@ parsingLoop: | ||||
| 					curFile.Mode = prepareValue(line, "new file mode ") | ||||
| 				} | ||||
| 				if strings.HasSuffix(line, " 160000\n") { | ||||
| 					curFile.IsSubmodule = true | ||||
| 					curFile.IsSubmodule, curFile.SubmoduleDiffInfo = true, &SubmoduleDiffInfo{} | ||||
| 				} | ||||
| 			case strings.HasPrefix(line, "deleted"): | ||||
| 				curFile.Type = DiffFileDel | ||||
| 				curFile.IsDeleted = true | ||||
| 				if strings.HasSuffix(line, " 160000\n") { | ||||
| 					curFile.IsSubmodule = true | ||||
| 					curFile.IsSubmodule, curFile.SubmoduleDiffInfo = true, &SubmoduleDiffInfo{} | ||||
| 				} | ||||
| 			case strings.HasPrefix(line, "index"): | ||||
| 				if strings.HasSuffix(line, " 160000\n") { | ||||
| 					curFile.IsSubmodule = true | ||||
| 					curFile.IsSubmodule, curFile.SubmoduleDiffInfo = true, &SubmoduleDiffInfo{} | ||||
| 				} | ||||
| 			case strings.HasPrefix(line, "similarity index 100%"): | ||||
| 				curFile.Type = DiffFileRename | ||||
| @@ -915,6 +916,13 @@ func parseHunks(ctx context.Context, curFile *DiffFile, maxLines, maxLineCharact | ||||
| 				} | ||||
| 			} | ||||
| 			curSection.Lines = append(curSection.Lines, diffLine) | ||||
|  | ||||
| 			// Parse submodule additions | ||||
| 			if curFile.SubmoduleDiffInfo != nil { | ||||
| 				if ref, found := bytes.CutPrefix(lineBytes, []byte("+Subproject commit ")); found { | ||||
| 					curFile.SubmoduleDiffInfo.NewRefID = string(bytes.TrimSpace(ref)) | ||||
| 				} | ||||
| 			} | ||||
| 		case '-': | ||||
| 			curFileLinesCount++ | ||||
| 			curFile.Deletion++ | ||||
| @@ -936,6 +944,13 @@ func parseHunks(ctx context.Context, curFile *DiffFile, maxLines, maxLineCharact | ||||
| 				lastLeftIdx = len(curSection.Lines) | ||||
| 			} | ||||
| 			curSection.Lines = append(curSection.Lines, diffLine) | ||||
|  | ||||
| 			// Parse submodule deletion | ||||
| 			if curFile.SubmoduleDiffInfo != nil { | ||||
| 				if ref, found := bytes.CutPrefix(lineBytes, []byte("-Subproject commit ")); found { | ||||
| 					curFile.SubmoduleDiffInfo.PreviousRefID = string(bytes.TrimSpace(ref)) | ||||
| 				} | ||||
| 			} | ||||
| 		case ' ': | ||||
| 			curFileLinesCount++ | ||||
| 			if maxLines > -1 && curFileLinesCount >= maxLines { | ||||
| @@ -1195,6 +1210,11 @@ func GetDiff(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, fi | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// Populate Submodule URLs | ||||
| 		if diffFile.SubmoduleDiffInfo != nil { | ||||
| 			diffFile.SubmoduleDiffInfo.PopulateURL(diffFile, beforeCommit, commit) | ||||
| 		} | ||||
|  | ||||
| 		if !isVendored.Has() { | ||||
| 			isVendored = optional.Some(analyze.IsVendor(diffFile.Name)) | ||||
| 		} | ||||
|   | ||||
							
								
								
									
										65
									
								
								services/gitdiff/submodule.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								services/gitdiff/submodule.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | ||||
| // Copyright 2025 The Gitea Authors. All rights reserved. | ||||
| // SPDX-License-Identifier: MIT | ||||
|  | ||||
| package gitdiff | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"html/template" | ||||
|  | ||||
| 	"code.gitea.io/gitea/modules/base" | ||||
| 	"code.gitea.io/gitea/modules/git" | ||||
| 	"code.gitea.io/gitea/modules/htmlutil" | ||||
| 	"code.gitea.io/gitea/modules/log" | ||||
| ) | ||||
|  | ||||
| type SubmoduleDiffInfo struct { | ||||
| 	SubmoduleName string | ||||
| 	SubmoduleFile *git.CommitSubmoduleFile // it might be nil if the submodule is not found or unable to parse | ||||
| 	NewRefID      string | ||||
| 	PreviousRefID string | ||||
| } | ||||
|  | ||||
| func (si *SubmoduleDiffInfo) PopulateURL(diffFile *DiffFile, leftCommit, rightCommit *git.Commit) { | ||||
| 	si.SubmoduleName = diffFile.Name | ||||
| 	submoduleCommit := rightCommit // If the submodule is added or updated, check at the right commit | ||||
| 	if diffFile.IsDeleted { | ||||
| 		submoduleCommit = leftCommit // If the submodule is deleted, check at the left commit | ||||
| 	} | ||||
| 	if submoduleCommit == nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	submodule, err := submoduleCommit.GetSubModule(diffFile.GetDiffFileName()) | ||||
| 	if err != nil { | ||||
| 		log.Error("Unable to PopulateURL for submodule %q: GetSubModule: %v", diffFile.GetDiffFileName(), err) | ||||
| 		return // ignore the error, do not cause 500 errors for end users | ||||
| 	} | ||||
| 	if submodule != nil { | ||||
| 		si.SubmoduleFile = git.NewCommitSubmoduleFile(submodule.URL, submoduleCommit.ID.String()) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (si *SubmoduleDiffInfo) CommitRefIDLinkHTML(ctx context.Context, commitID string) template.HTML { | ||||
| 	webLink := si.SubmoduleFile.SubmoduleWebLink(ctx, commitID) | ||||
| 	if webLink == nil { | ||||
| 		return htmlutil.HTMLFormat("%s", base.ShortSha(commitID)) | ||||
| 	} | ||||
| 	return htmlutil.HTMLFormat(`<a href="%s">%s</a>`, webLink.CommitWebLink, base.ShortSha(commitID)) | ||||
| } | ||||
|  | ||||
| func (si *SubmoduleDiffInfo) CompareRefIDLinkHTML(ctx context.Context) template.HTML { | ||||
| 	webLink := si.SubmoduleFile.SubmoduleWebLink(ctx, si.PreviousRefID, si.NewRefID) | ||||
| 	if webLink == nil { | ||||
| 		return htmlutil.HTMLFormat("%s...%s", base.ShortSha(si.PreviousRefID), base.ShortSha(si.NewRefID)) | ||||
| 	} | ||||
| 	return htmlutil.HTMLFormat(`<a href="%s">%s...%s</a>`, webLink.CommitWebLink, base.ShortSha(si.PreviousRefID), base.ShortSha(si.NewRefID)) | ||||
| } | ||||
|  | ||||
| func (si *SubmoduleDiffInfo) SubmoduleRepoLinkHTML(ctx context.Context) template.HTML { | ||||
| 	webLink := si.SubmoduleFile.SubmoduleWebLink(ctx) | ||||
| 	if webLink == nil { | ||||
| 		return htmlutil.HTMLFormat("%s", si.SubmoduleName) | ||||
| 	} | ||||
| 	return htmlutil.HTMLFormat(`<a href="%s">%s</a>`, webLink.RepoWebLink, si.SubmoduleName) | ||||
| } | ||||
							
								
								
									
										236
									
								
								services/gitdiff/submodule_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										236
									
								
								services/gitdiff/submodule_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,236 @@ | ||||
| // Copyright 2025 The Gitea Authors. All rights reserved. | ||||
| // SPDX-License-Identifier: MIT | ||||
|  | ||||
| package gitdiff | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
|  | ||||
| 	"code.gitea.io/gitea/models/db" | ||||
| 	"code.gitea.io/gitea/modules/git" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
|  | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
|  | ||||
| func TestParseSubmoduleInfo(t *testing.T) { | ||||
| 	type testcase struct { | ||||
| 		name    string | ||||
| 		gitdiff string | ||||
| 		infos   map[int]SubmoduleDiffInfo | ||||
| 	} | ||||
|  | ||||
| 	tests := []testcase{ | ||||
| 		{ | ||||
| 			name: "added", | ||||
| 			gitdiff: `diff --git a/.gitmodules b/.gitmodules | ||||
| new file mode 100644 | ||||
| index 0000000..4ac13c1 | ||||
| --- /dev/null | ||||
| +++ b/.gitmodules | ||||
| @@ -0,0 +1,3 @@ | ||||
| +[submodule "gitea-mirror"] | ||||
| +	path = gitea-mirror | ||||
| +	url = https://gitea.com/gitea/gitea-mirror | ||||
| diff --git a/gitea-mirror b/gitea-mirror | ||||
| new file mode 160000 | ||||
| index 0000000..68972a9 | ||||
| --- /dev/null | ||||
| +++ b/gitea-mirror | ||||
| @@ -0,0 +1 @@ | ||||
| +Subproject commit 68972a994719ae5c74e28d8fa82fa27c23399bc8 | ||||
| `, | ||||
| 			infos: map[int]SubmoduleDiffInfo{ | ||||
| 				1: {NewRefID: "68972a994719ae5c74e28d8fa82fa27c23399bc8"}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "updated", | ||||
| 			gitdiff: `diff --git a/gitea-mirror b/gitea-mirror | ||||
| index 68972a9..c8ffe77 160000 | ||||
| --- a/gitea-mirror | ||||
| +++ b/gitea-mirror | ||||
| @@ -1 +1 @@ | ||||
| -Subproject commit 68972a994719ae5c74e28d8fa82fa27c23399bc8 | ||||
| +Subproject commit c8ffe777cf9c5bb47a38e3e0b3a3b5de6cd8813d | ||||
| `, | ||||
| 			infos: map[int]SubmoduleDiffInfo{ | ||||
| 				0: { | ||||
| 					PreviousRefID: "68972a994719ae5c74e28d8fa82fa27c23399bc8", | ||||
| 					NewRefID:      "c8ffe777cf9c5bb47a38e3e0b3a3b5de6cd8813d", | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "rename", | ||||
| 			gitdiff: `diff --git a/.gitmodules b/.gitmodules | ||||
| index 4ac13c1..0510edd 100644 | ||||
| --- a/.gitmodules | ||||
| +++ b/.gitmodules | ||||
| @@ -1,3 +1,3 @@ | ||||
|  [submodule "gitea-mirror"] | ||||
| -	path = gitea-mirror | ||||
| +	path = gitea | ||||
|  	url = https://gitea.com/gitea/gitea-mirror | ||||
| diff --git a/gitea-mirror b/gitea | ||||
| similarity index 100% | ||||
| rename from gitea-mirror | ||||
| rename to gitea | ||||
| `, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "deleted", | ||||
| 			gitdiff: `diff --git a/.gitmodules b/.gitmodules | ||||
| index 0510edd..e69de29 100644 | ||||
| --- a/.gitmodules | ||||
| +++ b/.gitmodules | ||||
| @@ -1,3 +0,0 @@ | ||||
| -[submodule "gitea-mirror"] | ||||
| -	path = gitea | ||||
| -	url = https://gitea.com/gitea/gitea-mirror | ||||
| diff --git a/gitea b/gitea | ||||
| deleted file mode 160000 | ||||
| index c8ffe77..0000000 | ||||
| --- a/gitea | ||||
| +++ /dev/null | ||||
| @@ -1 +0,0 @@ | ||||
| -Subproject commit c8ffe777cf9c5bb47a38e3e0b3a3b5de6cd8813d | ||||
| `, | ||||
| 			infos: map[int]SubmoduleDiffInfo{ | ||||
| 				1: { | ||||
| 					PreviousRefID: "c8ffe777cf9c5bb47a38e3e0b3a3b5de6cd8813d", | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "moved and updated", | ||||
| 			gitdiff: `diff --git a/.gitmodules b/.gitmodules | ||||
| index 0510edd..bced3d8 100644 | ||||
| --- a/.gitmodules | ||||
| +++ b/.gitmodules | ||||
| @@ -1,3 +1,3 @@ | ||||
|  [submodule "gitea-mirror"] | ||||
| -	path = gitea | ||||
| +	path = gitea-1.22 | ||||
|  	url = https://gitea.com/gitea/gitea-mirror | ||||
| diff --git a/gitea b/gitea | ||||
| deleted file mode 160000 | ||||
| index c8ffe77..0000000 | ||||
| --- a/gitea | ||||
| +++ /dev/null | ||||
| @@ -1 +0,0 @@ | ||||
| -Subproject commit c8ffe777cf9c5bb47a38e3e0b3a3b5de6cd8813d | ||||
| diff --git a/gitea-1.22 b/gitea-1.22 | ||||
| new file mode 160000 | ||||
| index 0000000..8eefa1f | ||||
| --- /dev/null | ||||
| +++ b/gitea-1.22 | ||||
| @@ -0,0 +1 @@ | ||||
| +Subproject commit 8eefa1f6dedf2488db2c9e12c916e8e51f673160 | ||||
| `, | ||||
| 			infos: map[int]SubmoduleDiffInfo{ | ||||
| 				1: { | ||||
| 					PreviousRefID: "c8ffe777cf9c5bb47a38e3e0b3a3b5de6cd8813d", | ||||
| 				}, | ||||
| 				2: { | ||||
| 					NewRefID: "8eefa1f6dedf2488db2c9e12c916e8e51f673160", | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "converted to file", | ||||
| 			gitdiff: `diff --git a/.gitmodules b/.gitmodules | ||||
| index 0510edd..e69de29 100644 | ||||
| --- a/.gitmodules | ||||
| +++ b/.gitmodules | ||||
| @@ -1,3 +0,0 @@ | ||||
| -[submodule "gitea-mirror"] | ||||
| -	path = gitea | ||||
| -	url = https://gitea.com/gitea/gitea-mirror | ||||
| diff --git a/gitea b/gitea | ||||
| deleted file mode 160000 | ||||
| index c8ffe77..0000000 | ||||
| --- a/gitea | ||||
| +++ /dev/null | ||||
| @@ -1 +0,0 @@ | ||||
| -Subproject commit c8ffe777cf9c5bb47a38e3e0b3a3b5de6cd8813d | ||||
| diff --git a/gitea b/gitea | ||||
| new file mode 100644 | ||||
| index 0000000..33a9488 | ||||
| --- /dev/null | ||||
| +++ b/gitea | ||||
| @@ -0,0 +1 @@ | ||||
| +example | ||||
| `, | ||||
| 			infos: map[int]SubmoduleDiffInfo{ | ||||
| 				1: { | ||||
| 					PreviousRefID: "c8ffe777cf9c5bb47a38e3e0b3a3b5de6cd8813d", | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "converted to submodule", | ||||
| 			gitdiff: `diff --git a/.gitmodules b/.gitmodules | ||||
| index e69de29..14ee267 100644 | ||||
| --- a/.gitmodules | ||||
| +++ b/.gitmodules | ||||
| @@ -0,0 +1,3 @@ | ||||
| +[submodule "gitea"] | ||||
| +	path = gitea | ||||
| +	url = https://gitea.com/gitea/gitea-mirror | ||||
| diff --git a/gitea b/gitea | ||||
| deleted file mode 100644 | ||||
| index 33a9488..0000000 | ||||
| --- a/gitea | ||||
| +++ /dev/null | ||||
| @@ -1 +0,0 @@ | ||||
| -example | ||||
| diff --git a/gitea b/gitea | ||||
| new file mode 160000 | ||||
| index 0000000..68972a9 | ||||
| --- /dev/null | ||||
| +++ b/gitea | ||||
| @@ -0,0 +1 @@ | ||||
| +Subproject commit 68972a994719ae5c74e28d8fa82fa27c23399bc8 | ||||
| `, | ||||
| 			infos: map[int]SubmoduleDiffInfo{ | ||||
| 				2: { | ||||
| 					NewRefID: "68972a994719ae5c74e28d8fa82fa27c23399bc8", | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, testcase := range tests { | ||||
| 		testcase := testcase | ||||
| 		t.Run(testcase.name, func(t *testing.T) { | ||||
| 			diff, err := ParsePatch(db.DefaultContext, setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, strings.NewReader(testcase.gitdiff), "") | ||||
| 			assert.NoError(t, err) | ||||
|  | ||||
| 			for i, expected := range testcase.infos { | ||||
| 				actual := diff.Files[i] | ||||
| 				assert.NotNil(t, actual) | ||||
| 				assert.Equal(t, expected, *actual.SubmoduleDiffInfo) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestSubmoduleInfo(t *testing.T) { | ||||
| 	sdi := &SubmoduleDiffInfo{ | ||||
| 		SubmoduleName: "name", | ||||
| 		PreviousRefID: "aaaa", | ||||
| 		NewRefID:      "bbbb", | ||||
| 	} | ||||
| 	ctx := context.Background() | ||||
| 	assert.EqualValues(t, "1111", sdi.CommitRefIDLinkHTML(ctx, "1111")) | ||||
| 	assert.EqualValues(t, "aaaa...bbbb", sdi.CompareRefIDLinkHTML(ctx)) | ||||
| 	assert.EqualValues(t, "name", sdi.SubmoduleRepoLinkHTML(ctx)) | ||||
|  | ||||
| 	sdi.SubmoduleFile = git.NewCommitSubmoduleFile("https://github.com/owner/repo", "1234") | ||||
| 	assert.EqualValues(t, `<a href="https://github.com/owner/repo/commit/1111">1111</a>`, sdi.CommitRefIDLinkHTML(ctx, "1111")) | ||||
| 	assert.EqualValues(t, `<a href="https://github.com/owner/repo/compare/aaaa...bbbb">aaaa...bbbb</a>`, sdi.CompareRefIDLinkHTML(ctx)) | ||||
| 	assert.EqualValues(t, `<a href="https://github.com/owner/repo">name</a>`, sdi.SubmoduleRepoLinkHTML(ctx)) | ||||
| } | ||||
| @@ -58,7 +58,7 @@ | ||||
| 			</div> | ||||
| 		{{end}} | ||||
| 		<script id="diff-data-script" type="module"> | ||||
| 			const diffDataFiles = [{{range $i, $file := .Diff.Files}}{Name:"{{$file.Name}}",NameHash:"{{$file.NameHash}}",Type:{{$file.Type}},IsBin:{{$file.IsBin}},Addition:{{$file.Addition}},Deletion:{{$file.Deletion}},IsViewed:{{$file.IsViewed}}},{{end}}]; | ||||
| 			const diffDataFiles = [{{range $i, $file := .Diff.Files}}{Name:"{{$file.Name}}",NameHash:"{{$file.NameHash}}",Type:{{$file.Type}},IsBin:{{$file.IsBin}},IsSubmodule:{{$file.IsSubmodule}},Addition:{{$file.Addition}},Deletion:{{$file.Deletion}},IsViewed:{{$file.IsViewed}}},{{end}}]; | ||||
| 			const diffData = { | ||||
| 				isIncomplete: {{.Diff.IsIncomplete}}, | ||||
| 				tooManyFilesMessage: "{{ctx.Locale.Tr "repo.diff.too_many_files"}}", | ||||
| @@ -161,13 +161,14 @@ | ||||
| 										<input type="checkbox" name="{{$file.GetDiffFileName}}" autocomplete="off"{{if $file.IsViewed}} checked{{end}}> {{ctx.Locale.Tr "repo.pulls.has_viewed_file"}} | ||||
| 									</label> | ||||
| 								{{end}} | ||||
| 								{{if not $file.IsSubmodule}} | ||||
| 									<button class="btn diff-header-popup-btn tw-p-1">{{svg "octicon-kebab-horizontal" 18}}</button> | ||||
| 									<div class="tippy-target"> | ||||
| 									{{if not (or $file.IsIncomplete $file.IsBin $file.IsSubmodule)}} | ||||
| 										{{if not (or $file.IsIncomplete $file.IsBin)}} | ||||
| 											<button class="unescape-button item" data-unicode-content-selector="#diff-{{$file.NameHash}}">{{ctx.Locale.Tr "repo.unescape_control_characters"}}</button> | ||||
| 											<button class="escape-button tw-hidden item" data-unicode-content-selector="#diff-{{$file.NameHash}}">{{ctx.Locale.Tr "repo.escape_control_characters"}}</button> | ||||
| 										{{end}} | ||||
| 									{{if and (not $file.IsSubmodule) (not $.PageIsWiki)}} | ||||
| 										{{if not $.PageIsWiki}} | ||||
| 											{{if $file.IsDeleted}} | ||||
| 												<a class="item" rel="nofollow" href="{{$.BeforeSourcePath}}/{{PathEscapeSegments .Name}}">{{ctx.Locale.Tr "repo.diff.view_file"}}</a> | ||||
| 											{{else}} | ||||
| @@ -178,6 +179,7 @@ | ||||
| 											{{end}} | ||||
| 										{{end}} | ||||
| 									</div> | ||||
| 								{{end}} | ||||
| 							</div> | ||||
| 						</h4> | ||||
| 						<div class="diff-file-body ui attached unstackable table segment" {{if and $file.IsViewed $.IsShowingAllCommits}}data-folded="true"{{end}}> | ||||
| @@ -195,6 +197,17 @@ | ||||
| 											{{ctx.Locale.Tr "repo.diff.bin_not_shown"}} | ||||
| 										{{end}} | ||||
| 									</div> | ||||
| 								{{else if $file.SubmoduleDiffInfo}} | ||||
| 									<div class="tw-p-3">{{svg "octicon-file-submodule"}} {{$submoduleDiffInfo := $file.SubmoduleDiffInfo -}} | ||||
| 										{{- $submoduleName := $submoduleDiffInfo.SubmoduleRepoLinkHTML ctx -}} | ||||
| 										{{- if $file.IsDeleted -}} | ||||
| 											{{- ctx.Locale.Tr "repo.diff.submodule_deleted" $submoduleName ($submoduleDiffInfo.CommitRefIDLinkHTML ctx $submoduleDiffInfo.PreviousRefID) -}} | ||||
| 										{{- else if $file.IsCreated -}} | ||||
| 											{{- ctx.Locale.Tr "repo.diff.submodule_added" $submoduleName ($submoduleDiffInfo.CommitRefIDLinkHTML ctx $submoduleDiffInfo.NewRefID) -}} | ||||
| 										{{- else -}} | ||||
| 											{{- ctx.Locale.Tr "repo.diff.submodule_updated" $submoduleName ($submoduleDiffInfo.CompareRefIDLinkHTML ctx) -}} | ||||
| 										{{end}} | ||||
| 									</div> | ||||
| 								{{else}} | ||||
| 									<table class="chroma" data-new-comment-url="{{$.Issue.Link}}/files/reviews/new_comment" data-path="{{$file.Name}}"> | ||||
| 										{{if $.IsSplitStyle}} | ||||
|   | ||||
| @@ -13,15 +13,15 @@ | ||||
| 		<div class="repo-file-item"> | ||||
| 			{{$entry := $item.Entry}} | ||||
| 			{{$commit := $item.Commit}} | ||||
| 			{{$subModuleFile := $item.SubModuleFile}} | ||||
| 			{{$submoduleFile := $item.SubmoduleFile}} | ||||
| 			<div class="repo-file-cell name {{if not $commit}}notready{{end}}"> | ||||
| 				{{if $entry.IsSubModule}} | ||||
| 					{{svg "octicon-file-submodule"}} | ||||
| 					{{$refURL := $subModuleFile.RefURL AppUrl $.Repository.FullName $.SSHDomain}} {{/* FIXME: the usage of AppUrl seems incorrect, it would be fixed in the future, use AppSubUrl instead */}} | ||||
| 					{{if $refURL}} | ||||
| 						<a class="muted" href="{{$refURL}}">{{$entry.Name}}</a> <span class="at">@</span> <a href="{{$refURL}}/commit/{{PathEscape $subModuleFile.RefID}}">{{ShortSha $subModuleFile.RefID}}</a> | ||||
| 					{{$submoduleLink := $submoduleFile.SubmoduleWebLink ctx}} | ||||
| 					{{if $submoduleLink}} | ||||
| 						<a class="muted" href="{{$submoduleLink.RepoWebLink}}">{{$entry.Name}}</a> <span class="at">@</span> <a href="{{$submoduleLink.CommitWebLink}}">{{ShortSha $submoduleFile.RefID}}</a> | ||||
| 					{{else}} | ||||
| 						{{$entry.Name}} <span class="at">@</span> {{ShortSha $subModuleFile.RefID}} | ||||
| 						{{$entry.Name}} <span class="at">@</span> {{ShortSha $submoduleFile.RefID}} | ||||
| 					{{end}} | ||||
| 				{{else}} | ||||
| 					{{if $entry.IsDir}} | ||||
|   | ||||
| @@ -8,6 +8,7 @@ type File = { | ||||
|   NameHash: string; | ||||
|   Type: number; | ||||
|   IsViewed: boolean; | ||||
|   IsSubmodule: boolean; | ||||
| } | ||||
|  | ||||
| type Item = { | ||||
| @@ -34,6 +35,13 @@ function getIconForDiffType(pType) { | ||||
|   }; | ||||
|   return diffTypes[pType]; | ||||
| } | ||||
|  | ||||
| function fileIcon(file) { | ||||
|   if (file.IsSubmodule) { | ||||
|     return 'octicon-file-submodule'; | ||||
|   } | ||||
|   return 'octicon-file'; | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <template> | ||||
| @@ -44,7 +52,7 @@ function getIconForDiffType(pType) { | ||||
|     :title="item.name" :href="'#diff-' + item.file.NameHash" | ||||
|   > | ||||
|     <!-- file --> | ||||
|     <SvgIcon name="octicon-file"/> | ||||
|     <SvgIcon :name="fileIcon(item.file)"/> | ||||
|     <span class="gt-ellipsis tw-flex-1">{{ item.name }}</span> | ||||
|     <SvgIcon :name="getIconForDiffType(item.file.Type).name" :class="getIconForDiffType(item.file.Type).classes"/> | ||||
|   </a> | ||||
|   | ||||
| @@ -28,6 +28,7 @@ import octiconEye from '../../public/assets/img/svg/octicon-eye.svg'; | ||||
| import octiconFile from '../../public/assets/img/svg/octicon-file.svg'; | ||||
| import octiconFileDirectoryFill from '../../public/assets/img/svg/octicon-file-directory-fill.svg'; | ||||
| import octiconFileDirectoryOpenFill from '../../public/assets/img/svg/octicon-file-directory-open-fill.svg'; | ||||
| import octiconFileSubmodule from '../../public/assets/img/svg/octicon-file-submodule.svg'; | ||||
| import octiconFilter from '../../public/assets/img/svg/octicon-filter.svg'; | ||||
| import octiconGear from '../../public/assets/img/svg/octicon-gear.svg'; | ||||
| import octiconGitBranch from '../../public/assets/img/svg/octicon-git-branch.svg'; | ||||
| @@ -104,6 +105,7 @@ const svgs = { | ||||
|   'octicon-file': octiconFile, | ||||
|   'octicon-file-directory-fill': octiconFileDirectoryFill, | ||||
|   'octicon-file-directory-open-fill': octiconFileDirectoryOpenFill, | ||||
|   'octicon-file-submodule': octiconFileSubmodule, | ||||
|   'octicon-filter': octiconFilter, | ||||
|   'octicon-gear': octiconGear, | ||||
|   'octicon-git-branch': octiconGitBranch, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user