From 8c1d77847ba0d48a53faa064cdf1b3fc9d5f4185 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 7 Jun 2026 15:47:01 -0700 Subject: [PATCH] Fix the panic when ssh rmote lfs endpoint parsing failure --- modules/lfs/client.go | 14 ++++++++++++++ modules/lfs/client_test.go | 12 ++++++++++++ modules/lfs/endpoint.go | 14 +++++++++++++- modules/lfs/endpoint_test.go | 18 ++++++++++++++++++ services/mirror/mirror_pull.go | 7 ++++--- services/mirror/mirror_push.go | 6 ++++-- services/repository/migrate.go | 6 ++++-- 7 files changed, 69 insertions(+), 8 deletions(-) diff --git a/modules/lfs/client.go b/modules/lfs/client.go index f810e5c7aa..c353ecdad2 100644 --- a/modules/lfs/client.go +++ b/modules/lfs/client.go @@ -5,6 +5,7 @@ package lfs import ( "context" + "fmt" "io" "net/http" "net/url" @@ -30,3 +31,16 @@ func NewClient(endpoint *url.URL, httpTransport *http.Transport) Client { } return newHTTPClient(endpoint, httpTransport) } + +// NewClientFromEndpoint creates a LFS client after resolving its endpoint. +func NewClientFromEndpoint(cloneurl, lfsurl string, httpTransport *http.Transport) (Client, error) { + endpoint := DetermineEndpoint(cloneurl, lfsurl) + if endpoint == nil { + source := cloneurl + if lfsurl != "" { + source = lfsurl + } + return nil, fmt.Errorf("unable to determine LFS endpoint from %q", source) + } + return NewClient(endpoint, httpTransport), nil +} diff --git a/modules/lfs/client_test.go b/modules/lfs/client_test.go index a1369301e0..7e7d3883d7 100644 --- a/modules/lfs/client_test.go +++ b/modules/lfs/client_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestNewClient(t *testing.T) { @@ -19,3 +20,14 @@ func TestNewClient(t *testing.T) { c = NewClient(u, nil) assert.IsType(t, &HTTPClient{}, c) } + +func TestNewClientFromEndpoint(t *testing.T) { + client, err := NewClientFromEndpoint("ssh://git@example.com/owner/repo.git", "", nil) + require.NoError(t, err) + assert.NotNil(t, client) + + client, err = NewClientFromEndpoint("ftp://example.com/owner/repo.git", "", nil) + assert.Nil(t, client) + require.Error(t, err) + assert.Contains(t, err.Error(), "unable to determine LFS endpoint") +} diff --git a/modules/lfs/endpoint.go b/modules/lfs/endpoint.go index a7999fc1c4..83363f3b3b 100644 --- a/modules/lfs/endpoint.go +++ b/modules/lfs/endpoint.go @@ -10,6 +10,7 @@ import ( "path/filepath" "strings" + giturl "gitea.dev/modules/git/url" "gitea.dev/modules/log" "gitea.dev/modules/util" ) @@ -44,15 +45,20 @@ func endpointFromCloneURL(rawurl string) *url.URL { } func endpointFromURL(rawurl string) *url.URL { + if rawurl == "" { + return nil + } + if strings.HasPrefix(rawurl, "/") { return endpointFromLocalPath(rawurl) } - u, err := url.Parse(rawurl) + gitURL, err := giturl.ParseGitURL(rawurl) if err != nil { log.Error("lfs.endpointFromUrl: %v", err) return nil } + u := gitURL.URL switch u.Scheme { case "http", "https": @@ -60,6 +66,12 @@ func endpointFromURL(rawurl string) *url.URL { case "git": u.Scheme = "https" return u + case "ssh", "git+ssh": + u.Scheme = "https" + u.Host = util.IfZero(u.Hostname(), u.Host) + u.Path = "/" + strings.TrimPrefix(u.Path, "/") + u.User = nil + return u case "file": return u default: diff --git a/modules/lfs/endpoint_test.go b/modules/lfs/endpoint_test.go index 118abe2d4e..b1ed364c01 100644 --- a/modules/lfs/endpoint_test.go +++ b/modules/lfs/endpoint_test.go @@ -64,6 +64,24 @@ func TestDetermineEndpoint(t *testing.T) { lfsurl: "git://gitlfs.com/repo", expected: str2url("https://gitlfs.com/repo"), }, + // case 7 + { + cloneurl: "ssh://git@git.com/owner/repo.git", + lfsurl: "", + expected: str2url("https://git.com/owner/repo.git/info/lfs"), + }, + // case 8 + { + cloneurl: "git@git.com:owner/repo.git", + lfsurl: "", + expected: str2url("https://git.com/owner/repo.git/info/lfs"), + }, + // case 9 + { + cloneurl: "", + lfsurl: "ssh://git@gitlfs.com/owner/repo.git/info/lfs", + expected: str2url("https://gitlfs.com/owner/repo.git/info/lfs"), + }, } for n, c := range cases { diff --git a/services/mirror/mirror_pull.go b/services/mirror/mirror_pull.go index db569c8550..7037b9b921 100644 --- a/services/mirror/mirror_pull.go +++ b/services/mirror/mirror_pull.go @@ -172,9 +172,10 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*repo_module.SyncResu if m.LFS && setting.LFS.StartServer { log.Trace("SyncMirrors [repo: %-v]: syncing LFS objects...", m.Repo) - endpoint := lfs.DetermineEndpoint(remoteURL.String(), m.LFSEndpoint) - lfsClient := lfs.NewClient(endpoint, migrations.NewMigrationHTTPTransport()) - if err = repo_module.StoreMissingLfsObjectsInRepository(ctx, m.Repo, gitRepo, lfsClient); err != nil { + lfsClient, err := lfs.NewClientFromEndpoint(remoteURL.String(), m.LFSEndpoint, migrations.NewMigrationHTTPTransport()) + if err != nil { + log.Error("SyncMirrors [repo: %-v]: failed to initialize LFS client: %v", m.Repo.FullName(), err) + } else if err = repo_module.StoreMissingLfsObjectsInRepository(ctx, m.Repo, gitRepo, lfsClient); err != nil { log.Error("SyncMirrors [repo: %-v]: failed to synchronize LFS objects for repository: %v", m.Repo.FullName(), err) } } diff --git a/services/mirror/mirror_push.go b/services/mirror/mirror_push.go index 44a25825ee..39d8a725ed 100644 --- a/services/mirror/mirror_push.go +++ b/services/mirror/mirror_push.go @@ -144,8 +144,10 @@ func runPushSync(ctx context.Context, m *repo_model.PushMirror) error { } defer gitRepo.Close() - endpoint := lfs.DetermineEndpoint(remoteURL.String(), "") - lfsClient := lfs.NewClient(endpoint, migrations.NewMigrationHTTPTransport()) + lfsClient, err := lfs.NewClientFromEndpoint(remoteURL.String(), "", migrations.NewMigrationHTTPTransport()) + if err != nil { + return util.SanitizeErrorCredentialURLs(err) + } if err := pushAllLFSObjects(ctx, gitRepo, lfsClient); err != nil { return util.SanitizeErrorCredentialURLs(err) } diff --git a/services/repository/migrate.go b/services/repository/migrate.go index a0182769f4..c328d070c8 100644 --- a/services/repository/migrate.go +++ b/services/repository/migrate.go @@ -159,8 +159,10 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, } if opts.LFS { - endpoint := lfs.DetermineEndpoint(opts.CloneAddr, opts.LFSEndpoint) - lfsClient := lfs.NewClient(endpoint, httpTransport) + lfsClient, err := lfs.NewClientFromEndpoint(opts.CloneAddr, opts.LFSEndpoint, httpTransport) + if err != nil { + return repo, fmt.Errorf("NewClientFromEndpoint: %w", err) + } if err = repo_module.StoreMissingLfsObjectsInRepository(ctx, repo, gitRepo, lfsClient); err != nil { log.Error("Failed to store missing LFS objects for repository: %v", err) return repo, fmt.Errorf("StoreMissingLfsObjectsInRepository: %w", err)