| 134 | } |
| 135 | |
| 136 | func (p *CodeSearchProvider) gitGrep(ctx context.Context, searchText string, caseSensitive bool, usePerlRegexp bool, pathspec []string) (string, error) { |
| 137 | cmdArgs := p.buildGrepArgs(searchText, caseSensitive, usePerlRegexp, false, pathspec) |
| 138 | if cmdArgs == nil { |
| 139 | return "Error: ref must not start with '-'", nil |
| 140 | } |
| 141 | |
| 142 | outStr, errStr, err := p.runGitGrep(ctx, cmdArgs) |
| 143 | |
| 144 | // Non-git directory: `git grep` exits 128 with "not a git repository". |
| 145 | // `ocr scan` supports plain directories, so retry in --no-index mode, which |
| 146 | // searches the working tree directly while still honoring .gitignore. |
| 147 | // Ref-based search needs a real repo, so it is not retried. |
| 148 | if err != nil && p.FileReader.Ref == "" && isNotGitRepoError(err, errStr) { |
| 149 | cmdArgs = p.buildGrepArgs(searchText, caseSensitive, usePerlRegexp, true, pathspec) |
| 150 | outStr, errStr, err = p.runGitGrep(ctx, cmdArgs) |
| 151 | } |
| 152 | |
| 153 | if err != nil { |
| 154 | if errors.Is(err, context.DeadlineExceeded) { |
| 155 | return "", fmt.Errorf("git grep timed out; try narrowing file_patterns to a more specific path: %w", err) |
| 156 | } |
| 157 | if errors.Is(err, context.Canceled) { |
| 158 | return "", err |
| 159 | } |
| 160 | if outStr == "" { |
| 161 | var exitErr *exec.ExitError |
| 162 | exitCode := -1 |
| 163 | if errors.As(err, &exitErr) { |
| 164 | exitCode = exitErr.ExitCode() |
| 165 | } |
| 166 | if errStr == "" && exitCode == 1 { |
| 167 | return "No matches found", nil |
| 168 | } |
| 169 | trimmedErr := trimGitUsage(errStr, exitCode) |
| 170 | if trimmedErr == "" { |
| 171 | return "", fmt.Errorf("git grep failed: %w", err) |
| 172 | } |
| 173 | return "", fmt.Errorf("git grep failed: %w: %s", err, trimmedErr) |
| 174 | } |
| 175 | } |
| 176 | |
| 177 | lines := strings.Split(strings.TrimRight(outStr, "\n"), "\n") |
| 178 | truncated := len(lines) >= gitGrepMaxCount |
| 179 | |
| 180 | type match struct { |
| 181 | lineNum int |
| 182 | content string |
| 183 | } |
| 184 | fileMatches := make(map[string][]match) |
| 185 | var fileOrder []string |
| 186 | seen := make(map[string]bool) |
| 187 | |
| 188 | hasRef := p.FileReader.Ref != "" |
| 189 | splitN := 3 |
| 190 | offset := 0 |
| 191 | if hasRef { |
| 192 | splitN = 4 |
| 193 | offset = 1 |