From 7e0e18f9cb113af867739a90112847b8f2a42faf Mon Sep 17 00:00:00 2001 From: Bryn Bellomy Date: Fri, 22 Feb 2019 04:58:29 -0600 Subject: [PATCH 1/2] Bindings for transport plugins (git_register_transport) --- remote.go | 46 +++++++- repository.go | 16 +++ transport.go | 290 ++++++++++++++++++++++++++++++++++++++++++++++++++ wrapper.c | 57 ++++++++++ 4 files changed, 405 insertions(+), 4 deletions(-) create mode 100644 transport.go diff --git a/remote.go b/remote.go index b4b1dd72..29d8622a 100644 --- a/remote.go +++ b/remote.go @@ -35,6 +35,17 @@ func newTransferProgressFromC(c *C.git_transfer_progress) TransferProgress { ReceivedBytes: uint(c.received_bytes)} } +func (tp TransferProgress) toC() *C.git_transfer_progress { + return &C.git_transfer_progress{ + total_objects: C.uint(tp.TotalObjects), + indexed_objects: C.uint(tp.IndexedObjects), + received_objects: C.uint(tp.ReceivedObjects), + local_objects: C.uint(tp.LocalObjects), + total_deltas: C.uint(tp.TotalDeltas), + received_bytes: C.ulong(tp.ReceivedBytes), + } +} + type RemoteCompletion uint type ConnectDirection uint @@ -191,14 +202,41 @@ type PushOptions struct { } type RemoteHead struct { - Id *Oid - Name string + Id *Oid + Name string + SymrefTarget string } func newRemoteHeadFromC(ptr *C.git_remote_head) RemoteHead { return RemoteHead{ - Id: newOidFromC(&ptr.oid), - Name: C.GoString(ptr.name), + Id: newOidFromC(&ptr.oid), + Name: C.GoString(ptr.name), + SymrefTarget: C.GoString(ptr.symref_target), + } +} + +func newRemoteHeadsFromC(cHeads **C.git_remote_head, numHeads C.size_t) []RemoteHead { + hdr := reflect.SliceHeader{ + Data: uintptr(unsafe.Pointer(cHeads)), + Len: int(numHeads), + Cap: int(numHeads), + } + + goSlice := *(*[]*C.git_remote_head)(unsafe.Pointer(&hdr)) + + var heads []RemoteHead + for _, s := range goSlice { + head := newRemoteHeadFromC(s) + heads = append(heads, head) + } + return heads +} + +func (head RemoteHead) toC() *C.git_remote_head { + return &C.git_remote_head{ + oid: *head.Id.toC(), + name: C.CString(head.Name), + symref_target: C.CString(head.SymrefTarget), } } diff --git a/repository.go b/repository.go index d8de97a6..04676008 100644 --- a/repository.go +++ b/repository.go @@ -50,6 +50,22 @@ func newRepositoryFromC(ptr *C.git_repository) *Repository { return repo } +// In contrast to newRepositoryFromC, this function does not cause the C object to deallocate when +// its Go wrapper leaves scope. This is used by the transportNegotiateFetchCallback() and +// transportDownloadPackCallback() glue functions because `git_clone` manually deallocs the +// `git_repository` when it finishes. It's faster than using newRepositoryFromC and unsetting the +// returned Repository's finalizer +func newRepositoryFromCNoFinalizer(cRepo *C.git_repository) *Repository { + repo := &Repository{ptr: cRepo} + repo.Remotes.repo = repo + repo.Submodules.repo = repo + repo.References.repo = repo + repo.Notes.repo = repo + repo.Tags.repo = repo + repo.Stashes.repo = repo + return repo +} + func OpenRepository(path string) (*Repository, error) { cpath := C.CString(path) defer C.free(unsafe.Pointer(cpath)) diff --git a/transport.go b/transport.go new file mode 100644 index 00000000..eb0b547e --- /dev/null +++ b/transport.go @@ -0,0 +1,290 @@ +package git + +/* +#include +#include + +extern int _go_git_transport_register(const char *scheme, void *ptr); +extern void _go_git_init_transport_interface_wrapper(git_transport *transport); +extern int _go_git_call_progress_cb(git_transfer_progress_cb progress_cb, git_transfer_progress *stats, void *progress_payload); +extern void _go_git_call_push_transfer_progress(git_push_transfer_progress cb, unsigned int written, unsigned int total, size_t bytes, void *payload); + +*/ +import "C" +import ( + "fmt" + "runtime" + "sync" + "unsafe" +) + +// int git_transport_cb(git_transport **out, git_remote *owner, void *param); +type CreateTransportCallback func(owner *Remote) (Transport, error) + +//export CallbackGitCreateTransport +func CallbackGitCreateTransport(transportPtr **C.git_transport, remotePtr *C.git_remote, cbPtr unsafe.Pointer) C.int { + p := pointerHandles.Get(cbPtr) + if callback, ok := p.(CreateTransportCallback); ok { + remote := &Remote{ptr: remotePtr} + + goTransport, err := callback(remote) + if err != nil { + return -1 + } + + cTransport := &C.git_transport{} + ret := C.git_transport_init(cTransport, C.GIT_TRANSPORT_VERSION) + if ret < 0 { + return ret + } + + C._go_git_init_transport_interface_wrapper(cTransport) + + transportHandles.Track(cTransport, goTransport) + *transportPtr = cTransport + return 0 + + } else { + panic("invalid transport callback") + } +} + +func RegisterTransport(scheme string, callback CreateTransportCallback) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + cscheme := C.CString(scheme) + defer C.free(unsafe.Pointer(cscheme)) + + cb_ptr := pointerHandles.Track(callback) + + err := C._go_git_transport_register(cscheme, cb_ptr) + if err < 0 { + return MakeGitError(err) + } + + return nil +} + +type Transport interface { + // int (*set_custom_headers)( + // git_transport *transport, + // const git_strarray *custom_headers); + SetCustomHeaders(headers []string) error + + // int (*connect)( + // git_transport *transport, + // const char *url, + // git_cred_acquire_cb cred_acquire_cb, + // void *cred_acquire_payload, + // const git_proxy_options *proxy_opts, + // int direction, + // int flags); + Connect(url string) error + + // int (*ls)( + // const git_remote_head ***out, + // size_t *size, + // git_transport *transport); + Ls() ([]RemoteHead, error) + + // int (*negotiate_fetch)( + // git_transport *transport, + // git_repository *repo, + // const git_remote_head * const *refs, + // size_t count); + NegotiateFetch(r *Repository, wants []RemoteHead) error + + // int (*download_pack)( + // git_transport *transport, + // git_repository *repo, + // git_transfer_progress *stats, + // git_transfer_progress_cb progress_cb, + // void *progress_payload); + DownloadPack(r *Repository, progress *TransferProgress, progressCb TransferProgressCallback) error + + // int (*is_connected)(git_transport *transport); + IsConnected() (bool, error) + + // void (*cancel)(git_transport *transport); + Cancel() + + // int (*close)(git_transport *transport); + Close() error + + // void (*free)(git_transport *transport); + Free() + + // Push is apparently not supposed to be implemented by anything not conforming to the smart + // protocol (see smart_protocol.c). Seems strange, because it's clearly part of this interface. + // For more info: https://github.com/libgit2/libgit2/issues/4648 + // + // int(*push)(git_transport *transport, git_push *push, const git_remote_callbacks *callbacks); + // Push(progressCb func(objsWritten, objsTotal uint32, bytes uint)) error +} + +//export transportSetCustomHeadersCallback +func transportSetCustomHeadersCallback(cTransport *C.git_transport, cHeaders *C.git_strarray) int { + goTransport := transportHandles.Get(cTransport) + + err := goTransport.SetCustomHeaders(makeStringsFromCStrings(cHeaders.strings, int(cHeaders.count))) + if err != nil { + return -1 + } + + return 0 +} + +//export transportConnectCallback +func transportConnectCallback( + cTransport *C.git_transport, + url *C.char, + cred_acquire_cb C.git_cred_acquire_cb, + cred_acquire_payload unsafe.Pointer, + proxy_opts *C.git_proxy_options, + direction int, + flags int, +) int { + goTransport := transportHandles.Get(cTransport) + + err := goTransport.Connect(C.GoString(url)) + if err != nil { + return -1 + } + + return 0 +} + +//export transportLsCallback +func transportLsCallback(outRemoteHeads ***C.git_remote_head, outNumHeads *C.size_t, cTransport *C.git_transport) int { + goTransport := transportHandles.Get(cTransport) + + goRemoteHeads, err := goTransport.Ls() + if err != nil { + return -1 + } + + *outNumHeads = C.size_t(len(goRemoteHeads)) + cRemoteHeads := make([]*C.git_remote_head, len(goRemoteHeads)) + for i := 0; i < len(goRemoteHeads); i++ { + cRemoteHeads[i] = goRemoteHeads[i].toC() + } + if len(cRemoteHeads) > 0 { + *outRemoteHeads = (**C.git_remote_head)(unsafe.Pointer(&cRemoteHeads[0])) + } + + return 0 +} + +//export transportNegotiateFetchCallback +func transportNegotiateFetchCallback(cTransport *C.git_transport, cRepo *C.git_repository, cRefs **C.git_remote_head, numRefs C.size_t) int { + goTransport := transportHandles.Get(cTransport) + + repo := newRepositoryFromCNoFinalizer(cRepo) + + err := goTransport.NegotiateFetch(repo, newRemoteHeadsFromC(cRefs, numRefs)) + if err != nil { + return -1 + } + return 0 +} + +//export transportDownloadPackCallback +func transportDownloadPackCallback( + cTransport *C.git_transport, + cRepo *C.git_repository, + cProgress *C.git_transfer_progress, + progress_cb C.git_transfer_progress_cb, + progress_payload unsafe.Pointer, +) int { + goTransport := transportHandles.Get(cTransport) + + repo := newRepositoryFromCNoFinalizer(cRepo) + progress := newTransferProgressFromC(cProgress) + + progressCb := func(stats TransferProgress) ErrorCode { + return ErrorCode(C._go_git_call_progress_cb(progress_cb, stats.toC(), progress_payload)) + } + + err := goTransport.DownloadPack(repo, &progress, progressCb) + if err != nil { + return -1 + } + return 0 +} + +//export transportIsConnectedCallback +func transportIsConnectedCallback(cTransport *C.git_transport) int { + goTransport := transportHandles.Get(cTransport) + + is, err := goTransport.IsConnected() + if err != nil { + return -1 + } else if is { + return 1 + } + return 0 +} + +//export transportCancelCallback +func transportCancelCallback(cTransport *C.git_transport) { + goTransport := transportHandles.Get(cTransport) + goTransport.Cancel() +} + +//export transportCloseCallback +func transportCloseCallback(cTransport *C.git_transport) int { + goTransport := transportHandles.Get(cTransport) + err := goTransport.Close() + if err != nil { + return -1 + } + return 0 +} + +//export transportFreeCallback +func transportFreeCallback(cTransport *C.git_transport) { + goTransport := transportHandles.Get(cTransport) + goTransport.Free() + + transportHandles.Untrack(cTransport) +} + +// Maps a registered Transport instance to a C git_transport pointer so that Go code can retrieve +// the Transport given only a git_transport*. +type transportHandleList struct { + sync.RWMutex + handles map[*C.git_transport]Transport +} + +var transportHandles = newTransportHandleList() + +func newTransportHandleList() *transportHandleList { + return &transportHandleList{ + handles: make(map[*C.git_transport]Transport), + } +} + +func (v *transportHandleList) Track(cTransport *C.git_transport, goTransport Transport) { + v.Lock() + v.handles[cTransport] = goTransport + v.Unlock() +} + +func (v *transportHandleList) Untrack(cTransport *C.git_transport) { + v.Lock() + delete(v.handles, cTransport) + v.Unlock() +} + +func (v *transportHandleList) Get(cTransport *C.git_transport) Transport { + v.RLock() + defer v.RUnlock() + + ptr, ok := v.handles[cTransport] + if !ok { + panic(fmt.Sprintf("invalid pointer handle: %p", cTransport)) + } + + return ptr +} diff --git a/wrapper.c b/wrapper.c index 11c2f324..bd1e04dc 100644 --- a/wrapper.c +++ b/wrapper.c @@ -1,5 +1,6 @@ #include "_cgo_export.h" #include +#include #include #include @@ -180,4 +181,60 @@ void _go_git_writestream_free(git_writestream *stream) stream->free(stream); } +int _go_git_odb_write_pack(git_odb_writepack **out, git_odb *db, void *progress_payload) +{ + return git_odb_write_pack(out, db, (git_transfer_progress_cb)transferProgressCallback, progress_payload); +} + +int _go_git_odb_writepack_append(git_odb_writepack *writepack, const void *data, size_t size, git_transfer_progress *stats) +{ + return writepack->append(writepack, data, size, stats); +} + +int _go_git_odb_writepack_commit(git_odb_writepack *writepack, git_transfer_progress *stats) +{ + return writepack->commit(writepack, stats); +} + +void _go_git_odb_writepack_free(git_odb_writepack *writepack) +{ + writepack->free(writepack); +} + +int _go_git_indexer_new(git_indexer **out, const char *path, unsigned int mode, git_odb *odb, void *progress_cb_payload) +{ + return git_indexer_new(out, path, mode, odb, (git_transfer_progress_cb)transferProgressCallback, progress_cb_payload); +} + +int _go_git_transport_register(const char *scheme, void *ptr) +{ + return git_transport_register(scheme, (git_transport_cb)&CallbackGitCreateTransport, ptr); +} + +void _go_git_init_transport_interface_wrapper(git_transport *transport) { + typedef int (*set_custom_headers_cb)(git_transport *transport, const git_strarray *custom_headers); + typedef int (*ls_cb)(const git_remote_head ***remoteHeads, size_t *numHeads, git_transport *transport); + typedef int (*is_connected_cb)(git_transport *transport); + typedef int (*connect_cb)(git_transport *transport, const char *url, git_cred_acquire_cb cred_acquire_cb, void *cred_acquire_payload, const git_proxy_options *proxy_opts, int direction, int flags); + typedef int (*negotiate_fetch_cb)(git_transport *transport, git_repository *repo, const git_remote_head * const *refs, size_t count); + typedef int (*download_pack_cb)(git_transport *transport, git_repository *repo, git_transfer_progress *stats, git_transfer_progress_cb progress_cb, void *progress_payload); + typedef void (*cancel_cb)(git_transport *transport); + typedef int (*close_cb)(git_transport *transport); + typedef void (*free_cb)(git_transport *transport); + + transport->set_custom_headers = (set_custom_headers_cb)transportSetCustomHeadersCallback; + transport->negotiate_fetch = (negotiate_fetch_cb)transportNegotiateFetchCallback; + transport->ls = (ls_cb)transportLsCallback; + transport->connect = (connect_cb)transportConnectCallback; + transport->download_pack = (download_pack_cb)transportDownloadPackCallback; + transport->is_connected = (is_connected_cb)transportIsConnectedCallback; + transport->cancel = (cancel_cb)transportCancelCallback; + transport->close = (close_cb)transportCloseCallback; + transport->free = (free_cb)transportFreeCallback; +} + +int _go_git_call_progress_cb(git_transfer_progress_cb progress_cb, git_transfer_progress *stats, void *progress_payload) { + return progress_cb(stats, progress_payload); +} + /* EOF */ From 5505ad458bd94930bec0fd0243e56e4d150094e2 Mon Sep 17 00:00:00 2001 From: Bryn Bellomy Date: Fri, 22 Feb 2019 05:20:31 -0600 Subject: [PATCH 2/2] git_transfer_progress.received_bytes is a size_t --- remote.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/remote.go b/remote.go index 29d8622a..c1a0f9fc 100644 --- a/remote.go +++ b/remote.go @@ -42,7 +42,7 @@ func (tp TransferProgress) toC() *C.git_transfer_progress { received_objects: C.uint(tp.ReceivedObjects), local_objects: C.uint(tp.LocalObjects), total_deltas: C.uint(tp.TotalDeltas), - received_bytes: C.ulong(tp.ReceivedBytes), + received_bytes: C.size_t(tp.ReceivedBytes), } }