Merge remote-tracking branch 'upstream/master' into next
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..8eb5872
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "vendor/libgit2"]
+	path = vendor/libgit2
+	url = https://github.com/libgit2/libgit2
diff --git a/blob.go b/blob.go
index 8b3e94f..73a4a19 100644
--- a/blob.go
+++ b/blob.go
@@ -4,15 +4,13 @@
 #include <git2.h>
 #include <string.h>
 
-extern int _go_git_blob_create_fromchunks(git_oid *id,
-	git_repository *repo,
-	const char *hintpath,
-	void *payload);
-
+int _go_git_writestream_write(git_writestream *stream, const char *buffer, size_t len);
+void _go_git_writestream_free(git_writestream *stream);
 */
 import "C"
 import (
 	"io"
+	"reflect"
 	"runtime"
 	"unsafe"
 )
@@ -87,27 +85,71 @@
 	return len(goBuf)
 }
 
-func (repo *Repository) CreateBlobFromChunks(hintPath string, callback BlobChunkCallback) (*Oid, error) {
-	runtime.LockOSThread()
-	defer runtime.UnlockOSThread()
-
+func (repo *Repository) CreateFromStream(hintPath string) (*BlobWriteStream, error) {
 	var chintPath *C.char = nil
+	var stream *C.git_writestream
+
 	if len(hintPath) > 0 {
 		chintPath = C.CString(hintPath)
 		defer C.free(unsafe.Pointer(chintPath))
 	}
-	oid := C.git_oid{}
 
-	payload := &BlobCallbackData{Callback: callback}
-	handle := pointerHandles.Track(payload)
-	defer pointerHandles.Untrack(handle)
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
 
-	ecode := C._go_git_blob_create_fromchunks(&oid, repo.ptr, chintPath, handle)
-	if payload.Error != nil {
-		return nil, payload.Error
-	}
+	ecode := C.git_blob_create_fromstream(&stream, repo.ptr, chintPath)
 	if ecode < 0 {
 		return nil, MakeGitError(ecode)
 	}
+
+	return newBlobWriteStreamFromC(stream), nil
+}
+
+type BlobWriteStream struct {
+	ptr *C.git_writestream
+}
+
+func newBlobWriteStreamFromC(ptr *C.git_writestream) *BlobWriteStream {
+	stream := &BlobWriteStream{
+		ptr: ptr,
+	}
+
+	runtime.SetFinalizer(stream, (*BlobWriteStream).Free)
+	return stream
+}
+
+// Implement io.Writer
+func (stream *BlobWriteStream) Write(p []byte) (int, error) {
+	header := (*reflect.SliceHeader)(unsafe.Pointer(&p))
+	ptr := (*C.char)(unsafe.Pointer(header.Data))
+	size := C.size_t(header.Len)
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ecode := C._go_git_writestream_write(stream.ptr, ptr, size)
+	if ecode < 0 {
+		return 0, MakeGitError(ecode)
+	}
+
+	return len(p), nil
+}
+
+func (stream *BlobWriteStream) Free() {
+	runtime.SetFinalizer(stream, nil)
+	C._go_git_writestream_free(stream.ptr)
+}
+
+func (stream *BlobWriteStream) Commit() (*Oid, error) {
+	oid := C.git_oid{}
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ecode := C.git_blob_create_fromstream_commit(&oid, stream.ptr)
+	if ecode < 0 {
+		return nil, MakeGitError(ecode)
+	}
+
 	return newOidFromC(&oid), nil
 }
diff --git a/diff.go b/diff.go
index 8ae0512..e8d5007 100644
--- a/diff.go
+++ b/diff.go
@@ -20,6 +20,7 @@
 	DiffFlagBinary    DiffFlag = C.GIT_DIFF_FLAG_BINARY
 	DiffFlagNotBinary DiffFlag = C.GIT_DIFF_FLAG_NOT_BINARY
 	DiffFlagValidOid  DiffFlag = C.GIT_DIFF_FLAG_VALID_ID
+	DiffFlagExists    DiffFlag = C.GIT_DIFF_FLAG_EXISTS
 )
 
 type Delta int
@@ -34,6 +35,8 @@
 	DeltaIgnored    Delta = C.GIT_DELTA_IGNORED
 	DeltaUntracked  Delta = C.GIT_DELTA_UNTRACKED
 	DeltaTypeChange Delta = C.GIT_DELTA_TYPECHANGE
+	DeltaUnreadable Delta = C.GIT_DELTA_UNREADABLE
+	DeltaConflicted Delta = C.GIT_DELTA_CONFLICTED
 )
 
 type DiffLineType int
@@ -399,6 +402,7 @@
 	DiffIgnoreFilemode         DiffOptionsFlag = C.GIT_DIFF_IGNORE_FILEMODE
 	DiffIgnoreSubmodules       DiffOptionsFlag = C.GIT_DIFF_IGNORE_SUBMODULES
 	DiffIgnoreCase             DiffOptionsFlag = C.GIT_DIFF_IGNORE_CASE
+	DiffIncludeCaseChange      DiffOptionsFlag = C.GIT_DIFF_INCLUDE_CASECHANGE
 
 	DiffDisablePathspecMatch    DiffOptionsFlag = C.GIT_DIFF_DISABLE_PATHSPEC_MATCH
 	DiffSkipBinaryCheck         DiffOptionsFlag = C.GIT_DIFF_SKIP_BINARY_CHECK
diff --git a/git_test.go b/git_test.go
index bdb6837..807dcc2 100644
--- a/git_test.go
+++ b/git_test.go
@@ -58,6 +58,8 @@
 	checkFatal(t, err)
 	err = idx.AddByPath("README")
 	checkFatal(t, err)
+	err = idx.Write()
+	checkFatal(t, err)
 	treeId, err := idx.WriteTree()
 	checkFatal(t, err)
 
@@ -91,6 +93,8 @@
 	checkFatal(t, err)
 	err = idx.AddByPath("README")
 	checkFatal(t, err)
+	err = idx.Write()
+	checkFatal(t, err)
 	treeId, err := idx.WriteTree()
 	checkFatal(t, err)
 
diff --git a/odb.go b/odb.go
index 9c6baa3..28bfad6 100644
--- a/odb.go
+++ b/odb.go
@@ -226,6 +226,10 @@
 	return uint64(C.git_odb_object_size(object.ptr))
 }
 
+func (object *OdbObject) Type() ObjectType {
+	return ObjectType(C.git_odb_object_type(object.ptr))
+}
+
 func (object *OdbObject) Data() (data []byte) {
 	var c_blob unsafe.Pointer = C.git_odb_object_data(object.ptr)
 	var blob []byte
diff --git a/remote.go b/remote.go
index 8a57280..bacdfa3 100644
--- a/remote.go
+++ b/remote.go
@@ -117,6 +117,30 @@
 	Headers []string
 }
 
+type ProxyType uint
+
+const (
+	// Do not attempt to connect through a proxy
+	//
+	// If built against lbicurl, it itself may attempt to connect
+	// to a proxy if the environment variables specify it.
+	ProxyTypeNone ProxyType = C.GIT_PROXY_NONE
+
+	// Try to auto-detect the proxy from the git configuration.
+	ProxyTypeAuto ProxyType = C.GIT_PROXY_AUTO
+
+	// Connect via the URL given in the options
+	ProxyTypeSpecified ProxyType = C.GIT_PROXY_SPECIFIED
+)
+
+type ProxyOptions struct {
+	// The type of proxy to use (or none)
+	Type ProxyType
+
+	// The proxy's URL
+	Url string
+}
+
 type Remote struct {
 	ptr       *C.git_remote
 	callbacks RemoteCallbacks
@@ -320,6 +344,20 @@
 	return int(callbacks.PushUpdateReferenceCallback(C.GoString(refname), C.GoString(status)))
 }
 
+func populateProxyOptions(ptr *C.git_proxy_options, opts *ProxyOptions) {
+	C.git_proxy_init_options(ptr, C.GIT_PROXY_OPTIONS_VERSION)
+	if opts == nil {
+		return
+	}
+
+	ptr._type = C.git_proxy_t(opts.Type)
+	ptr.url = C.CString(opts.Url)
+}
+
+func freeProxyOptions(ptr *C.git_proxy_options) {
+	C.free(unsafe.Pointer(ptr.url))
+}
+
 func RemoteIsValidName(name string) bool {
 	cname := C.CString(name)
 	defer C.free(unsafe.Pointer(cname))
@@ -654,12 +692,12 @@
 	return nil
 }
 
-func (o *Remote) ConnectFetch(callbacks *RemoteCallbacks, headers []string) error {
-	return o.Connect(ConnectDirectionFetch, callbacks, headers)
+func (o *Remote) ConnectFetch(callbacks *RemoteCallbacks, proxyOpts *ProxyOptions, headers []string) error {
+	return o.Connect(ConnectDirectionFetch, callbacks, proxyOpts, headers)
 }
 
-func (o *Remote) ConnectPush(callbacks *RemoteCallbacks, headers []string) error {
-	return o.Connect(ConnectDirectionPush, callbacks, headers)
+func (o *Remote) ConnectPush(callbacks *RemoteCallbacks, proxyOpts *ProxyOptions, headers []string) error {
+	return o.Connect(ConnectDirectionPush, callbacks, proxyOpts, headers)
 }
 
 // Connect opens a connection to a remote.
@@ -669,19 +707,24 @@
 // starts up a specific binary which can only do the one or the other.
 //
 // 'headers' are extra HTTP headers to use in this connection.
-func (o *Remote) Connect(direction ConnectDirection, callbacks *RemoteCallbacks, headers []string) error {
+func (o *Remote) Connect(direction ConnectDirection, callbacks *RemoteCallbacks, proxyOpts *ProxyOptions, headers []string) error {
 	var ccallbacks C.git_remote_callbacks
 	populateRemoteCallbacks(&ccallbacks, callbacks)
 
+	var cproxy C.git_proxy_options
+	populateProxyOptions(&cproxy, proxyOpts)
+	defer freeProxyOptions(&cproxy)
+	
 	cheaders := C.git_strarray{}
 	cheaders.count = C.size_t(len(headers))
 	cheaders.strings = makeCStringsFromStrings(headers)
 	defer freeStrarray(&cheaders)
 
+
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	if ret := C.git_remote_connect(o.ptr, C.git_direction(direction), &ccallbacks, &cheaders); ret != 0 {
+	if ret := C.git_remote_connect(o.ptr, C.git_direction(direction), &ccallbacks, &cproxy, &cheaders); ret != 0 {
 		return MakeGitError(ret)
 	}
 	return nil
diff --git a/remote_test.go b/remote_test.go
index 3d00640..8b20fd2 100644
--- a/remote_test.go
+++ b/remote_test.go
@@ -61,7 +61,7 @@
 	remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository")
 	checkFatal(t, err)
 
-	err = remote.ConnectFetch(nil, nil)
+	err = remote.ConnectFetch(nil, nil, nil)
 	checkFatal(t, err)
 }
 
@@ -73,7 +73,7 @@
 	remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository")
 	checkFatal(t, err)
 
-	err = remote.ConnectFetch(nil, nil)
+	err = remote.ConnectFetch(nil, nil, nil)
 	checkFatal(t, err)
 
 	heads, err := remote.Ls()
@@ -92,7 +92,7 @@
 	remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository")
 	checkFatal(t, err)
 
-	err = remote.ConnectFetch(nil, nil)
+	err = remote.ConnectFetch(nil, nil, nil)
 	checkFatal(t, err)
 
 	heads, err := remote.Ls("master")
@@ -173,7 +173,7 @@
 	rr, err := repo.Remotes.Lookup("origin")
 	checkFatal(t, err)
 
-	err = rr.ConnectFetch(nil, nil)
+	err = rr.ConnectFetch(nil, nil, nil)
 	checkFatal(t, err)
 
 	err = rr.Prune(nil)
diff --git a/repository.go b/repository.go
index efc506e..0612b5f 100644
--- a/repository.go
+++ b/repository.go
@@ -30,6 +30,9 @@
 	// Tags represents the collection of tags and can be used to create,
 	// list, iterate and remove tags in this repository.
 	Tags TagsCollection
+	// Stashes represents the collection of stashes and can be used to
+	// save, apply and iterate over stash states in this repository.
+	Stashes StashCollection
 }
 
 func newRepositoryFromC(ptr *C.git_repository) *Repository {
@@ -40,6 +43,7 @@
 	repo.References.repo = repo
 	repo.Notes.repo = repo
 	repo.Tags.repo = repo
+	repo.Stashes.repo = repo
 
 	runtime.SetFinalizer(repo, (*Repository).Free)
 
@@ -264,6 +268,40 @@
 	return ret != 0, nil
 }
 
+func (v *Repository) IsHeadUnborn() (bool, error) {
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ret := C.git_repository_head_unborn(v.ptr)
+	if ret < 0 {
+		return false, MakeGitError(ret)
+	}
+	return ret != 0, nil
+}
+
+func (v *Repository) IsEmpty() (bool, error) {
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ret := C.git_repository_is_empty(v.ptr)
+	if ret < 0 {
+		return false, MakeGitError(ret)
+	}
+
+	return ret != 0, nil
+}
+
+func (v *Repository) IsShallow() (bool, error) {
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ret := C.git_repository_is_shallow(v.ptr)
+	if ret < 0 {
+		return false, MakeGitError(ret)
+	}
+	return ret != 0, nil
+}
+
 func (v *Repository) Walk() (*RevWalk, error) {
 
 	var walkPtr *C.git_revwalk
diff --git a/stash.go b/stash.go
new file mode 100644
index 0000000..809732e
--- /dev/null
+++ b/stash.go
@@ -0,0 +1,338 @@
+package git
+
+/*
+#include <git2.h>
+
+extern void _go_git_setup_stash_apply_progress_callbacks(git_stash_apply_options *opts);
+extern int _go_git_stash_foreach(git_repository *repo, void *payload);
+*/
+import "C"
+import (
+	"runtime"
+	"unsafe"
+)
+
+// StashFlag are flags that affect the stash save operation.
+type StashFlag int
+
+const (
+	// StashDefault represents no option, default.
+	StashDefault StashFlag = C.GIT_STASH_DEFAULT
+
+	// StashKeepIndex leaves all changes already added to the
+	// index intact in the working directory.
+	StashKeepIndex StashFlag = C.GIT_STASH_KEEP_INDEX
+
+	// StashIncludeUntracked means all untracked files are also
+	// stashed and then cleaned up from the working directory.
+	StashIncludeUntracked StashFlag = C.GIT_STASH_INCLUDE_UNTRACKED
+
+	// StashIncludeIgnored means all ignored files are also
+	// stashed and then cleaned up from the working directory.
+	StashIncludeIgnored StashFlag = C.GIT_STASH_INCLUDE_IGNORED
+)
+
+// StashCollection represents the possible operations that can be
+// performed on the collection of stashes for a repository.
+type StashCollection struct {
+	repo *Repository
+}
+
+// Save saves the local modifications to a new stash.
+//
+// Stasher is the identity of the person performing the stashing.
+// Message is the optional description along with the stashed state.
+// Flags control the stashing process and are given as bitwise OR.
+func (c *StashCollection) Save(
+	stasher *Signature, message string, flags StashFlag) (*Oid, error) {
+
+	oid := new(Oid)
+
+	stasherC, err := stasher.toC()
+	if err != nil {
+		return nil, err
+	}
+	defer C.git_signature_free(stasherC)
+
+	messageC := C.CString(message)
+	defer C.free(unsafe.Pointer(messageC))
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ret := C.git_stash_save(
+		oid.toC(), c.repo.ptr,
+		stasherC, messageC, C.uint32_t(flags))
+
+	if ret < 0 {
+		return nil, MakeGitError(ret)
+	}
+	return oid, nil
+}
+
+// StashApplyFlag are flags that affect the stash apply operation.
+type StashApplyFlag int
+
+const (
+	// StashApplyDefault is the default.
+	StashApplyDefault StashApplyFlag = C.GIT_STASH_APPLY_DEFAULT
+
+	// StashApplyReinstateIndex will try to reinstate not only the
+	// working tree's changes, but also the index's changes.
+	StashApplyReinstateIndex StashApplyFlag = C.GIT_STASH_APPLY_REINSTATE_INDEX
+)
+
+// StashApplyProgress are flags describing the progress of the apply operation.
+type StashApplyProgress int
+
+const (
+	// StashApplyProgressNone means loading the stashed data from the object store.
+	StashApplyProgressNone StashApplyProgress = C.GIT_STASH_APPLY_PROGRESS_NONE
+
+	// StashApplyProgressLoadingStash means the stored index is being analyzed.
+	StashApplyProgressLoadingStash StashApplyProgress = C.GIT_STASH_APPLY_PROGRESS_LOADING_STASH
+
+	// StashApplyProgressAnalyzeIndex means the stored index is being analyzed.
+	StashApplyProgressAnalyzeIndex StashApplyProgress = C.GIT_STASH_APPLY_PROGRESS_ANALYZE_INDEX
+
+	// StashApplyProgressAnalyzeModified means the modified files are being analyzed.
+	StashApplyProgressAnalyzeModified StashApplyProgress = C.GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED
+
+	// StashApplyProgressAnalyzeUntracked means the untracked and ignored files are being analyzed.
+	StashApplyProgressAnalyzeUntracked StashApplyProgress = C.GIT_STASH_APPLY_PROGRESS_ANALYZE_UNTRACKED
+
+	// StashApplyProgressCheckoutUntracked means the untracked files are being written to disk.
+	StashApplyProgressCheckoutUntracked StashApplyProgress = C.GIT_STASH_APPLY_PROGRESS_CHECKOUT_UNTRACKED
+
+	// StashApplyProgressCheckoutModified means the modified files are being written to disk.
+	StashApplyProgressCheckoutModified StashApplyProgress = C.GIT_STASH_APPLY_PROGRESS_CHECKOUT_MODIFIED
+
+	// StashApplyProgressDone means the stash was applied successfully.
+	StashApplyProgressDone StashApplyProgress = C.GIT_STASH_APPLY_PROGRESS_DONE
+)
+
+// StashApplyProgressCallback is the apply operation notification callback.
+type StashApplyProgressCallback func(progress StashApplyProgress) error
+
+type stashApplyProgressData struct {
+	Callback StashApplyProgressCallback
+	Error    error
+}
+
+//export stashApplyProgressCb
+func stashApplyProgressCb(progress C.git_stash_apply_progress_t, handle unsafe.Pointer) int {
+	payload := pointerHandles.Get(handle)
+	data, ok := payload.(*stashApplyProgressData)
+	if !ok {
+		panic("could not retrieve data for handle")
+	}
+
+	if data != nil {
+		err := data.Callback(StashApplyProgress(progress))
+		if err != nil {
+			data.Error = err
+			return C.GIT_EUSER
+		}
+	}
+	return 0
+}
+
+// StashApplyOptions represents options to control the apply operation.
+type StashApplyOptions struct {
+	Flags            StashApplyFlag
+	CheckoutOptions  CheckoutOpts               // options to use when writing files to the working directory
+	ProgressCallback StashApplyProgressCallback // optional callback to notify the consumer of application progress
+}
+
+// DefaultStashApplyOptions initializes the structure with default values.
+func DefaultStashApplyOptions() (StashApplyOptions, error) {
+	optsC := C.git_stash_apply_options{}
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ecode := C.git_stash_apply_init_options(&optsC, C.GIT_STASH_APPLY_OPTIONS_VERSION)
+	if ecode < 0 {
+		return StashApplyOptions{}, MakeGitError(ecode)
+	}
+	return StashApplyOptions{
+		Flags:           StashApplyFlag(optsC.flags),
+		CheckoutOptions: checkoutOptionsFromC(&optsC.checkout_options),
+	}, nil
+}
+
+func (opts *StashApplyOptions) toC() (
+	optsC *C.git_stash_apply_options, progressData *stashApplyProgressData) {
+
+	if opts != nil {
+		progressData = &stashApplyProgressData{
+			Callback: opts.ProgressCallback,
+		}
+
+		optsC = &C.git_stash_apply_options{
+			version: C.GIT_STASH_APPLY_OPTIONS_VERSION,
+			flags:   C.git_stash_apply_flags(opts.Flags),
+		}
+		populateCheckoutOpts(&optsC.checkout_options, &opts.CheckoutOptions)
+		if opts.ProgressCallback != nil {
+			C._go_git_setup_stash_apply_progress_callbacks(optsC)
+			optsC.progress_payload = pointerHandles.Track(progressData)
+		}
+	}
+	return
+}
+
+// should be called after every call to toC() as deferred.
+func untrackStashApplyOptionsCallback(optsC *C.git_stash_apply_options) {
+	if optsC != nil && optsC.progress_payload != nil {
+		pointerHandles.Untrack(optsC.progress_payload)
+	}
+}
+
+func freeStashApplyOptions(optsC *C.git_stash_apply_options) {
+	if optsC != nil {
+		freeCheckoutOpts(&optsC.checkout_options)
+	}
+}
+
+// Apply applies a single stashed state from the stash list.
+//
+// If local changes in the working directory conflict with changes in the
+// stash then ErrConflict will be returned.  In this case, the index
+// will always remain unmodified and all files in the working directory will
+// remain unmodified.  However, if you are restoring untracked files or
+// ignored files and there is a conflict when applying the modified files,
+// then those files will remain in the working directory.
+//
+// If passing the StashApplyReinstateIndex flag and there would be conflicts
+// when reinstating the index, the function will return ErrConflict
+// and both the working directory and index will be left unmodified.
+//
+// Note that a minimum checkout strategy of 'CheckoutSafe' is implied.
+//
+// 'index' is the position within the stash list. 0 points to the most
+// recent stashed state.
+//
+// Returns error code ErrNotFound if there's no stashed state for the given
+// index, error code ErrConflict if local changes in the working directory
+// conflict with changes in the stash, the user returned error from the
+// StashApplyProgressCallback, if any, or other error code.
+//
+// Error codes can be interogated with IsErrorCode(err, ErrNotFound).
+func (c *StashCollection) Apply(index int, opts StashApplyOptions) error {
+	optsC, progressData := opts.toC()
+	defer untrackStashApplyOptionsCallback(optsC)
+	defer freeStashApplyOptions(optsC)
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ret := C.git_stash_apply(c.repo.ptr, C.size_t(index), optsC)
+	if ret == C.GIT_EUSER {
+		return progressData.Error
+	}
+	if ret < 0 {
+		return MakeGitError(ret)
+	}
+	return nil
+}
+
+// StashCallback is called per entry when interating over all
+// the stashed states.
+//
+// 'index' is the position of the current stash in the stash list,
+// 'message' is the message used when creating the stash and 'id'
+// is the commit id of the stash.
+type StashCallback func(index int, message string, id *Oid) error
+
+type stashCallbackData struct {
+	Callback StashCallback
+	Error    error
+}
+
+//export stashForeachCb
+func stashForeachCb(index C.size_t, message *C.char, id *C.git_oid, handle unsafe.Pointer) int {
+	payload := pointerHandles.Get(handle)
+	data, ok := payload.(*stashCallbackData)
+	if !ok {
+		panic("could not retrieve data for handle")
+	}
+
+	err := data.Callback(int(index), C.GoString(message), newOidFromC(id))
+	if err != nil {
+		data.Error = err
+		return C.GIT_EUSER
+	}
+	return 0
+}
+
+// Foreach loops over all the stashed states and calls the callback
+// for each one.
+//
+// If callback returns an error, this will stop looping.
+func (c *StashCollection) Foreach(callback StashCallback) error {
+	data := stashCallbackData{
+		Callback: callback,
+	}
+
+	handle := pointerHandles.Track(&data)
+	defer pointerHandles.Untrack(handle)
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ret := C._go_git_stash_foreach(c.repo.ptr, handle)
+	if ret == C.GIT_EUSER {
+		return data.Error
+	}
+	if ret < 0 {
+		return MakeGitError(ret)
+	}
+	return nil
+}
+
+// Drop removes a single stashed state from the stash list.
+//
+// 'index' is the position within the stash list. 0 points
+// to the most recent stashed state.
+//
+// Returns error code ErrNotFound if there's no stashed
+// state for the given index.
+func (c *StashCollection) Drop(index int) error {
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ret := C.git_stash_drop(c.repo.ptr, C.size_t(index))
+	if ret < 0 {
+		return MakeGitError(ret)
+	}
+	return nil
+}
+
+// Pop applies a single stashed state from the stash list
+// and removes it from the list if successful.
+//
+// 'index' is the position within the stash list. 0 points
+// to the most recent stashed state.
+//
+// 'opts' controls how stashes are applied.
+//
+// Returns error code ErrNotFound if there's no stashed
+// state for the given index.
+func (c *StashCollection) Pop(index int, opts StashApplyOptions) error {
+	optsC, progressData := opts.toC()
+	defer untrackStashApplyOptionsCallback(optsC)
+	defer freeStashApplyOptions(optsC)
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ret := C.git_stash_pop(c.repo.ptr, C.size_t(index), optsC)
+	if ret == C.GIT_EUSER {
+		return progressData.Error
+	}
+	if ret < 0 {
+		return MakeGitError(ret)
+	}
+	return nil
+}
diff --git a/stash_test.go b/stash_test.go
new file mode 100644
index 0000000..180a16b
--- /dev/null
+++ b/stash_test.go
@@ -0,0 +1,198 @@
+package git
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path"
+	"reflect"
+	"runtime"
+	"testing"
+	"time"
+)
+
+func TestStash(t *testing.T) {
+	repo := createTestRepo(t)
+	defer cleanupTestRepo(t, repo)
+
+	prepareStashRepo(t, repo)
+
+	sig := &Signature{
+		Name:  "Rand Om Hacker",
+		Email: "random@hacker.com",
+		When:  time.Now(),
+	}
+
+	stash1, err := repo.Stashes.Save(sig, "First stash", StashDefault)
+	checkFatal(t, err)
+
+	_, err = repo.LookupCommit(stash1)
+	checkFatal(t, err)
+
+	b, err := ioutil.ReadFile(pathInRepo(repo, "README"))
+	checkFatal(t, err)
+	if string(b) == "Update README goes to stash\n" {
+		t.Errorf("README still contains the uncommitted changes")
+	}
+
+	if !fileExistsInRepo(repo, "untracked.txt") {
+		t.Errorf("untracked.txt doesn't exist in the repo; should be untracked")
+	}
+
+	// Apply: default
+
+	opts, err := DefaultStashApplyOptions()
+	checkFatal(t, err)
+
+	err = repo.Stashes.Apply(0, opts)
+	checkFatal(t, err)
+
+	b, err = ioutil.ReadFile(pathInRepo(repo, "README"))
+	checkFatal(t, err)
+	if string(b) != "Update README goes to stash\n" {
+		t.Errorf("README changes aren't here")
+	}
+
+	// Apply: no stash for the given index
+
+	err = repo.Stashes.Apply(1, opts)
+	if !IsErrorCode(err, ErrNotFound) {
+		t.Errorf("expecting GIT_ENOTFOUND error code %d, got %v", ErrNotFound, err)
+	}
+
+	// Apply: callback stopped
+
+	opts.ProgressCallback = func(progress StashApplyProgress) error {
+		if progress == StashApplyProgressCheckoutModified {
+			return fmt.Errorf("Stop")
+		}
+		return nil
+	}
+
+	err = repo.Stashes.Apply(0, opts)
+	if err.Error() != "Stop" {
+		t.Errorf("expecting error 'Stop', got %v", err)
+	}
+
+	// Create second stash with ignored files
+
+	os.MkdirAll(pathInRepo(repo, "tmp"), os.ModeDir|os.ModePerm)
+	err = ioutil.WriteFile(pathInRepo(repo, "tmp/ignored.txt"), []byte("Ignore me\n"), 0644)
+	checkFatal(t, err)
+
+	stash2, err := repo.Stashes.Save(sig, "Second stash", StashIncludeIgnored)
+	checkFatal(t, err)
+
+	if fileExistsInRepo(repo, "tmp/ignored.txt") {
+		t.Errorf("tmp/ignored.txt should not exist anymore in the work dir")
+	}
+
+	// Stash foreach
+
+	expected := []stash{
+		{0, "On master: Second stash", stash2.String()},
+		{1, "On master: First stash", stash1.String()},
+	}
+	checkStashes(t, repo, expected)
+
+	// Stash pop
+
+	opts, _ = DefaultStashApplyOptions()
+	err = repo.Stashes.Pop(1, opts)
+	checkFatal(t, err)
+
+	b, err = ioutil.ReadFile(pathInRepo(repo, "README"))
+	checkFatal(t, err)
+	if string(b) != "Update README goes to stash\n" {
+		t.Errorf("README changes aren't here")
+	}
+
+	expected = []stash{
+		{0, "On master: Second stash", stash2.String()},
+	}
+	checkStashes(t, repo, expected)
+
+	// Stash drop
+
+	err = repo.Stashes.Drop(0)
+	checkFatal(t, err)
+
+	expected = []stash{}
+	checkStashes(t, repo, expected)
+}
+
+type stash struct {
+	index int
+	msg   string
+	id    string
+}
+
+func checkStashes(t *testing.T, repo *Repository, expected []stash) {
+	var actual []stash
+
+	repo.Stashes.Foreach(func(index int, msg string, id *Oid) error {
+		stash := stash{index, msg, id.String()}
+		if len(expected) > len(actual) {
+			if s := expected[len(actual)]; s.id == "" {
+				stash.id = "" //  don't check id
+			}
+		}
+		actual = append(actual, stash)
+		return nil
+	})
+
+	if len(expected) > 0 && !reflect.DeepEqual(expected, actual) {
+		// The failure happens at wherever we were called, not here
+		_, file, line, ok := runtime.Caller(1)
+		if !ok {
+			t.Fatalf("Unable to get caller")
+		}
+		t.Errorf("%v:%v: expecting %#v\ngot %#v", path.Base(file), line, expected, actual)
+	}
+}
+
+func prepareStashRepo(t *testing.T, repo *Repository) {
+	seedTestRepo(t, repo)
+
+	err := ioutil.WriteFile(pathInRepo(repo, ".gitignore"), []byte("tmp\n"), 0644)
+	checkFatal(t, err)
+
+	sig := &Signature{
+		Name:  "Rand Om Hacker",
+		Email: "random@hacker.com",
+		When:  time.Now(),
+	}
+
+	idx, err := repo.Index()
+	checkFatal(t, err)
+	err = idx.AddByPath(".gitignore")
+	checkFatal(t, err)
+	treeID, err := idx.WriteTree()
+	checkFatal(t, err)
+	err = idx.Write()
+	checkFatal(t, err)
+
+	currentBranch, err := repo.Head()
+	checkFatal(t, err)
+	currentTip, err := repo.LookupCommit(currentBranch.Target())
+	checkFatal(t, err)
+
+	message := "Add .gitignore\n"
+	tree, err := repo.LookupTree(treeID)
+	checkFatal(t, err)
+	_, err = repo.CreateCommit("HEAD", sig, sig, message, tree, currentTip)
+	checkFatal(t, err)
+
+	err = ioutil.WriteFile(pathInRepo(repo, "README"), []byte("Update README goes to stash\n"), 0644)
+	checkFatal(t, err)
+
+	err = ioutil.WriteFile(pathInRepo(repo, "untracked.txt"), []byte("Hello, World\n"), 0644)
+	checkFatal(t, err)
+}
+
+func fileExistsInRepo(repo *Repository, name string) bool {
+	if _, err := os.Stat(pathInRepo(repo, name)); err != nil {
+		return false
+	}
+	return true
+}
diff --git a/vendor/libgit2 b/vendor/libgit2
new file mode 160000
index 0000000..73dab76
--- /dev/null
+++ b/vendor/libgit2
@@ -0,0 +1 @@
+Subproject commit 73dab7692e780e1df96093a54854795428eb66b4
diff --git a/wrapper.c b/wrapper.c
index a0688c0..7407611 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -108,19 +108,6 @@
 	callbacks->push_update_reference = (push_update_reference_cb) pushUpdateReferenceCallback;
 }
 
-int _go_blob_chunk_cb(char *buffer, size_t maxLen, void *payload)
-{
-    return blobChunkCb(buffer, maxLen, payload);
-}
-
-int _go_git_blob_create_fromchunks(git_oid *id,
-	git_repository *repo,
-	const char *hintpath,
-	void *payload)
-{
-    return git_blob_create_fromchunks(id, repo, hintpath, _go_blob_chunk_cb, payload);
-}
-
 int _go_git_index_add_all(git_index *index, const git_strarray *pathspec, unsigned int flags, void *callback) {
 	git_index_matched_path_cb cb = callback ? (git_index_matched_path_cb) &indexMatchedPathCallback : NULL;
 	return git_index_add_all(index, pathspec, flags, cb, callback);
@@ -164,4 +151,27 @@
 	return git_merge_file(out, &ancestor, &ours, &theirs, copts);
 }
 
+void _go_git_setup_stash_apply_progress_callbacks(git_stash_apply_options *opts) {
+	opts->progress_cb = (git_stash_apply_progress_cb)stashApplyProgressCb;
+}
+
+int _go_git_stash_foreach(git_repository *repo, void *payload) {
+    return git_stash_foreach(repo, (git_stash_cb)&stashForeachCb, payload);
+}
+
+int _go_git_writestream_write(git_writestream *stream, const char *buffer, size_t len)
+{
+	return stream->write(stream, buffer, len);
+}
+
+int _go_git_writestream_close(git_writestream *stream)
+{
+	return stream->close(stream);
+}
+
+void _go_git_writestream_free(git_writestream *stream)
+{
+	stream->free(stream);
+}
+
 /* EOF */