Skip to content
This repository was archived by the owner on Sep 11, 2020. It is now read-only.

Commit 155de5e

Browse files
committed
core: RefSpec support
1 parent 0fa637d commit 155de5e

File tree

2 files changed

+159
-0
lines changed

2 files changed

+159
-0
lines changed

core/reference.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,3 +225,107 @@ func resolveReference(s ReferenceStorage, r *Reference, recursion int) (*Referen
225225
recursion++
226226
return resolveReference(s, t, recursion)
227227
}
228+
229+
const (
230+
refSpecWildcard = "*"
231+
refSpecForce = "+"
232+
refSpecSeparator = ":"
233+
)
234+
235+
// RefSpec is a mapping from local branches to remote references
236+
// The format of the refspec is an optional +, followed by <src>:<dst>, where
237+
// <src> is the pattern for references on the remote side and <dst> is where
238+
// those references will be written locally. The + tells Git to update the
239+
// reference even if it isn’t a fast-forward.
240+
// eg.: "+refs/*/*:refs/remotes/origin/*"
241+
//
242+
// https://git-scm.com/book/es/v2/Git-Internals-The-Refspec
243+
type RefSpec string
244+
245+
// IsValid validates the RefSpec
246+
func (s RefSpec) IsValid() bool {
247+
spec := string(s)
248+
if strings.Count(spec, refSpecSeparator) != 1 {
249+
return false
250+
}
251+
252+
sep := strings.Index(spec, refSpecSeparator)
253+
if sep == len(spec) {
254+
return false
255+
}
256+
257+
ws := strings.Count(spec[0:sep], refSpecWildcard)
258+
wd := strings.Count(spec[sep+1:len(spec)], refSpecWildcard)
259+
return ws == wd && ws < 2 && wd < 2
260+
}
261+
262+
// IsForceUpdate returns if update is allowed in non fast-forward merges
263+
func (s RefSpec) IsForceUpdate() bool {
264+
if s[0] == refSpecForce[0] {
265+
return true
266+
}
267+
268+
return false
269+
}
270+
271+
// Src return the src side
272+
func (s RefSpec) Src() string {
273+
spec := string(s)
274+
start := strings.Index(spec, refSpecForce) + 1
275+
end := strings.Index(spec, refSpecSeparator)
276+
277+
return spec[start:end]
278+
}
279+
280+
// Match match the given ReferenceName against the source
281+
func (s RefSpec) Match(n ReferenceName) bool {
282+
if !s.isGlob() {
283+
return s.matchExact(n)
284+
}
285+
286+
return s.matchGlob(n)
287+
}
288+
289+
func (s RefSpec) isGlob() bool {
290+
return strings.Index(string(s), refSpecWildcard) != -1
291+
}
292+
293+
func (s RefSpec) matchExact(n ReferenceName) bool {
294+
return s.Src() == n.String()
295+
}
296+
297+
func (s RefSpec) matchGlob(n ReferenceName) bool {
298+
src := s.Src()
299+
name := n.String()
300+
wildcard := strings.Index(src, refSpecWildcard)
301+
302+
var prefix, suffix string
303+
prefix = src[0:wildcard]
304+
if len(src) < wildcard {
305+
suffix = src[wildcard+1 : len(suffix)]
306+
}
307+
308+
return len(name) > len(prefix)+len(suffix) &&
309+
strings.HasPrefix(name, prefix) &&
310+
strings.HasSuffix(name, suffix)
311+
}
312+
313+
// Dst returns the destination for the given remote reference
314+
func (s RefSpec) Dst(n ReferenceName) ReferenceName {
315+
spec := string(s)
316+
start := strings.Index(spec, refSpecSeparator) + 1
317+
dst := spec[start:len(spec)]
318+
src := s.Src()
319+
320+
if !s.isGlob() {
321+
return ReferenceName(dst)
322+
}
323+
324+
name := n.String()
325+
ws := strings.Index(src, refSpecWildcard)
326+
wd := strings.Index(dst, refSpecWildcard)
327+
match := name[ws : len(name)-(len(src)-(ws+1))]
328+
329+
return ReferenceName(dst[0:wd] + match + dst[wd+1:len(dst)])
330+
331+
}

core/reference_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,58 @@ func (s *ReferenceSuite) TestReferenceSliceIterForEachStop(c *C) {
121121

122122
c.Assert(count, Equals, 1)
123123
}
124+
125+
func (s *ReferenceSuite) TestRefSpecIsValid(c *C) {
126+
spec := RefSpec("+refs/heads/*:refs/remotes/origin/*")
127+
c.Assert(spec.IsValid(), Equals, true)
128+
129+
spec = RefSpec("refs/heads/*:refs/remotes/origin/")
130+
c.Assert(spec.IsValid(), Equals, false)
131+
132+
spec = RefSpec("refs/heads/master:refs/remotes/origin/master")
133+
c.Assert(spec.IsValid(), Equals, true)
134+
135+
spec = RefSpec("refs/heads/*")
136+
c.Assert(spec.IsValid(), Equals, false)
137+
}
138+
139+
func (s *ReferenceSuite) TestRefSpecIsForceUpdate(c *C) {
140+
spec := RefSpec("+refs/heads/*:refs/remotes/origin/*")
141+
c.Assert(spec.IsForceUpdate(), Equals, true)
142+
143+
spec = RefSpec("refs/heads/*:refs/remotes/origin/*")
144+
c.Assert(spec.IsForceUpdate(), Equals, false)
145+
}
146+
147+
func (s *ReferenceSuite) TestRefSpecSrc(c *C) {
148+
spec := RefSpec("refs/heads/*:refs/remotes/origin/*")
149+
c.Assert(spec.Src(), Equals, "refs/heads/*")
150+
}
151+
152+
func (s *ReferenceSuite) TestRefSpecMatch(c *C) {
153+
spec := RefSpec("refs/heads/master:refs/remotes/origin/master")
154+
c.Assert(spec.Match(ReferenceName("refs/heads/foo")), Equals, false)
155+
c.Assert(spec.Match(ReferenceName("refs/heads/master")), Equals, true)
156+
}
157+
158+
func (s *ReferenceSuite) TestRefSpecMatchBlob(c *C) {
159+
spec := RefSpec("refs/heads/*:refs/remotes/origin/*")
160+
c.Assert(spec.Match(ReferenceName("refs/tag/foo")), Equals, false)
161+
c.Assert(spec.Match(ReferenceName("refs/heads/foo")), Equals, true)
162+
}
163+
164+
func (s *ReferenceSuite) TestRefSpecDst(c *C) {
165+
spec := RefSpec("refs/heads/master:refs/remotes/origin/master")
166+
c.Assert(
167+
spec.Dst(ReferenceName("refs/heads/master")).String(), Equals,
168+
"refs/remotes/origin/master",
169+
)
170+
}
171+
172+
func (s *ReferenceSuite) TestRefSpecDstBlob(c *C) {
173+
spec := RefSpec("refs/heads/*:refs/remotes/origin/*")
174+
c.Assert(
175+
spec.Dst(ReferenceName("refs/heads/foo")).String(), Equals,
176+
"refs/remotes/origin/foo",
177+
)
178+
}

0 commit comments

Comments
 (0)