Add support for custom smart transports (#806) #809
1
clone.go
1
clone.go
|
@ -85,6 +85,7 @@ func remoteCreateCallback(
|
||||||
// clear finalizer as the calling C function will
|
// clear finalizer as the calling C function will
|
||||||
// free the remote itself
|
// free the remote itself
|
||||||
runtime.SetFinalizer(remote, nil)
|
runtime.SetFinalizer(remote, nil)
|
||||||
|
remote.repo.Remotes.untrackRemote(remote)
|
||||||
|
|
||||||
return C.int(ErrorCodeOK)
|
return C.int(ErrorCodeOK)
|
||||||
}
|
}
|
||||||
|
|
6
git.go
6
git.go
|
@ -124,6 +124,7 @@ var (
|
||||||
type doNotCompare [0]func()
|
type doNotCompare [0]func()
|
||||||
|
|
||||||
var pointerHandles *HandleList
|
var pointerHandles *HandleList
|
||||||
|
var remotePointers *remotePointerList
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
initLibGit2()
|
initLibGit2()
|
||||||
|
@ -131,6 +132,7 @@ func init() {
|
||||||
|
|
||||||
func initLibGit2() {
|
func initLibGit2() {
|
||||||
pointerHandles = NewHandleList()
|
pointerHandles = NewHandleList()
|
||||||
|
remotePointers = newRemotePointerList()
|
||||||
|
|
||||||
C.git_libgit2_init()
|
C.git_libgit2_init()
|
||||||
|
|
||||||
|
@ -156,7 +158,11 @@ func initLibGit2() {
|
||||||
// After this is called, invoking any function from this library will result in
|
// After this is called, invoking any function from this library will result in
|
||||||
// undefined behavior, so make sure this is called carefully.
|
// undefined behavior, so make sure this is called carefully.
|
||||||
func Shutdown() {
|
func Shutdown() {
|
||||||
|
if err := unregisterManagedTransports(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
pointerHandles.Clear()
|
pointerHandles.Clear()
|
||||||
|
remotePointers.clear()
|
||||||
|
|
||||||
C.git_libgit2_shutdown()
|
C.git_libgit2_shutdown()
|
||||||
}
|
}
|
||||||
|
|
14
git_test.go
14
git_test.go
|
@ -13,6 +13,10 @@ import (
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
ret := m.Run()
|
ret := m.Run()
|
||||||
|
|
||||||
|
if err := unregisterManagedTransports(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure that we are not leaking any pointer handles.
|
// Ensure that we are not leaking any pointer handles.
|
||||||
pointerHandles.Lock()
|
pointerHandles.Lock()
|
||||||
if len(pointerHandles.handles) > 0 {
|
if len(pointerHandles.handles) > 0 {
|
||||||
|
@ -23,6 +27,16 @@ func TestMain(m *testing.M) {
|
||||||
}
|
}
|
||||||
pointerHandles.Unlock()
|
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()
|
Shutdown()
|
||||||
|
|
||||||
os.Exit(ret)
|
os.Exit(ret)
|
||||||
|
|
110
remote.go
110
remote.go
|
@ -14,6 +14,7 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -156,6 +157,64 @@ type Remote struct {
|
||||||
repo *Repository
|
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
|
type CertificateKind uint
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -490,17 +549,42 @@ func RemoteIsValidName(name string) bool {
|
||||||
return C.git_remote_is_valid_name(cname) == 1
|
return C.git_remote_is_valid_name(cname) == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Free releases the resources of the Remote.
|
// free releases the resources of the Remote.
|
||||||
func (r *Remote) Free() {
|
func (r *Remote) free() {
|
||||||
runtime.SetFinalizer(r, nil)
|
runtime.SetFinalizer(r, nil)
|
||||||
C.git_remote_free(r.ptr)
|
C.git_remote_free(r.ptr)
|
||||||
r.ptr = nil
|
r.ptr = nil
|
||||||
r.repo = 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 {
|
type RemoteCollection struct {
|
||||||
doNotCompare
|
doNotCompare
|
||||||
repo *Repository
|
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) {
|
func (c *RemoteCollection) List() ([]string, error) {
|
||||||
|
@ -535,7 +619,7 @@ func (c *RemoteCollection) Create(name string, url string) (*Remote, error) {
|
||||||
if ret < 0 {
|
if ret < 0 {
|
||||||
return nil, MakeGitError(ret)
|
return nil, MakeGitError(ret)
|
||||||
}
|
}
|
||||||
runtime.SetFinalizer(remote, (*Remote).Free)
|
c.trackRemote(remote)
|
||||||
return remote, nil
|
return remote, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -571,7 +655,7 @@ func (c *RemoteCollection) CreateWithFetchspec(name string, url string, fetch st
|
||||||
if ret < 0 {
|
if ret < 0 {
|
||||||
return nil, MakeGitError(ret)
|
return nil, MakeGitError(ret)
|
||||||
}
|
}
|
||||||
runtime.SetFinalizer(remote, (*Remote).Free)
|
c.trackRemote(remote)
|
||||||
return remote, nil
|
return remote, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -588,7 +672,7 @@ func (c *RemoteCollection) CreateAnonymous(url string) (*Remote, error) {
|
||||||
if ret < 0 {
|
if ret < 0 {
|
||||||
return nil, MakeGitError(ret)
|
return nil, MakeGitError(ret)
|
||||||
}
|
}
|
||||||
runtime.SetFinalizer(remote, (*Remote).Free)
|
c.trackRemote(remote)
|
||||||
return remote, nil
|
return remote, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -605,10 +689,24 @@ func (c *RemoteCollection) Lookup(name string) (*Remote, error) {
|
||||||
if ret < 0 {
|
if ret < 0 {
|
||||||
return nil, MakeGitError(ret)
|
return nil, MakeGitError(ret)
|
||||||
}
|
}
|
||||||
runtime.SetFinalizer(remote, (*Remote).Free)
|
c.trackRemote(remote)
|
||||||
return remote, nil
|
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 {
|
func (o *Remote) Name() string {
|
||||||
s := C.git_remote_name(o.ptr)
|
s := C.git_remote_name(o.ptr)
|
||||||
runtime.KeepAlive(o)
|
runtime.KeepAlive(o)
|
||||||
|
|
|
@ -46,6 +46,7 @@ func newRepositoryFromC(ptr *C.git_repository) *Repository {
|
||||||
repo := &Repository{ptr: ptr}
|
repo := &Repository{ptr: ptr}
|
||||||
|
|
||||||
repo.Remotes.repo = repo
|
repo.Remotes.repo = repo
|
||||||
|
repo.Remotes.remotes = make(map[*C.git_remote]*Remote)
|
||||||
repo.Submodules.repo = repo
|
repo.Submodules.repo = repo
|
||||||
repo.References.repo = repo
|
repo.References.repo = repo
|
||||||
repo.Notes.repo = repo
|
repo.Notes.repo = repo
|
||||||
|
@ -144,6 +145,7 @@ func (v *Repository) Free() {
|
||||||
ptr := v.ptr
|
ptr := v.ptr
|
||||||
v.ptr = nil
|
v.ptr = nil
|
||||||
runtime.SetFinalizer(v, nil)
|
runtime.SetFinalizer(v, nil)
|
||||||
|
v.Remotes.Free()
|
||||||
if v.weak {
|
if v.weak {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,430 @@
|
||||||
|
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 CredType) (*Cred, error) {
|
||||||
|
cred := &Cred{}
|
||||||
|
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 {
|
||||||
|
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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
113
wrapper.c
113
wrapper.c
|
@ -467,3 +467,116 @@ int _go_git_indexer_new(
|
||||||
{
|
{
|
||||||
return git_indexer_new(out, path, mode, odb, transfer_progress_callback, progress_cb_payload);
|
return git_indexer_new(out, path, mode, odb, transfer_progress_callback, progress_cb_payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue