Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion internal/clients/git/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ var (
ErrEmptyRemoteRepository = errors.New("remote repository is empty")
ErrAuthenticationRequired = errors.New("authentication required")
ErrAuthorizationFailed = errors.New("authorization failed")
ErrBranchNotFound = errors.New("branch not found")
NoErrAlreadyUpToDate = git.NoErrAlreadyUpToDate
)

Expand Down Expand Up @@ -191,7 +192,7 @@ func GetLatestCommitRemote(opts ListOptions) (*string, error) {
}
}

return nil, fmt.Errorf("Branch %s reference %s not found on remote %s", opts.Branch, repoRef, opts.URL)
return nil, fmt.Errorf("%w: branch %s reference %s not found on remote %s", ErrBranchNotFound, opts.Branch, repoRef, opts.URL)
}

func restoreUnsupportedCapabilities(oldUnsupportedCaps []capability.Capability) {
Expand Down Expand Up @@ -390,6 +391,9 @@ func Clone(opts CloneOptions) (*Repo, error) {
GitCookies: opts.GitCookies,
})
if err != nil {
if !errors.Is(err, ErrBranchNotFound) {
return nil, fmt.Errorf("failed to inspect remote branch: %w", err)
}
cloneOpts = git.CloneOptions{
RemoteName: "origin",
URL: opts.URL,
Expand Down
60 changes: 60 additions & 0 deletions internal/clients/git/git_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
package git

import (
"errors"
"net/http"
"net/http/cgi"
"net/http/httptest"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"sync/atomic"
"testing"

"github.com/go-git/go-git/v5"
Expand Down Expand Up @@ -361,3 +369,55 @@ func TestCommitPreservesFileMode(t *testing.T) {
assert.Equal(t, filemode.Executable, entry.Mode, name)
}
}

func TestGetLatestCommitRemoteBranchNotFound(t *testing.T) {
baseRepo := BaseSuite{}
baseRepo.BuildBasicRepository()

_, err := GetLatestCommitRemote(ListOptions{
URL: baseRepo.GetBasicLocalRepositoryURL(),
Branch: "missing-branch",
})
require.Error(t, err)
assert.True(t, errors.Is(err, ErrBranchNotFound))
}

// A failed branch lookup that is not "branch not found" must not be treated as a new branch:
// that path empties the worktree, so the next commit would delete every existing file.
func TestCloneFailsOnTransientBranchLookupError(t *testing.T) {
gitExecPath, err := exec.Command("git", "--exec-path").Output()
if err != nil {
t.Skip("git not available")
}

baseRepo := BaseSuite{}
baseRepo.BuildBasicRepository()
dotGit := baseRepo.GetBasicLocalRepositoryURL()

backend := &cgi.Handler{
Path: filepath.Join(strings.TrimSpace(string(gitExecPath)), "git-http-backend"),
Env: []string{
"GIT_PROJECT_ROOT=" + filepath.Dir(dotGit),
"GIT_HTTP_EXPORT_ALL=1",
},
}
var calls atomic.Int32
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if calls.Add(1) == 1 {
http.Error(w, "unavailable", http.StatusServiceUnavailable)
return
}
backend.ServeHTTP(w, r)
}))
defer srv.Close()

repo, err := Clone(CloneOptions{
URL: srv.URL + "/" + filepath.Base(dotGit),
Branch: "master",
})
if repo != nil {
defer repo.Cleanup()
}
require.Error(t, err)
assert.False(t, errors.Is(err, ErrBranchNotFound))
}