mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-04 07:04:27 +00:00 
			
		
		
		
	Fix missing Close when error occurs and abused connection pool (#35658)
Fix #35649 * Use upstream `git-lfs-transfer` * The Close should be called when error occurs (bug fix) * The connection pool should be shared (bug fix) * Add more tests to cover "LFS over SSH download"
This commit is contained in:
		
							
								
								
									
										9
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										9
									
								
								go.mod
									
									
									
									
									
								
							@@ -35,7 +35,7 @@ require (
 | 
				
			|||||||
	github.com/bohde/codel v0.2.0
 | 
						github.com/bohde/codel v0.2.0
 | 
				
			||||||
	github.com/buildkite/terminal-to-html/v3 v3.16.8
 | 
						github.com/buildkite/terminal-to-html/v3 v3.16.8
 | 
				
			||||||
	github.com/caddyserver/certmagic v0.24.0
 | 
						github.com/caddyserver/certmagic v0.24.0
 | 
				
			||||||
	github.com/charmbracelet/git-lfs-transfer v0.2.0
 | 
						github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20251013092601-6327009efd21
 | 
				
			||||||
	github.com/chi-middleware/proxy v1.1.1
 | 
						github.com/chi-middleware/proxy v1.1.1
 | 
				
			||||||
	github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21
 | 
						github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21
 | 
				
			||||||
	github.com/djherbis/buffer v1.2.0
 | 
						github.com/djherbis/buffer v1.2.0
 | 
				
			||||||
@@ -56,7 +56,7 @@ require (
 | 
				
			|||||||
	github.com/go-co-op/gocron v1.37.0
 | 
						github.com/go-co-op/gocron v1.37.0
 | 
				
			||||||
	github.com/go-enry/go-enry/v2 v2.9.2
 | 
						github.com/go-enry/go-enry/v2 v2.9.2
 | 
				
			||||||
	github.com/go-git/go-billy/v5 v5.6.2
 | 
						github.com/go-git/go-billy/v5 v5.6.2
 | 
				
			||||||
	github.com/go-git/go-git/v5 v5.16.2
 | 
						github.com/go-git/go-git/v5 v5.16.3
 | 
				
			||||||
	github.com/go-ldap/ldap/v3 v3.4.11
 | 
						github.com/go-ldap/ldap/v3 v3.4.11
 | 
				
			||||||
	github.com/go-redsync/redsync/v4 v4.13.0
 | 
						github.com/go-redsync/redsync/v4 v4.13.0
 | 
				
			||||||
	github.com/go-sql-driver/mysql v1.9.3
 | 
						github.com/go-sql-driver/mysql v1.9.3
 | 
				
			||||||
@@ -121,7 +121,7 @@ require (
 | 
				
			|||||||
	golang.org/x/net v0.44.0
 | 
						golang.org/x/net v0.44.0
 | 
				
			||||||
	golang.org/x/oauth2 v0.30.0
 | 
						golang.org/x/oauth2 v0.30.0
 | 
				
			||||||
	golang.org/x/sync v0.17.0
 | 
						golang.org/x/sync v0.17.0
 | 
				
			||||||
	golang.org/x/sys v0.36.0
 | 
						golang.org/x/sys v0.37.0
 | 
				
			||||||
	golang.org/x/text v0.30.0
 | 
						golang.org/x/text v0.30.0
 | 
				
			||||||
	google.golang.org/grpc v1.75.0
 | 
						google.golang.org/grpc v1.75.0
 | 
				
			||||||
	google.golang.org/protobuf v1.36.8
 | 
						google.golang.org/protobuf v1.36.8
 | 
				
			||||||
@@ -298,9 +298,6 @@ replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
replace github.com/nektos/act => gitea.com/gitea/act v0.261.7-0.20251003180512-ac6e4b751763
 | 
					replace github.com/nektos/act => gitea.com/gitea/act v0.261.7-0.20251003180512-ac6e4b751763
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO: the only difference is in `PutObject`: the fork doesn't use `NewVerifyingReader(r, sha256.New(), oid, expectedSize)`, need to figure out why
 | 
					 | 
				
			||||||
replace github.com/charmbracelet/git-lfs-transfer => gitea.com/gitea/git-lfs-transfer v0.2.0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
replace git.sr.ht/~mariusor/go-xsd-duration => gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078
 | 
					replace git.sr.ht/~mariusor/go-xsd-duration => gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078
 | 
				
			||||||
 | 
					
 | 
				
			||||||
exclude github.com/gofrs/uuid v3.2.0+incompatible
 | 
					exclude github.com/gofrs/uuid v3.2.0+incompatible
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										12
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								go.sum
									
									
									
									
									
								
							@@ -33,8 +33,6 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
 | 
				
			|||||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
 | 
					filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
 | 
				
			||||||
gitea.com/gitea/act v0.261.7-0.20251003180512-ac6e4b751763 h1:ohdxegvslDEllZmRNDqpKun6L4Oq81jNdEDtGgHEV2c=
 | 
					gitea.com/gitea/act v0.261.7-0.20251003180512-ac6e4b751763 h1:ohdxegvslDEllZmRNDqpKun6L4Oq81jNdEDtGgHEV2c=
 | 
				
			||||||
gitea.com/gitea/act v0.261.7-0.20251003180512-ac6e4b751763/go.mod h1:Pg5C9kQY1CEA3QjthjhlrqOC/QOT5NyWNjOjRHw23Ok=
 | 
					gitea.com/gitea/act v0.261.7-0.20251003180512-ac6e4b751763/go.mod h1:Pg5C9kQY1CEA3QjthjhlrqOC/QOT5NyWNjOjRHw23Ok=
 | 
				
			||||||
gitea.com/gitea/git-lfs-transfer v0.2.0 h1:baHaNoBSRaeq/xKayEXwiDQtlIjps4Ac/Ll4KqLMB40=
 | 
					 | 
				
			||||||
gitea.com/gitea/git-lfs-transfer v0.2.0/go.mod h1:UrXUCm3xLQkq15fu7qlXHUMlrhdlXHoi13KH2Dfiits=
 | 
					 | 
				
			||||||
gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:BAFmdZpRW7zMQZQDClaCWobRj9uL1MR3MzpCVJvc5s4=
 | 
					gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:BAFmdZpRW7zMQZQDClaCWobRj9uL1MR3MzpCVJvc5s4=
 | 
				
			||||||
gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs=
 | 
					gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs=
 | 
				
			||||||
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed h1:EZZBtilMLSZNWtHHcgq2mt6NSGhJSZBuduAlinMEmso=
 | 
					gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed h1:EZZBtilMLSZNWtHHcgq2mt6NSGhJSZBuduAlinMEmso=
 | 
				
			||||||
@@ -219,6 +217,8 @@ github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a h1:MISbI8sU/PSK/
 | 
				
			|||||||
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a/go.mod h1:2GxOXOlEPAMFPfp014mK1SWq8G8BN8o7/dfYqJrVGn8=
 | 
					github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a/go.mod h1:2GxOXOlEPAMFPfp014mK1SWq8G8BN8o7/dfYqJrVGn8=
 | 
				
			||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
 | 
					github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
 | 
				
			||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 | 
					github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 | 
				
			||||||
 | 
					github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20251013092601-6327009efd21 h1:2d64+4Jek9vjYwhY93AjbleiVH+AeWvPwPmDi1mfKFQ=
 | 
				
			||||||
 | 
					github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20251013092601-6327009efd21/go.mod h1:fNlYtCHWTRC8MofQERZkVUNUWaOvZeTBqHn/amSbKZI=
 | 
				
			||||||
github.com/chi-middleware/proxy v1.1.1 h1:4HaXUp8o2+bhHr1OhVy+VjN0+L7/07JDcn6v7YrTjrQ=
 | 
					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/chi-middleware/proxy v1.1.1/go.mod h1:jQwMEJct2tz9VmtCELxvnXoMfa+SOdikvbVJVHv/M+0=
 | 
				
			||||||
github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
 | 
					github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
 | 
				
			||||||
@@ -339,8 +339,8 @@ github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UN
 | 
				
			|||||||
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
 | 
					github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
 | 
				
			||||||
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 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-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
 | 
				
			||||||
github.com/go-git/go-git/v5 v5.16.2 h1:fT6ZIOjE5iEnkzKyxTHK1W4HGAsPhqEqiSAssSO77hM=
 | 
					github.com/go-git/go-git/v5 v5.16.3 h1:Z8BtvxZ09bYm/yYNgPKCzgWtaRqDTgIKRgIRHBfU6Z8=
 | 
				
			||||||
github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
 | 
					github.com/go-git/go-git/v5 v5.16.3/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
 | 
				
			||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 | 
					github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 | 
				
			||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 | 
					github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 | 
				
			||||||
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
 | 
					github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
 | 
				
			||||||
@@ -975,8 +975,8 @@ golang.org/x/sys v0.17.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.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 | 
				
			||||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 | 
					golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 | 
				
			||||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 | 
					golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 | 
				
			||||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
 | 
					golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
 | 
				
			||||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
 | 
					golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
 | 
				
			||||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
 | 
					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-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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,54 +7,53 @@ package httplib
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"bytes"
 | 
						"bytes"
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"crypto/tls"
 | 
					 | 
				
			||||||
	"errors"
 | 
					 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"io"
 | 
						"io"
 | 
				
			||||||
	"net"
 | 
						"net"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"net/url"
 | 
						"net/url"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
						"sync"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var defaultSetting = Settings{"GiteaServer", 60 * time.Second, 60 * time.Second, nil, nil}
 | 
					var defaultTransport = sync.OnceValue(func() http.RoundTripper {
 | 
				
			||||||
 | 
						return &http.Transport{
 | 
				
			||||||
// newRequest returns *Request with specific method
 | 
							Proxy:       http.ProxyFromEnvironment,
 | 
				
			||||||
func newRequest(url, method string) *Request {
 | 
							DialContext: DialContextWithTimeout(10 * time.Second), // it is good enough in modern days
 | 
				
			||||||
	var resp http.Response
 | 
						}
 | 
				
			||||||
	req := http.Request{
 | 
					})
 | 
				
			||||||
		Method:     method,
 | 
					
 | 
				
			||||||
		Header:     make(http.Header),
 | 
					func DialContextWithTimeout(timeout time.Duration) func(ctx context.Context, network, address string) (net.Conn, error) {
 | 
				
			||||||
		Proto:      "HTTP/1.1",
 | 
						return func(ctx context.Context, network, address string) (net.Conn, error) {
 | 
				
			||||||
		ProtoMajor: 1,
 | 
							return (&net.Dialer{Timeout: timeout}).DialContext(ctx, network, address)
 | 
				
			||||||
		ProtoMinor: 1,
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return &Request{url, &req, map[string]string{}, defaultSetting, &resp, nil}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewRequest returns *Request with specific method
 | 
					 | 
				
			||||||
func NewRequest(url, method string) *Request {
 | 
					func NewRequest(url, method string) *Request {
 | 
				
			||||||
	return newRequest(url, method)
 | 
						return &Request{
 | 
				
			||||||
 | 
							url: url,
 | 
				
			||||||
 | 
							req: &http.Request{
 | 
				
			||||||
 | 
								Method:     method,
 | 
				
			||||||
 | 
								Header:     make(http.Header),
 | 
				
			||||||
 | 
								Proto:      "HTTP/1.1", // FIXME: from legacy httplib, it shouldn't be hardcoded
 | 
				
			||||||
 | 
								ProtoMajor: 1,
 | 
				
			||||||
 | 
								ProtoMinor: 1,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							params: map[string]string{},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// ATTENTION: from legacy httplib, callers must pay more attention to it, it will cause annoying bugs when the response takes a long time
 | 
				
			||||||
 | 
							readWriteTimeout: 60 * time.Second,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Settings is the default settings for http client
 | 
					 | 
				
			||||||
type Settings struct {
 | 
					 | 
				
			||||||
	UserAgent        string
 | 
					 | 
				
			||||||
	ConnectTimeout   time.Duration
 | 
					 | 
				
			||||||
	ReadWriteTimeout time.Duration
 | 
					 | 
				
			||||||
	TLSClientConfig  *tls.Config
 | 
					 | 
				
			||||||
	Transport        http.RoundTripper
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Request provides more useful methods for requesting one url than http.Request.
 | 
					 | 
				
			||||||
type Request struct {
 | 
					type Request struct {
 | 
				
			||||||
	url     string
 | 
						url    string
 | 
				
			||||||
	req     *http.Request
 | 
						req    *http.Request
 | 
				
			||||||
	params  map[string]string
 | 
						params map[string]string
 | 
				
			||||||
	setting Settings
 | 
					
 | 
				
			||||||
	resp    *http.Response
 | 
						readWriteTimeout time.Duration
 | 
				
			||||||
	body    []byte
 | 
						transport        http.RoundTripper
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// SetContext sets the request's Context
 | 
					// SetContext sets the request's Context
 | 
				
			||||||
@@ -63,36 +62,24 @@ func (r *Request) SetContext(ctx context.Context) *Request {
 | 
				
			|||||||
	return r
 | 
						return r
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// SetTimeout sets connect time out and read-write time out for BeegoRequest.
 | 
					// SetTransport sets the request transport, if not set, will use httplib's default transport with environment proxy support
 | 
				
			||||||
func (r *Request) SetTimeout(connectTimeout, readWriteTimeout time.Duration) *Request {
 | 
					// ATTENTION: the http.Transport has a connection pool, so it should be reused as much as possible, do not create a lot of transports
 | 
				
			||||||
	r.setting.ConnectTimeout = connectTimeout
 | 
					func (r *Request) SetTransport(transport http.RoundTripper) *Request {
 | 
				
			||||||
	r.setting.ReadWriteTimeout = readWriteTimeout
 | 
						r.transport = transport
 | 
				
			||||||
	return r
 | 
						return r
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (r *Request) SetReadWriteTimeout(readWriteTimeout time.Duration) *Request {
 | 
					func (r *Request) SetReadWriteTimeout(readWriteTimeout time.Duration) *Request {
 | 
				
			||||||
	r.setting.ReadWriteTimeout = readWriteTimeout
 | 
						r.readWriteTimeout = readWriteTimeout
 | 
				
			||||||
	return r
 | 
						return r
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// SetTLSClientConfig sets tls connection configurations if visiting https url.
 | 
					// Header set header item string in request.
 | 
				
			||||||
func (r *Request) SetTLSClientConfig(config *tls.Config) *Request {
 | 
					 | 
				
			||||||
	r.setting.TLSClientConfig = config
 | 
					 | 
				
			||||||
	return r
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Header add header item string in request.
 | 
					 | 
				
			||||||
func (r *Request) Header(key, value string) *Request {
 | 
					func (r *Request) Header(key, value string) *Request {
 | 
				
			||||||
	r.req.Header.Set(key, value)
 | 
						r.req.Header.Set(key, value)
 | 
				
			||||||
	return r
 | 
						return r
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// SetTransport sets transport to
 | 
					 | 
				
			||||||
func (r *Request) SetTransport(transport http.RoundTripper) *Request {
 | 
					 | 
				
			||||||
	r.setting.Transport = transport
 | 
					 | 
				
			||||||
	return r
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Param adds query param in to request.
 | 
					// Param adds query param in to request.
 | 
				
			||||||
// params build query string as ?key1=value1&key2=value2...
 | 
					// params build query string as ?key1=value1&key2=value2...
 | 
				
			||||||
func (r *Request) Param(key, value string) *Request {
 | 
					func (r *Request) Param(key, value string) *Request {
 | 
				
			||||||
@@ -125,11 +112,9 @@ func (r *Request) Body(data any) *Request {
 | 
				
			|||||||
	return r
 | 
						return r
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (r *Request) getResponse() (*http.Response, error) {
 | 
					// Response executes request client and returns the response.
 | 
				
			||||||
	if r.resp.StatusCode != 0 {
 | 
					// Caller MUST close the response body if no error occurs.
 | 
				
			||||||
		return r.resp, nil
 | 
					func (r *Request) Response() (*http.Response, error) {
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var paramBody string
 | 
						var paramBody string
 | 
				
			||||||
	if len(r.params) > 0 {
 | 
						if len(r.params) > 0 {
 | 
				
			||||||
		var buf bytes.Buffer
 | 
							var buf bytes.Buffer
 | 
				
			||||||
@@ -160,59 +145,19 @@ func (r *Request) getResponse() (*http.Response, error) {
 | 
				
			|||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	trans := r.setting.Transport
 | 
					 | 
				
			||||||
	if trans == nil {
 | 
					 | 
				
			||||||
		// create default transport
 | 
					 | 
				
			||||||
		trans = &http.Transport{
 | 
					 | 
				
			||||||
			TLSClientConfig: r.setting.TLSClientConfig,
 | 
					 | 
				
			||||||
			Proxy:           http.ProxyFromEnvironment,
 | 
					 | 
				
			||||||
			DialContext:     TimeoutDialer(r.setting.ConnectTimeout),
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	} else if t, ok := trans.(*http.Transport); ok {
 | 
					 | 
				
			||||||
		if t.TLSClientConfig == nil {
 | 
					 | 
				
			||||||
			t.TLSClientConfig = r.setting.TLSClientConfig
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if t.DialContext == nil {
 | 
					 | 
				
			||||||
			t.DialContext = TimeoutDialer(r.setting.ConnectTimeout)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	client := &http.Client{
 | 
						client := &http.Client{
 | 
				
			||||||
		Transport: trans,
 | 
							Transport: r.transport,
 | 
				
			||||||
		Timeout:   r.setting.ReadWriteTimeout,
 | 
							Timeout:   r.readWriteTimeout,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if client.Transport == nil {
 | 
				
			||||||
 | 
							client.Transport = defaultTransport()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if len(r.setting.UserAgent) > 0 && len(r.req.Header.Get("User-Agent")) == 0 {
 | 
						if r.req.Header.Get("User-Agent") == "" {
 | 
				
			||||||
		r.req.Header.Set("User-Agent", r.setting.UserAgent)
 | 
							r.req.Header.Set("User-Agent", "GiteaHttpLib")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	resp, err := client.Do(r.req)
 | 
						return client.Do(r.req)
 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	r.resp = resp
 | 
					 | 
				
			||||||
	return resp, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Response executes request client gets response manually.
 | 
					 | 
				
			||||||
// Caller MUST close the response body if no error occurs
 | 
					 | 
				
			||||||
func (r *Request) Response() (*http.Response, error) {
 | 
					 | 
				
			||||||
	if r == nil {
 | 
					 | 
				
			||||||
		return nil, errors.New("invalid request")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return r.getResponse()
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// TimeoutDialer returns functions of connection dialer with timeout settings for http.Transport Dial field.
 | 
					 | 
				
			||||||
func TimeoutDialer(cTimeout time.Duration) func(ctx context.Context, net, addr string) (c net.Conn, err error) {
 | 
					 | 
				
			||||||
	return func(ctx context.Context, netw, addr string) (net.Conn, error) {
 | 
					 | 
				
			||||||
		d := net.Dialer{Timeout: cTimeout}
 | 
					 | 
				
			||||||
		conn, err := d.DialContext(ctx, netw, addr)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return nil, err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return conn, nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (r *Request) GoString() string {
 | 
					func (r *Request) GoString() string {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -157,7 +157,7 @@ func (g *GiteaBackend) Batch(_ string, pointers []transfer.BatchItem, args trans
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Download implements transfer.Backend. The returned reader must be closed by the caller.
 | 
					// Download implements transfer.Backend. The returned reader must be closed by the caller.
 | 
				
			||||||
func (g *GiteaBackend) Download(oid string, args transfer.Args) (io.ReadCloser, int64, error) {
 | 
					func (g *GiteaBackend) Download(oid string, args transfer.Args) (_ io.ReadCloser, _ int64, retErr error) {
 | 
				
			||||||
	idMapStr, exists := args[argID]
 | 
						idMapStr, exists := args[argID]
 | 
				
			||||||
	if !exists {
 | 
						if !exists {
 | 
				
			||||||
		return nil, 0, ErrMissingID
 | 
							return nil, 0, ErrMissingID
 | 
				
			||||||
@@ -188,7 +188,15 @@ func (g *GiteaBackend) Download(oid string, args transfer.Args) (io.ReadCloser,
 | 
				
			|||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, 0, fmt.Errorf("failed to get response: %w", err)
 | 
							return nil, 0, fmt.Errorf("failed to get response: %w", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	// no need to close the body here by "defer resp.Body.Close()", see below
 | 
						// We must return the ReaderCloser but not "ReadAll", to avoid OOM.
 | 
				
			||||||
 | 
						// "transfer.Backend" will check io.Closer interface and close the Body reader.
 | 
				
			||||||
 | 
						// So only close the Body when error occurs
 | 
				
			||||||
 | 
						defer func() {
 | 
				
			||||||
 | 
							if retErr != nil {
 | 
				
			||||||
 | 
								_ = resp.Body.Close()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if resp.StatusCode != http.StatusOK {
 | 
						if resp.StatusCode != http.StatusOK {
 | 
				
			||||||
		return nil, 0, statusCodeToErr(resp.StatusCode)
 | 
							return nil, 0, statusCodeToErr(resp.StatusCode)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -197,7 +205,6 @@ func (g *GiteaBackend) Download(oid string, args transfer.Args) (io.ReadCloser,
 | 
				
			|||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, 0, fmt.Errorf("failed to parse content length: %w", err)
 | 
							return nil, 0, fmt.Errorf("failed to parse content length: %w", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	// transfer.Backend will check io.Closer interface and close this Body reader
 | 
					 | 
				
			||||||
	return resp.Body, respSize, nil
 | 
						return resp.Body, respSize, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,7 @@ import (
 | 
				
			|||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
						"sync"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/modules/httplib"
 | 
						"code.gitea.io/gitea/modules/httplib"
 | 
				
			||||||
@@ -33,6 +34,35 @@ func getClientIP() string {
 | 
				
			|||||||
	return strings.Fields(sshConnEnv)[0]
 | 
						return strings.Fields(sshConnEnv)[0]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func dialContextInternalAPI(ctx context.Context, network, address string) (conn net.Conn, err error) {
 | 
				
			||||||
 | 
						d := net.Dialer{Timeout: 10 * time.Second}
 | 
				
			||||||
 | 
						if setting.Protocol == setting.HTTPUnix {
 | 
				
			||||||
 | 
							conn, err = d.DialContext(ctx, "unix", setting.HTTPAddr)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							conn, err = d.DialContext(ctx, network, address)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if setting.LocalUseProxyProtocol {
 | 
				
			||||||
 | 
							if err = proxyprotocol.WriteLocalHeader(conn); err != nil {
 | 
				
			||||||
 | 
								_ = conn.Close()
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return conn, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var internalAPITransport = sync.OnceValue(func() http.RoundTripper {
 | 
				
			||||||
 | 
						return &http.Transport{
 | 
				
			||||||
 | 
							DialContext: dialContextInternalAPI,
 | 
				
			||||||
 | 
							TLSClientConfig: &tls.Config{
 | 
				
			||||||
 | 
								InsecureSkipVerify: true,
 | 
				
			||||||
 | 
								ServerName:         setting.Domain,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func NewInternalRequest(ctx context.Context, url, method string) *httplib.Request {
 | 
					func NewInternalRequest(ctx context.Context, url, method string) *httplib.Request {
 | 
				
			||||||
	if setting.InternalToken == "" {
 | 
						if setting.InternalToken == "" {
 | 
				
			||||||
		log.Fatal(`The INTERNAL_TOKEN setting is missing from the configuration file: %q.
 | 
							log.Fatal(`The INTERNAL_TOKEN setting is missing from the configuration file: %q.
 | 
				
			||||||
@@ -43,49 +73,11 @@ Ensure you are running in the correct environment or set the correct configurati
 | 
				
			|||||||
		log.Fatal("Invalid internal request URL: %q", url)
 | 
							log.Fatal("Invalid internal request URL: %q", url)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	req := httplib.NewRequest(url, method).
 | 
						return httplib.NewRequest(url, method).
 | 
				
			||||||
		SetContext(ctx).
 | 
							SetContext(ctx).
 | 
				
			||||||
 | 
							SetTransport(internalAPITransport()).
 | 
				
			||||||
		Header("X-Real-IP", getClientIP()).
 | 
							Header("X-Real-IP", getClientIP()).
 | 
				
			||||||
		Header("X-Gitea-Internal-Auth", "Bearer "+setting.InternalToken).
 | 
							Header("X-Gitea-Internal-Auth", "Bearer "+setting.InternalToken)
 | 
				
			||||||
		SetTLSClientConfig(&tls.Config{
 | 
					 | 
				
			||||||
			InsecureSkipVerify: true,
 | 
					 | 
				
			||||||
			ServerName:         setting.Domain,
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if setting.Protocol == setting.HTTPUnix {
 | 
					 | 
				
			||||||
		req.SetTransport(&http.Transport{
 | 
					 | 
				
			||||||
			DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
 | 
					 | 
				
			||||||
				var d net.Dialer
 | 
					 | 
				
			||||||
				conn, err := d.DialContext(ctx, "unix", setting.HTTPAddr)
 | 
					 | 
				
			||||||
				if err != nil {
 | 
					 | 
				
			||||||
					return conn, err
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				if setting.LocalUseProxyProtocol {
 | 
					 | 
				
			||||||
					if err = proxyprotocol.WriteLocalHeader(conn); err != nil {
 | 
					 | 
				
			||||||
						_ = conn.Close()
 | 
					 | 
				
			||||||
						return nil, err
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				return conn, err
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
	} else if setting.LocalUseProxyProtocol {
 | 
					 | 
				
			||||||
		req.SetTransport(&http.Transport{
 | 
					 | 
				
			||||||
			DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
 | 
					 | 
				
			||||||
				var d net.Dialer
 | 
					 | 
				
			||||||
				conn, err := d.DialContext(ctx, network, address)
 | 
					 | 
				
			||||||
				if err != nil {
 | 
					 | 
				
			||||||
					return conn, err
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				if err = proxyprotocol.WriteLocalHeader(conn); err != nil {
 | 
					 | 
				
			||||||
					_ = conn.Close()
 | 
					 | 
				
			||||||
					return nil, err
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				return conn, err
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return req
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func newInternalRequestAPI(ctx context.Context, url, method string, body ...any) *httplib.Request {
 | 
					func newInternalRequestAPI(ctx context.Context, url, method string, body ...any) *httplib.Request {
 | 
				
			||||||
@@ -98,6 +90,6 @@ func newInternalRequestAPI(ctx context.Context, url, method string, body ...any)
 | 
				
			|||||||
		log.Fatal("Too many arguments for newInternalRequestAPI")
 | 
							log.Fatal("Too many arguments for newInternalRequestAPI")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	req.SetTimeout(10*time.Second, 60*time.Second)
 | 
						req.SetReadWriteTimeout(60 * time.Second)
 | 
				
			||||||
	return req
 | 
						return req
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,7 +6,6 @@ package private
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"time"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/modules/setting"
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@@ -31,6 +30,6 @@ func RestoreRepo(ctx context.Context, repoDir, ownerName, repoName string, units
 | 
				
			|||||||
		Units:      units,
 | 
							Units:      units,
 | 
				
			||||||
		Validation: validation,
 | 
							Validation: validation,
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	req.SetTimeout(3*time.Second, 0) // since the request will spend much time, don't timeout
 | 
						req.SetReadWriteTimeout(0) // since the request will spend much time, don't timeout
 | 
				
			||||||
	return requestJSONClientMsg(req, fmt.Sprintf("Restore repo %s/%s successfully", ownerName, repoName))
 | 
						return requestJSONClientMsg(req, fmt.Sprintf("Restore repo %s/%s successfully", ownerName, repoName))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,6 +5,8 @@ package integration
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"net/url"
 | 
						"net/url"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
	"slices"
 | 
						"slices"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"sync"
 | 
						"sync"
 | 
				
			||||||
@@ -23,7 +25,8 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func TestGitLFSSSH(t *testing.T) {
 | 
					func TestGitLFSSSH(t *testing.T) {
 | 
				
			||||||
	onGiteaRun(t, func(t *testing.T, u *url.URL) {
 | 
						onGiteaRun(t, func(t *testing.T, u *url.URL) {
 | 
				
			||||||
		dstPath := t.TempDir()
 | 
							localRepoForUpload := filepath.Join(t.TempDir(), "test-upload")
 | 
				
			||||||
 | 
							localRepoForDownload := filepath.Join(t.TempDir(), "test-download")
 | 
				
			||||||
		apiTestContext := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
 | 
							apiTestContext := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		var mu sync.Mutex
 | 
							var mu sync.Mutex
 | 
				
			||||||
@@ -37,7 +40,7 @@ func TestGitLFSSSH(t *testing.T) {
 | 
				
			|||||||
		withKeyFile(t, "my-testing-key", func(keyFile string) {
 | 
							withKeyFile(t, "my-testing-key", func(keyFile string) {
 | 
				
			||||||
			t.Run("CreateUserKey", doAPICreateUserKey(apiTestContext, "test-key", keyFile))
 | 
								t.Run("CreateUserKey", doAPICreateUserKey(apiTestContext, "test-key", keyFile))
 | 
				
			||||||
			cloneURL := createSSHUrl(apiTestContext.GitPath(), u)
 | 
								cloneURL := createSSHUrl(apiTestContext.GitPath(), u)
 | 
				
			||||||
			t.Run("Clone", doGitClone(dstPath, cloneURL))
 | 
								t.Run("CloneOrigin", doGitClone(localRepoForUpload, cloneURL))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			cfg, err := setting.CfgProvider.PrepareSaving()
 | 
								cfg, err := setting.CfgProvider.PrepareSaving()
 | 
				
			||||||
			require.NoError(t, err)
 | 
								require.NoError(t, err)
 | 
				
			||||||
@@ -46,10 +49,15 @@ func TestGitLFSSSH(t *testing.T) {
 | 
				
			|||||||
			require.NoError(t, cfg.Save())
 | 
								require.NoError(t, cfg.Save())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			_, _, cmdErr := gitcmd.NewCommand("config", "lfs.sshtransfer", "always").
 | 
								_, _, cmdErr := gitcmd.NewCommand("config", "lfs.sshtransfer", "always").
 | 
				
			||||||
				WithDir(dstPath).
 | 
									WithDir(localRepoForUpload).
 | 
				
			||||||
				RunStdString(t.Context())
 | 
									RunStdString(t.Context())
 | 
				
			||||||
			assert.NoError(t, cmdErr)
 | 
								assert.NoError(t, cmdErr)
 | 
				
			||||||
			lfsCommitAndPushTest(t, dstPath, 10)
 | 
								pushedFiles := lfsCommitAndPushTest(t, localRepoForUpload, 10)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								t.Run("CloneLFS", doGitClone(localRepoForDownload, cloneURL))
 | 
				
			||||||
 | 
								content, err := os.ReadFile(filepath.Join(localRepoForDownload, pushedFiles[0]))
 | 
				
			||||||
 | 
								assert.NoError(t, err)
 | 
				
			||||||
 | 
								assert.Len(t, content, 10)
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		countBatch := slices.ContainsFunc(routerCalls, func(s string) bool {
 | 
							countBatch := slices.ContainsFunc(routerCalls, func(s string) bool {
 | 
				
			||||||
@@ -58,12 +66,16 @@ func TestGitLFSSSH(t *testing.T) {
 | 
				
			|||||||
		countUpload := slices.ContainsFunc(routerCalls, func(s string) bool {
 | 
							countUpload := slices.ContainsFunc(routerCalls, func(s string) bool {
 | 
				
			||||||
			return strings.Contains(s, "PUT /api/internal/repo/user2/repo1.git/info/lfs/objects/")
 | 
								return strings.Contains(s, "PUT /api/internal/repo/user2/repo1.git/info/lfs/objects/")
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
 | 
							countDownload := slices.ContainsFunc(routerCalls, func(s string) bool {
 | 
				
			||||||
 | 
								return strings.Contains(s, "GET /api/internal/repo/user2/repo1.git/info/lfs/objects/")
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
		nonAPIRequests := slices.ContainsFunc(routerCalls, func(s string) bool {
 | 
							nonAPIRequests := slices.ContainsFunc(routerCalls, func(s string) bool {
 | 
				
			||||||
			fields := strings.Fields(s)
 | 
								fields := strings.Fields(s)
 | 
				
			||||||
			return !strings.HasPrefix(fields[1], "/api/")
 | 
								return !strings.HasPrefix(fields[1], "/api/")
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
		assert.NotZero(t, countBatch)
 | 
							assert.NotZero(t, countBatch)
 | 
				
			||||||
		assert.NotZero(t, countUpload)
 | 
							assert.NotZero(t, countUpload)
 | 
				
			||||||
 | 
							assert.NotZero(t, countDownload)
 | 
				
			||||||
		assert.Zero(t, nonAPIRequests)
 | 
							assert.Zero(t, nonAPIRequests)
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user