From 8fa32bd3ffe9e74da1bb23c5e961a24a52324b3c Mon Sep 17 00:00:00 2001 From: Gusted Date: Thu, 13 Nov 2025 13:22:09 +0100 Subject: [PATCH] fix: make `Fetch` work with git < 2.41 (#10095) `git fetch --porcelain` was introduced in 2.41, add a fallback for older git clients. Resolves forgejo/forgejo#10080 Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/10095 Reviewed-by: Michael Kriese Reviewed-by: Mathieu Fenniak Co-authored-by: Gusted Co-committed-by: Gusted --- modules/git/fetch.go | 50 ++++++++++++++++++++++++++++++++++++-------- modules/git/git.go | 2 ++ 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/modules/git/fetch.go b/modules/git/fetch.go index 886ca9ca60..af8bb34d38 100644 --- a/modules/git/fetch.go +++ b/modules/git/fetch.go @@ -6,6 +6,9 @@ import ( "errors" "fmt" "strings" + + "forgejo.org/modules/log" + "forgejo.org/modules/util" ) var ErrRemoteRefNotFound = errors.New("unable to find remote ref") @@ -25,7 +28,20 @@ func (repo *Repository) Fetch(remoteURL, refspec string) (string, error) { return "", err } - stdout, stderr, err := NewCommand(repo.Ctx, "fetch", "--porcelain", "--end-of-options").AddDynamicArguments(remoteURL, refspec).RunStdString(&RunOpts{Dir: repo.Path}) + cmd := NewCommand(repo.Ctx, "fetch") + if SupportFetchPorcelain { + cmd.AddArguments("--porcelain") + } else if !strings.Contains(refspec, ":") { + randomString, err := util.CryptoRandomString(8) + if err != nil { + return "", err + } + refspec += ":refs/tmp/" + randomString + } + + cmd.AddArguments("--end-of-options").AddDynamicArguments(remoteURL, refspec) + + stdout, stderr, err := cmd.RunStdString(&RunOpts{Dir: repo.Path}) if err != nil { if strings.HasPrefix(stderr, "fatal: couldn't find remote ref ") { return "", ErrRemoteRefNotFound @@ -33,19 +49,35 @@ func (repo *Repository) Fetch(remoteURL, refspec string) (string, error) { return "", err } - localReference, _, ok := strings.Cut(refspec, ":") + _, localReference, ok := strings.Cut(refspec, ":") if !ok { localReference = "FETCH_HEAD" } - // The porcelain format, per section OUTPUT of git-fetch(1), is expected to be: - // \n - // flag is one character. - if expectedLen := 1 + 1 + objectFormat.FullLength() + 1 + objectFormat.FullLength() + 1 + len(localReference) + 1; len(stdout) != expectedLen { - return "", fmt.Errorf("output of git-fetch(1) is unexpected, we expected it to be %d bytes but it is %d bytes. stdout: %s", expectedLen, len(stdout), stdout) + // Happy path + if SupportFetchPorcelain { + // The porcelain format, per section OUTPUT of git-fetch(1), is expected to be: + // \n + // flag is one character. + if expectedLen := 1 + 1 + objectFormat.FullLength() + 1 + objectFormat.FullLength() + 1 + len(localReference) + 1; len(stdout) != expectedLen { + return "", fmt.Errorf("output of git-fetch(1) is unexpected, we expected it to be %d bytes but it is %d bytes. stdout: %s", expectedLen, len(stdout), stdout) + } + + // Extract the new objectID. + newObjectID := stdout[1+1+objectFormat.FullLength()+1 : 1+1+objectFormat.FullLength()+1+objectFormat.FullLength()] + return newObjectID, nil + } + + defer func() { + if err := repo.RemoveReference(localReference); err != nil { + log.Error("Could not remove reference %q from repository %q: %v", localReference, repo.Path, err) + } + }() + + newObjectID, err := repo.ResolveReference(localReference) + if err != nil { + return "", err } - // Extract the new objectID. - newObjectID := stdout[1+1+objectFormat.FullLength()+1 : 1+1+objectFormat.FullLength()+1+objectFormat.FullLength()] return newObjectID, nil } diff --git a/modules/git/git.go b/modules/git/git.go index e3ffa39514..650fd3b5af 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -35,6 +35,7 @@ var ( SupportHashSha256 bool // >= 2.42, SHA-256 repositories no longer an ‘experimental curiosity’ InvertedGitFlushEnv bool // 2.43.1 + SupportFetchPorcelain bool // >= 2.41 SupportCheckAttrOnBare bool // >= 2.40 SupportGitMergeTree bool // >= 2.38 SupportGrepMaxCount bool // >= 2.38 @@ -179,6 +180,7 @@ func InitFull(ctx context.Context) (err error) { globalCommandArgs = append(globalCommandArgs, "-c", "credential.helper=") SupportHashSha256 = CheckGitVersionAtLeast("2.42") == nil + SupportFetchPorcelain = CheckGitVersionAtLeast("2.41") == nil SupportCheckAttrOnBare = CheckGitVersionAtLeast("2.40") == nil if SupportHashSha256 { SupportedObjectFormats = append(SupportedObjectFormats, Sha256ObjectFormat)