Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions clone.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ func remoteCreateCallback(
// clear finalizer as the calling C function will
// free the remote itself
runtime.SetFinalizer(remote, nil)
remote.repo.Remotes.untrackRemote(remote)

return C.int(ErrorCodeOK)
}
Expand Down
6 changes: 6 additions & 0 deletions git.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,13 +128,15 @@ var (
type doNotCompare [0]func()

var pointerHandles *HandleList
var remotePointers *remotePointerList

func init() {
initLibGit2()
}

func initLibGit2() {
pointerHandles = NewHandleList()
remotePointers = newRemotePointerList()

C.git_libgit2_init()

Expand All @@ -160,7 +162,11 @@ func initLibGit2() {
// After this is called, invoking any function from this library will result in
// undefined behavior, so make sure this is called carefully.
func Shutdown() {
if err := unregisterManagedTransports(); err != nil {
panic(err)
}
pointerHandles.Clear()
remotePointers.clear()

C.git_libgit2_shutdown()
}
Expand Down
14 changes: 14 additions & 0 deletions git_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ import (
func TestMain(m *testing.M) {
ret := m.Run()

if err := unregisterManagedTransports(); err != nil {
panic(err)
}

// Ensure that we are not leaking any pointer handles.
pointerHandles.Lock()
if len(pointerHandles.handles) > 0 {
Expand All @@ -23,6 +27,16 @@ func TestMain(m *testing.M) {
}
pointerHandles.Unlock()

// Or remote pointers.
remotePointers.Lock()
if len(remotePointers.pointers) > 0 {
for ptr, remote := range remotePointers.pointers {
fmt.Printf("%016p: %+v\n", ptr, remote)
}
panic("remote pointer list not empty")
}
remotePointers.Unlock()

Shutdown()

os.Exit(ret)
Expand Down
114 changes: 106 additions & 8 deletions remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"reflect"
"runtime"
"strings"
"sync"
"unsafe"
)

Expand Down Expand Up @@ -174,6 +175,64 @@ type Remote struct {
repo *Repository
}

type remotePointerList struct {
sync.RWMutex
// stores the Go pointers
pointers map[*C.git_remote]*Remote
}

func newRemotePointerList() *remotePointerList {
return &remotePointerList{
pointers: make(map[*C.git_remote]*Remote),
}
}

// track adds the given pointer to the list of pointers to track and
// returns a pointer value which can be passed to C as an opaque
// pointer.
func (v *remotePointerList) track(remote *Remote) {
v.Lock()
v.pointers[remote.ptr] = remote
v.Unlock()

runtime.SetFinalizer(remote, (*Remote).Free)
}

// untrack stops tracking the git_remote pointer.
func (v *remotePointerList) untrack(remote *Remote) {
v.Lock()
delete(v.pointers, remote.ptr)
v.Unlock()
}

// clear stops tracking all the git_remote pointers.
func (v *remotePointerList) clear() {
v.Lock()
var remotes []*Remote
for remotePtr, remote := range v.pointers {
remotes = append(remotes, remote)
delete(v.pointers, remotePtr)
}
v.Unlock()

for _, remote := range remotes {
remote.free()
}
}

// get retrieves the pointer from the given *git_remote.
func (v *remotePointerList) get(ptr *C.git_remote) (*Remote, bool) {
v.RLock()
defer v.RUnlock()

r, ok := v.pointers[ptr]
if !ok {
return nil, false
}

return r, true
}

type CertificateKind uint

const (
Expand Down Expand Up @@ -509,17 +568,42 @@ func RemoteIsValidName(name string) bool {
return C.git_remote_is_valid_name(cname) == 1
}

// Free releases the resources of the Remote.
func (r *Remote) Free() {
// free releases the resources of the Remote.
func (r *Remote) free() {
runtime.SetFinalizer(r, nil)
C.git_remote_free(r.ptr)
r.ptr = nil
r.repo = nil
}

// Free releases the resources of the Remote.
func (r *Remote) Free() {
r.repo.Remotes.untrackRemote(r)
r.free()
}

type RemoteCollection struct {
doNotCompare
repo *Repository

sync.RWMutex
remotes map[*C.git_remote]*Remote
}

func (c *RemoteCollection) trackRemote(r *Remote) {
c.Lock()
c.remotes[r.ptr] = r
c.Unlock()

remotePointers.track(r)
}

func (c *RemoteCollection) untrackRemote(r *Remote) {
c.Lock()
delete(c.remotes, r.ptr)
c.Unlock()

remotePointers.untrack(r)
}

func (c *RemoteCollection) List() ([]string, error) {
Expand Down Expand Up @@ -554,7 +638,7 @@ func (c *RemoteCollection) Create(name string, url string) (*Remote, error) {
if ret < 0 {
return nil, MakeGitError(ret)
}
runtime.SetFinalizer(remote, (*Remote).Free)
c.trackRemote(remote)
return remote, nil
}

Expand All @@ -570,13 +654,13 @@ func (c *RemoteCollection) CreateWithOptions(url string, option *RemoteCreateOpt

copts := populateRemoteCreateOptions(&C.git_remote_create_options{}, option, c.repo)
defer freeRemoteCreateOptions(copts)

ret := C.git_remote_create_with_opts(&remote.ptr, curl, copts)
runtime.KeepAlive(c.repo)
if ret < 0 {
return nil, MakeGitError(ret)
}

runtime.SetFinalizer(remote, (*Remote).Free)
c.trackRemote(remote)
return remote, nil
}

Expand Down Expand Up @@ -612,7 +696,7 @@ func (c *RemoteCollection) CreateWithFetchspec(name string, url string, fetch st
if ret < 0 {
return nil, MakeGitError(ret)
}
runtime.SetFinalizer(remote, (*Remote).Free)
c.trackRemote(remote)
return remote, nil
}

Expand All @@ -629,7 +713,7 @@ func (c *RemoteCollection) CreateAnonymous(url string) (*Remote, error) {
if ret < 0 {
return nil, MakeGitError(ret)
}
runtime.SetFinalizer(remote, (*Remote).Free)
c.trackRemote(remote)
return remote, nil
}

Expand All @@ -646,10 +730,24 @@ func (c *RemoteCollection) Lookup(name string) (*Remote, error) {
if ret < 0 {
return nil, MakeGitError(ret)
}
runtime.SetFinalizer(remote, (*Remote).Free)
c.trackRemote(remote)
return remote, nil
}

func (c *RemoteCollection) Free() {
var remotes []*Remote
c.Lock()
for remotePtr, remote := range c.remotes {
remotes = append(remotes, remote)
delete(c.remotes, remotePtr)
}
c.Unlock()

for _, remote := range remotes {
remotePointers.untrack(remote)
}
}

func (o *Remote) Name() string {
s := C.git_remote_name(o.ptr)
runtime.KeepAlive(o)
Expand Down
1 change: 1 addition & 0 deletions remote_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ func TestRemoteConnectOption(t *testing.T) {

remote, err := repo.Remotes.CreateWithOptions("https://github.com/libgit2/TestGitRepository", option)
checkFatal(t, err)
defer remote.Free()

err = remote.ConnectFetch(nil, nil, nil)
checkFatal(t, err)
Expand Down
2 changes: 2 additions & 0 deletions repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ func newRepositoryFromC(ptr *C.git_repository) *Repository {
repo := &Repository{ptr: ptr}

repo.Remotes.repo = repo
repo.Remotes.remotes = make(map[*C.git_remote]*Remote)
repo.Submodules.repo = repo
repo.References.repo = repo
repo.Notes.repo = repo
Expand Down Expand Up @@ -144,6 +145,7 @@ func (v *Repository) Free() {
ptr := v.ptr
v.ptr = nil
runtime.SetFinalizer(v, nil)
v.Remotes.Free()
if v.weak {
return
}
Expand Down
Loading