(ctx context.Context, refString string)
| 139 | type gitEndpointError struct{ error } |
| 140 | |
| 141 | func ParseGitRefString(ctx context.Context, refString string) (_ ParsedGitRefString, rerr error) { |
| 142 | _, span := Tracer(ctx).Start(ctx, fmt.Sprintf("parseGitRefString: %s", refString), telemetry.Internal()) |
| 143 | defer telemetry.EndWithCause(span, &rerr) |
| 144 | |
| 145 | scheme, schemelessRef := parseScheme(refString) |
| 146 | |
| 147 | if scheme == NoScheme && isSCPLike(schemelessRef) { |
| 148 | scheme = SchemeSCPLike |
| 149 | // transform the ":" into a "/" to rely on a unified logic after |
| 150 | // works because "git@github.com:user" is equivalent to "ssh://git@ref/user" |
| 151 | schemelessRef = strings.Replace(schemelessRef, ":", "/", 1) |
| 152 | } |
| 153 | |
| 154 | // Trick: |
| 155 | // as we removed the scheme above with `parseScheme``, and the SCP-like refs are |
| 156 | // now without ":", all refs are in such format: "[git@]github.com/user/path...@version" |
| 157 | // transport.NewEndpoint parses users only for SSH refs. As HTTP refs without scheme are valid SSH refs |
| 158 | // we use the "ssh://" prefix to parse properly both explicit / SCP-like and HTTP refs |
| 159 | // and delegate the logic to parse the host / path and user to the library |
| 160 | endpoint, err := transport.NewEndpoint("ssh://" + schemelessRef) |
| 161 | if err != nil { |
| 162 | return ParsedGitRefString{}, gitEndpointError{fmt.Errorf("failed to create git endpoint: %w", err)} |
| 163 | } |
| 164 | |
| 165 | gitParsed := ParsedGitRefString{ |
| 166 | modPath: endpoint.Host + endpoint.Path, |
| 167 | scheme: scheme, |
| 168 | } |
| 169 | |
| 170 | parts := strings.SplitN(endpoint.Path, "@", 2) |
| 171 | if len(parts) == 2 { |
| 172 | gitParsed.modPath = endpoint.Host + parts[0] |
| 173 | gitParsed.ModVersion = parts[1] |
| 174 | gitParsed.hasVersion = true |
| 175 | } |
| 176 | |
| 177 | // Try to isolate the root of the git repo |
| 178 | // RepoRootForImportPath does not support SCP-like ref style. In parseGitEndpoint, we made sure that all refs |
| 179 | // would be compatible with this function to benefit from the repo URL and root splitting |
| 180 | repoRoot, err := vcs.RepoRootForImportPath(gitParsed.modPath, false) |
| 181 | if err != nil { |
| 182 | return ParsedGitRefString{}, gitEndpointError{fmt.Errorf("failed to get repo root for import path: %w", err)} |
| 183 | } |
| 184 | if repoRoot == nil || repoRoot.VCS == nil { |
| 185 | return ParsedGitRefString{}, fmt.Errorf("invalid repo root for import path: %s", gitParsed.modPath) |
| 186 | } |
| 187 | if repoRoot.VCS.Name != "Git" { |
| 188 | return ParsedGitRefString{}, fmt.Errorf("repo root is not a Git repo: %s", gitParsed.modPath) |
| 189 | } |
| 190 | |
| 191 | gitParsed.RepoRoot = repoRoot |
| 192 | |
| 193 | // the extra "/" trim is important as subpath traversal such as /../ are being cleaned by filePath.Clean |
| 194 | gitParsed.RepoRootSubdir = strings.TrimPrefix(strings.TrimPrefix(gitParsed.modPath, repoRoot.Root), "/") |
| 195 | if gitParsed.RepoRootSubdir == "" { |
| 196 | gitParsed.RepoRootSubdir = "/" |
| 197 | } |
| 198 | gitParsed.RepoRootSubdir = filepath.Clean(gitParsed.RepoRootSubdir) |
no test coverage detected