Skip to content

Commit

Permalink
Add refspec bindings (#898) (#906)
Browse files Browse the repository at this point in the history
This add support for the parse, access, and transform functions for
refspec objects.

(cherry picked from commit eae0077)

Co-authored-by: William Bain <[email protected]>
  • Loading branch information
github-actions[bot] and wabain authored Feb 25, 2022
1 parent 88968b1 commit cbca5b8
Show file tree
Hide file tree
Showing 2 changed files with 224 additions and 0 deletions.
149 changes: 149 additions & 0 deletions refspec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package git

/*
#include <git2.h>
*/
import "C"
import (
"runtime"
"unsafe"
)

type Refspec struct {
doNotCompare
ptr *C.git_refspec
}

// ParseRefspec parses a given refspec string
func ParseRefspec(input string, isFetch bool) (*Refspec, error) {
var ptr *C.git_refspec

cinput := C.CString(input)
defer C.free(unsafe.Pointer(cinput))

runtime.LockOSThread()
defer runtime.UnlockOSThread()

ret := C.git_refspec_parse(&ptr, cinput, cbool(isFetch))
if ret < 0 {
return nil, MakeGitError(ret)
}

spec := &Refspec{ptr: ptr}
runtime.SetFinalizer(spec, (*Refspec).Free)
return spec, nil
}

// Free releases a refspec object which has been created by ParseRefspec
func (s *Refspec) Free() {
runtime.SetFinalizer(s, nil)
C.git_refspec_free(s.ptr)
}

// Direction returns the refspec's direction
func (s *Refspec) Direction() ConnectDirection {
direction := C.git_refspec_direction(s.ptr)
return ConnectDirection(direction)
}

// Src returns the refspec's source specifier
func (s *Refspec) Src() string {
var ret string
cstr := C.git_refspec_src(s.ptr)

if cstr != nil {
ret = C.GoString(cstr)
}

runtime.KeepAlive(s)
return ret
}

// Dst returns the refspec's destination specifier
func (s *Refspec) Dst() string {
var ret string
cstr := C.git_refspec_dst(s.ptr)

if cstr != nil {
ret = C.GoString(cstr)
}

runtime.KeepAlive(s)
return ret
}

// Force returns the refspec's force-update setting
func (s *Refspec) Force() bool {
force := C.git_refspec_force(s.ptr)
return force != 0
}

// String returns the refspec's string representation
func (s *Refspec) String() string {
var ret string
cstr := C.git_refspec_string(s.ptr)

if cstr != nil {
ret = C.GoString(cstr)
}

runtime.KeepAlive(s)
return ret
}

// SrcMatches checks if a refspec's source descriptor matches a reference
func (s *Refspec) SrcMatches(refname string) bool {
cname := C.CString(refname)
defer C.free(unsafe.Pointer(cname))

matches := C.git_refspec_src_matches(s.ptr, cname)
return matches != 0
}

// SrcMatches checks if a refspec's destination descriptor matches a reference
func (s *Refspec) DstMatches(refname string) bool {
cname := C.CString(refname)
defer C.free(unsafe.Pointer(cname))

matches := C.git_refspec_dst_matches(s.ptr, cname)
return matches != 0
}

// Transform a reference to its target following the refspec's rules
func (s *Refspec) Transform(refname string) (string, error) {
buf := C.git_buf{}

cname := C.CString(refname)
defer C.free(unsafe.Pointer(cname))

runtime.LockOSThread()
defer runtime.UnlockOSThread()

ret := C.git_refspec_transform(&buf, s.ptr, cname)
if ret < 0 {
return "", MakeGitError(ret)
}
defer C.git_buf_dispose(&buf)

return C.GoString(buf.ptr), nil
}

// Rtransform converts a target reference to its source reference following the
// refspec's rules
func (s *Refspec) Rtransform(refname string) (string, error) {
buf := C.git_buf{}

cname := C.CString(refname)
defer C.free(unsafe.Pointer(cname))

runtime.LockOSThread()
defer runtime.UnlockOSThread()

ret := C.git_refspec_rtransform(&buf, s.ptr, cname)
if ret < 0 {
return "", MakeGitError(ret)
}
defer C.git_buf_dispose(&buf)

return C.GoString(buf.ptr), nil
}
75 changes: 75 additions & 0 deletions refspec_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package git

import (
"testing"
)

func TestRefspec(t *testing.T) {
t.Parallel()

const (
input = "+refs/heads/*:refs/remotes/origin/*"
mainLocal = "refs/heads/main"
mainRemote = "refs/remotes/origin/main"
)

refspec, err := ParseRefspec(input, true)
checkFatal(t, err)

// Accessors

s := refspec.String()
if s != input {
t.Errorf("expected string %q, got %q", input, s)
}

if d := refspec.Direction(); d != ConnectDirectionFetch {
t.Errorf("expected fetch refspec, got direction %v", d)
}

if pat, expected := refspec.Src(), "refs/heads/*"; pat != expected {
t.Errorf("expected refspec src %q, got %q", expected, pat)
}

if pat, expected := refspec.Dst(), "refs/remotes/origin/*"; pat != expected {
t.Errorf("expected refspec dst %q, got %q", expected, pat)
}

if !refspec.Force() {
t.Error("expected refspec force flag")
}

// SrcMatches

if !refspec.SrcMatches(mainLocal) {
t.Errorf("refspec source did not match %q", mainLocal)
}

if refspec.SrcMatches("refs/tags/v1.0") {
t.Error("refspec source matched under refs/tags")
}

// DstMatches

if !refspec.DstMatches(mainRemote) {
t.Errorf("refspec destination did not match %q", mainRemote)
}

if refspec.DstMatches("refs/tags/v1.0") {
t.Error("refspec destination matched under refs/tags")
}

// Transforms

fromLocal, err := refspec.Transform(mainLocal)
checkFatal(t, err)
if fromLocal != mainRemote {
t.Errorf("transform by refspec returned %s; expected %s", fromLocal, mainRemote)
}

fromRemote, err := refspec.Rtransform(mainRemote)
checkFatal(t, err)
if fromRemote != mainLocal {
t.Errorf("rtransform by refspec returned %s; expected %s", fromRemote, mainLocal)
}
}

0 comments on commit cbca5b8

Please sign in to comment.