fork of go-git with some jj specific features
1package config
2
3import (
4 "errors"
5 "strings"
6
7 "gopkg.in/src-d/go-git.v4/plumbing"
8)
9
10const (
11 refSpecWildcard = "*"
12 refSpecForce = "+"
13 refSpecSeparator = ":"
14)
15
16var (
17 ErrRefSpecMalformedSeparator = errors.New("malformed refspec, separators are wrong")
18 ErrRefSpecMalformedWildcard = errors.New("malformed refspec, mismatched number of wildcards")
19)
20
21// RefSpec is a mapping from local branches to remote references
22// The format of the refspec is an optional +, followed by <src>:<dst>, where
23// <src> is the pattern for references on the remote side and <dst> is where
24// those references will be written locally. The + tells Git to update the
25// reference even if it isn’t a fast-forward.
26// eg.: "+refs/heads/*:refs/remotes/origin/*"
27//
28// https://git-scm.com/book/es/v2/Git-Internals-The-Refspec
29type RefSpec string
30
31// Validate validates the RefSpec
32func (s RefSpec) Validate() error {
33 spec := string(s)
34 if strings.Count(spec, refSpecSeparator) != 1 {
35 return ErrRefSpecMalformedSeparator
36 }
37
38 sep := strings.Index(spec, refSpecSeparator)
39 if sep == len(spec)-1 {
40 return ErrRefSpecMalformedSeparator
41 }
42
43 ws := strings.Count(spec[0:sep], refSpecWildcard)
44 wd := strings.Count(spec[sep+1:], refSpecWildcard)
45 if ws == wd && ws < 2 && wd < 2 {
46 return nil
47 }
48
49 return ErrRefSpecMalformedWildcard
50}
51
52// IsForceUpdate returns if update is allowed in non fast-forward merges.
53func (s RefSpec) IsForceUpdate() bool {
54 return s[0] == refSpecForce[0]
55}
56
57// IsDelete returns true if the refspec indicates a delete (empty src).
58func (s RefSpec) IsDelete() bool {
59 return s[0] == refSpecSeparator[0]
60}
61
62// Src return the src side.
63func (s RefSpec) Src() string {
64 spec := string(s)
65
66 var start int
67 if s.IsForceUpdate() {
68 start = 1
69 } else {
70 start = 0
71 }
72 end := strings.Index(spec, refSpecSeparator)
73
74 return spec[start:end]
75}
76
77// Match match the given plumbing.ReferenceName against the source.
78func (s RefSpec) Match(n plumbing.ReferenceName) bool {
79 if !s.IsWildcard() {
80 return s.matchExact(n)
81 }
82
83 return s.matchGlob(n)
84}
85
86// IsWildcard returns true if the RefSpec contains a wildcard.
87func (s RefSpec) IsWildcard() bool {
88 return strings.Contains(string(s), refSpecWildcard)
89}
90
91func (s RefSpec) matchExact(n plumbing.ReferenceName) bool {
92 return s.Src() == n.String()
93}
94
95func (s RefSpec) matchGlob(n plumbing.ReferenceName) bool {
96 src := s.Src()
97 name := n.String()
98 wildcard := strings.Index(src, refSpecWildcard)
99
100 var prefix, suffix string
101 prefix = src[0:wildcard]
102 if len(src) < wildcard {
103 suffix = src[wildcard+1 : len(suffix)]
104 }
105
106 return len(name) > len(prefix)+len(suffix) &&
107 strings.HasPrefix(name, prefix) &&
108 strings.HasSuffix(name, suffix)
109}
110
111// Dst returns the destination for the given remote reference.
112func (s RefSpec) Dst(n plumbing.ReferenceName) plumbing.ReferenceName {
113 spec := string(s)
114 start := strings.Index(spec, refSpecSeparator) + 1
115 dst := spec[start:]
116 src := s.Src()
117
118 if !s.IsWildcard() {
119 return plumbing.ReferenceName(dst)
120 }
121
122 name := n.String()
123 ws := strings.Index(src, refSpecWildcard)
124 wd := strings.Index(dst, refSpecWildcard)
125 match := name[ws : len(name)-(len(src)-(ws+1))]
126
127 return plumbing.ReferenceName(dst[0:wd] + match + dst[wd+1:])
128}
129
130func (s RefSpec) String() string {
131 return string(s)
132}
133
134// MatchAny returns true if any of the RefSpec match with the given ReferenceName.
135func MatchAny(l []RefSpec, n plumbing.ReferenceName) bool {
136 for _, r := range l {
137 if r.Match(n) {
138 return true
139 }
140 }
141
142 return false
143}