~bigbes/sr-ht-compare

ref: 9df88758b3964d70c568f5675bee9a775bb85d70 sr-ht-compare/core/spec.go -rw-r--r-- 2.2 KiB
9df88758 — bigbes web: tighten diff and file-tree spacing 30 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
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
}