feat(git): add FetchRemoteBranch, Checkout, ListRemoteBranches to Runner

This commit is contained in:
2026-06-10 17:56:47 +08:00
parent b20435c027
commit a49d45a846
3 changed files with 72 additions and 0 deletions
+64
View File
@@ -0,0 +1,64 @@
package git
import (
"bufio"
"bytes"
"context"
"strings"
)
// FetchRemoteBranch fetches a single remote branch (origin/<branch>).
// Lighter than Fetch --all when we only need one branch.
func (r *DefaultRunner) FetchRemoteBranch(ctx context.Context, dir, branch string, cred *Credential) error {
ctx, cancel := withCtx(ctx, r.cfg.FetchTimeout)
defer cancel()
env, cleanup, err := credentialEnv(r.cfg.TmpDir, cred)
if err != nil {
return err
}
defer cleanup()
_, _, err = r.exec(ctx, dir, env, "fetch", "origin", branch)
return err
}
// Checkout switches the working tree to the given branch.
func (r *DefaultRunner) Checkout(ctx context.Context, dir, branch string) error {
ctx, cancel := withCtx(ctx, r.cfg.OpsTimeout)
defer cancel()
_, _, err := r.exec(ctx, dir, nil, "checkout", branch)
return err
}
// ListRemoteBranches queries the remote for all branch names via git ls-remote.
// Returns branch names with refs/heads/ prefix stripped.
func (r *DefaultRunner) ListRemoteBranches(ctx context.Context, dir string, cred *Credential) ([]string, error) {
ctx, cancel := withCtx(ctx, r.cfg.FetchTimeout)
defer cancel()
env, cleanup, err := credentialEnv(r.cfg.TmpDir, cred)
if err != nil {
return nil, err
}
defer cleanup()
out, _, err := r.exec(ctx, dir, env, "ls-remote", "--heads")
if err != nil {
return nil, err
}
return parseRemoteBranches(out), nil
}
// parseRemoteBranches parses git ls-remote --heads output.
// Each line: "<sha>\trefs/heads/<branch>"
func parseRemoteBranches(buf []byte) []string {
var branches []string
sc := bufio.NewScanner(bytes.NewReader(buf))
for sc.Scan() {
line := sc.Text()
if idx := strings.Index(line, "\trefs/heads/"); idx >= 0 {
name := line[idx+len("\trefs/heads/"):]
if name != "" {
branches = append(branches, name)
}
}
}
return branches
}
+3
View File
@@ -23,6 +23,7 @@ const stderrTruncateLimit = 2000
type Runner interface { type Runner interface {
Clone(ctx context.Context, dst, remoteURL, branch string, cred *Credential) error Clone(ctx context.Context, dst, remoteURL, branch string, cred *Credential) error
Fetch(ctx context.Context, dir string, cred *Credential) error Fetch(ctx context.Context, dir string, cred *Credential) error
FetchRemoteBranch(ctx context.Context, dir, branch string, cred *Credential) error
Push(ctx context.Context, dir, branch string, cred *Credential) error Push(ctx context.Context, dir, branch string, cred *Credential) error
Commit(ctx context.Context, dir, message string, addAll bool) (sha string, err error) Commit(ctx context.Context, dir, message string, addAll bool) (sha string, err error)
Status(ctx context.Context, dir string) ([]FileStatus, error) Status(ctx context.Context, dir string) ([]FileStatus, error)
@@ -30,6 +31,8 @@ type Runner interface {
WorktreeAdd(ctx context.Context, dir, branch, path, startPoint string) error WorktreeAdd(ctx context.Context, dir, branch, path, startPoint string) error
WorktreeRemove(ctx context.Context, dir, path string) error WorktreeRemove(ctx context.Context, dir, path string) error
WorktreeList(ctx context.Context, dir string) ([]Worktree, error) WorktreeList(ctx context.Context, dir string) ([]Worktree, error)
ListRemoteBranches(ctx context.Context, dir string, cred *Credential) ([]string, error)
Checkout(ctx context.Context, dir, branch string) error
} }
// FileStatus 表示 `git status --porcelain=v1` 的一行。 // FileStatus 表示 `git status --porcelain=v1` 的一行。
@@ -226,6 +226,11 @@ func (f *fakeGit) WorktreeRemove(_ context.Context, _, _ string) error
func (f *fakeGit) WorktreeList(_ context.Context, _ string) ([]git.Worktree, error) { func (f *fakeGit) WorktreeList(_ context.Context, _ string) ([]git.Worktree, error) {
return nil, nil return nil, nil
} }
func (f *fakeGit) FetchRemoteBranch(_ context.Context, _, _ string, _ *git.Credential) error { return nil }
func (f *fakeGit) Checkout(_ context.Context, _, _ string) error { return nil }
func (f *fakeGit) ListRemoteBranches(_ context.Context, _ string, _ *git.Credential) ([]string, error) {
return nil, nil
}
type noopAudit struct{} type noopAudit struct{}