Merge pull request #321 from netnose/checkout-callbacks

Checkout callbacks
diff --git a/checkout.go b/checkout.go
index a2e312b..f5822c9 100644
--- a/checkout.go
+++ b/checkout.go
@@ -2,6 +2,8 @@
 
 /*
 #include <git2.h>
+
+extern void _go_git_populate_checkout_cb(git_checkout_options *opts);
 */
 import "C"
 import (
@@ -10,9 +12,18 @@
 	"unsafe"
 )
 
+type CheckoutNotifyType uint
 type CheckoutStrategy uint
 
 const (
+	CheckoutNotifyNone      CheckoutNotifyType = C.GIT_CHECKOUT_NOTIFY_NONE
+	CheckoutNotifyConflict  CheckoutNotifyType = C.GIT_CHECKOUT_NOTIFY_CONFLICT
+	CheckoutNotifyDirty     CheckoutNotifyType = C.GIT_CHECKOUT_NOTIFY_DIRTY
+	CheckoutNotifyUpdated   CheckoutNotifyType = C.GIT_CHECKOUT_NOTIFY_UPDATED
+	CheckoutNotifyUntracked CheckoutNotifyType = C.GIT_CHECKOUT_NOTIFY_UNTRACKED
+	CheckoutNotifyIgnored   CheckoutNotifyType = C.GIT_CHECKOUT_NOTIFY_IGNORED
+	CheckoutNotifyAll       CheckoutNotifyType = C.GIT_CHECKOUT_NOTIFY_ALL
+
 	CheckoutNone                      CheckoutStrategy = C.GIT_CHECKOUT_NONE                         // Dry run, no actual updates
 	CheckoutSafe                      CheckoutStrategy = C.GIT_CHECKOUT_SAFE                         // Allow safe updates that cannot overwrite uncommitted data
 	CheckoutForce                     CheckoutStrategy = C.GIT_CHECKOUT_FORCE                        // Allow all updates to force working directory to look like index
@@ -37,15 +48,21 @@
 	CheckoutUpdateSubmodulesIfChanged CheckoutStrategy = C.GIT_CHECKOUT_UPDATE_SUBMODULES_IF_CHANGED // Recursively checkout submodules if HEAD moved in super repo (NOT IMPLEMENTED)
 )
 
+type CheckoutNotifyCallback func(why CheckoutNotifyType, path string, baseline, target, workdir DiffFile) ErrorCode
+type CheckoutProgressCallback func(path string, completed, total uint) ErrorCode
+
 type CheckoutOpts struct {
-	Strategy        CheckoutStrategy // Default will be a dry run
-	DisableFilters  bool             // Don't apply filters like CRLF conversion
-	DirMode         os.FileMode      // Default is 0755
-	FileMode        os.FileMode      // Default is 0644 or 0755 as dictated by blob
-	FileOpenFlags   int              // Default is O_CREAT | O_TRUNC | O_WRONLY
-	TargetDirectory string           // Alternative checkout path to workdir
-	Paths           []string
-	Baseline        *Tree
+	Strategy         CheckoutStrategy   // Default will be a dry run
+	DisableFilters   bool               // Don't apply filters like CRLF conversion
+	DirMode          os.FileMode        // Default is 0755
+	FileMode         os.FileMode        // Default is 0644 or 0755 as dictated by blob
+	FileOpenFlags    int                // Default is O_CREAT | O_TRUNC | O_WRONLY
+	NotifyFlags      CheckoutNotifyType // Default will be none
+	NotifyCallback   CheckoutNotifyCallback
+	ProgressCallback CheckoutProgressCallback
+	TargetDirectory  string // Alternative checkout path to workdir
+	Paths            []string
+	Baseline         *Tree
 }
 
 func checkoutOptionsFromC(c *C.git_checkout_options) CheckoutOpts {
@@ -55,6 +72,13 @@
 	opts.DirMode = os.FileMode(c.dir_mode)
 	opts.FileMode = os.FileMode(c.file_mode)
 	opts.FileOpenFlags = int(c.file_open_flags)
+	opts.NotifyFlags = CheckoutNotifyType(c.notify_flags)
+	if c.notify_payload != nil {
+		opts.NotifyCallback = pointerHandles.Get(c.notify_payload).(*CheckoutOpts).NotifyCallback
+	}
+	if c.progress_payload != nil {
+		opts.ProgressCallback = pointerHandles.Get(c.progress_payload).(*CheckoutOpts).ProgressCallback
+	}
 	if c.target_directory != nil {
 		opts.TargetDirectory = C.GoString(c.target_directory)
 	}
@@ -70,6 +94,38 @@
 	return &c
 }
 
+//export checkoutNotifyCallback
+func checkoutNotifyCallback(why C.git_checkout_notify_t, cpath *C.char, cbaseline, ctarget, cworkdir, data unsafe.Pointer) int {
+	if data == nil {
+		return 0
+	}
+	path := C.GoString(cpath)
+	var baseline, target, workdir DiffFile
+	if cbaseline != nil {
+		baseline = diffFileFromC((*C.git_diff_file)(cbaseline))
+	}
+	if ctarget != nil {
+		target = diffFileFromC((*C.git_diff_file)(ctarget))
+	}
+	if cworkdir != nil {
+		workdir = diffFileFromC((*C.git_diff_file)(cworkdir))
+	}
+	opts := pointerHandles.Get(data).(*CheckoutOpts)
+	if opts.NotifyCallback == nil {
+		return 0
+	}
+	return int(opts.NotifyCallback(CheckoutNotifyType(why), path, baseline, target, workdir))
+}
+
+//export checkoutProgressCallback
+func checkoutProgressCallback(path *C.char, completed_steps, total_steps C.size_t, data unsafe.Pointer) int {
+	opts := pointerHandles.Get(data).(*CheckoutOpts)
+	if opts.ProgressCallback == nil {
+		return 0
+	}
+	return int(opts.ProgressCallback(C.GoString(path), uint(completed_steps), uint(total_steps)))
+}
+
 // Convert the CheckoutOpts struct to the corresponding
 // C-struct. Returns a pointer to ptr, or nil if opts is nil, in order
 // to help with what to pass.
@@ -83,6 +139,17 @@
 	ptr.disable_filters = cbool(opts.DisableFilters)
 	ptr.dir_mode = C.uint(opts.DirMode.Perm())
 	ptr.file_mode = C.uint(opts.FileMode.Perm())
+	ptr.notify_flags = C.uint(opts.NotifyFlags)
+	if opts.NotifyCallback != nil || opts.ProgressCallback != nil {
+		C._go_git_populate_checkout_cb(ptr)
+	}
+	payload := pointerHandles.Track(opts)
+	if opts.NotifyCallback != nil {
+		ptr.notify_payload = payload
+	}
+	if opts.ProgressCallback != nil {
+		ptr.progress_payload = payload
+	}
 	if opts.TargetDirectory != "" {
 		ptr.target_directory = C.CString(opts.TargetDirectory)
 	}
@@ -106,6 +173,9 @@
 	if ptr.paths.count > 0 {
 		freeStrarray(&ptr.paths)
 	}
+	if ptr.notify_payload != nil {
+		pointerHandles.Untrack(ptr.notify_payload)
+	}
 }
 
 // Updates files in the index and the working tree to match the content of
diff --git a/clone.go b/clone.go
index e80d14d..1ff5124 100644
--- a/clone.go
+++ b/clone.go
@@ -41,7 +41,6 @@
 
 	var ptr *C.git_repository
 	ret := C.git_clone(&ptr, curl, cpath, copts)
-	freeCheckoutOpts(&copts.checkout_opts)
 
 	if ret < 0 {
 		return nil, MakeGitError(ret)
diff --git a/wrapper.c b/wrapper.c
index a0688c0..f4b4ecc 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -10,6 +10,12 @@
 	opts->remote_cb = (git_remote_create_cb)remoteCreateCallback;
 }
 
+void _go_git_populate_checkout_cb(git_checkout_options *opts)
+{
+	opts->notify_cb = (git_checkout_notify_cb)checkoutNotifyCallback;
+	opts->progress_cb = (git_checkout_progress_cb)checkoutProgressCallback;
+}
+
 int _go_git_visit_submodule(git_repository *repo, void *fct)
 {
 	  return git_submodule_foreach(repo, (gogit_submodule_cbk)&SubmoduleVisitor, fct);