返回 DeepSeek-Reasonix
workspace_git_tally.go
根目录 / desktop / workspace_git_tally.go
1 package main
2
3 import (
4 "bytes"
5 "context"
6 "io"
7 "os"
8 "path/filepath"
9 "strconv"
10 "strings"
11 )
12
13 const (
14 workspaceDiffTallyFileLimit = 1 << 20
15 workspaceDiffTallyReadLimit = 16 << 20
16 )
17
18 func workspaceGitDiffTally(ctx context.Context, base string, untracked []string) (added, removed int, incomplete bool) {
19 raw, err := workspaceGitCommand(ctx, "-C", base, "diff", "--numstat", "--no-textconv", "HEAD", "--", ".").Output()
20 if err != nil && ctx.Err() == nil {
21 incomplete = true
22 raw, err = workspaceGitCommand(ctx, "-C", base, "diff", "--numstat", "--no-textconv", "--", ".").Output()
23 }
24 if err != nil {
25 return 0, 0, true
26 }
27 for line := range strings.SplitSeq(strings.TrimSpace(string(raw)), "\n") {
28 fields := strings.Fields(line)
29 if len(fields) < 2 {
30 continue
31 }
32 n, _ := strconv.Atoi(fields[0])
33 added += n
34 n, _ = strconv.Atoi(fields[1])
35 removed += n
36 }
37 if len(untracked) == 0 {
38 return added, removed, incomplete || ctx.Err() != nil
39 }
40 root, err := os.OpenRoot(base)
41 if err != nil {
42 return added, removed, true
43 }
44 defer root.Close()
45 remaining := int64(workspaceDiffTallyReadLimit)
46 for _, rel := range untracked {
47 if ctx.Err() != nil || remaining <= 0 {
48 return added, removed, true
49 }
50 count, partial := workspaceCountFileLines(ctx, root, filepath.FromSlash(rel), &remaining)
51 added += count
52 incomplete = incomplete || partial
53 }
54 return added, removed, incomplete
55 }
56
57 func workspaceCountFileLines(ctx context.Context, root *os.Root, path string, remaining *int64) (int, bool) {
58 if !filepath.IsLocal(path) || ctx.Err() != nil {
59 return 0, true
60 }
61 // Check every component; Root also prevents a concurrent replacement escaping
62 // the workspace. The platform open rejects final symlinks and blocking FIFOs.
63 for parent := path; parent != "."; parent = filepath.Dir(parent) {
64 info, err := root.Lstat(parent)
65 if err != nil || info.Mode()&os.ModeSymlink != 0 {
66 return 0, true
67 }
68 }
69 before, err := root.Lstat(path)
70 if err != nil || !before.Mode().IsRegular() {
71 return 0, true
72 }
73 f, err := openWorkspaceTallyFile(root, path)
74 if err != nil {
75 return 0, true
76 }
77 defer f.Close()
78 info, err := f.Stat()
79 if err != nil || !info.Mode().IsRegular() || !os.SameFile(before, info) {
80 return 0, true
81 }
82 limit := min(int64(workspaceDiffTallyFileLimit), *remaining)
83 partial := info.Size() > limit
84 reader := io.LimitReader(f, limit)
85 buf := make([]byte, 64*1024)
86 count, total := 0, int64(0)
87 var last byte
88 for {
89 if ctx.Err() != nil {
90 return count, true
91 }
92 n, readErr := reader.Read(buf)
93 *remaining -= int64(n)
94 total += int64(n)
95 if n > 0 {
96 chunk := buf[:n]
97 if bytes.IndexByte(chunk, 0) >= 0 {
98 return 0, false
99 }
100 count += bytes.Count(chunk, []byte{'\n'})
101 last = chunk[n-1]
102 }
103 if readErr != nil {
104 if readErr != io.EOF {
105 return count, true
106 }
107 break
108 }
109 }
110 if total > 0 && last != '\n' && !partial {
111 count++
112 }
113 return count, partial
114 }
115
115 lines GO