mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2025-12-08 06:29:47 +00:00
- Similair spirit of forgejo/forgejo!7453. - Refactor the code in such a way that it always succeeds. - To avoid doing mathematics if you use this function, define three security level (64, 128 and 256 bits) that correspond to a specific length which has that a security guarantee. I picked them as they fit the need for the existing usages of the code. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/10110 Reviewed-by: Michael Kriese <michael.kriese@gmx.de> Reviewed-by: Lucas <sclu1034@noreply.codeberg.org> Co-authored-by: Gusted <postmaster@gusted.xyz> Co-committed-by: Gusted <postmaster@gusted.xyz>
79 lines
2.5 KiB
Go
79 lines
2.5 KiB
Go
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
package git
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"forgejo.org/modules/log"
|
|
"forgejo.org/modules/util"
|
|
)
|
|
|
|
var ErrRemoteRefNotFound = errors.New("unable to find remote ref")
|
|
|
|
// Fetch executes git-fetch(1) for the repository, it will fetch the refspec
|
|
// from the remote into the repository.
|
|
//
|
|
// Valid remote URLs are denoted in section GIT URLS of git-fetch(1).
|
|
//
|
|
// The return value, on success, is the object ID of the remote reference. If
|
|
// no local reference is given in `refspec` then do not assume it's available in
|
|
// the `FETCH_HEAD` reference, it might have been overwritten by the time you
|
|
// read or use it.
|
|
func (repo *Repository) Fetch(remoteURL, refspec string) (string, error) {
|
|
objectFormat, err := repo.GetObjectFormat()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
cmd := NewCommand(repo.Ctx, "fetch")
|
|
if SupportFetchPorcelain {
|
|
cmd.AddArguments("--porcelain")
|
|
} else if !strings.Contains(refspec, ":") {
|
|
refspec += ":refs/tmp/" + util.CryptoRandomString(util.RandomStringLow)
|
|
}
|
|
|
|
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
|
|
}
|
|
return "", err
|
|
}
|
|
|
|
_, localReference, ok := strings.Cut(refspec, ":")
|
|
if !ok {
|
|
localReference = "FETCH_HEAD"
|
|
}
|
|
|
|
// Happy path
|
|
if SupportFetchPorcelain {
|
|
// The porcelain format, per section OUTPUT of git-fetch(1), is expected to be:
|
|
// <flag><space><old-object-id><space><new-object-id><space><local-reference>\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
|
|
}
|
|
|
|
return newObjectID, nil
|
|
}
|