| 88 | } |
| 89 | |
| 90 | func Files(ref Ref, repoDir string) ([]Blob, error) { |
| 91 | if ref.IsEmpty() { |
| 92 | ref = NewRef("HEAD") |
| 93 | } |
| 94 | |
| 95 | // -r: recurse into subtrees |
| 96 | // -l: include blob size |
| 97 | cmd := exec.Command("git", "ls-tree", "--full-tree", "-r", "-l", ref.String()) |
| 98 | if repoDir != "" { |
| 99 | cmd.Dir = repoDir |
| 100 | } |
| 101 | stdout, err := cmd.StdoutPipe() |
| 102 | if err != nil { |
| 103 | return nil, fmt.Errorf("failed to get stdout pipe: %w", err) |
| 104 | } |
| 105 | |
| 106 | stderr, err := cmd.StderrPipe() |
| 107 | if err != nil { |
| 108 | return nil, fmt.Errorf("failed to get stderr pipe: %w", err) |
| 109 | } |
| 110 | |
| 111 | if err := cmd.Start(); err != nil { |
| 112 | return nil, fmt.Errorf("failed to start git ls-tree: %w", err) |
| 113 | } |
| 114 | |
| 115 | files := make([]Blob, 0, 256) |
| 116 | |
| 117 | // Read stdout line by line; each line is like: |
| 118 | // <mode> <type> <object> <size>\t<path> |
| 119 | // Example: "100644 blob e69de29... 12\tREADME.md" |
| 120 | scanner := bufio.NewScanner(stdout) |
| 121 | |
| 122 | // Allow long paths by increasing the scanner buffer limit |
| 123 | scanner.Buffer(make([]byte, 0, 64*1024), 2*1024*1024) |
| 124 | |
| 125 | for scanner.Scan() { |
| 126 | line := scanner.Text() |
| 127 | if line == "" { |
| 128 | continue |
| 129 | } |
| 130 | |
| 131 | // Split header and path using the tab delimiter |
| 132 | // to preserve spaces in file names |
| 133 | tab := strings.IndexByte(line, '\t') |
| 134 | if tab == -1 { |
| 135 | return nil, fmt.Errorf("expected tab delimiter in ls-tree output: %s", line) |
| 136 | } |
| 137 | header := line[:tab] |
| 138 | path := line[tab+1:] |
| 139 | |
| 140 | // header fields: mode, type, object, size |
| 141 | parts := strings.Fields(header) |
| 142 | if len(parts) < 4 { |
| 143 | return nil, fmt.Errorf("unexpected ls-tree output format: %s", line) |
| 144 | } |
| 145 | modeNumber := parts[0] |
| 146 | typ := parts[1] |
| 147 | // object := parts[2] |