package core import ( "fmt" "net/url" "strings" ) // RepoRef identifies a repository by its owner (without the leading '~') and // name. Both fields should be validated with ValidOwner / ValidRepoName before // they are used to build a filesystem path. type RepoRef struct { Owner string Name string } // CompareSpec is a parsed "base..head" or "base...head" comparison request. // ThreeDot selects merge-base (symmetric-difference) semantics, matching git's // "base...head"; when false the comparison is the plain "base..head" range. type CompareSpec struct { Base string Head string ThreeDot bool } // ParseCompareSpec parses the compare wildcard from a URL path segment into a // CompareSpec. The grammar is: // // base "..." head -> ThreeDot = true (merge-base / symmetric diff) // base ".." head -> ThreeDot = false (direct range) // // The three-dot form is tried first, because "..." contains "..". Each side is // then url.PathUnescape'd and validated with ValidRef. Both sides must be // non-empty and valid or ErrBadRef (wrapped with detail) is returned. // // Stripping a trailing ".patch" (the raw-diff escape hatch) is the caller's // responsibility and is intentionally not handled here. func ParseCompareSpec(raw string) (CompareSpec, error) { var base, head string var threeDot bool switch { case strings.Contains(raw, "..."): i := strings.Index(raw, "...") threeDot = true base, head = raw[:i], raw[i+3:] case strings.Contains(raw, ".."): i := strings.Index(raw, "..") threeDot = false base, head = raw[:i], raw[i+2:] default: return CompareSpec{}, fmt.Errorf("%w: missing '..' or '...' separator in %q", ErrBadRef, raw) } b, err := url.PathUnescape(base) if err != nil { return CompareSpec{}, fmt.Errorf("%w: bad percent-encoding in base: %v", ErrBadRef, err) } h, err := url.PathUnescape(head) if err != nil { return CompareSpec{}, fmt.Errorf("%w: bad percent-encoding in head: %v", ErrBadRef, err) } if !ValidRef(b) { return CompareSpec{}, fmt.Errorf("%w: invalid base ref %q", ErrBadRef, b) } if !ValidRef(h) { return CompareSpec{}, fmt.Errorf("%w: invalid head ref %q", ErrBadRef, h) } return CompareSpec{Base: b, Head: h, ThreeDot: threeDot}, nil }