Introduce an indirection layer for pointers

As the Go runtime can move stacks at any point and the C code runs
concurrently with the rest of the system, we cannot assume that the
payloads we give to the C code will stay valid for any particular
duration.

We must therefore give the C code handles which we can then look up in
our own list when the callbacks get called.
diff --git a/git.go b/git.go
index 9496d2d..4f1a65e 100644
--- a/git.go
+++ b/git.go
@@ -93,7 +93,11 @@
 	ErrInvalid = errors.New("Invalid state for operation")
 )
 
+var pointerHandles *HandleList
+
 func init() {
+	pointerHandles = NewHandleList()
+
 	C.git_libgit2_init()
 
 	// This is not something we should be doing, as we may be
diff --git a/handles.go b/handles.go
new file mode 100644
index 0000000..c0d1889
--- /dev/null
+++ b/handles.go
@@ -0,0 +1,82 @@
+package git
+
+import (
+	"sync"
+	"unsafe"
+)
+
+type HandleList struct {
+	sync.RWMutex
+	// stores the Go pointers
+	handles []interface{}
+	// indicates which indices are in use
+	set map[uintptr]bool
+}
+
+func NewHandleList() *HandleList {
+	return &HandleList{
+		handles: make([]interface{}, 5),
+	}
+}
+
+// findUnusedSlot finds the smallest-index empty space in our
+// list. You must only run this function while holding a write lock.
+func (v *HandleList) findUnusedSlot() uintptr {
+	for i := 0; i < len(v.handles); i++ {
+		isUsed := v.set[uintptr(i)]
+		if !isUsed {
+			return uintptr(i)
+		}
+	}
+
+	// reaching here means we've run out of entries so append and
+	// return the new index, which is equal to the old length.
+	slot := len(v.handles)
+	v.handles = append(v.handles, nil)
+
+	return uintptr(slot)
+}
+
+// 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 *HandleList) Track(pointer interface{}) unsafe.Pointer {
+	v.Lock()
+
+	slot := v.findUnusedSlot()
+	v.handles[slot] = pointer
+	v.set[slot] = true
+
+	v.Unlock()
+
+	return unsafe.Pointer(slot)
+}
+
+// Untrack stops tracking the pointer given by the handle
+func (v *HandleList) Untrack(handle unsafe.Pointer) {
+	slot := uintptr(handle)
+
+	v.Lock()
+
+	v.handles[slot] = nil
+	delete(v.set, slot)
+
+	v.Unlock()
+}
+
+// Get retrieves the pointer from the given handle
+func (v *HandleList) Get(handle unsafe.Pointer) interface{} {
+	slot := uintptr(handle)
+
+	v.RLock()
+
+	if _, ok := v.set[slot]; !ok {
+		panic("invalid pointer handle")
+	}
+
+	ptr := v.handles[slot]
+
+	v.RUnlock()
+
+	return ptr
+}