diff --git a/.travis.yml b/.travis.yml index 209d89f..f796389 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ go: - 1.2 - 1.3 - 1.4 + - 1.5 - tip matrix: diff --git a/README.md b/README.md index 490386c..a5e6100 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ Parallelism and network operations ---------------------------------- libgit2 uses OpenSSL and LibSSH2 for performing encrypted network connections. For now, git2go asks libgit2 to set locking for OpenSSL. This makes HTTPS connections thread-safe, but it is fragile and will likely stop doing it soon. This may also make SSH connections thread-safe if your copy of libssh2 is linked against OpenSSL. Check libgit2's `THREADSAFE.md` for more information. -[ + Running the tests ----------------- diff --git a/branch.go b/branch.go index d0a0835..df72dba 100644 --- a/branch.go +++ b/branch.go @@ -94,6 +94,7 @@ func (repo *Repository) CreateBranch(branchName string, target *Commit, force bo var ptr *C.git_reference cBranchName := C.CString(branchName) + defer C.free(unsafe.Pointer(cBranchName)) cForce := cbool(force) runtime.LockOSThread() @@ -120,6 +121,7 @@ func (b *Branch) Delete() error { func (b *Branch) Move(newBranchName string, force bool) (*Branch, error) { var ptr *C.git_reference cNewBranchName := C.CString(newBranchName) + defer C.free(unsafe.Pointer(cNewBranchName)) cForce := cbool(force) runtime.LockOSThread() @@ -152,6 +154,7 @@ func (repo *Repository) LookupBranch(branchName string, bt BranchType) (*Branch, var ptr *C.git_reference cName := C.CString(branchName) + defer C.free(unsafe.Pointer(cName)) runtime.LockOSThread() defer runtime.UnlockOSThread() @@ -180,6 +183,7 @@ func (b *Branch) Name() (string, error) { func (repo *Repository) RemoteName(canonicalBranchName string) (string, error) { cName := C.CString(canonicalBranchName) + defer C.free(unsafe.Pointer(cName)) nameBuf := C.git_buf{} @@ -197,6 +201,7 @@ func (repo *Repository) RemoteName(canonicalBranchName string) (string, error) { func (b *Branch) SetUpstream(upstreamName string) error { cName := C.CString(upstreamName) + defer C.free(unsafe.Pointer(cName)) runtime.LockOSThread() defer runtime.UnlockOSThread() @@ -223,6 +228,7 @@ func (b *Branch) Upstream() (*Reference, error) { func (repo *Repository) UpstreamName(canonicalBranchName string) (string, error) { cName := C.CString(canonicalBranchName) + defer C.free(unsafe.Pointer(cName)) nameBuf := C.git_buf{} diff --git a/clone.go b/clone.go index b5c5a5b..e80d14d 100644 --- a/clone.go +++ b/clone.go @@ -3,6 +3,7 @@ package git /* #include +extern void _go_git_populate_remote_cb(git_clone_options *opts); */ import "C" import ( @@ -10,13 +11,14 @@ import ( "unsafe" ) +type RemoteCreateCallback func(repo *Repository, name, url string) (*Remote, ErrorCode) + type CloneOptions struct { *CheckoutOpts *FetchOptions Bare bool CheckoutBranch string - RemoteCreateCallback C.git_remote_create_cb - RemoteCreatePayload unsafe.Pointer + RemoteCreateCallback RemoteCreateCallback } func Clone(url string, path string, options *CloneOptions) (*Repository, error) { @@ -28,6 +30,7 @@ func Clone(url string, path string, options *CloneOptions) (*Repository, error) copts := (*C.git_clone_options)(C.calloc(1, C.size_t(unsafe.Sizeof(C.git_clone_options{})))) populateCloneOptions(copts, options) + defer freeCloneOptions(copts) if len(options.CheckoutBranch) != 0 { copts.checkout_branch = C.CString(options.CheckoutBranch) @@ -35,11 +38,10 @@ func Clone(url string, path string, options *CloneOptions) (*Repository, error) runtime.LockOSThread() defer runtime.UnlockOSThread() + var ptr *C.git_repository ret := C.git_clone(&ptr, curl, cpath, copts) freeCheckoutOpts(&copts.checkout_opts) - C.free(unsafe.Pointer(copts.checkout_branch)) - C.free(unsafe.Pointer(copts)) if ret < 0 { return nil, MakeGitError(ret) @@ -48,6 +50,33 @@ func Clone(url string, path string, options *CloneOptions) (*Repository, error) return newRepositoryFromC(ptr), nil } +//export remoteCreateCallback +func remoteCreateCallback(cremote unsafe.Pointer, crepo unsafe.Pointer, cname, curl *C.char, payload unsafe.Pointer) C.int { + name := C.GoString(cname) + url := C.GoString(curl) + repo := newRepositoryFromC((*C.git_repository)(crepo)) + // We don't own this repository, so make sure we don't try to free it + runtime.SetFinalizer(repo, nil) + + if opts, ok := pointerHandles.Get(payload).(CloneOptions); ok { + remote, err := opts.RemoteCreateCallback(repo, name, url) + // clear finalizer as the calling C function will + // free the remote itself + runtime.SetFinalizer(remote, nil) + + if err == ErrOk && remote != nil { + cptr := (**C.git_remote)(cremote) + *cptr = remote.ptr + } else if err == ErrOk && remote == nil { + panic("no remote created by callback") + } + + return C.int(err) + } else { + panic("invalid remote create callback") + } +} + func populateCloneOptions(ptr *C.git_clone_options, opts *CloneOptions) { C.git_clone_init_options(ptr, C.GIT_CLONE_OPTIONS_VERSION) @@ -59,12 +88,23 @@ func populateCloneOptions(ptr *C.git_clone_options, opts *CloneOptions) { ptr.bare = cbool(opts.Bare) if opts.RemoteCreateCallback != nil { - ptr.remote_cb = opts.RemoteCreateCallback - defer C.free(unsafe.Pointer(opts.RemoteCreateCallback)) - - if opts.RemoteCreatePayload != nil { - ptr.remote_cb_payload = opts.RemoteCreatePayload - defer C.free(opts.RemoteCreatePayload) - } + // Go v1.1 does not allow to assign a C function pointer + C._go_git_populate_remote_cb(ptr) + ptr.remote_cb_payload = pointerHandles.Track(*opts) } } + +func freeCloneOptions(ptr *C.git_clone_options) { + if ptr == nil { + return + } + + freeCheckoutOpts(&ptr.checkout_opts) + + if ptr.remote_cb_payload != nil { + pointerHandles.Untrack(ptr.remote_cb_payload) + } + + C.free(unsafe.Pointer(ptr.checkout_branch)) + C.free(unsafe.Pointer(ptr)) +} diff --git a/clone_test.go b/clone_test.go index 7cdc362..a6bbf94 100644 --- a/clone_test.go +++ b/clone_test.go @@ -5,8 +5,11 @@ import ( "testing" ) -func TestClone(t *testing.T) { +const ( + REMOTENAME = "testremote" +) +func TestClone(t *testing.T) { repo := createTestRepo(t) defer cleanupTestRepo(t, repo) @@ -30,3 +33,43 @@ func TestClone(t *testing.T) { t.Fatal("reference in clone does not match original ref") } } + +func TestCloneWithCallback(t *testing.T) { + testPayload := 0 + + repo := createTestRepo(t) + defer cleanupTestRepo(t, repo) + + seedTestRepo(t, repo) + + path, err := ioutil.TempDir("", "git2go") + checkFatal(t, err) + + opts := CloneOptions{ + Bare: true, + RemoteCreateCallback: func(r *Repository, name, url string) (*Remote, ErrorCode) { + testPayload += 1 + + remote, err := r.Remotes.Create(REMOTENAME, url) + if err != nil { + return nil, ErrGeneric + } + + return remote, ErrOk + }, + } + + repo2, err := Clone(repo.Path(), path, &opts) + defer cleanupTestRepo(t, repo2) + + checkFatal(t, err) + + if testPayload != 1 { + t.Fatal("Payload's value has not been changed") + } + + remote, err := repo2.Remotes.Lookup(REMOTENAME) + if err != nil || remote == nil { + t.Fatal("Remote was not created properly") + } +} diff --git a/merge.go b/merge.go index f1a6f40..a52e8f8 100644 --- a/merge.go +++ b/merge.go @@ -394,6 +394,7 @@ func MergeFile(ancestor MergeFileInput, ours MergeFileInput, theirs MergeFileInp return nil, MakeGitError(ecode) } populateCMergeFileOptions(copts, *options) + defer freeCMergeFileOptions(copts) } runtime.LockOSThread() diff --git a/script/install-libgit2.sh b/script/install-libgit2.sh index a6c3202..9bf6b37 100755 --- a/script/install-libgit2.sh +++ b/script/install-libgit2.sh @@ -13,9 +13,9 @@ if [ "x$TRAVIS_BRANCH" = "xnext" ]; then fi cd "${HOME}" -wget -O libgit2-0.22.3.tar.gz https://github.com/libgit2/libgit2/archive/v0.22.1.tar.gz -tar -xzvf libgit2-0.22.3.tar.gz -cd libgit2-0.22.1 && mkdir build && cd build +wget -O libgit2-0.23.1.tar.gz https://github.com/libgit2/libgit2/archive/v0.23.1.tar.gz +tar -xzvf libgit2-0.23.1.tar.gz +cd libgit2-0.23.1 && mkdir build && cd build cmake -DTHREADSAFE=ON -DBUILD_CLAR=OFF -DCMAKE_BUILD_TYPE="RelWithDebInfo" .. && make && sudo make install sudo ldconfig cd "${TRAVIS_BUILD_DIR}" diff --git a/status.go b/status.go index 3f5a06d..068a474 100644 --- a/status.go +++ b/status.go @@ -126,34 +126,24 @@ type StatusOptions struct { Pathspec []string } -func (opts *StatusOptions) toC() *C.git_status_options { - if opts == nil { - return nil - } - - cpathspec := C.git_strarray{} - if opts.Pathspec != nil { - cpathspec.count = C.size_t(len(opts.Pathspec)) - cpathspec.strings = makeCStringsFromStrings(opts.Pathspec) - defer freeStrarray(&cpathspec) - } - - copts := &C.git_status_options{ - version: C.GIT_STATUS_OPTIONS_VERSION, - show: C.git_status_show_t(opts.Show), - flags: C.uint(opts.Flags), - pathspec: cpathspec, - } - - return copts -} - func (v *Repository) StatusList(opts *StatusOptions) (*StatusList, error) { var ptr *C.git_status_list var copts *C.git_status_options if opts != nil { - copts = opts.toC() + cpathspec := C.git_strarray{} + if opts.Pathspec != nil { + cpathspec.count = C.size_t(len(opts.Pathspec)) + cpathspec.strings = makeCStringsFromStrings(opts.Pathspec) + defer freeStrarray(&cpathspec) + } + + copts = &C.git_status_options{ + version: C.GIT_STATUS_OPTIONS_VERSION, + show: C.git_status_show_t(opts.Show), + flags: C.uint(opts.Flags), + pathspec: cpathspec, + } } else { copts = &C.git_status_options{} ret := C.git_status_init_options(copts, C.GIT_STATUS_OPTIONS_VERSION) diff --git a/walk.go b/walk.go index d02044a..c314f60 100644 --- a/walk.go +++ b/walk.go @@ -173,6 +173,10 @@ func (v *RevWalk) Iterate(fun RevWalkIterator) (err error) { return nil } if err != nil { + if err.(GitError).Code == ErrIterOver { + err = nil + } + return err } diff --git a/wrapper.c b/wrapper.c index 1efe5d7..2b1a180 100644 --- a/wrapper.c +++ b/wrapper.c @@ -5,6 +5,11 @@ typedef int (*gogit_submodule_cbk)(git_submodule *sm, const char *name, void *payload); +void _go_git_populate_remote_cb(git_clone_options *opts) +{ + opts->remote_cb = (git_remote_create_cb)remoteCreateCallback; +} + int _go_git_visit_submodule(git_repository *repo, void *fct) { return git_submodule_foreach(repo, (gogit_submodule_cbk)&SubmoduleVisitor, fct);