From cbca5b82b8c22c08c183a1f44cad4b8b51ba6f25 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 24 Feb 2022 19:30:40 -0800 Subject: [PATCH] Add refspec bindings (#898) (#906) This add support for the parse, access, and transform functions for refspec objects. (cherry picked from commit eae00773cce87d5282a8ac7c10b5c1961ee6f9cb) Co-authored-by: William Bain --- refspec.go | 149 ++++++++++++++++++++++++++++++++++++++++++++++++ refspec_test.go | 75 ++++++++++++++++++++++++ 2 files changed, 224 insertions(+) create mode 100644 refspec.go create mode 100644 refspec_test.go diff --git a/refspec.go b/refspec.go new file mode 100644 index 00000000..450e5afc --- /dev/null +++ b/refspec.go @@ -0,0 +1,149 @@ +package git + +/* +#include +*/ +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 +} diff --git a/refspec_test.go b/refspec_test.go new file mode 100644 index 00000000..70641989 --- /dev/null +++ b/refspec_test.go @@ -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) + } +}