fix: refactor git error handling and make archive streaming handle non-existing commit id (#38007)

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
Sandro
2026-06-06 13:06:08 +02:00
committed by GitHub
parent e88650cfcf
commit 743bbaa9c2
10 changed files with 57 additions and 50 deletions

View File

@@ -9,6 +9,8 @@ import (
"fmt"
"os/exec"
"strings"
"gitea.dev/modules/util"
)
type RunStdError interface {
@@ -41,32 +43,14 @@ func (r *runStdError) Stderr() string {
}
func ErrorAsStderr(err error) (string, bool) {
var runErr RunStdError
if errors.As(err, &runErr) {
if runErr, ok := errors.AsType[RunStdError](err); ok {
return runErr.Stderr(), true
}
return "", false
}
func StderrHasPrefix(err error, prefix string) bool {
stderr, ok := ErrorAsStderr(err)
if !ok {
return false
}
return strings.HasPrefix(stderr, prefix)
}
func StderrContains(err error, sub string) bool {
stderr, ok := ErrorAsStderr(err)
if !ok {
return false
}
return strings.Contains(stderr, sub)
}
func IsErrorExitCode(err error, code int) bool {
var exitError *exec.ExitError
if errors.As(err, &exitError) {
if exitError, ok := errors.AsType[*exec.ExitError](err); ok {
return exitError.ExitCode() == code
}
return false
@@ -85,11 +69,41 @@ func IsErrorCanceledOrKilled(err error) bool {
return errors.Is(err, context.Canceled) || IsErrorSignalKilled(err)
}
func IsStdErrorNotValidObjectName(err error) bool {
type StderrPrefix string
type StderrSubStr string
const (
StderrNotValidObjectName StderrPrefix = "fatal: not a valid object name"
StderrNotTreeObject StderrPrefix = "fatal: not a tree object"
StderrPathSpec StderrPrefix = "fatal: pathspec"
StderrBadRevision StderrPrefix = "fatal: bad revision"
StderrNoSuchRemote1 StderrPrefix = "fatal: no such remote" // git < 2.30, exit status 128
StderrNoSuchRemote2 StderrPrefix = "error: no such remote" // git >= 2.30. exit status 2
// fatal: ambiguous argument 'origin': unknown revision or path not in the working tree.
StderrUnknownRevisionOrPath StderrSubStr = "unknown revision or path not in the working tree"
)
func IsStderr[T StderrPrefix | StderrSubStr](err error, check T) bool {
stderr, ok := ErrorAsStderr(err)
// Git is lowercasing the "fatal: Not a valid object name" error message
// ref: https://lore.kernel.org/git/pull.2052.git.1771836302101.gitgitgadget@gmail.com
return ok && strings.Contains(strings.ToLower(stderr), "fatal: not a valid object name")
if !ok {
return false
}
checkLen := len(check)
if len(stderr) < checkLen {
return false
}
switch any(check).(type) {
case StderrPrefix:
// Git is lowercasing the "fatal: Not a valid object name" error message
// ref: https://lore.kernel.org/git/pull.2052.git.1771836302101.gitgitgadget@gmail.com
return util.AsciiEqualFold(stderr[:checkLen], string(check))
case StderrSubStr:
return strings.Contains(stderr, string(check))
}
return false
}
type pipelineError struct {

View File

@@ -66,11 +66,7 @@ func (err *ErrInvalidCloneAddr) Unwrap() error {
// IsRemoteNotExistError checks the prefix of the error message to see whether a remote does not exist.
func IsRemoteNotExistError(err error) bool {
// see: https://github.com/go-gitea/gitea/issues/32889#issuecomment-2571848216
// Should not add space in the end, sometimes git will add a `:`
prefix1 := "fatal: No such remote" // git < 2.30, exit status 128
prefix2 := "error: No such remote" // git >= 2.30. exit status 2
return gitcmd.StderrHasPrefix(err, prefix1) || gitcmd.StderrHasPrefix(err, prefix2)
return gitcmd.IsStderr(err, gitcmd.StderrNoSuchRemote1) || gitcmd.IsStderr(err, gitcmd.StderrNoSuchRemote2)
}
// ParseRemoteAddr checks if given remote address is valid,

View File

@@ -24,9 +24,9 @@ func (repo *Repository) GetTagCommitID(name string) (string, error) {
return repo.GetRefCommitID(TagPrefix + name)
}
// GetCommit returns commit object of by ID string.
func (repo *Repository) GetCommit(commitID string) (*Commit, error) {
id, err := repo.ConvertToGitID(commitID)
// GetCommit returns a commit object of by the git ref.
func (repo *Repository) GetCommit(ref string) (*Commit, error) {
id, err := repo.ConvertToGitID(ref)
if err != nil {
return nil, err
}

View File

@@ -109,16 +109,16 @@ func (repo *Repository) getCommitWithBatch(batch CatFileBatch, id ObjectID) (*Co
}
}
// ConvertToGitID returns a GitHash object from a potential ID string
func (repo *Repository) ConvertToGitID(commitID string) (ObjectID, error) {
// ConvertToGitID returns a git object ID from the git ref, it doesn't guarantee the returned ID really exists
func (repo *Repository) ConvertToGitID(ref string) (ObjectID, error) {
objectFormat, err := repo.GetObjectFormat()
if err != nil {
return nil, err
}
if len(commitID) == objectFormat.FullLength() && objectFormat.IsValid(commitID) {
ID, err := NewIDFromString(commitID)
if len(ref) == objectFormat.FullLength() && objectFormat.IsValid(ref) {
id, err := NewIDFromString(ref)
if err == nil {
return ID, nil
return id, nil
}
}
@@ -127,10 +127,10 @@ func (repo *Repository) ConvertToGitID(commitID string) (ObjectID, error) {
return nil, err
}
defer cancel()
info, err := batch.QueryInfo(commitID)
info, err := batch.QueryInfo(ref)
if err != nil {
if IsErrNotExist(err) {
return nil, ErrNotExist{commitID, ""}
return nil, ErrNotExist{ref, ""}
}
return nil, err
}

View File

@@ -7,7 +7,6 @@ package git
import (
"io"
"strings"
"gitea.dev/modules/git/gitcmd"
)
@@ -65,7 +64,7 @@ func (t *Tree) ListEntries() (Entries, error) {
stdout, _, runErr := gitcmd.NewCommand("ls-tree", "-l").AddDynamicArguments(t.ID.String()).WithDir(t.repo.Path).RunStdBytes(t.repo.Ctx)
if runErr != nil {
if gitcmd.IsStdErrorNotValidObjectName(runErr) || strings.Contains(runErr.Error(), "fatal: not a tree object") {
if gitcmd.IsStderr(runErr, gitcmd.StderrNotValidObjectName) || gitcmd.IsStderr(runErr, gitcmd.StderrNotTreeObject) {
return nil, ErrNotExist{
ID: t.ID.String(),
}