Skip to content

Commit 0b530c1

Browse files
committed
clone: improve handling of remote create callback
The clone options contain fields for ae remote create callback and its payload, which can be used to override the behavior when the default remote is being created for newly cloned repositories. Currently we only accept a C function as callback, though, making it overly complicated to use it. We also unconditionally `free` the payload if its address is non-`nil`, which may cause the program to segfault when the memory is not dynamically allocated. Instead, we want callers to provide a Go function that is subsequently being called by us. To do this, we introduce an indirection such that we are able to extract the provided function and payload when being called by `git_clone` and handle the return values of the user-provided function.
1 parent 4eae20e commit 0b530c1

File tree

3 files changed

+97
-12
lines changed

3 files changed

+97
-12
lines changed

clone.go

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,22 @@ package git
33
/*
44
#include <git2.h>
55
6+
extern void _go_git_populate_remote_cb(git_clone_options *opts);
67
*/
78
import "C"
89
import (
910
"runtime"
1011
"unsafe"
1112
)
1213

14+
type RemoteCreateCallback func(repo Repository, name, url string) (*Remote, ErrorCode)
15+
1316
type CloneOptions struct {
1417
*CheckoutOpts
1518
*RemoteCallbacks
1619
Bare bool
1720
CheckoutBranch string
18-
RemoteCreateCallback C.git_remote_create_cb
19-
RemoteCreatePayload unsafe.Pointer
21+
RemoteCreateCallback RemoteCreateCallback
2022
}
2123

2224
func Clone(url string, path string, options *CloneOptions) (*Repository, error) {
@@ -30,6 +32,7 @@ func Clone(url string, path string, options *CloneOptions) (*Repository, error)
3032

3133
copts := (*C.git_clone_options)(C.calloc(1, C.size_t(unsafe.Sizeof(C.git_clone_options{}))))
3234
populateCloneOptions(copts, options)
35+
defer freeCloneOptions(copts)
3336

3437
if len(options.CheckoutBranch) != 0 {
3538
copts.checkout_branch = C.CString(options.CheckoutBranch)
@@ -38,9 +41,6 @@ func Clone(url string, path string, options *CloneOptions) (*Repository, error)
3841
runtime.LockOSThread()
3942
defer runtime.UnlockOSThread()
4043
ret := C.git_clone(&repo.ptr, curl, cpath, copts)
41-
freeCheckoutOpts(&copts.checkout_opts)
42-
C.free(unsafe.Pointer(copts.checkout_branch))
43-
C.free(unsafe.Pointer(copts))
4444

4545
if ret < 0 {
4646
return nil, MakeGitError(ret)
@@ -50,6 +50,32 @@ func Clone(url string, path string, options *CloneOptions) (*Repository, error)
5050
return repo, nil
5151
}
5252

53+
//export remoteCreateCallback
54+
func remoteCreateCallback(cremote unsafe.Pointer, crepo unsafe.Pointer, cname, curl *C.char, payload unsafe.Pointer) C.int {
55+
name := C.GoString(cname)
56+
url := C.GoString(curl)
57+
repo := Repository{(*C.git_repository)(crepo)}
58+
59+
if opts, ok := pointerHandles.Get(payload).(CloneOptions); ok {
60+
remote, err := opts.RemoteCreateCallback(repo, name, url)
61+
62+
if err == ErrOk && remote != nil {
63+
// clear finalizer as the calling C function will
64+
// free the remote itself
65+
runtime.SetFinalizer(remote, nil)
66+
67+
cptr := (**C.git_remote)(cremote)
68+
*cptr = remote.ptr
69+
} else if err == ErrOk && remote == nil {
70+
panic("no remote created by callback")
71+
}
72+
73+
return C.int(err)
74+
} else {
75+
panic("invalid remote create callback")
76+
}
77+
}
78+
5379
func populateCloneOptions(ptr *C.git_clone_options, opts *CloneOptions) {
5480
C.git_clone_init_options(ptr, C.GIT_CLONE_OPTIONS_VERSION)
5581

@@ -61,12 +87,23 @@ func populateCloneOptions(ptr *C.git_clone_options, opts *CloneOptions) {
6187
ptr.bare = cbool(opts.Bare)
6288

6389
if opts.RemoteCreateCallback != nil {
64-
ptr.remote_cb = opts.RemoteCreateCallback
65-
defer C.free(unsafe.Pointer(opts.RemoteCreateCallback))
90+
// Go v1.1 does not allow to assign a C function pointer
91+
C._go_git_populate_remote_cb(ptr)
92+
ptr.remote_cb_payload = pointerHandles.Track(*opts)
93+
}
94+
}
6695

67-
if opts.RemoteCreatePayload != nil {
68-
ptr.remote_cb_payload = opts.RemoteCreatePayload
69-
defer C.free(opts.RemoteCreatePayload)
70-
}
96+
func freeCloneOptions(ptr *C.git_clone_options) {
97+
if ptr == nil {
98+
return
7199
}
100+
101+
freeCheckoutOpts(&ptr.checkout_opts)
102+
103+
if ptr.remote_cb_payload != nil {
104+
pointerHandles.Untrack(ptr.remote_cb_payload)
105+
}
106+
107+
C.free(unsafe.Pointer(ptr.checkout_branch))
108+
C.free(unsafe.Pointer(ptr))
72109
}

clone_test.go

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@ import (
55
"testing"
66
)
77

8-
func TestClone(t *testing.T) {
8+
const (
9+
REMOTENAME = "testremote"
10+
)
911

12+
func TestClone(t *testing.T) {
1013
repo := createTestRepo(t)
1114
defer cleanupTestRepo(t, repo)
1215

@@ -20,3 +23,43 @@ func TestClone(t *testing.T) {
2023

2124
checkFatal(t, err)
2225
}
26+
27+
func TestCloneWithCallback(t *testing.T) {
28+
testPayload := 0
29+
30+
repo := createTestRepo(t)
31+
defer cleanupTestRepo(t, repo)
32+
33+
seedTestRepo(t, repo)
34+
35+
path, err := ioutil.TempDir("", "git2go")
36+
checkFatal(t, err)
37+
38+
opts := CloneOptions{
39+
Bare: true,
40+
RemoteCreateCallback: func(r Repository, name, url string) (*Remote, ErrorCode) {
41+
testPayload += 1
42+
43+
remote, err := r.CreateRemote(REMOTENAME, url)
44+
if err != nil {
45+
return nil, ErrGeneric
46+
}
47+
48+
return remote, ErrOk
49+
},
50+
}
51+
52+
repo2, err := Clone(repo.Path(), path, &opts)
53+
defer cleanupTestRepo(t, repo2)
54+
55+
checkFatal(t, err)
56+
57+
if testPayload != 1 {
58+
t.Fatal("Payload's value has not been changed")
59+
}
60+
61+
remote, err := repo2.LookupRemote(REMOTENAME)
62+
if err != nil || remote == nil {
63+
t.Fatal("Remote was not created properly")
64+
}
65+
}

wrapper.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55

66
typedef int (*gogit_submodule_cbk)(git_submodule *sm, const char *name, void *payload);
77

8+
void _go_git_populate_remote_cb(git_clone_options *opts)
9+
{
10+
opts->remote_cb = (git_remote_create_cb)remoteCreateCallback;
11+
}
12+
813
int _go_git_visit_submodule(git_repository *repo, void *fct)
914
{
1015
return git_submodule_foreach(repo, (gogit_submodule_cbk)&SubmoduleVisitor, fct);

0 commit comments

Comments
 (0)