返回 DeepSeek-Reasonix
stage.go
根目录 / internal / gitcmd / stage.go
1 package gitcmd
2
3 import (
4 "bytes"
5 "context"
6 "fmt"
7 "os"
8 "path/filepath"
9 "strings"
10 )
11
12 // StageAll is `git add -A` over r's work tree into the index env names, never
13 // entering a nested repository: git checks a submodule by running inside it,
14 // under its config. A gitlink present as a directory keeps the commit the
15 // index records; an untracked nested repository is left out.
16 func (r Repo) StageAll(ctx context.Context, env ...string) error {
17 top := r.Top()
18 nested, err := top.nestedRepositories(ctx, env)
19 if err != nil {
20 return err
21 }
22 var specs bytes.Buffer
23 specs.WriteString(".\x00")
24 for _, path := range nested {
25 specs.WriteString(":(exclude,literal)" + path + "\x00")
26 }
27 cmd := top.Command(ctx, "add", "-A", "--pathspec-from-file=-", "--pathspec-file-nul")
28 cmd.Env = append(cmd.Env, env...)
29 cmd.Stdin = &specs
30 var stderr bytes.Buffer
31 cmd.Stderr = &stderr
32 if err := cmd.Run(); err != nil {
33 return fmt.Errorf("git add: %w: %s", err, strings.TrimSpace(stderr.String()))
34 }
35 return nil
36 }
37
38 // nestedRepositories lists, relative to the work tree root, the gitlinks
39 // present as directories and the untracked nested repositories git reports
40 // (with a trailing slash) instead of descending into them.
41 func (r Repo) nestedRepositories(ctx context.Context, env []string) ([]string, error) {
42 var nested []string
43 staged, err := r.output(ctx, env, "ls-files", "-s", "-z")
44 if err != nil {
45 return nil, err
46 }
47 for entry := range strings.SplitSeq(staged, "\x00") {
48 meta, path, ok := strings.Cut(entry, "\t")
49 if !ok || !strings.HasPrefix(meta, "160000 ") {
50 continue
51 }
52 if st, err := os.Lstat(filepath.Join(r.WorkTree, filepath.FromSlash(path))); err == nil && st.IsDir() {
53 nested = append(nested, path)
54 }
55 }
56 untracked, err := r.output(ctx, env, "ls-files", "-o", "-z", "--exclude-standard")
57 if err != nil {
58 return nil, err
59 }
60 for path := range strings.SplitSeq(untracked, "\x00") {
61 if dir, ok := strings.CutSuffix(path, "/"); ok {
62 nested = append(nested, dir)
63 }
64 }
65 return nested, nil
66 }
67
68 func (r Repo) output(ctx context.Context, env []string, args ...string) (string, error) {
69 cmd := r.Command(ctx, args...)
70 cmd.Env = append(cmd.Env, env...)
71 var stderr bytes.Buffer
72 cmd.Stderr = &stderr
73 out, err := cmd.Output()
74 if err != nil {
75 return "", fmt.Errorf("git %s: %w: %s", args[0], err, strings.TrimSpace(stderr.String()))
76 }
77 return string(out), nil
78 }
79
79 lines GO