151 lines
3.6 KiB
Go
151 lines
3.6 KiB
Go
package config
|
||
|
||
import (
|
||
"errors"
|
||
"strings"
|
||
|
||
"gopkg.in/src-d/go-git.v4/plumbing"
|
||
)
|
||
|
||
const (
|
||
refSpecWildcard = "*"
|
||
refSpecForce = "+"
|
||
refSpecSeparator = ":"
|
||
)
|
||
|
||
var (
|
||
ErrRefSpecMalformedSeparator = errors.New("malformed refspec, separators are wrong")
|
||
ErrRefSpecMalformedWildcard = errors.New("malformed refspec, mismatched number of wildcards")
|
||
)
|
||
|
||
// RefSpec is a mapping from local branches to remote references.
|
||
// The format of the refspec is an optional +, followed by <src>:<dst>, where
|
||
// <src> is the pattern for references on the remote side and <dst> is where
|
||
// those references will be written locally. The + tells Git to update the
|
||
// reference even if it isn’t a fast-forward.
|
||
// eg.: "+refs/heads/*:refs/remotes/origin/*"
|
||
//
|
||
// https://git-scm.com/book/es/v2/Git-Internals-The-Refspec
|
||
type RefSpec string
|
||
|
||
// Validate validates the RefSpec
|
||
func (s RefSpec) Validate() error {
|
||
spec := string(s)
|
||
if strings.Count(spec, refSpecSeparator) != 1 {
|
||
return ErrRefSpecMalformedSeparator
|
||
}
|
||
|
||
sep := strings.Index(spec, refSpecSeparator)
|
||
if sep == len(spec)-1 {
|
||
return ErrRefSpecMalformedSeparator
|
||
}
|
||
|
||
ws := strings.Count(spec[0:sep], refSpecWildcard)
|
||
wd := strings.Count(spec[sep+1:], refSpecWildcard)
|
||
if ws == wd && ws < 2 && wd < 2 {
|
||
return nil
|
||
}
|
||
|
||
return ErrRefSpecMalformedWildcard
|
||
}
|
||
|
||
// IsForceUpdate returns if update is allowed in non fast-forward merges.
|
||
func (s RefSpec) IsForceUpdate() bool {
|
||
return s[0] == refSpecForce[0]
|
||
}
|
||
|
||
// IsDelete returns true if the refspec indicates a delete (empty src).
|
||
func (s RefSpec) IsDelete() bool {
|
||
return s[0] == refSpecSeparator[0]
|
||
}
|
||
|
||
// Src return the src side.
|
||
func (s RefSpec) Src() string {
|
||
spec := string(s)
|
||
|
||
var start int
|
||
if s.IsForceUpdate() {
|
||
start = 1
|
||
} else {
|
||
start = 0
|
||
}
|
||
end := strings.Index(spec, refSpecSeparator)
|
||
|
||
return spec[start:end]
|
||
}
|
||
|
||
// Match match the given plumbing.ReferenceName against the source.
|
||
func (s RefSpec) Match(n plumbing.ReferenceName) bool {
|
||
if !s.IsWildcard() {
|
||
return s.matchExact(n)
|
||
}
|
||
|
||
return s.matchGlob(n)
|
||
}
|
||
|
||
// IsWildcard returns true if the RefSpec contains a wildcard.
|
||
func (s RefSpec) IsWildcard() bool {
|
||
return strings.Contains(string(s), refSpecWildcard)
|
||
}
|
||
|
||
func (s RefSpec) matchExact(n plumbing.ReferenceName) bool {
|
||
return s.Src() == n.String()
|
||
}
|
||
|
||
func (s RefSpec) matchGlob(n plumbing.ReferenceName) bool {
|
||
src := s.Src()
|
||
name := n.String()
|
||
wildcard := strings.Index(src, refSpecWildcard)
|
||
|
||
var prefix, suffix string
|
||
prefix = src[0:wildcard]
|
||
if len(src) > wildcard+1 {
|
||
suffix = src[wildcard+1:]
|
||
}
|
||
|
||
return len(name) >= len(prefix)+len(suffix) &&
|
||
strings.HasPrefix(name, prefix) &&
|
||
strings.HasSuffix(name, suffix)
|
||
}
|
||
|
||
// Dst returns the destination for the given remote reference.
|
||
func (s RefSpec) Dst(n plumbing.ReferenceName) plumbing.ReferenceName {
|
||
spec := string(s)
|
||
start := strings.Index(spec, refSpecSeparator) + 1
|
||
dst := spec[start:]
|
||
src := s.Src()
|
||
|
||
if !s.IsWildcard() {
|
||
return plumbing.ReferenceName(dst)
|
||
}
|
||
|
||
name := n.String()
|
||
ws := strings.Index(src, refSpecWildcard)
|
||
wd := strings.Index(dst, refSpecWildcard)
|
||
match := name[ws : len(name)-(len(src)-(ws+1))]
|
||
|
||
return plumbing.ReferenceName(dst[0:wd] + match + dst[wd+1:])
|
||
}
|
||
|
||
func (s RefSpec) Reverse() RefSpec {
|
||
spec := string(s)
|
||
separator := strings.Index(spec, refSpecSeparator)
|
||
|
||
return RefSpec(spec[separator+1:] + refSpecSeparator + spec[:separator])
|
||
}
|
||
|
||
func (s RefSpec) String() string {
|
||
return string(s)
|
||
}
|
||
|
||
// MatchAny returns true if any of the RefSpec match with the given ReferenceName.
|
||
func MatchAny(l []RefSpec, n plumbing.ReferenceName) bool {
|
||
for _, r := range l {
|
||
if r.Match(n) {
|
||
return true
|
||
}
|
||
}
|
||
|
||
return false
|
||
}
|