summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--clone.go1
-rw-r--r--git.go6
-rw-r--r--git_test.go14
-rw-r--r--remote.go114
-rw-r--r--remote_test.go1
-rw-r--r--repository.go2
-rw-r--r--transport.go431
-rw-r--r--transport_test.go72
-rw-r--r--wrapper.c113
9 files changed, 746 insertions, 8 deletions
diff --git a/clone.go b/clone.go
index 4921e12..b02a43e 100644
--- a/clone.go
+++ b/clone.go
@@ -85,6 +85,7 @@ func remoteCreateCallback(
// clear finalizer as the calling C function will
// free the remote itself
runtime.SetFinalizer(remote, nil)
+ remote.repo.Remotes.untrackRemote(remote)
return C.int(ErrorCodeOK)
}
diff --git a/git.go b/git.go
index 3f51048..adf07ae 100644
--- a/git.go
+++ b/git.go
@@ -128,6 +128,7 @@ var (
type doNotCompare [0]func()
var pointerHandles *HandleList
+var remotePointers *remotePointerList
func init() {
initLibGit2()
@@ -135,6 +136,7 @@ func init() {
func initLibGit2() {
pointerHandles = NewHandleList()
+ remotePointers = newRemotePointerList()
C.git_libgit2_init()
@@ -160,7 +162,11 @@ func initLibGit2() {
// After this is called, invoking any function from this library will result in
// undefined behavior, so make sure this is called carefully.
func Shutdown() {
+ if err := unregisterManagedTransports(); err != nil {
+ panic(err)
+ }
pointerHandles.Clear()
+ remotePointers.clear()
C.git_libgit2_shutdown()
}
diff --git a/git_test.go b/git_test.go
index 1c57f79..101350f 100644
--- a/git_test.go
+++ b/git_test.go
@@ -13,6 +13,10 @@ import (
func TestMain(m *testing.M) {
ret := m.Run()
+ if err := unregisterManagedTransports(); err != nil {
+ panic(err)
+ }
+
// Ensure that we are not leaking any pointer handles.
pointerHandles.Lock()
if len(pointerHandles.handles) > 0 {
@@ -23,6 +27,16 @@ func TestMain(m *testing.M) {
}
pointerHandles.Unlock()
+ // Or remote pointers.
+ remotePointers.Lock()
+ if len(remotePointers.pointers) > 0 {
+ for ptr, remote := range remotePointers.pointers {
+ fmt.Printf("%016p: %+v\n", ptr, remote)
+ }
+ panic("remote pointer list not empty")
+ }
+ remotePointers.Unlock()
+
Shutdown()
os.Exit(ret)
diff --git a/remote.go b/remote.go
index c8076d5..fb70f55 100644
--- a/remote.go
+++ b/remote.go
@@ -15,6 +15,7 @@ import (
"reflect"
"runtime"
"strings"
+ "sync"
"unsafe"
)
@@ -174,6 +175,64 @@ type Remote struct {
repo *Repository
}
+type remotePointerList struct {
+ sync.RWMutex
+ // stores the Go pointers
+ pointers map[*C.git_remote]*Remote
+}
+
+func newRemotePointerList() *remotePointerList {
+ return &remotePointerList{
+ pointers: make(map[*C.git_remote]*Remote),
+ }
+}
+
+// track adds the given pointer to the list of pointers to track and
+// returns a pointer value which can be passed to C as an opaque
+// pointer.
+func (v *remotePointerList) track(remote *Remote) {
+ v.Lock()
+ v.pointers[remote.ptr] = remote
+ v.Unlock()
+
+ runtime.SetFinalizer(remote, (*Remote).Free)
+}
+
+// untrack stops tracking the git_remote pointer.
+func (v *remotePointerList) untrack(remote *Remote) {
+ v.Lock()
+ delete(v.pointers, remote.ptr)
+ v.Unlock()
+}
+
+// clear stops tracking all the git_remote pointers.
+func (v *remotePointerList) clear() {
+ v.Lock()
+ var remotes []*Remote
+ for remotePtr, remote := range v.pointers {
+ remotes = append(remotes, remote)
+ delete(v.pointers, remotePtr)
+ }
+ v.Unlock()
+
+ for _, remote := range remotes {
+ remote.free()
+ }
+}
+
+// get retrieves the pointer from the given *git_remote.
+func (v *remotePointerList) get(ptr *C.git_remote) (*Remote, bool) {
+ v.RLock()
+ defer v.RUnlock()
+
+ r, ok := v.pointers[ptr]
+ if !ok {
+ return nil, false
+ }
+
+ return r, true
+}
+
type CertificateKind uint
const (
@@ -509,17 +568,42 @@ func RemoteIsValidName(name string) bool {
return C.git_remote_is_valid_name(cname) == 1
}
-// Free releases the resources of the Remote.
-func (r *Remote) Free() {
+// free releases the resources of the Remote.
+func (r *Remote) free() {
runtime.SetFinalizer(r, nil)
C.git_remote_free(r.ptr)
r.ptr = nil
r.repo = nil
}
+// Free releases the resources of the Remote.
+func (r *Remote) Free() {
+ r.repo.Remotes.untrackRemote(r)
+ r.free()
+}
+
type RemoteCollection struct {
doNotCompare
repo *Repository
+
+ sync.RWMutex
+ remotes map[*C.git_remote]*Remote
+}
+
+func (c *RemoteCollection) trackRemote(r *Remote) {
+ c.Lock()
+ c.remotes[r.ptr] = r
+ c.Unlock()
+
+ remotePointers.track(r)
+}
+
+func (c *RemoteCollection) untrackRemote(r *Remote) {
+ c.Lock()
+ delete(c.remotes, r.ptr)
+ c.Unlock()
+
+ remotePointers.untrack(r)
}
func (c *RemoteCollection) List() ([]string, error) {
@@ -554,7 +638,7 @@ func (c *RemoteCollection) Create(name string, url string) (*Remote, error) {
if ret < 0 {
return nil, MakeGitError(ret)
}
- runtime.SetFinalizer(remote, (*Remote).Free)
+ c.trackRemote(remote)
return remote, nil
}
@@ -570,13 +654,13 @@ func (c *RemoteCollection) CreateWithOptions(url string, option *RemoteCreateOpt
copts := populateRemoteCreateOptions(&C.git_remote_create_options{}, option, c.repo)
defer freeRemoteCreateOptions(copts)
+
ret := C.git_remote_create_with_opts(&remote.ptr, curl, copts)
runtime.KeepAlive(c.repo)
if ret < 0 {
return nil, MakeGitError(ret)
}
-
- runtime.SetFinalizer(remote, (*Remote).Free)
+ c.trackRemote(remote)
return remote, nil
}
@@ -612,7 +696,7 @@ func (c *RemoteCollection) CreateWithFetchspec(name string, url string, fetch st
if ret < 0 {
return nil, MakeGitError(ret)
}
- runtime.SetFinalizer(remote, (*Remote).Free)
+ c.trackRemote(remote)
return remote, nil
}
@@ -629,7 +713,7 @@ func (c *RemoteCollection) CreateAnonymous(url string) (*Remote, error) {
if ret < 0 {
return nil, MakeGitError(ret)
}
- runtime.SetFinalizer(remote, (*Remote).Free)
+ c.trackRemote(remote)
return remote, nil
}
@@ -646,10 +730,24 @@ func (c *RemoteCollection) Lookup(name string) (*Remote, error) {
if ret < 0 {
return nil, MakeGitError(ret)
}
- runtime.SetFinalizer(remote, (*Remote).Free)
+ c.trackRemote(remote)
return remote, nil
}
+func (c *RemoteCollection) Free() {
+ var remotes []*Remote
+ c.Lock()
+ for remotePtr, remote := range c.remotes {
+ remotes = append(remotes, remote)
+ delete(c.remotes, remotePtr)
+ }
+ c.Unlock()
+
+ for _, remote := range remotes {
+ remotePointers.untrack(remote)
+ }
+}
+
func (o *Remote) Name() string {
s := C.git_remote_name(o.ptr)
runtime.KeepAlive(o)
diff --git a/remote_test.go b/remote_test.go
index 22fd292..7e37274 100644
--- a/remote_test.go
+++ b/remote_test.go
@@ -97,6 +97,7 @@ func TestRemoteConnectOption(t *testing.T) {
remote, err := repo.Remotes.CreateWithOptions("https://github.com/libgit2/TestGitRepository", option)
checkFatal(t, err)
+ defer remote.Free()
err = remote.ConnectFetch(nil, nil, nil)
checkFatal(t, err)
diff --git a/repository.go b/repository.go
index c7ec9eb..5bdaacd 100644
--- a/repository.go
+++ b/repository.go
@@ -46,6 +46,7 @@ func newRepositoryFromC(ptr *C.git_repository) *Repository {
repo := &Repository{ptr: ptr}
repo.Remotes.repo = repo
+ repo.Remotes.remotes = make(map[*C.git_remote]*Remote)
repo.Submodules.repo = repo
repo.References.repo = repo
repo.Notes.repo = repo
@@ -144,6 +145,7 @@ func (v *Repository) Free() {
ptr := v.ptr
v.ptr = nil
runtime.SetFinalizer(v, nil)
+ v.Remotes.Free()
if v.weak {
return
}
diff --git a/transport.go b/transport.go
new file mode 100644
index 0000000..94c9ffa
--- /dev/null
+++ b/transport.go
@@ -0,0 +1,431 @@
+package git
+
+/*
+#include <git2.h>
+#include <git2/sys/transport.h>
+
+typedef struct {
+ git_smart_subtransport parent;
+ void *handle;
+} _go_managed_smart_subtransport;
+
+typedef struct {
+ git_smart_subtransport_stream parent;
+ void *handle;
+} _go_managed_smart_subtransport_stream;
+
+int _go_git_transport_register(const char *prefix, void *handle);
+int _go_git_transport_smart(git_transport **out, git_remote *owner, int stateless, _go_managed_smart_subtransport *subtransport_payload);
+void _go_git_setup_smart_subtransport_stream(_go_managed_smart_subtransport_stream *stream);
+*/
+import "C"
+import (
+ "errors"
+ "fmt"
+ "io"
+ "reflect"
+ "runtime"
+ "sync"
+ "unsafe"
+)
+
+var (
+ // globalRegisteredSmartTransports is a mapping of global, git2go-managed
+ // transports.
+ globalRegisteredSmartTransports = struct {
+ sync.Mutex
+ transports map[string]*RegisteredSmartTransport
+ }{
+ transports: make(map[string]*RegisteredSmartTransport),
+ }
+)
+
+// unregisterManagedTransports unregisters all git2go-managed transports.
+func unregisterManagedTransports() error {
+ globalRegisteredSmartTransports.Lock()
+ originalTransports := globalRegisteredSmartTransports.transports
+ globalRegisteredSmartTransports.transports = make(map[string]*RegisteredSmartTransport)
+ globalRegisteredSmartTransports.Unlock()
+
+ var err error
+ for protocol, managed := range originalTransports {
+ unregisterErr := managed.Free()
+ if err == nil && unregisterErr != nil {
+ err = fmt.Errorf("failed to unregister transport for %q: %v", protocol, unregisterErr)
+ }
+ }
+ return err
+}
+
+// SmartServiceAction is an action that the smart transport can ask a
+// subtransport to perform.
+type SmartServiceAction int
+
+const (
+ // SmartServiceActionUploadpackLs is used upon connecting to a remote, and is
+ // used to perform reference discovery prior to performing a pull operation.
+ SmartServiceActionUploadpackLs SmartServiceAction = C.GIT_SERVICE_UPLOADPACK_LS
+
+ // SmartServiceActionUploadpack is used when performing a pull operation.
+ SmartServiceActionUploadpack SmartServiceAction = C.GIT_SERVICE_UPLOADPACK
+
+ // SmartServiceActionReceivepackLs is used upon connecting to a remote, and is
+ // used to perform reference discovery prior to performing a push operation.
+ SmartServiceActionReceivepackLs SmartServiceAction = C.GIT_SERVICE_RECEIVEPACK_LS
+
+ // SmartServiceActionReceivepack is used when performing a push operation.
+ SmartServiceActionReceivepack SmartServiceAction = C.GIT_SERVICE_RECEIVEPACK
+)
+
+// Transport encapsulates a way to communicate with a Remote.
+type Transport struct {
+ doNotCompare
+ ptr *C.git_transport
+}
+
+// SmartCredentials calls the credentials callback for this transport.
+func (t *Transport) SmartCredentials(user string, methods CredentialType) (*Credential, error) {
+ cred := newCredential()
+ var cstr *C.char
+
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ if user != "" {
+ cstr = C.CString(user)
+ defer C.free(unsafe.Pointer(cstr))
+ }
+ ret := C.git_transport_smart_credentials(&cred.ptr, t.ptr, cstr, C.int(methods))
+ if ret != 0 {
+ cred.Free()
+ return nil, MakeGitError(ret)
+ }
+
+ return cred, nil
+}
+
+// SmartSubtransport is the interface for custom subtransports which carry data
+// for the smart transport.
+type SmartSubtransport interface {
+ // Action creates a SmartSubtransportStream for the provided url and
+ // requested action.
+ Action(url string, action SmartServiceAction) (SmartSubtransportStream, error)
+
+ // Close closes the SmartSubtransport.
+ //
+ // Subtransports are guaranteed a call to Close between
+ // calls to Action, except for the following two "natural" progressions
+ // of actions against a constant URL.
+ //
+ // 1. UPLOADPACK_LS -> UPLOADPACK
+ // 2. RECEIVEPACK_LS -> RECEIVEPACK
+ Close() error
+
+ // Free releases the resources of the SmartSubtransport.
+ Free()
+}
+
+// SmartSubtransportStream is the interface for streams used by the smart
+// transport to read and write data from a subtransport.
+type SmartSubtransportStream interface {
+ io.Reader
+ io.Writer
+
+ // Free releases the resources of the SmartSubtransportStream.
+ Free()
+}
+
+// SmartSubtransportCallback is a function which creates a new subtransport for
+// the smart transport.
+type SmartSubtransportCallback func(remote *Remote, transport *Transport) (SmartSubtransport, error)
+
+// RegisteredSmartTransport represents a transport that has been registered.
+type RegisteredSmartTransport struct {
+ doNotCompare
+ name string
+ stateless bool
+ callback SmartSubtransportCallback
+ handle unsafe.Pointer
+}
+
+// NewRegisteredSmartTransport adds a custom transport definition, to be used
+// in addition to the built-in set of transports that come with libgit2.
+func NewRegisteredSmartTransport(
+ name string,
+ stateless bool,
+ callback SmartSubtransportCallback,
+) (*RegisteredSmartTransport, error) {
+ return newRegisteredSmartTransport(name, stateless, callback, false)
+}
+
+func newRegisteredSmartTransport(
+ name string,
+ stateless bool,
+ callback SmartSubtransportCallback,
+ global bool,
+) (*RegisteredSmartTransport, error) {
+ if !global {
+ // Check if we had already registered a smart transport for this protocol. If
+ // we had, free it. The user is now responsible for this transport for the
+ // lifetime of the library.
+ globalRegisteredSmartTransports.Lock()
+ if managed, ok := globalRegisteredSmartTransports.transports[name]; ok {
+ delete(globalRegisteredSmartTransports.transports, name)
+ globalRegisteredSmartTransports.Unlock()
+
+ err := managed.Free()
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ globalRegisteredSmartTransports.Unlock()
+ }
+ }
+
+ cstr := C.CString(name)
+ defer C.free(unsafe.Pointer(cstr))
+
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ registeredSmartTransport := &RegisteredSmartTransport{
+ name: name,
+ stateless: stateless,
+ callback: callback,
+ }
+ registeredSmartTransport.handle = pointerHandles.Track(registeredSmartTransport)
+
+ ret := C._go_git_transport_register(cstr, registeredSmartTransport.handle)
+ if ret != 0 {
+ pointerHandles.Untrack(registeredSmartTransport.handle)
+ return nil, MakeGitError(ret)
+ }
+
+ runtime.SetFinalizer(registeredSmartTransport, (*RegisteredSmartTransport).Free)
+ return registeredSmartTransport, nil
+}
+
+// Free releases all resources used by the RegisteredSmartTransport and
+// unregisters the custom transport definition referenced by it.
+func (t *RegisteredSmartTransport) Free() error {
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ cstr := C.CString(t.name)
+ defer C.free(unsafe.Pointer(cstr))
+
+ if ret := C.git_transport_unregister(cstr); ret < 0 {
+ return MakeGitError(ret)
+ }
+
+ pointerHandles.Untrack(t.handle)
+ runtime.SetFinalizer(t, nil)
+ t.handle = nil
+ return nil
+}
+
+//export smartTransportCallback
+func smartTransportCallback(
+ errorMessage **C.char,
+ out **C.git_transport,
+ owner *C.git_remote,
+ handle unsafe.Pointer,
+) C.int {
+ registeredSmartTransport := pointerHandles.Get(handle).(*RegisteredSmartTransport)
+ remote, ok := remotePointers.get(owner)
+ if !ok {
+ err := errors.New("remote pointer not found")
+ return setCallbackError(errorMessage, err)
+ }
+
+ managed := &managedSmartSubtransport{
+ remote: remote,
+ callback: registeredSmartTransport.callback,
+ subtransport: (*C._go_managed_smart_subtransport)(C.calloc(1, C.size_t(unsafe.Sizeof(C._go_managed_smart_subtransport{})))),
+ }
+ managedHandle := pointerHandles.Track(managed)
+ managed.handle = managedHandle
+ managed.subtransport.handle = managedHandle
+
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ ret := C._go_git_transport_smart(out, owner, cbool(registeredSmartTransport.stateless), managed.subtransport)
+ if ret != 0 {
+ pointerHandles.Untrack(managedHandle)
+ }
+ return ret
+}
+
+//export smartTransportSubtransportCallback
+func smartTransportSubtransportCallback(
+ errorMessage **C.char,
+ wrapperPtr *C._go_managed_smart_subtransport,
+ owner *C.git_transport,
+) C.int {
+ subtransport := pointerHandles.Get(wrapperPtr.handle).(*managedSmartSubtransport)
+
+ underlyingSmartSubtransport, err := subtransport.callback(subtransport.remote, &Transport{ptr: owner})
+ if err != nil {
+ return setCallbackError(errorMessage, err)
+ }
+ subtransport.underlying = underlyingSmartSubtransport
+ return C.int(ErrorCodeOK)
+}
+
+type managedSmartSubtransport struct {
+ owner *C.git_transport
+ callback SmartSubtransportCallback
+ remote *Remote
+ subtransport *C._go_managed_smart_subtransport
+ underlying SmartSubtransport
+ handle unsafe.Pointer
+ currentManagedStream *managedSmartSubtransportStream
+}
+
+func getSmartSubtransportInterface(subtransport *C.git_smart_subtransport) *managedSmartSubtransport {
+ wrapperPtr := (*C._go_managed_smart_subtransport)(unsafe.Pointer(subtransport))
+ return pointerHandles.Get(wrapperPtr.handle).(*managedSmartSubtransport)
+}
+
+//export smartSubtransportActionCallback
+func smartSubtransportActionCallback(
+ errorMessage **C.char,
+ out **C.git_smart_subtransport_stream,
+ t *C.git_smart_subtransport,
+ url *C.char,
+ action C.git_smart_service_t,
+) C.int {
+ subtransport := getSmartSubtransportInterface(t)
+
+ underlyingStream, err := subtransport.underlying.Action(C.GoString(url), SmartServiceAction(action))
+ if err != nil {
+ return setCallbackError(errorMessage, err)
+ }
+
+ // It's okay to do strict equality here: we expect both to be identical.
+ if subtransport.currentManagedStream == nil || subtransport.currentManagedStream.underlying != underlyingStream {
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ stream := (*C._go_managed_smart_subtransport_stream)(C.calloc(1, C.size_t(unsafe.Sizeof(C._go_managed_smart_subtransport_stream{}))))
+ managed := &managedSmartSubtransportStream{
+ underlying: underlyingStream,
+ streamPtr: stream,
+ }
+ managedHandle := pointerHandles.Track(managed)
+ managed.handle = managedHandle
+ stream.handle = managedHandle
+
+ C._go_git_setup_smart_subtransport_stream(stream)
+
+ subtransport.currentManagedStream = managed
+ }
+
+ *out = &subtransport.currentManagedStream.streamPtr.parent
+ return C.int(ErrorCodeOK)
+}
+
+//export smartSubtransportCloseCallback
+func smartSubtransportCloseCallback(errorMessage **C.char, t *C.git_smart_subtransport) C.int {
+ subtransport := getSmartSubtransportInterface(t)
+
+ subtransport.currentManagedStream = nil
+
+ if subtransport.underlying != nil {
+ err := subtransport.underlying.Close()
+ if err != nil {
+ return setCallbackError(errorMessage, err)
+ }
+ }
+
+ return C.int(ErrorCodeOK)
+}
+
+//export smartSubtransportFreeCallback
+func smartSubtransportFreeCallback(t *C.git_smart_subtransport) {
+ subtransport := getSmartSubtransportInterface(t)
+
+ if subtransport.underlying != nil {
+ subtransport.underlying.Free()
+ subtransport.underlying = nil
+ }
+ pointerHandles.Untrack(subtransport.handle)
+ C.free(unsafe.Pointer(subtransport.subtransport))
+ subtransport.handle = nil
+ subtransport.subtransport = nil
+}
+
+type managedSmartSubtransportStream struct {
+ owner *C.git_smart_subtransport_stream
+ streamPtr *C._go_managed_smart_subtransport_stream
+ underlying SmartSubtransportStream
+ handle unsafe.Pointer
+}
+
+func getSmartSubtransportStreamInterface(subtransportStream *C.git_smart_subtransport_stream) *managedSmartSubtransportStream {
+ managedSubtransportStream := (*C._go_managed_smart_subtransport_stream)(unsafe.Pointer(subtransportStream))
+ return pointerHandles.Get(managedSubtransportStream.handle).(*managedSmartSubtransportStream)
+}
+
+//export smartSubtransportStreamReadCallback
+func smartSubtransportStreamReadCallback(
+ errorMessage **C.char,
+ s *C.git_smart_subtransport_stream,
+ buffer *C.char,
+ bufSize C.size_t,
+ bytesRead *C.size_t,
+) C.int {
+ stream := getSmartSubtransportStreamInterface(s)
+
+ var p []byte
+ header := (*reflect.SliceHeader)(unsafe.Pointer(&p))
+ header.Cap = int(bufSize)
+ header.Len = int(bufSize)
+ header.Data = uintptr(unsafe.Pointer(buffer))
+
+ n, err := stream.underlying.Read(p)
+ *bytesRead = C.size_t(n)
+ if n == 0 && err != nil {
+ if err == io.EOF {
+ return C.int(ErrorCodeOK)
+ }
+
+ return setCallbackError(errorMessage, err)
+ }
+
+ return C.int(ErrorCodeOK)
+}
+
+//export smartSubtransportStreamWriteCallback
+func smartSubtransportStreamWriteCallback(
+ errorMessage **C.char,
+ s *C.git_smart_subtransport_stream,
+ buffer *C.char,
+ bufLen C.size_t,
+) C.int {
+ stream := getSmartSubtransportStreamInterface(s)
+
+ var p []byte
+ header := (*reflect.SliceHeader)(unsafe.Pointer(&p))
+ header.Cap = int(bufLen)
+ header.Len = int(bufLen)
+ header.Data = uintptr(unsafe.Pointer(buffer))
+
+ if _, err := stream.underlying.Write(p); err != nil {
+ return setCallbackError(errorMessage, err)
+ }
+
+ return C.int(ErrorCodeOK)
+}
+
+//export smartSubtransportStreamFreeCallback
+func smartSubtransportStreamFreeCallback(s *C.git_smart_subtransport_stream) {
+ stream := getSmartSubtransportStreamInterface(s)
+
+ stream.underlying.Free()
+ pointerHandles.Untrack(stream.handle)
+ C.free(unsafe.Pointer(stream.streamPtr))
+ stream.handle = nil
+ stream.streamPtr = nil
+}
diff --git a/transport_test.go b/transport_test.go
new file mode 100644
index 0000000..9f50047
--- /dev/null
+++ b/transport_test.go
@@ -0,0 +1,72 @@
+package git
+
+import (
+ "io"
+ "reflect"
+ "testing"
+)
+
+type testSmartSubtransport struct {
+}
+
+func (t *testSmartSubtransport) Action(url string, action SmartServiceAction) (SmartSubtransportStream, error) {
+ return &testSmartSubtransportStream{}, nil
+}
+
+func (t *testSmartSubtransport) Close() error {
+ return nil
+}
+
+func (t *testSmartSubtransport) Free() {
+}
+
+type testSmartSubtransportStream struct {
+}
+
+func (s *testSmartSubtransportStream) Read(buf []byte) (int, error) {
+ payload := "" +
+ "001e# service=git-upload-pack\n" +
+ "0000005d0000000000000000000000000000000000000000 HEAD\x00symref=HEAD:refs/heads/master agent=libgit\n" +
+ "003f0000000000000000000000000000000000000000 refs/heads/master\n" +
+ "0000"
+
+ return copy(buf, []byte(payload)), io.EOF
+}
+
+func (s *testSmartSubtransportStream) Write(buf []byte) (int, error) {
+ return 0, io.EOF
+}
+
+func (s *testSmartSubtransportStream) Free() {
+}
+
+func TestTransport(t *testing.T) {
+ t.Parallel()
+ repo := createTestRepo(t)
+ defer cleanupTestRepo(t, repo)
+
+ callback := func(remote *Remote, transport *Transport) (SmartSubtransport, error) {
+ return &testSmartSubtransport{}, nil
+ }
+ registeredSmartTransport, err := NewRegisteredSmartTransport("foo", true, callback)
+ checkFatal(t, err)
+ defer registeredSmartTransport.Free()
+
+ remote, err := repo.Remotes.Create("test", "foo://bar")
+ checkFatal(t, err)
+ defer remote.Free()
+
+ err = remote.ConnectFetch(nil, nil, nil)
+ checkFatal(t, err)
+
+ remoteHeads, err := remote.Ls()
+ checkFatal(t, err)
+
+ expectedRemoteHeads := []RemoteHead{
+ {&Oid{}, "HEAD"},
+ {&Oid{}, "refs/heads/master"},
+ }
+ if !reflect.DeepEqual(expectedRemoteHeads, remoteHeads) {
+ t.Errorf("mismatched remote heads. expected %v, got %v", expectedRemoteHeads, remoteHeads)
+ }
+}
diff --git a/wrapper.c b/wrapper.c
index 282d9a8..6f65f71 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -504,3 +504,116 @@ int _go_git_indexer_new(
indexer_options.progress_cb_payload = progress_cb_payload;
return git_indexer_new(out, path, mode, odb, &indexer_options);
}
+
+static int smart_transport_callback(
+ git_transport **out,
+ git_remote *owner,
+ void *param)
+{
+ char *error_message = NULL;
+ const int ret = smartTransportCallback(
+ &error_message,
+ out,
+ owner,
+ param);
+ return set_callback_error(error_message, ret);
+}
+
+int _go_git_transport_register(const char *prefix, void *param)
+{
+ return git_transport_register(prefix, smart_transport_callback, param);
+}
+
+static int smart_subtransport_action_callback(
+ git_smart_subtransport_stream **out,
+ git_smart_subtransport *transport,
+ const char *url,
+ git_smart_service_t action)
+{
+ char *error_message = NULL;
+ const int ret = smartSubtransportActionCallback(
+ &error_message,
+ out,
+ transport,
+ (char *)url,
+ action);
+ return set_callback_error(error_message, ret);
+}
+
+static int smart_subtransport_close_callback(git_smart_subtransport *transport)
+{
+ char *error_message = NULL;
+ const int ret = smartSubtransportCloseCallback(
+ &error_message,
+ transport);
+ return set_callback_error(error_message, ret);
+}
+
+static int smart_subtransport_callback(
+ git_smart_subtransport **out,
+ git_transport *owner,
+ void *param)
+{
+ _go_managed_smart_subtransport *subtransport = (_go_managed_smart_subtransport *)param;
+ subtransport->parent.action = smart_subtransport_action_callback;
+ subtransport->parent.close = smart_subtransport_close_callback;
+ subtransport->parent.free = smartSubtransportFreeCallback;
+
+ *out = &subtransport->parent;
+ char *error_message = NULL;
+ const int ret = smartTransportSubtransportCallback(&error_message, subtransport, owner);
+ return set_callback_error(error_message, ret);
+}
+
+int _go_git_transport_smart(
+ git_transport **out,
+ git_remote *owner,
+ int stateless,
+ _go_managed_smart_subtransport *subtransport_payload)
+{
+ git_smart_subtransport_definition definition = {
+ smart_subtransport_callback,
+ stateless,
+ subtransport_payload,
+ };
+
+ return git_transport_smart(out, owner, &definition);
+}
+
+static int smart_subtransport_stream_read_callback(
+ git_smart_subtransport_stream *stream,
+ char *buffer,
+ size_t buf_size,
+ size_t *bytes_read)
+{
+ char *error_message = NULL;
+ const int ret = smartSubtransportStreamReadCallback(
+ &error_message,
+ stream,
+ buffer,
+ buf_size,
+ bytes_read);
+ return set_callback_error(error_message, ret);
+}
+
+static int smart_subtransport_stream_write_callback(
+ git_smart_subtransport_stream *stream,
+ const char *buffer,
+ size_t len)
+{
+ char *error_message = NULL;
+ const int ret = smartSubtransportStreamWriteCallback(
+ &error_message,
+ stream,
+ (char *)buffer,
+ len);
+ return set_callback_error(error_message, ret);
+}
+
+void _go_git_setup_smart_subtransport_stream(_go_managed_smart_subtransport_stream *stream)
+{
+ _go_managed_smart_subtransport_stream *managed_stream = (_go_managed_smart_subtransport_stream *)stream;
+ managed_stream->parent.read = smart_subtransport_stream_read_callback;
+ managed_stream->parent.write = smart_subtransport_stream_write_callback;
+ managed_stream->parent.free = smartSubtransportStreamFreeCallback;
+}