Merge pull request #362 from libgit2/cmn/master-tip-static

Update master to latest libgit2 and build statically
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/.travis.yml b/.travis.yml
index 26524e0..d9130bc 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,10 +5,6 @@
 install: ./script/install-libgit2.sh
 
 go:
-  - 1.1
-  - 1.2
-  - 1.3
-  - 1.4
   - 1.5
   - 1.6
   - 1.7
diff --git a/Makefile b/Makefile
index 9c42283..39fc558 100644
--- a/Makefile
+++ b/Makefile
@@ -1,8 +1,11 @@
 default: test
 
-test:
+build-libgit2:
+	./script/build-libgit2-static.sh
+
+test: build-libgit2
 	go run script/check-MakeGitError-thread-lock.go
 	go test ./...
 
-install:
+install: build-libgit2
 	go install ./...
diff --git a/README.md b/README.md
index 315032f..4ecfa34 100644
--- a/README.md
+++ b/README.md
@@ -2,57 +2,59 @@
 ======
 [![GoDoc](https://godoc.org/github.com/libgit2/git2go?status.svg)](http://godoc.org/github.com/libgit2/git2go) [![Build Status](https://travis-ci.org/libgit2/git2go.svg?branch=master)](https://travis-ci.org/libgit2/git2go)
 
+Go bindings for [libgit2](http://libgit2.github.com/).
 
-Go bindings for [libgit2](http://libgit2.github.com/). The `master` branch follows the latest libgit2 release. The versioned branches indicate which libgit2 version they work against.
+### Which branch to use
+
+The numbered branches work against the version of libgit2 as specified by their number. You can import them in your project via gopkg.in, e.g. if you have libgit2 v0.25 installed you'd import with
+
+    import "gopkg.in/libgit2/git2go.v25"
+
+which will ensure there are no sudden changes to the API.
+
+The `master` branch follows the tip of libgit2 itself (with some lag) and as such has no guarantees on its own API nor does it have expectations the stability of libgit2's. Thus this only supports statically linking against libgit2.
 
 Installing
 ----------
 
-This project wraps the functionality provided by libgit2. If you're using a stable version, install it to your system via your system's package manager and then install git2go as usual.
+This project wraps the functionality provided by libgit2. It thus needs it in order to perform the work.
 
-Otherwise (`next` which tracks an unstable version), we need to build libgit2 as well. In order to build it, you need `cmake`, `pkg-config` and a C compiler. You will also need the development packages for OpenSSL and LibSSH2 installed if you want libgit2 to support HTTPS and SSH respectively.
+This project wraps the functionality provided by libgit2. If you're using a versioned branch, install it to your system via your system's package manager and then install git2go.
 
-### Stable version
 
-git2go has `master` which tracks the latest release of libgit2, and versioned branches which indicate which version of libgit2 they work against. Install the development package on your system via your favourite package manager or from source and you can use a service like gopkg.in to use the appropriate version. For the libgit2 v0.22 case, you can use
+### Versioned branch, dynamic linking
 
-    import "gopkg.in/libgit2/git2go.v22"
+When linking dynamically against a released version of libgit2, install it via your system's package manager. CGo will take care of finding its pkg-config file and set up the linking. Import via gopkg.in, e.g. to work against libgit2 v0.25
 
-to use a version of git2go which will work against libgit2 v0.22 and dynamically link to the library. You can use
+    import "gopkg.in/libgit2/git2go.v25"
 
-    import "github.com/libgit2/git2go"
+### Master branch, or static linking
 
-to use the version which works against the latest release.
-
-### From `next`
-
-The `next` branch follows libgit2's master branch, which means there is no stable API or ABI to link against. git2go can statically link against a vendored version of libgit2.
+If using `master` or building a branch statically, we need to build libgit2 first. In order to build it, you need `cmake`, `pkg-config` and a C compiler. You will also need the development packages for OpenSSL (outside of Windows or macOS) and LibSSH2 installed if you want libgit2 to support HTTPS and SSH respectively. Note that even if libgit2 is included in the resulting binary, its dependencies will not be.
 
 Run `go get -d github.com/libgit2/git2go` to download the code and go to your `$GOPATH/src/github.com/libgit2/git2go` directory. From there, we need to build the C code and put it into the resulting go binary.
 
-    git checkout next
     git submodule update --init # get libgit2
     make install
 
-will compile libgit2. Run `go install` so that it's statically linked to the git2go package.
+will compile libgit2, link it into git2go and install it.
 
 Parallelism and network operations
 ----------------------------------
 
-libgit2 uses OpenSSL and LibSSH2 for performing encrypted network connections. For now, git2go asks libgit2 to set locking for OpenSSL. This makes HTTPS connections thread-safe, but it is fragile and will likely stop doing it soon. This may also make SSH connections thread-safe if your copy of libssh2 is linked against OpenSSL. Check libgit2's `THREADSAFE.md` for more information.
+libgit2 may use OpenSSL and LibSSH2 for performing encrypted network connections. For now, git2go asks libgit2 to set locking for OpenSSL. This makes HTTPS connections thread-safe, but it is fragile and will likely stop doing it soon. This may also make SSH connections thread-safe if your copy of libssh2 is linked against OpenSSL. Check libgit2's `THREADSAFE.md` for more information.
 
 Running the tests
 -----------------
 
-For the stable version, `go test` will work as usual. For the `next` branch, similarly to installing, running the tests requires linking against the local libgit2 library, so the Makefile provides a wrapper
+For the stable version, `go test` will work as usual. For the `next` branch, similarly to installing, running the tests requires building a local libgit2 library, so the Makefile provides a wrapper that makes sure it's built
 
     make test
 
-Alternatively, if you want to pass arguments to `go test`, you can use the script that sets it all up
+Alternatively, you can build the library manually first and then run the tests
 
-    ./script/with-static.sh go test -v
-
-which will run the specified arguments with the correct environment variables.
+    ./script/build-libgit2-static.sh
+    go test -v
 
 License
 -------
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/commit.go b/commit.go
index 6830da3..07b7c37 100644
--- a/commit.go
+++ b/commit.go
@@ -22,6 +22,10 @@
 	return C.GoString(C.git_commit_message(c.cast_ptr))
 }
 
+func (c Commit) RawMessage() string {
+	return C.GoString(C.git_commit_message_raw(c.cast_ptr))
+}
+
 func (c Commit) Summary() string {
 	return C.GoString(C.git_commit_summary(c.cast_ptr))
 }
diff --git a/describe_test.go b/describe_test.go
index f0a45f4..3181f23 100644
--- a/describe_test.go
+++ b/describe_test.go
@@ -93,14 +93,16 @@
 func checkDescribeNoRefsFound(t *testing.T, err error) {
 	// The failure happens at wherever we were called, not here
 	_, file, line, ok := runtime.Caller(1)
+	expectedString := "no reference found, cannot describe anything"
 	if !ok {
 		t.Fatalf("Unable to get caller")
 	}
-	if err == nil || !strings.Contains(err.Error(), "No reference found, cannot describe anything") {
+	if err == nil || !strings.Contains(err.Error(), expectedString) {
 		t.Fatalf(
-			"%s:%v: was expecting error 'No reference found, cannot describe anything', got %v",
+			"%s:%v: was expecting error %v, got %v",
 			path.Base(file),
 			line,
+			expectedString,
 			err,
 		)
 	}
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/features.go b/features.go
new file mode 100644
index 0000000..f6474a0
--- /dev/null
+++ b/features.go
@@ -0,0 +1,30 @@
+package git
+
+/*
+#include <git2.h>
+*/
+import "C"
+
+type Feature int
+
+const (
+	// libgit2 was built with threading support
+	FeatureThreads Feature = C.GIT_FEATURE_THREADS
+
+	// libgit2 was built with HTTPS support built-in
+	FeatureHttps Feature = C.GIT_FEATURE_HTTPS
+
+	// libgit2 was build with SSH support built-in
+	FeatureSsh Feature = C.GIT_FEATURE_SSH
+
+	// libgit2 was built with nanosecond support for files
+	FeatureNSec Feature = C.GIT_FEATURE_NSEC
+)
+
+// Features returns a bit-flag of Feature values indicating which features the
+// loaded libgit2 library has.
+func Features() Feature {
+	features := C.git_libgit2_features()
+
+	return Feature(features)
+}
diff --git a/git.go b/git.go
index ed891e6..cb973ee 100644
--- a/git.go
+++ b/git.go
@@ -1,12 +1,15 @@
 package git
 
 /*
+#cgo CFLAGS: -I${SRCDIR}/vendor/libgit2/include
+#cgo LDFLAGS: -L${SRCDIR}/vendor/libgit2/build/ -lgit2
+#cgo windows LDFLAGS: -lwinhttp
+#cgo !windows pkg-config: --static ${SRCDIR}/vendor/libgit2/build/libgit2.pc
 #include <git2.h>
 #include <git2/sys/openssl.h>
-#cgo pkg-config: libgit2
 
-#if LIBGIT2_VER_MAJOR != 0 || LIBGIT2_VER_MINOR != 24
-# error "Invalid libgit2 version; this git2go supports libgit2 v0.24"
+#if LIBGIT2_VER_MAJOR != 0 || LIBGIT2_VER_MINOR != 25
+# error "Invalid libgit2 version; this git2go supports libgit2 v0.25"
 #endif
 
 */
@@ -125,6 +128,15 @@
 
 	C.git_libgit2_init()
 
+	// Due to the multithreaded nature of Go and its interaction with
+	// calling C functions, we cannot work with a library that was not built
+	// with multi-threading support. The most likely outcome is a segfault
+	// or panic at an incomprehensible time, so let's make it easy by
+	// panicking right here.
+	if Features()&FeatureThreads == 0 {
+		panic("libgit2 was not built with threading support")
+	}
+
 	// This is not something we should be doing, as we may be
 	// stomping all over someone else's setup. The user should do
 	// this themselves or use some binding/wrapper which does it
diff --git a/git_test.go b/git_test.go
index 3385a72..807dcc2 100644
--- a/git_test.go
+++ b/git_test.go
@@ -93,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 9ba2ea2..a728cd3 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 c537932..d3f437d 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))
@@ -674,12 +712,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.
@@ -689,19 +727,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/script/with-static.sh b/script/with-static.sh
deleted file mode 100755
index 3f60e31..0000000
--- a/script/with-static.sh
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/bin/sh
-
-set -ex
-
-export BUILD="$PWD/vendor/libgit2/build"
-export PCFILE="$BUILD/libgit2.pc"
-
-FLAGS=$(pkg-config --static --libs $PCFILE) || exit 1
-export CGO_LDFLAGS="$BUILD/libgit2.a -L$BUILD ${FLAGS}"
-export CGO_CFLAGS="-I$PWD/vendor/libgit2/include"
-
-$@
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..df4dfaa
--- /dev/null
+++ b/vendor/libgit2
@@ -0,0 +1 @@
+Subproject commit df4dfaadcf709646ebab2e57e3589952cf1ac809
diff --git a/wrapper.c b/wrapper.c
index f4b4ecc..11c2f32 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -114,19 +114,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);
@@ -170,4 +157,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 */