Add support for custom smart transports (#806)
This change adds support for git smart transports. This will be then used to implement http, https, and ssh transports that don't rely on the libgit2 library.
This commit is contained in:
parent
dbe032c347
commit
f1fa96c7b7
1
clone.go
1
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)
|
||||
}
|
||||
|
|
6
git.go
6
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()
|
||||
}
|
||||
|
|
14
git_test.go
14
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)
|
||||
|
|
114
remote.go
114
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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue