| 161 | } |
| 162 | |
| 163 | func detectGitPath(path string, depth int) (string, error) { |
| 164 | if depth >= 10 { |
| 165 | return "", fmt.Errorf("gitdir loop detected") |
| 166 | } |
| 167 | |
| 168 | // normalize the path |
| 169 | path, err := filepath.Abs(path) |
| 170 | if err != nil { |
| 171 | return "", err |
| 172 | } |
| 173 | |
| 174 | for { |
| 175 | fi, err := os.Stat(filepath.Join(path, ".git")) |
| 176 | if err == nil { |
| 177 | if !fi.IsDir() { |
| 178 | // See if our .git item is a dotfile that holds a submodule reference |
| 179 | dotfile, err := os.Open(filepath.Join(path, fi.Name())) |
| 180 | if err != nil { |
| 181 | // Can't open error |
| 182 | return "", fmt.Errorf(".git exists but is not a directory or a readable file: %w", err) |
| 183 | } |
| 184 | // We aren't going to defer the dotfile.Close, because we might keep looping, so we have to be sure to |
| 185 | // clean up before returning an error |
| 186 | reader := bufio.NewReader(io.LimitReader(dotfile, 2048)) |
| 187 | line, _, err := reader.ReadLine() |
| 188 | _ = dotfile.Close() |
| 189 | if err != nil { |
| 190 | return "", fmt.Errorf(".git exists but is not a directory and cannot be read: %w", err) |
| 191 | } |
| 192 | dotContent := string(line) |
| 193 | if strings.HasPrefix(dotContent, "gitdir:") { |
| 194 | // This is a submodule parent path link. Strip the prefix, clean the string of whitespace just to |
| 195 | // be safe, and return |
| 196 | dotContent = strings.TrimSpace(strings.TrimPrefix(dotContent, "gitdir: ")) |
| 197 | p, err := detectGitPath(dotContent, depth+1) |
| 198 | if err != nil { |
| 199 | return "", fmt.Errorf(".git gitdir error: %w", err) |
| 200 | } |
| 201 | return p, nil |
| 202 | } |
| 203 | return "", fmt.Errorf(".git exist but is not a directory or module/workspace file") |
| 204 | } |
| 205 | return filepath.Join(path, ".git"), nil |
| 206 | } |
| 207 | if !os.IsNotExist(err) { |
| 208 | // unknown error |
| 209 | return "", err |
| 210 | } |
| 211 | |
| 212 | // detect bare repo |
| 213 | ok, err := isGitDir(path) |
| 214 | if err != nil { |
| 215 | return "", err |
| 216 | } |
| 217 | if ok { |
| 218 | return path, nil |
| 219 | } |
| 220 | |