From b069daf2ecd7c3cb7312f2fadafee3d3a2d06514 Mon Sep 17 00:00:00 2001 From: Gusted Date: Thu, 18 Dec 2025 23:23:07 +0100 Subject: [PATCH] fix: don't push LFS when using SSH authentication (#10475) We would need to understand LFS over SSH, which is not implemented. Ref: forgejo/forgejo#5925 Skip pushing LFS when SSH authentication is used. Resolves: Codeberg/Community#2156 Add a test to verify that you can push mirror a LFS repository Documentation: forgejo/docs!1639 Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/10475 Reviewed-by: Mathieu Fenniak Co-authored-by: Gusted Co-committed-by: Gusted --- services/mirror/mirror_push.go | 6 +++-- tests/integration/mirror_push_test.go | 33 ++++++++++++++++++++++----- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/services/mirror/mirror_push.go b/services/mirror/mirror_push.go index fdd02dedea..e250ee93f9 100644 --- a/services/mirror/mirror_push.go +++ b/services/mirror/mirror_push.go @@ -192,7 +192,9 @@ func runPushSync(ctx context.Context, m *repo_model.PushMirror) error { return errors.New("Unexpected error") } - if setting.LFS.StartServer { + useSSHAuthentication := len(m.PublicKey) != 0 + + if setting.LFS.StartServer && !useSSHAuthentication { log.Trace("SyncMirrors [repo: %-v]: syncing LFS objects...", m.Repo) var gitRepo *git.Repository @@ -220,7 +222,7 @@ func runPushSync(ctx context.Context, m *repo_model.PushMirror) error { // Therefore, we need to create a temporary file that stores the private key, so that OpenSSH can use it. // We delete the temporary file afterwards. privateKeyPath := "" - if m.PublicKey != "" { + if useSSHAuthentication { f, err := os.CreateTemp(os.TempDir(), m.RemoteName) if err != nil { log.Error("os.CreateTemp: %v", err) diff --git a/tests/integration/mirror_push_test.go b/tests/integration/mirror_push_test.go index c7f1983281..b02f894b58 100644 --- a/tests/integration/mirror_push_test.go +++ b/tests/integration/mirror_push_test.go @@ -227,14 +227,15 @@ func TestSSHPushMirror(t *testing.T) { srcRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) assert.False(t, srcRepo.HasWiki()) sess := loginUser(t, user.Name) + pushToRepo, _, f := tests.CreateDeclarativeRepoWithOptions(t, user, tests.DeclarativeRepoOptions{ - Name: optional.Some("push-mirror-test"), + Name: optional.Some("push-mirror-misc-test"), AutoInit: optional.Some(false), EnabledUnits: optional.Some([]unit.Type{unit.TypeCode}), }) defer f() - sshURL := fmt.Sprintf("ssh://%s@%s/%s.git", setting.SSH.User, net.JoinHostPort(setting.SSH.ListenHost, strconv.Itoa(setting.SSH.ListenPort)), pushToRepo.FullName()) + t.Run("Mutual exclusive", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -282,7 +283,17 @@ func TestSSHPushMirror(t *testing.T) { htmlDoc.AssertElement(t, inputSelector, true) }) - t.Run("Normal", func(t *testing.T) { + testMirrorPush := func(t *testing.T, srcRepo *repo_model.Repository, expectedSHA string) { + t.Helper() + + pushToRepo, _, f := tests.CreateDeclarativeRepoWithOptions(t, user, tests.DeclarativeRepoOptions{ + Name: optional.Some("push-mirror-test"), + AutoInit: optional.Some(false), + EnabledUnits: optional.Some([]unit.Type{unit.TypeCode}), + }) + defer f() + sshURL := fmt.Sprintf("ssh://%s@%s/%s.git", setting.SSH.User, net.JoinHostPort(setting.SSH.ListenHost, strconv.Itoa(setting.SSH.ListenPort)), pushToRepo.FullName()) + var pushMirror *repo_model.PushMirror t.Run("Adding", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -341,20 +352,19 @@ func TestSSHPushMirror(t *testing.T) { t.Run("Check mirrored content", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - shortSHA := "1032bbf17f" req := NewRequest(t, "GET", fmt.Sprintf("/%s", srcRepo.FullName())) resp := sess.MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) - assert.Contains(t, htmlDoc.Find(".shortsha").Text(), shortSHA) + assert.Contains(t, htmlDoc.Find(".shortsha").Text(), expectedSHA) assert.Eventually(t, func() bool { req = NewRequest(t, "GET", fmt.Sprintf("/%s", pushToRepo.FullName())) resp = sess.MakeRequest(t, req, NoExpectedStatus) htmlDoc = NewHTMLParser(t, resp.Body) - return resp.Code == http.StatusOK && htmlDoc.Find(".shortsha").Text() == shortSHA + return resp.Code == http.StatusOK && htmlDoc.Find(".shortsha").Text() == expectedSHA }, time.Second*30, time.Second) }) @@ -369,6 +379,17 @@ func TestSSHPushMirror(t *testing.T) { assert.Contains(t, string(knownHosts), string(publicKey)) }) + } + + t.Run("Normal", func(t *testing.T) { + testMirrorPush(t, srcRepo, "1032bbf17f") + }) + + t.Run("LFS", func(t *testing.T) { + defer test.MockVariableValue(&setting.LFS.StartServer, true)() + + srcRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 54}) + testMirrorPush(t, srcRepo, "e9c32647ba") }) }) }