canonicalRemote reduces a git remote URL to a stable, credential-free identity string for hashing into repository.identity_sha256, so the same repository yields the same identity regardless of transport or embedded credentials. Network remotes canonicalize to "host[:port]/path": the host is lowerca
(raw string)
| 408 | // |
| 409 | // An empty or unrecognizable input yields "". |
| 410 | func canonicalRemote(raw string) string { |
| 411 | s := strings.TrimSpace(raw) |
| 412 | if s == "" { |
| 413 | return "" |
| 414 | } |
| 415 | // Drop query (?…) and fragment (#…): never part of repository identity. |
| 416 | if i := strings.IndexAny(s, "?#"); i >= 0 { |
| 417 | s = s[:i] |
| 418 | } |
| 419 | // Local remotes carry no stable network identity (see doc comment). Detect |
| 420 | // them before the scp split so a Windows "C:\…" path is not mistaken for a |
| 421 | // "host:path" with host "c". |
| 422 | if isLocalRemote(s) { |
| 423 | return "" |
| 424 | } |
| 425 | // scheme://[user[:pass]@]host[:port]/path. url.Host is "host[:port]" and |
| 426 | // never includes userinfo, so credentials drop out and the port is kept. |
| 427 | if strings.Contains(s, "://") { |
| 428 | if u, err := url.Parse(s); err == nil && u.Scheme != "" && u.Host != "" { |
| 429 | return joinHostPath(strings.ToLower(u.Host), u.Path) |
| 430 | } |
| 431 | return "" |
| 432 | } |
| 433 | // scp-like: [user@]host:path. The userinfo "@" lives in the host segment, |
| 434 | // which ends at the FIRST ":"; split there first so any "@" inside the path |
| 435 | // is preserved rather than truncated as if it were userinfo. |
| 436 | colon := strings.IndexByte(s, ':') |
| 437 | if colon < 0 { |
| 438 | return "" |
| 439 | } |
| 440 | hostSeg, path := s[:colon], s[colon+1:] |
| 441 | if at := strings.LastIndexByte(hostSeg, '@'); at >= 0 { |
| 442 | hostSeg = hostSeg[at+1:] |
| 443 | } |
| 444 | host := strings.ToLower(hostSeg) |
| 445 | if host == "" { |
| 446 | return "" |
| 447 | } |
| 448 | return joinHostPath(host, path) |
| 449 | } |
| 450 | |
| 451 | // joinHostPath assembles the canonical "host[/path]" form, trimming a leading |
| 452 | // "/" and a trailing ".git"/"/" from the path while preserving its case. |