Add a managed HTTP(S) transport implementation
This implements a "smart subtransport" which lets us implement just the I/O portion and leave the Git Smart Protocol logic in the library.
This commit is contained in:
parent
2cff3f2ef4
commit
1be680fdf0
8
git.go
8
git.go
|
@ -129,6 +129,14 @@ func init() {
|
|||
panic("libgit2 was not built with threading support")
|
||||
}
|
||||
|
||||
if err := RegisterManagedHttp(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := RegisterManagedHttps(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// This is not something we should be doing, as we may be
|
||||
// stomping all over someone else's setup. The user should do
|
||||
// this themselves or use some binding/wrapper which does it
|
||||
|
|
|
@ -0,0 +1,315 @@
|
|||
package git
|
||||
|
||||
/*
|
||||
#include <git2.h>
|
||||
#include <git2/sys/transport.h>
|
||||
|
||||
typedef struct {
|
||||
git_smart_subtransport parent;
|
||||
void *ptr;
|
||||
} managed_smart_subtransport;
|
||||
|
||||
typedef struct {
|
||||
git_smart_subtransport_stream parent;
|
||||
void *ptr;
|
||||
} managed_smart_subtransport_stream;
|
||||
|
||||
int _go_git_transport_register(const char *scheme);
|
||||
int _go_git_transport_smart(git_transport **out, git_remote *owner);
|
||||
void _go_git_setup_smart_subtransport(managed_smart_subtransport *t, void *ptr);
|
||||
void _go_git_setup_smart_subtransport_stream(managed_smart_subtransport_stream *t, void *ptr);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"runtime"
|
||||
//"runtime/debug"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type SmartService int
|
||||
|
||||
const (
|
||||
SmartServiceUploadpackLs = C.GIT_SERVICE_UPLOADPACK_LS
|
||||
SmartServiceUploadpack = C.GIT_SERVICE_UPLOADPACK
|
||||
SmartServiceReceivepackLs = C.GIT_SERVICE_RECEIVEPACK_LS
|
||||
SmartServiceReceivepack = C.GIT_SERVICE_RECEIVEPACK
|
||||
)
|
||||
|
||||
type SmartSubtransport interface {
|
||||
Action(url string, action SmartService) (SmartSubtransportStream, error)
|
||||
Close() error
|
||||
Free()
|
||||
}
|
||||
|
||||
type SmartSubtransportStream interface {
|
||||
Read(buf []byte) (int, error)
|
||||
Write(buf []byte) error
|
||||
Free()
|
||||
}
|
||||
|
||||
func RegisterManagedHttp() error {
|
||||
httpStr := C.CString("http")
|
||||
defer C.free(unsafe.Pointer(httpStr))
|
||||
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
ret := C._go_git_transport_register(httpStr)
|
||||
if ret != 0 {
|
||||
return MakeGitError(ret)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func RegisterManagedHttps() error {
|
||||
httpsStr := C.CString("https")
|
||||
defer C.free(unsafe.Pointer(httpsStr))
|
||||
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
ret := C._go_git_transport_register(httpsStr)
|
||||
if ret != 0 {
|
||||
return MakeGitError(ret)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type ManagedTransport struct{}
|
||||
|
||||
func (self *ManagedTransport) Action(url string, action SmartService) (SmartSubtransportStream, error) {
|
||||
var req *http.Request
|
||||
var err error
|
||||
switch action {
|
||||
case SmartServiceUploadpackLs:
|
||||
req, err = http.NewRequest("GET", url+"/info/refs?service=git-upload-pack", nil)
|
||||
|
||||
case SmartServiceUploadpack:
|
||||
req, err = http.NewRequest("POST", url+"/git-upload-pack", nil)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
req.Header["Content-Type"] = []string{"application/x-git-upload-pack-request"}
|
||||
|
||||
case SmartServiceReceivepackLs:
|
||||
req, err = http.NewRequest("GET", url+"/info/refs?service=git-receive-pack", nil)
|
||||
|
||||
case SmartServiceReceivepack:
|
||||
req, err = http.NewRequest("POST", url+"/info/refs?service=git-upload-pack", nil)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
req.Header["Content-Type"] = []string{"application/x-git-receive-pack-request"}
|
||||
default:
|
||||
err = errors.New("unknown action")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header["User-Agent"] = []string{"git/2.0 (git2go)"}
|
||||
return newManagedHttpStream(req), nil
|
||||
}
|
||||
|
||||
func (self *ManagedTransport) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *ManagedTransport) Free() {
|
||||
}
|
||||
|
||||
type ManagedHttpStream struct {
|
||||
req *http.Request
|
||||
resp *http.Response
|
||||
postBuffer bytes.Buffer
|
||||
sentRequest bool
|
||||
}
|
||||
|
||||
func newManagedHttpStream(req *http.Request) *ManagedHttpStream {
|
||||
return &ManagedHttpStream{
|
||||
req: req,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *ManagedHttpStream) Read(buf []byte) (int, error) {
|
||||
if !self.sentRequest {
|
||||
if err := self.sendRequest(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return self.resp.Body.Read(buf)
|
||||
}
|
||||
|
||||
func (self *ManagedHttpStream) Write(buf []byte) error {
|
||||
// We write it all into a buffer and send it off when the transport asks
|
||||
// us to read.
|
||||
self.postBuffer.Write(buf)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *ManagedHttpStream) Free() {
|
||||
self.resp.Body.Close()
|
||||
}
|
||||
|
||||
func (self *ManagedHttpStream) sendRequest() error {
|
||||
self.req.Body = ioutil.NopCloser(&self.postBuffer)
|
||||
resp, err := http.DefaultClient.Do(self.req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
self.sentRequest = true
|
||||
self.resp = resp
|
||||
return nil
|
||||
}
|
||||
|
||||
func setLibgit2Error(err error) C.int {
|
||||
cstr := C.CString(err.Error())
|
||||
defer C.free(unsafe.Pointer(cstr))
|
||||
C.giterr_set_str(C.GITERR_NET, cstr)
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
//export httpAction
|
||||
func httpAction(out **C.git_smart_subtransport_stream, t *C.git_smart_subtransport, url *C.char, action C.git_smart_service_t) C.int {
|
||||
transport, err := getSmartSubtransportInterface(t)
|
||||
if err != nil {
|
||||
return setLibgit2Error(err)
|
||||
}
|
||||
|
||||
managed, err := transport.Action(C.GoString(url), SmartService(action))
|
||||
if err != nil {
|
||||
return setLibgit2Error(err)
|
||||
}
|
||||
|
||||
stream := C.calloc(1, C.size_t(unsafe.Sizeof(C.managed_smart_subtransport_stream{})))
|
||||
managedPtr := pointerHandles.Track(managed)
|
||||
C._go_git_setup_smart_subtransport_stream(stream, managedPtr)
|
||||
|
||||
*out = (*C.git_smart_subtransport_stream)(stream)
|
||||
return 0
|
||||
}
|
||||
|
||||
//export httpClose
|
||||
func httpClose(t *C.git_smart_subtransport) C.int {
|
||||
transport, err := getSmartSubtransportInterface(t)
|
||||
if err != nil {
|
||||
return setLibgit2Error(err)
|
||||
}
|
||||
|
||||
if err := transport.Close(); err != nil {
|
||||
return setLibgit2Error(err)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
//export httpFree
|
||||
func httpFree(transport *C.git_smart_subtransport) {
|
||||
wrapperPtr := (*C.managed_smart_subtransport)(unsafe.Pointer(transport))
|
||||
pointerHandles.Untrack(wrapperPtr.ptr)
|
||||
}
|
||||
|
||||
var errNoSmartSubtransport = errors.New("passed object does not implement SmartSubtransport")
|
||||
|
||||
func getSmartSubtransportInterface(_t *C.git_smart_subtransport) (SmartSubtransport, error) {
|
||||
wrapperPtr := (*C.managed_smart_subtransport)(unsafe.Pointer(_t))
|
||||
|
||||
transport, ok := pointerHandles.Get(wrapperPtr.ptr).(SmartSubtransport)
|
||||
if !ok {
|
||||
return nil, errNoSmartSubtransport
|
||||
}
|
||||
|
||||
return transport, nil
|
||||
}
|
||||
|
||||
//export httpTransportCb
|
||||
func httpTransportCb(out **C.git_transport, owner *C.git_remote, param unsafe.Pointer) C.int {
|
||||
return C._go_git_transport_smart(out, owner)
|
||||
}
|
||||
|
||||
//export httpSmartSubtransportCb
|
||||
func httpSmartSubtransportCb(out **C.git_smart_subtransport, owner *C.git_transport, param unsafe.Pointer) C.int {
|
||||
if out == nil {
|
||||
return -1
|
||||
}
|
||||
|
||||
transport := C.calloc(1, C.size_t(unsafe.Sizeof(C.managed_smart_subtransport{})))
|
||||
managed := &ManagedTransport{}
|
||||
managedPtr := pointerHandles.Track(managed)
|
||||
C._go_git_setup_smart_subtransport(transport, managedPtr)
|
||||
|
||||
*out = (*C.git_smart_subtransport)(transport)
|
||||
return 0
|
||||
}
|
||||
|
||||
var errNoSmartSubtransportStream = errors.New("passed object does not implement SmartSubtransportStream")
|
||||
|
||||
func getSmartSubtransportStreamInterface(_s *C.git_smart_subtransport_stream) (SmartSubtransportStream, error) {
|
||||
wrapperPtr := (*C.managed_smart_subtransport_stream)(unsafe.Pointer(_s))
|
||||
|
||||
transport, ok := pointerHandles.Get(wrapperPtr.ptr).(SmartSubtransportStream)
|
||||
if !ok {
|
||||
return nil, errNoSmartSubtransportStream
|
||||
}
|
||||
|
||||
return transport, nil
|
||||
}
|
||||
|
||||
//export smartSubtransportRead
|
||||
func smartSubtransportRead(s *C.git_smart_subtransport_stream, data unsafe.Pointer, l C.size_t, read *C.size_t) C.int {
|
||||
stream, err := getSmartSubtransportStreamInterface(s)
|
||||
if err != nil {
|
||||
return setLibgit2Error(err)
|
||||
}
|
||||
|
||||
var p []byte
|
||||
header := (*reflect.SliceHeader)(unsafe.Pointer(&p))
|
||||
header.Cap = int(l)
|
||||
header.Len = int(l)
|
||||
header.Data = uintptr(data)
|
||||
|
||||
n, err := stream.Read(p)
|
||||
if err != nil {
|
||||
return setLibgit2Error(err)
|
||||
}
|
||||
|
||||
*read = C.size_t(n)
|
||||
return 0
|
||||
}
|
||||
|
||||
//export smartSubtransportWrite
|
||||
func smartSubtransportWrite(s *C.git_smart_subtransport_stream, data unsafe.Pointer, l C.size_t) C.int {
|
||||
stream, err := getSmartSubtransportStreamInterface(s)
|
||||
if err != nil {
|
||||
return setLibgit2Error(err)
|
||||
}
|
||||
|
||||
var p []byte
|
||||
header := (*reflect.SliceHeader)(unsafe.Pointer(&p))
|
||||
header.Cap = int(l)
|
||||
header.Len = int(l)
|
||||
header.Data = uintptr(data)
|
||||
|
||||
if err := stream.Write(p); err != nil {
|
||||
return setLibgit2Error(err)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
//export smartSubtransportFree
|
||||
func smartSubtransportFree(s *C.git_smart_subtransport_stream) {
|
||||
}
|
34
wrapper.c
34
wrapper.c
|
@ -2,6 +2,7 @@
|
|||
#include <git2.h>
|
||||
#include <git2/sys/odb_backend.h>
|
||||
#include <git2/sys/refdb_backend.h>
|
||||
#include <git2/sys/transport.h>
|
||||
|
||||
typedef int (*gogit_submodule_cbk)(git_submodule *sm, const char *name, void *payload);
|
||||
|
||||
|
@ -180,4 +181,37 @@ void _go_git_writestream_free(git_writestream *stream)
|
|||
stream->free(stream);
|
||||
}
|
||||
|
||||
int _go_git_transport_register(const char *scheme)
|
||||
{
|
||||
return git_transport_register(scheme, httpTransportCb, NULL);
|
||||
}
|
||||
|
||||
int _go_git_transport_smart(git_transport **out, git_remote *owner)
|
||||
{
|
||||
git_smart_subtransport_definition defn = {
|
||||
httpSmartSubtransportCb,
|
||||
1, // RPC/Stateless
|
||||
};
|
||||
|
||||
return git_transport_smart(out, owner, &defn);
|
||||
}
|
||||
|
||||
void _go_git_setup_smart_subtransport(managed_smart_subtransport *t, void *ptr)
|
||||
{
|
||||
t->parent.action = httpAction;
|
||||
t->parent.close = httpClose;
|
||||
t->parent.free = httpFree;
|
||||
|
||||
t->ptr = ptr;
|
||||
}
|
||||
|
||||
void _go_git_setup_smart_subtransport_stream(managed_smart_subtransport_stream *s, void *ptr)
|
||||
{
|
||||
s->parent.read = smartSubtransportRead;
|
||||
s->parent.write = smartSubtransportWrite;
|
||||
s->parent.free = smartSubtransportFree;
|
||||
|
||||
s->ptr = ptr;
|
||||
}
|
||||
|
||||
/* EOF */
|
||||
|
|
Loading…
Reference in New Issue