diff --git a/clone.go b/clone.go new file mode 100644 index 0000000..630c343 --- /dev/null +++ b/clone.go @@ -0,0 +1,79 @@ +package git + +/* +#include +#include + +static git_clone_options git_clone_options_init() { + git_clone_options ret = GIT_CLONE_OPTIONS_INIT; + return ret; +} + +*/ +import "C" +import ( + "runtime" + "unsafe" +) + +type CloneOptions struct { + *CheckoutOpts + *RemoteCallbacks + Bare bool + IgnoreCertErrors bool + RemoteName string + CheckoutBranch string +} + +func Clone(url string, path string, options *CloneOptions) (*Repository, error) { + repo := new(Repository) + + curl := C.CString(url) + defer C.free(unsafe.Pointer(curl)) + + cpath := C.CString(path) + defer C.free(unsafe.Pointer(cpath)) + + var copts C.git_clone_options + populateCloneOptions(&copts, options) + + // finish populating clone options here so we can defer CString free + if len(options.RemoteName) != 0 { + copts.remote_name = C.CString(options.RemoteName) + defer C.free(unsafe.Pointer(copts.remote_name)) + } + + if len(options.CheckoutBranch) != 0 { + copts.checkout_branch = C.CString(options.CheckoutBranch) + defer C.free(unsafe.Pointer(copts.checkout_branch)) + } + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_clone(&repo.ptr, curl, cpath, &copts) + if ret < 0 { + return nil, MakeGitError(ret) + } + + runtime.SetFinalizer(repo, (*Repository).Free) + return repo, nil +} + +func populateCloneOptions(ptr *C.git_clone_options, opts *CloneOptions) { + *ptr = C.git_clone_options_init() + if opts == nil { + return + } + populateCheckoutOpts(&ptr.checkout_opts, opts.CheckoutOpts) + populateRemoteCallbacks(&ptr.remote_callbacks, opts.RemoteCallbacks) + if opts.Bare { + ptr.bare = 1 + } else { + ptr.bare = 0 + } + if opts.IgnoreCertErrors { + ptr.ignore_cert_errors = 1 + } else { + ptr.ignore_cert_errors = 0 + } +} diff --git a/git.go b/git.go index 7c76c30..db9522b 100644 --- a/git.go +++ b/git.go @@ -10,8 +10,8 @@ import ( "bytes" "errors" "runtime" - "unsafe" "strings" + "unsafe" ) const ( diff --git a/remote.go b/remote.go new file mode 100644 index 0000000..38c1d47 --- /dev/null +++ b/remote.go @@ -0,0 +1,410 @@ +package git + +/* +#include +#include + +extern void _go_git_setup_callbacks(git_remote_callbacks *callbacks); +extern git_remote_callbacks _go_git_remote_callbacks_init(); +extern void _go_git_set_strarray_n(git_strarray *array, char *str, size_t n); +extern char *_go_git_get_strarray_n(git_strarray *array, size_t n); + +*/ +import "C" +import "unsafe" +import "runtime" + +type TransferProgress struct { + ptr *C.git_transfer_progress +} + +type RemoteCompletion uint + +const ( + RemoteCompletionDownload RemoteCompletion = C.GIT_REMOTE_COMPLETION_DOWNLOAD + RemoteCompletionIndexing = C.GIT_REMOTE_COMPLETION_INDEXING + RemoteCompletionError = C.GIT_REMOTE_COMPLETION_ERROR +) + +type ProgressCallback func(str string) int +type CompletionCallback func(RemoteCompletion) int +type CredentialsCallback func(url string, username_from_url string, allowed_types CredType) (int, Cred) +type TransferProgressCallback func(stats TransferProgress) int +type UpdateTipsCallback func(refname string, a *Oid, b *Oid) int + +type RemoteCallbacks struct { + ProgressCallback + CompletionCallback + CredentialsCallback + TransferProgressCallback + UpdateTipsCallback +} + +type Remote interface { + Save() error + Owner() Repository + Name() string + Url() string + PushUrl() string + + SetUrl(url string) error + SetPushUrl(url string) error + + AddFetch(refspec string) error + GetFetchRefspecs() ([]string, error) + SetFetchRefspecs(refspecs []string) error + AddPush(refspec string) error + GetPushRefspecs() ([]string, error) + SetPushRefspecs(refspecs []string) error + ClearRefspecs() + RefspecCount() uint +} + +type gitRemote struct { + ptr *C.git_remote +} + +func populateRemoteCallbacks(ptr *C.git_remote_callbacks, callbacks *RemoteCallbacks) { + *ptr = C._go_git_remote_callbacks_init() + if callbacks == nil { + return + } + C._go_git_setup_callbacks(ptr) + ptr.payload = unsafe.Pointer(callbacks) +} + +//export progressCallback +func progressCallback(_str *C.char, _len C.int, data unsafe.Pointer) int { + callbacks := (*RemoteCallbacks)(data) + if callbacks.ProgressCallback == nil { + return 0 + } + str := C.GoStringN(_str, _len) + return callbacks.ProgressCallback(str) +} + +//export completionCallback +func completionCallback(completion_type C.git_remote_completion_type, data unsafe.Pointer) int { + callbacks := (*RemoteCallbacks)(data) + if callbacks.CompletionCallback == nil { + return 0 + } + return callbacks.CompletionCallback((RemoteCompletion)(completion_type)) +} + +//export credentialsCallback +func credentialsCallback(_cred **C.git_cred, _url *C.char, _username_from_url *C.char, allowed_types uint, data unsafe.Pointer) int { + callbacks := (*RemoteCallbacks)(data) + if callbacks.CredentialsCallback == nil { + return 0 + } + url := C.GoString(_url) + username_from_url := C.GoString(_username_from_url) + ret, cred := callbacks.CredentialsCallback(url, username_from_url, (CredType)(allowed_types)) + if gcred, ok := cred.(gitCred); ok { + *_cred = gcred.ptr + } + return ret +} + +//export transferProgressCallback +func transferProgressCallback(stats *C.git_transfer_progress, data unsafe.Pointer) int { + callbacks := (*RemoteCallbacks)(data) + if callbacks.TransferProgressCallback == nil { + return 0 + } + return callbacks.TransferProgressCallback(TransferProgress{stats}) +} + +//export updateTipsCallback +func updateTipsCallback(_refname *C.char, _a *C.git_oid, _b *C.git_oid, data unsafe.Pointer) int { + callbacks := (*RemoteCallbacks)(data) + if callbacks.UpdateTipsCallback == nil { + return 0 + } + refname := C.GoString(_refname) + a := newOidFromC(_a) + b := newOidFromC(_b) + return callbacks.UpdateTipsCallback(refname, a, b) +} + +func (o TransferProgress) TotalObjects() uint { + return uint(o.ptr.total_objects) +} + +func (o TransferProgress) IndexedObjects() uint { + return uint(o.ptr.indexed_objects) +} + +func (o TransferProgress) ReceivedObjects() uint { + return uint(o.ptr.received_objects) +} + +func (o TransferProgress) LocalObjects() uint { + return uint(o.ptr.local_objects) +} + +func (o TransferProgress) TotalDeltas() uint { + return uint(o.ptr.total_deltas) +} + +func (o TransferProgress) ReceivedBytes() uint { + return uint(o.ptr.received_bytes) +} + +func RemoteIsValidName(name string) bool { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + if C.git_remote_is_valid_name(cname) == 1 { + return true + } + return false +} + +func freeRemote(o *gitRemote) { + C.git_remote_free(o.ptr) +} + +func CreateRemote(repo *Repository, name string, url string) (Remote, error) { + remote := &gitRemote{} + runtime.SetFinalizer(remote, freeRemote) + + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + curl := C.CString(url) + defer C.free(unsafe.Pointer(curl)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_create(&remote.ptr, repo.ptr, cname, curl) + if ret < 0 { + return nil, MakeGitError(ret) + } + return remote, nil +} + +func CreateRemoteWithFetchspec(repo *Repository, name string, url string, fetch string) (Remote, error) { + remote := &gitRemote{} + runtime.SetFinalizer(remote, freeRemote) + + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + curl := C.CString(url) + defer C.free(unsafe.Pointer(curl)) + cfetch := C.CString(fetch) + defer C.free(unsafe.Pointer(cfetch)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_create_with_fetchspec(&remote.ptr, repo.ptr, cname, curl, cfetch) + if ret < 0 { + return nil, MakeGitError(ret) + } + return remote, nil +} + +func CreateRemoteInMemory(repo *Repository, fetch string, url string) (Remote, error) { + remote := &gitRemote{} + runtime.SetFinalizer(remote, freeRemote) + + curl := C.CString(url) + defer C.free(unsafe.Pointer(curl)) + cfetch := C.CString(fetch) + defer C.free(unsafe.Pointer(cfetch)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_create_inmemory(&remote.ptr, repo.ptr, cfetch, curl) + if ret < 0 { + return nil, MakeGitError(ret) + } + return remote, nil +} + +func LoadRemote(repo *Repository, name string) (Remote, error) { + remote := &gitRemote{} + runtime.SetFinalizer(remote, freeRemote) + + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_load(&remote.ptr, repo.ptr, cname) + if ret < 0 { + return nil, MakeGitError(ret) + } + return remote, nil +} + +func (o *gitRemote) Save() error { + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_save(o.ptr) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +func (o *gitRemote) Owner() Repository { + return Repository{C.git_remote_owner(o.ptr)} +} + +func (o *gitRemote) Name() string { + return C.GoString(C.git_remote_name(o.ptr)) +} + +func (o *gitRemote) Url() string { + return C.GoString(C.git_remote_url(o.ptr)) +} + +func (o *gitRemote) PushUrl() string { + return C.GoString(C.git_remote_pushurl(o.ptr)) +} + +func (o *gitRemote) SetUrl(url string) error { + curl := C.CString(url) + defer C.free(unsafe.Pointer(curl)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_set_url(o.ptr, curl) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +func (o *gitRemote) SetPushUrl(url string) error { + curl := C.CString(url) + defer C.free(unsafe.Pointer(curl)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_set_pushurl(o.ptr, curl) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +func (o *gitRemote) AddFetch(refspec string) error { + crefspec := C.CString(refspec) + defer C.free(unsafe.Pointer(crefspec)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_add_fetch(o.ptr, crefspec) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +func (o *gitRemote) GetFetchRefspecs() ([]string, error) { + crefspecs := C.git_strarray{} + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_get_fetch_refspecs(&crefspecs, o.ptr) + if ret < 0 { + return nil, MakeGitError(ret) + } + defer C.git_strarray_free(&crefspecs) + refspecs := make([]string, crefspecs.count) + + for i := 0; i < int(crefspecs.count); i++ { + refspecs[i] = C.GoString(C._go_git_get_strarray_n(&crefspecs, C.size_t(i))) + } + return refspecs, nil +} + +func (o *gitRemote) SetFetchRefspecs(refspecs []string) error { + crefspecs := C.git_strarray{} + crefspecs.count = C.size_t(len(refspecs)) + crefspecs.strings = (**C.char)(C.malloc(C.size_t(unsafe.Sizeof(unsafe.Pointer(nil)) * uintptr(crefspecs.count)))) + for i, refspec := range refspecs { + C._go_git_set_strarray_n(&crefspecs, C.CString(refspec), C.size_t(i)) + } + defer C.git_strarray_free(&crefspecs) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_set_fetch_refspecs(o.ptr, &crefspecs) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +func (o *gitRemote) AddPush(refspec string) error { + crefspec := C.CString(refspec) + defer C.free(unsafe.Pointer(crefspec)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_add_push(o.ptr, crefspec) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +func (o *gitRemote) GetPushRefspecs() ([]string, error) { + crefspecs := C.git_strarray{} + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_get_push_refspecs(&crefspecs, o.ptr) + if ret < 0 { + return nil, MakeGitError(ret) + } + defer C.git_strarray_free(&crefspecs) + refspecs := make([]string, crefspecs.count) + + for i := 0; i < int(crefspecs.count); i++ { + refspecs[i] = C.GoString(C._go_git_get_strarray_n(&crefspecs, C.size_t(i))) + } + return refspecs, nil +} + +func (o *gitRemote) SetPushRefspecs(refspecs []string) error { + crefspecs := C.git_strarray{} + crefspecs.count = C.size_t(len(refspecs)) + crefspecs.strings = (**C.char)(C.malloc(C.size_t(unsafe.Sizeof(unsafe.Pointer(nil)) * uintptr(crefspecs.count)))) + for i, refspec := range refspecs { + C._go_git_set_strarray_n(&crefspecs, C.CString(refspec), C.size_t(i)) + } + defer C.git_strarray_free(&crefspecs) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_set_push_refspecs(o.ptr, &crefspecs) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +func (o *gitRemote) ClearRefspecs() { + C.git_remote_clear_refspecs(o.ptr) +} + +func (o *gitRemote) RefspecCount() uint { + return uint(C.git_remote_refspec_count(o.ptr)) +} diff --git a/submodule.go b/submodule.go index dcc4723..a94afd4 100644 --- a/submodule.go +++ b/submodule.go @@ -238,7 +238,7 @@ func (sub *Submodule) SetUpdate(update SubmoduleUpdate) SubmoduleUpdate { } func (sub *Submodule) FetchRecurseSubmodules() SubmoduleRecurse { - return SubmoduleRecurse(C.git_submodule_fetch_recurse_submodules(sub.ptr)); + return SubmoduleRecurse(C.git_submodule_fetch_recurse_submodules(sub.ptr)) } func (sub *Submodule) SetFetchRecurseSubmodules(recurse SubmoduleRecurse) error { diff --git a/transport.go b/transport.go new file mode 100644 index 0000000..e97a70c --- /dev/null +++ b/transport.go @@ -0,0 +1,79 @@ +package git + +/* +#include +#include +*/ +import "C" +import "unsafe" + +type CredType uint +const ( + CredTypeUserpassPlaintext CredType = C.GIT_CREDTYPE_USERPASS_PLAINTEXT + CredTypeSshKey = C.GIT_CREDTYPE_SSH_KEY + CredTypeSshCustom = C.GIT_CREDTYPE_SSH_CUSTOM + CredTypeDefault = C.GIT_CREDTYPE_DEFAULT +) + +type Cred interface { + HasUsername() bool + Type() CredType +} + +type gitCred struct { + ptr *C.git_cred +} + +func (o gitCred) HasUsername() bool { + if C.git_cred_has_username(o.ptr) == 1 { + return true + } + return false +} + +func (o gitCred) Type() CredType { + return (CredType)(o.ptr.credtype); +} + +func credFromC(ptr *C.git_cred) Cred { + return gitCred{ptr} +} + +func NewCredUserpassPlaintext(username string, password string) (int, Cred) { + cred := gitCred{} + cusername := C.CString(username) + defer C.free(unsafe.Pointer(cusername)) + cpassword := C.CString(password) + defer C.free(unsafe.Pointer(cpassword)) + ret := C.git_cred_userpass_plaintext_new(&cred.ptr, cusername, cpassword) + return int(ret), cred +} + +func NewCredSshKey(username string, publickey string, privatekey string, passphrase string) (int, Cred) { + cred := gitCred{} + cusername := C.CString(username) + defer C.free(unsafe.Pointer(cusername)) + cpublickey := C.CString(publickey) + defer C.free(unsafe.Pointer(cpublickey)) + cprivatekey := C.CString(privatekey) + defer C.free(unsafe.Pointer(cprivatekey)) + cpassphrase := C.CString(passphrase) + defer C.free(unsafe.Pointer(cpassphrase)) + ret := C.git_cred_ssh_key_new(&cred.ptr, cusername, cpublickey, cprivatekey, cpassphrase) + return int(ret), cred +} + +func NewCredSshKeyFromAgent(username string) (int, Cred) { + cred := gitCred{} + cusername := C.CString(username) + defer C.free(unsafe.Pointer(cusername)) + ret := C.git_cred_ssh_key_from_agent(&cred.ptr, cusername) + return int(ret), cred +} + +func NewCredDefault() (int, Cred) { + cred := gitCred{} + ret := C.git_cred_default_new(&cred.ptr) + return int(ret), cred +} + diff --git a/wrapper.c b/wrapper.c index 2af3974..7519a96 100644 --- a/wrapper.c +++ b/wrapper.c @@ -24,4 +24,31 @@ int _go_git_odb_foreach(git_odb *db, void *payload) { return git_odb_foreach(db, (git_odb_foreach_cb)&odbForEachCb, payload); } + +void _go_git_setup_callbacks(git_remote_callbacks *callbacks) { + typedef int (*progress_cb)(const char *str, int len, void *data); + typedef int (*completion_cb)(git_remote_completion_type type, void *data); + typedef int (*credentials_cb)(git_cred **cred, const char *url, const char *username_from_url, unsigned int allowed_types, void *data); + typedef int (*transfer_progress_cb)(const git_transfer_progress *stats, void *data); + typedef int (*update_tips_cb)(const char *refname, const git_oid *a, const git_oid *b, void *data); + callbacks->progress = (progress_cb)progressCallback; + callbacks->completion = (completion_cb)completionCallback; + callbacks->credentials = (credentials_cb)credentialsCallback; + callbacks->transfer_progress = (transfer_progress_cb)transferProgressCallback; + callbacks->update_tips = (update_tips_cb)updateTipsCallback; +} + +git_remote_callbacks _go_git_remote_callbacks_init() { + git_remote_callbacks ret = GIT_REMOTE_CALLBACKS_INIT; + return ret; +} + +void _go_git_set_strarray_n(git_strarray *array, char *str, size_t n) { + array->strings[n] = str; +} + +char *_go_git_get_strarray_n(git_strarray *array, size_t n) { + return array->strings[n]; +} + /* EOF */