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 {
Clone(ctx context.Context, dst, remoteURL, branch 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
Commit(ctx context.Context, dir, message string, addAll bool) (sha string, err 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
WorktreeRemove(ctx context.Context, dir, path string) 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` 的一行。