aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMáximo Cuadros <mcuadros@gmail.com>2016-08-18 01:48:25 +0200
committerMáximo Cuadros <mcuadros@gmail.com>2016-08-18 01:48:25 +0200
commit0ed8e180c604f8ff6d68fcaad081978a407e4653 (patch)
tree794cb3b46c71c5a705f4c57f5f76c81d27e4f845
parent46048b6f6cbbdf20d130540e9ef823574e786240 (diff)
downloadgo-git-0ed8e180c604f8ff6d68fcaad081978a407e4653.tar.gz
core: RefSpec support
-rw-r--r--core/reference.go104
-rw-r--r--core/reference_test.go55
2 files changed, 159 insertions, 0 deletions
diff --git a/core/reference.go b/core/reference.go
index bde3ff4..72bfe9d 100644
--- a/core/reference.go
+++ b/core/reference.go
@@ -225,3 +225,107 @@ func resolveReference(s ReferenceStorage, r *Reference, recursion int) (*Referen
recursion++
return resolveReference(s, t, recursion)
}
+
+const (
+ refSpecWildcard = "*"
+ refSpecForce = "+"
+ refSpecSeparator = ":"
+)
+
+// 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/*/*:refs/remotes/origin/*"
+//
+// https://git-scm.com/book/es/v2/Git-Internals-The-Refspec
+type RefSpec string
+
+// IsValid validates the RefSpec
+func (s RefSpec) IsValid() bool {
+ spec := string(s)
+ if strings.Count(spec, refSpecSeparator) != 1 {
+ return false
+ }
+
+ sep := strings.Index(spec, refSpecSeparator)
+ if sep == len(spec) {
+ return false
+ }
+
+ ws := strings.Count(spec[0:sep], refSpecWildcard)
+ wd := strings.Count(spec[sep+1:len(spec)], refSpecWildcard)
+ return ws == wd && ws < 2 && wd < 2
+}
+
+// IsForceUpdate returns if update is allowed in non fast-forward merges
+func (s RefSpec) IsForceUpdate() bool {
+ if s[0] == refSpecForce[0] {
+ return true
+ }
+
+ return false
+}
+
+// Src return the src side
+func (s RefSpec) Src() string {
+ spec := string(s)
+ start := strings.Index(spec, refSpecForce) + 1
+ end := strings.Index(spec, refSpecSeparator)
+
+ return spec[start:end]
+}
+
+// Match match the given ReferenceName against the source
+func (s RefSpec) Match(n ReferenceName) bool {
+ if !s.isGlob() {
+ return s.matchExact(n)
+ }
+
+ return s.matchGlob(n)
+}
+
+func (s RefSpec) isGlob() bool {
+ return strings.Index(string(s), refSpecWildcard) != -1
+}
+
+func (s RefSpec) matchExact(n ReferenceName) bool {
+ return s.Src() == n.String()
+}
+
+func (s RefSpec) matchGlob(n 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 {
+ suffix = src[wildcard+1 : len(suffix)]
+ }
+
+ 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 ReferenceName) ReferenceName {
+ spec := string(s)
+ start := strings.Index(spec, refSpecSeparator) + 1
+ dst := spec[start:len(spec)]
+ src := s.Src()
+
+ if !s.isGlob() {
+ return 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 ReferenceName(dst[0:wd] + match + dst[wd+1:len(dst)])
+
+}
diff --git a/core/reference_test.go b/core/reference_test.go
index 3739a15..3d5a9b2 100644
--- a/core/reference_test.go
+++ b/core/reference_test.go
@@ -121,3 +121,58 @@ func (s *ReferenceSuite) TestReferenceSliceIterForEachStop(c *C) {
c.Assert(count, Equals, 1)
}
+
+func (s *ReferenceSuite) TestRefSpecIsValid(c *C) {
+ spec := RefSpec("+refs/heads/*:refs/remotes/origin/*")
+ c.Assert(spec.IsValid(), Equals, true)
+
+ spec = RefSpec("refs/heads/*:refs/remotes/origin/")
+ c.Assert(spec.IsValid(), Equals, false)
+
+ spec = RefSpec("refs/heads/master:refs/remotes/origin/master")
+ c.Assert(spec.IsValid(), Equals, true)
+
+ spec = RefSpec("refs/heads/*")
+ c.Assert(spec.IsValid(), Equals, false)
+}
+
+func (s *ReferenceSuite) TestRefSpecIsForceUpdate(c *C) {
+ spec := RefSpec("+refs/heads/*:refs/remotes/origin/*")
+ c.Assert(spec.IsForceUpdate(), Equals, true)
+
+ spec = RefSpec("refs/heads/*:refs/remotes/origin/*")
+ c.Assert(spec.IsForceUpdate(), Equals, false)
+}
+
+func (s *ReferenceSuite) TestRefSpecSrc(c *C) {
+ spec := RefSpec("refs/heads/*:refs/remotes/origin/*")
+ c.Assert(spec.Src(), Equals, "refs/heads/*")
+}
+
+func (s *ReferenceSuite) TestRefSpecMatch(c *C) {
+ spec := RefSpec("refs/heads/master:refs/remotes/origin/master")
+ c.Assert(spec.Match(ReferenceName("refs/heads/foo")), Equals, false)
+ c.Assert(spec.Match(ReferenceName("refs/heads/master")), Equals, true)
+}
+
+func (s *ReferenceSuite) TestRefSpecMatchBlob(c *C) {
+ spec := RefSpec("refs/heads/*:refs/remotes/origin/*")
+ c.Assert(spec.Match(ReferenceName("refs/tag/foo")), Equals, false)
+ c.Assert(spec.Match(ReferenceName("refs/heads/foo")), Equals, true)
+}
+
+func (s *ReferenceSuite) TestRefSpecDst(c *C) {
+ spec := RefSpec("refs/heads/master:refs/remotes/origin/master")
+ c.Assert(
+ spec.Dst(ReferenceName("refs/heads/master")).String(), Equals,
+ "refs/remotes/origin/master",
+ )
+}
+
+func (s *ReferenceSuite) TestRefSpecDstBlob(c *C) {
+ spec := RefSpec("refs/heads/*:refs/remotes/origin/*")
+ c.Assert(
+ spec.Dst(ReferenceName("refs/heads/foo")).String(), Equals,
+ "refs/remotes/origin/foo",
+ )
+}