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
}