返回 DeepSeek-Reasonix
git_capability.go
根目录 / internal / sandbox / git_capability.go
1 package sandbox
2
3 import (
4 "context"
5 "path/filepath"
6 "strings"
7 "time"
8
9 "reasonix/internal/proc"
10 "reasonix/internal/secrets"
11 )
12
13 // discoverGitCapability shares the shell inventory snapshot but remains a
14 // separate capability: Git availability neither implies Bash on Unix nor
15 // participates in ResolveShell's interpreter decision.
16 func discoverGitCapability(snap *shellSnapshot) ExecutableCapability {
17 capability := ExecutableCapability{ID: HostCapabilityGit}
18 found := false
19 probes := map[string]bool{}
20 usable := func(path string) bool {
21 found = true
22 key := strings.ToLower(filepath.Clean(path))
23 if result, ok := probes[key]; ok {
24 return result
25 }
26 if snap.gitPreflight != nil && !snap.gitPreflight(path) {
27 probes[key] = false
28 return false
29 }
30 result := snap.gitProbe == nil || snap.gitProbe(path)
31 probes[key] = result
32 return result
33 }
34 for _, name := range []string{"git", "git.exe"} {
35 if path, err := snap.lookPath(name); err == nil && usable(path) {
36 capability.Available = true
37 capability.Path = path
38 capability.Source = ShellSourcePath
39 return capability
40 }
41 }
42
43 if snap.goos == "windows" {
44 var bashPaths []string
45 if path, err := snap.lookPath("bash"); err == nil {
46 bashPaths = append(bashPaths, path)
47 }
48 bashPaths = append(bashPaths, snap.bashCands...)
49 for _, bashPath := range bashPaths {
50 for _, path := range gitCandidatesFromWindowsBash(bashPath) {
51 if !snap.exists(path) || !usable(path) {
52 continue
53 }
54 capability.Available = true
55 capability.Path = path
56 capability.Source = snap.sources[strings.ToLower(bashPath)]
57 if capability.Source == "" {
58 capability.Source = ShellSourceGitDerived
59 }
60 return capability
61 }
62 }
63 } else {
64 for _, path := range []string{"/opt/homebrew/bin/git", "/usr/local/bin/git", "/usr/bin/git", "/bin/git"} {
65 if snap.exists(path) && usable(path) {
66 capability.Available = true
67 capability.Path = path
68 capability.Source = ShellSourceStandard
69 return capability
70 }
71 }
72 }
73 capability.Reason = "not-found"
74 if found {
75 capability.Reason = "not-usable"
76 }
77 return capability
78 }
79
80 func probeGit(path string) bool {
81 ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
82 defer cancel()
83 cmd := proc.CommandContext(ctx, path, "--version")
84 cmd.Env = secrets.ProcessEnv()
85 proc.HideWindow(cmd)
86 return cmd.Run() == nil
87 }
88
89 func gitCandidatesFromWindowsBash(bashPath string) []string {
90 var out []string
91 dir := pathDir(bashPath)
92 for range 3 {
93 if dir == "" || dir == "." {
94 break
95 }
96 out = append(out,
97 filepath.Join(dir, "cmd", "git.exe"),
98 filepath.Join(dir, "bin", "git.exe"),
99 filepath.Join(dir, "git.exe"),
100 )
101 next := pathDir(dir)
102 if next == dir {
103 break
104 }
105 dir = next
106 }
107 return out
108 }
109
109 lines GO