Merge pull request #365 from AaronO/fix/memleak-tree-entrybyname

Fix memleaks in Tree.EntryBy(Name/Path/Index), fixes #313
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 f8b7e93..d9130bc 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,21 +1,21 @@
 language: go
 
-install:
-  - cd "${HOME}"
-  - wget -O libgit2-0.22.1.tar.gz https://github.com/libgit2/libgit2/archive/v0.22.1.tar.gz
-  - tar -xzvf libgit2-0.22.1.tar.gz
-  - cd libgit2-0.22.1 && mkdir build && cd build
-  - cmake -DTHREADSAFE=ON -DBUILD_CLAR=OFF -DCMAKE_C_FLAGS=-fPIC -DCMAKE_BUILD_TYPE="RelWithDebInfo" -DCMAKE_INSTALL_PREFIX=/usr/local .. && make && sudo make install
-  - sudo ldconfig
-  - cd "${TRAVIS_BUILD_DIR}"
+sudo: required
+
+install: ./script/install-libgit2.sh
 
 go:
-  - 1.1
-  - 1.2
-  - 1.3
-  - 1.4
+  - 1.5
+  - 1.6
+  - 1.7
   - tip
 
 matrix:
   allow_failures:
     - go: tip
+
+branches:
+  only:
+  - master
+  - /v\d+/
+  - next
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 a5e6100..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 manaager 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/blame.go b/blame.go
index c24c934..b07d6bc 100644
--- a/blame.go
+++ b/blame.go
@@ -58,8 +58,8 @@
 			version:              C.GIT_BLAME_OPTIONS_VERSION,
 			flags:                C.uint32_t(opts.Flags),
 			min_match_characters: C.uint16_t(opts.MinMatchCharacters),
-			min_line:             C.uint32_t(opts.MinLine),
-			max_line:             C.uint32_t(opts.MaxLine),
+			min_line:             C.size_t(opts.MinLine),
+			max_line:             C.size_t(opts.MaxLine),
 		}
 		if opts.NewestCommit != nil {
 			copts.newest_commit = *opts.NewestCommit.toC()
@@ -100,7 +100,7 @@
 }
 
 func (blame *Blame) HunkByLine(lineno int) (BlameHunk, error) {
-	ptr := C.git_blame_get_hunk_byline(blame.ptr, C.uint32_t(lineno))
+	ptr := C.git_blame_get_hunk_byline(blame.ptr, C.size_t(lineno))
 	if ptr == nil {
 		return BlameHunk{}, ErrInvalid
 	}
diff --git a/blame_test.go b/blame_test.go
index a2a4d38..ec96af7 100644
--- a/blame_test.go
+++ b/blame_test.go
@@ -6,6 +6,7 @@
 )
 
 func TestBlame(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
diff --git a/blob.go b/blob.go
index b1fc78a..73a4a19 100644
--- a/blob.go
+++ b/blob.go
@@ -4,21 +4,19 @@
 #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"
 )
 
 type Blob struct {
-	gitObject
+	Object
 	cast_ptr *C.git_blob
 }
 
@@ -37,15 +35,24 @@
 	defer runtime.UnlockOSThread()
 
 	var id C.git_oid
-	var ptr unsafe.Pointer
+	var size C.size_t
 
+	// Go 1.6 added some increased checking of passing pointer to
+	// C, but its check depends on its expectations of waht we
+	// pass to the C function, so unless we take the address of
+	// its contents at the call site itself, it can fail when
+	// 'data' is a slice of a slice.
+	//
+	// When we're given an empty slice, create a dummy one where 0
+	// isn't out of bounds.
 	if len(data) > 0 {
-		ptr = unsafe.Pointer(&data[0])
+		size = C.size_t(len(data))
 	} else {
-		ptr = unsafe.Pointer(nil)
+		data = []byte{0}
+		size = C.size_t(0)
 	}
 
-	ecode := C.git_blob_create_frombuffer(&id, repo.ptr, ptr, C.size_t(len(data)))
+	ecode := C.git_blob_create_frombuffer(&id, repo.ptr, unsafe.Pointer(&data[0]), size)
 	if ecode < 0 {
 		return nil, MakeGitError(ecode)
 	}
@@ -78,27 +85,71 @@
 	return len(goBuf)
 }
 
-func (repo *Repository) CreateBlobFromChunks(hintPath string, callback BlobChunkCallback) (*Oid, error) {
+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))
+	}
+
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	var chintPath *C.char = nil
-	if len(hintPath) > 0 {
-		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)
-
-	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/blob_test.go b/blob_test.go
index 2b5ec4f..815ab3d 100644
--- a/blob_test.go
+++ b/blob_test.go
@@ -1,10 +1,23 @@
 package git
 
 import (
+	"bytes"
 	"testing"
 )
 
+type bufWrapper struct {
+	buf     [64]byte
+	pointer []byte
+}
+
+func doublePointerBytes() []byte {
+	o := &bufWrapper{}
+	o.pointer = o.buf[0:10]
+	return o.pointer[0:1]
+}
+
 func TestCreateBlobFromBuffer(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
@@ -14,4 +27,16 @@
 	if id.String() != "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391" {
 		t.Fatal("Empty buffer did not deliver empty blob id")
 	}
+
+	for _, data := range []([]byte){[]byte("hello there"), doublePointerBytes()} {
+		id, err = repo.CreateBlobFromBuffer(data)
+		checkFatal(t, err)
+
+		blob, err := repo.LookupBlob(id)
+		checkFatal(t, err)
+		if !bytes.Equal(blob.Contents(), data) {
+			t.Fatal("Loaded bytes don't match original bytes:",
+				blob.Contents(), "!=", data)
+		}
+	}
 }
diff --git a/branch.go b/branch.go
index 8cf73b6..d381c23 100644
--- a/branch.go
+++ b/branch.go
@@ -13,6 +13,7 @@
 type BranchType uint
 
 const (
+	BranchAll    BranchType = C.GIT_BRANCH_ALL
 	BranchLocal  BranchType = C.GIT_BRANCH_LOCAL
 	BranchRemote BranchType = C.GIT_BRANCH_REMOTE
 )
@@ -72,6 +73,10 @@
 		}
 	}
 
+	if err != nil && IsErrorCode(err, ErrIterOver) {
+		return nil
+	}
+
 	return err
 }
 
@@ -90,31 +95,17 @@
 	return newBranchIteratorFromC(repo, ptr), nil
 }
 
-func (repo *Repository) CreateBranch(branchName string, target *Commit, force bool, signature *Signature, msg string) (*Branch, error) {
+func (repo *Repository) CreateBranch(branchName string, target *Commit, force bool) (*Branch, error) {
 
 	var ptr *C.git_reference
 	cBranchName := C.CString(branchName)
 	defer C.free(unsafe.Pointer(cBranchName))
 	cForce := cbool(force)
 
-	cSignature, err := signature.toC()
-	if err != nil {
-		return nil, err
-	}
-	defer C.git_signature_free(cSignature)
-
-	var cmsg *C.char
-	if msg == "" {
-		cmsg = nil
-	} else {
-		cmsg = C.CString(msg)
-		defer C.free(unsafe.Pointer(cmsg))
-	}
-
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	ret := C.git_branch_create(&ptr, repo.ptr, cBranchName, target.cast_ptr, cForce, cSignature, cmsg)
+	ret := C.git_branch_create(&ptr, repo.ptr, cBranchName, target.cast_ptr, cForce)
 	if ret < 0 {
 		return nil, MakeGitError(ret)
 	}
@@ -132,30 +123,16 @@
 	return nil
 }
 
-func (b *Branch) Move(newBranchName string, force bool, signature *Signature, msg string) (*Branch, error) {
+func (b *Branch) Move(newBranchName string, force bool) (*Branch, error) {
 	var ptr *C.git_reference
 	cNewBranchName := C.CString(newBranchName)
 	defer C.free(unsafe.Pointer(cNewBranchName))
 	cForce := cbool(force)
 
-	cSignature, err := signature.toC()
-	if err != nil {
-		return nil, err
-	}
-	defer C.git_signature_free(cSignature)
-
-	var cmsg *C.char
-	if msg == "" {
-		cmsg = nil
-	} else {
-		cmsg = C.CString(msg)
-		defer C.free(unsafe.Pointer(cmsg))
-	}
-
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	ret := C.git_branch_move(&ptr, b.Reference.ptr, cNewBranchName, cForce, cSignature, cmsg)
+	ret := C.git_branch_move(&ptr, b.Reference.ptr, cNewBranchName, cForce)
 	if ret < 0 {
 		return nil, MakeGitError(ret)
 	}
diff --git a/branch_test.go b/branch_test.go
index a0834a8..01a2e28 100644
--- a/branch_test.go
+++ b/branch_test.go
@@ -5,6 +5,7 @@
 )
 
 func TestBranchIterator(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
@@ -27,6 +28,7 @@
 }
 
 func TestBranchIteratorEach(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
diff --git a/checkout.go b/checkout.go
index 9874d2b..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,35 +12,57 @@
 	"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
-	CheckoutSafeCreate                CheckoutStrategy = C.GIT_CHECKOUT_SAFE_CREATE                  // Allow safe updates plus creation of missing files
 	CheckoutForce                     CheckoutStrategy = C.GIT_CHECKOUT_FORCE                        // Allow all updates to force working directory to look like index
+	CheckoutRecreateMissing           CheckoutStrategy = C.GIT_CHECKOUT_RECREATE_MISSING             // Allow checkout to recreate missing files
 	CheckoutAllowConflicts            CheckoutStrategy = C.GIT_CHECKOUT_ALLOW_CONFLICTS              // Allow checkout to make safe updates even if conflicts are found
 	CheckoutRemoveUntracked           CheckoutStrategy = C.GIT_CHECKOUT_REMOVE_UNTRACKED             // Remove untracked files not in index (that are not ignored)
 	CheckoutRemoveIgnored             CheckoutStrategy = C.GIT_CHECKOUT_REMOVE_IGNORED               // Remove ignored files not in index
 	CheckoutUpdateOnly                CheckoutStrategy = C.GIT_CHECKOUT_UPDATE_ONLY                  // Only update existing files, don't create new ones
 	CheckoutDontUpdateIndex           CheckoutStrategy = C.GIT_CHECKOUT_DONT_UPDATE_INDEX            // Normally checkout updates index entries as it goes; this stops that
 	CheckoutNoRefresh                 CheckoutStrategy = C.GIT_CHECKOUT_NO_REFRESH                   // Don't refresh index/config/etc before doing checkout
+	CheckoutSkipUnmerged              CheckoutStrategy = C.GIT_CHECKOUT_SKIP_UNMERGED                // Allow checkout to skip unmerged files
+	CheckoutUserOurs                  CheckoutStrategy = C.GIT_CHECKOUT_USE_OURS                     // For unmerged files, checkout stage 2 from index
+	CheckoutUseTheirs                 CheckoutStrategy = C.GIT_CHECKOUT_USE_THEIRS                   // For unmerged files, checkout stage 3 from index
 	CheckoutDisablePathspecMatch      CheckoutStrategy = C.GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH       // Treat pathspec as simple list of exact match file paths
-	CheckoutSkipUnmerged              CheckoutStrategy = C.GIT_CHECKOUT_SKIP_UNMERGED                // Allow checkout to skip unmerged files (NOT IMPLEMENTED)
-	CheckoutUserOurs                  CheckoutStrategy = C.GIT_CHECKOUT_USE_OURS                     // For unmerged files, checkout stage 2 from index (NOT IMPLEMENTED)
-	CheckoutUseTheirs                 CheckoutStrategy = C.GIT_CHECKOUT_USE_THEIRS                   // For unmerged files, checkout stage 3 from index (NOT IMPLEMENTED)
+	CheckoutSkipLockedDirectories     CheckoutStrategy = C.GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES      // Ignore directories in use, they will be left empty
+	CheckoutDontOverwriteIgnored      CheckoutStrategy = C.GIT_CHECKOUT_DONT_OVERWRITE_IGNORED       // Don't overwrite ignored files that exist in the checkout target
+	CheckoutConflictStyleMerge        CheckoutStrategy = C.GIT_CHECKOUT_CONFLICT_STYLE_MERGE         // Write normal merge files for conflicts
+	CheckoutConflictStyleDiff3        CheckoutStrategy = C.GIT_CHECKOUT_CONFLICT_STYLE_DIFF3         // Include common ancestor data in diff3 format files for conflicts
+	CheckoutDontRemoveExisting        CheckoutStrategy = C.GIT_CHECKOUT_DONT_REMOVE_EXISTING         // Don't overwrite existing files or folders
+	CheckoutDontWriteIndex            CheckoutStrategy = C.GIT_CHECKOUT_DONT_WRITE_INDEX             // Normally checkout writes the index upon completion; this prevents that
 	CheckoutUpdateSubmodules          CheckoutStrategy = C.GIT_CHECKOUT_UPDATE_SUBMODULES            // Recursively checkout submodules with same options (NOT IMPLEMENTED)
 	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
+	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 {
@@ -48,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)
 	}
@@ -63,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.
@@ -76,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)
 	}
@@ -84,6 +158,10 @@
 		ptr.paths.count = C.size_t(len(opts.Paths))
 	}
 
+	if opts.Baseline != nil {
+		ptr.baseline = opts.Baseline.cast_ptr
+	}
+
 	return ptr
 }
 
@@ -95,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
@@ -150,4 +231,4 @@
 	}
 
 	return nil
-}
\ No newline at end of file
+}
diff --git a/cherrypick_test.go b/cherrypick_test.go
index 09bc524..bfa5ca8 100644
--- a/cherrypick_test.go
+++ b/cherrypick_test.go
@@ -16,7 +16,7 @@
 		t.Fatal(err)
 	}
 
-	err = repo.SetHeadDetached(commit.Id(), commit.Author(), "checkout")
+	err = repo.SetHeadDetached(commit.Id())
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -33,6 +33,7 @@
 }
 
 func TestCherrypick(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
diff --git a/clone.go b/clone.go
index 4de4aea..1ff5124 100644
--- a/clone.go
+++ b/clone.go
@@ -3,6 +3,7 @@
 /*
 #include <git2.h>
 
+extern void _go_git_populate_remote_cb(git_clone_options *opts);
 */
 import "C"
 import (
@@ -10,18 +11,17 @@
 	"unsafe"
 )
 
+type RemoteCreateCallback func(repo *Repository, name, url string) (*Remote, ErrorCode)
+
 type CloneOptions struct {
 	*CheckoutOpts
-	*RemoteCallbacks
+	*FetchOptions
 	Bare                 bool
 	CheckoutBranch       string
-	RemoteCreateCallback C.git_remote_create_cb
-	RemoteCreatePayload  unsafe.Pointer
+	RemoteCreateCallback RemoteCreateCallback
 }
 
 func Clone(url string, path string, options *CloneOptions) (*Repository, error) {
-	repo := new(Repository)
-
 	curl := C.CString(url)
 	defer C.free(unsafe.Pointer(curl))
 
@@ -30,6 +30,7 @@
 
 	copts := (*C.git_clone_options)(C.calloc(1, C.size_t(unsafe.Sizeof(C.git_clone_options{}))))
 	populateCloneOptions(copts, options)
+	defer freeCloneOptions(copts)
 
 	if len(options.CheckoutBranch) != 0 {
 		copts.checkout_branch = C.CString(options.CheckoutBranch)
@@ -37,17 +38,42 @@
 
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
-	ret := C.git_clone(&repo.ptr, curl, cpath, copts)
-	freeCheckoutOpts(&copts.checkout_opts)
-	C.free(unsafe.Pointer(copts.checkout_branch))
-	C.free(unsafe.Pointer(copts))
+
+	var ptr *C.git_repository
+	ret := C.git_clone(&ptr, curl, cpath, copts)
 
 	if ret < 0 {
 		return nil, MakeGitError(ret)
 	}
 
-	runtime.SetFinalizer(repo, (*Repository).Free)
-	return repo, nil
+	return newRepositoryFromC(ptr), nil
+}
+
+//export remoteCreateCallback
+func remoteCreateCallback(cremote unsafe.Pointer, crepo unsafe.Pointer, cname, curl *C.char, payload unsafe.Pointer) C.int {
+	name := C.GoString(cname)
+	url := C.GoString(curl)
+	repo := newRepositoryFromC((*C.git_repository)(crepo))
+	// We don't own this repository, so make sure we don't try to free it
+	runtime.SetFinalizer(repo, nil)
+
+	if opts, ok := pointerHandles.Get(payload).(CloneOptions); ok {
+		remote, err := opts.RemoteCreateCallback(repo, name, url)
+		// clear finalizer as the calling C function will
+		// free the remote itself
+		runtime.SetFinalizer(remote, nil)
+
+		if err == ErrOk && remote != nil {
+			cptr := (**C.git_remote)(cremote)
+			*cptr = remote.ptr
+		} else if err == ErrOk && remote == nil {
+			panic("no remote created by callback")
+		}
+
+		return C.int(err)
+	} else {
+		panic("invalid remote create callback")
+	}
 }
 
 func populateCloneOptions(ptr *C.git_clone_options, opts *CloneOptions) {
@@ -57,16 +83,27 @@
 		return
 	}
 	populateCheckoutOpts(&ptr.checkout_opts, opts.CheckoutOpts)
-	populateRemoteCallbacks(&ptr.remote_callbacks, opts.RemoteCallbacks)
+	populateFetchOptions(&ptr.fetch_opts, opts.FetchOptions)
 	ptr.bare = cbool(opts.Bare)
 
 	if opts.RemoteCreateCallback != nil {
-		ptr.remote_cb = opts.RemoteCreateCallback
-		defer C.free(unsafe.Pointer(opts.RemoteCreateCallback))
-
-		if opts.RemoteCreatePayload != nil {
-			ptr.remote_cb_payload = opts.RemoteCreatePayload
-			defer C.free(opts.RemoteCreatePayload)
-		}
+		// Go v1.1 does not allow to assign a C function pointer
+		C._go_git_populate_remote_cb(ptr)
+		ptr.remote_cb_payload = pointerHandles.Track(*opts)
 	}
 }
+
+func freeCloneOptions(ptr *C.git_clone_options) {
+	if ptr == nil {
+		return
+	}
+
+	freeCheckoutOpts(&ptr.checkout_opts)
+
+	if ptr.remote_cb_payload != nil {
+		pointerHandles.Untrack(ptr.remote_cb_payload)
+	}
+
+	C.free(unsafe.Pointer(ptr.checkout_branch))
+	C.free(unsafe.Pointer(ptr))
+}
diff --git a/clone_test.go b/clone_test.go
index fd83fec..24c3a09 100644
--- a/clone_test.go
+++ b/clone_test.go
@@ -5,7 +5,39 @@
 	"testing"
 )
 
+const (
+	REMOTENAME = "testremote"
+)
+
 func TestClone(t *testing.T) {
+	t.Parallel()
+	repo := createTestRepo(t)
+	defer cleanupTestRepo(t, repo)
+
+	seedTestRepo(t, repo)
+
+	path, err := ioutil.TempDir("", "git2go")
+	checkFatal(t, err)
+
+	ref, err := repo.References.Lookup("refs/heads/master")
+	checkFatal(t, err)
+
+	repo2, err := Clone(repo.Path(), path, &CloneOptions{Bare: true})
+	defer cleanupTestRepo(t, repo2)
+
+	checkFatal(t, err)
+
+	ref2, err := repo2.References.Lookup("refs/heads/master")
+	checkFatal(t, err)
+
+	if ref.Cmp(ref2) != 0 {
+		t.Fatal("reference in clone does not match original ref")
+	}
+}
+
+func TestCloneWithCallback(t *testing.T) {
+	t.Parallel()
+	testPayload := 0
 
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
@@ -15,8 +47,31 @@
 	path, err := ioutil.TempDir("", "git2go")
 	checkFatal(t, err)
 
-	repo2, err := Clone(repo.Path(), path, &CloneOptions{Bare: true})
+	opts := CloneOptions{
+		Bare: true,
+		RemoteCreateCallback: func(r *Repository, name, url string) (*Remote, ErrorCode) {
+			testPayload += 1
+
+			remote, err := r.Remotes.Create(REMOTENAME, url)
+			if err != nil {
+				return nil, ErrGeneric
+			}
+
+			return remote, ErrOk
+		},
+	}
+
+	repo2, err := Clone(repo.Path(), path, &opts)
 	defer cleanupTestRepo(t, repo2)
 
 	checkFatal(t, err)
+
+	if testPayload != 1 {
+		t.Fatal("Payload's value has not been changed")
+	}
+
+	remote, err := repo2.Remotes.Lookup(REMOTENAME)
+	if err != nil || remote == nil {
+		t.Fatal("Remote was not created properly")
+	}
 }
diff --git a/commit.go b/commit.go
index 52f7c01..07b7c37 100644
--- a/commit.go
+++ b/commit.go
@@ -14,7 +14,7 @@
 
 // Commit
 type Commit struct {
-	gitObject
+	Object
 	cast_ptr *C.git_commit
 }
 
@@ -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))
 }
@@ -37,7 +41,7 @@
 		return nil, MakeGitError(err)
 	}
 
-	return allocObject((*C.git_object)(ptr), c.repo).(*Tree), nil
+	return allocTree(ptr, c.repo), nil
 }
 
 func (c Commit) TreeId() *Oid {
@@ -61,7 +65,7 @@
 		return nil
 	}
 
-	return allocObject((*C.git_object)(cobj), c.repo).(*Commit)
+	return allocCommit(cobj, c.repo)
 }
 
 func (c *Commit) ParentId(n uint) *Oid {
diff --git a/config.go b/config.go
index 9d25e35..7408fbc 100644
--- a/config.go
+++ b/config.go
@@ -12,6 +12,9 @@
 type ConfigLevel int
 
 const (
+	// System-wide on Windows, for compatibility with portable git
+	ConfigLevelProgramdata ConfigLevel = C.GIT_CONFIG_LEVEL_PROGRAMDATA
+
 	// System-wide configuration file; /etc/gitconfig on Linux systems
 	ConfigLevelSystem ConfigLevel = C.GIT_CONFIG_LEVEL_SYSTEM
 
@@ -115,18 +118,20 @@
 }
 
 func (c *Config) LookupString(name string) (string, error) {
-	var ptr *C.char
 	cname := C.CString(name)
 	defer C.free(unsafe.Pointer(cname))
 
+	valBuf := C.git_buf{}
+
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	if ret := C.git_config_get_string(&ptr, c.ptr, cname); ret < 0 {
+	if ret := C.git_config_get_string_buf(&valBuf, c.ptr, cname); ret < 0 {
 		return "", MakeGitError(ret)
 	}
+	defer C.git_buf_free(&valBuf)
 
-	return C.GoString(ptr), nil
+	return C.GoString(valBuf.ptr), nil
 }
 
 func (c *Config) LookupBool(name string) (bool, error) {
@@ -410,3 +415,21 @@
 
 	return C.GoString(buf.ptr), nil
 }
+
+// ConfigFindProgramdata locate the path to the configuration file in ProgramData.
+//
+// Look for the file in %PROGRAMDATA%\Git\config used by portable git.
+func ConfigFindProgramdata() (string, error) {
+	var buf C.git_buf
+	defer C.git_buf_free(&buf)
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ret := C.git_config_find_programdata(&buf)
+	if ret < 0 {
+		return "", MakeGitError(ret)
+	}
+
+	return C.GoString(buf.ptr), nil
+}
diff --git a/config_test.go b/config_test.go
new file mode 100644
index 0000000..f31e73e
--- /dev/null
+++ b/config_test.go
@@ -0,0 +1,109 @@
+package git
+
+import (
+	"os"
+	"testing"
+)
+
+var tempConfig = "./temp.gitconfig"
+
+func setupConfig() (*Config, error) {
+	var (
+		c   *Config
+		err error
+	)
+
+	c, err = OpenOndisk(nil, tempConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	err = c.SetString("foo.bar", "baz")
+	if err != nil {
+		return nil, err
+	}
+	err = c.SetBool("foo.bool", true)
+	if err != nil {
+		return nil, err
+	}
+	err = c.SetInt32("foo.int32", 32)
+	if err != nil {
+		return nil, err
+	}
+	err = c.SetInt64("foo.int64", 64)
+	if err != nil {
+		return nil, err
+	}
+
+	return c, err
+}
+
+func cleanupConfig() {
+	os.Remove(tempConfig)
+}
+
+type TestRunner func(*Config, *testing.T)
+
+var tests = []TestRunner{
+	// LookupString
+	func(c *Config, t *testing.T) {
+		val, err := c.LookupString("foo.bar")
+		if err != nil {
+			t.Errorf("Got LookupString error: '%v', expected none\n", err)
+		}
+		if val != "baz" {
+			t.Errorf("Got '%s' from LookupString, expected 'bar'\n", val)
+		}
+	},
+	// LookupBool
+	func(c *Config, t *testing.T) {
+		val, err := c.LookupBool("foo.bool")
+		if err != nil {
+			t.Errorf("Got LookupBool error: '%v', expected none\n", err)
+		}
+		if !val {
+			t.Errorf("Got %b from LookupBool, expected 'false'\n", val)
+		}
+	},
+	// LookupInt32
+	func(c *Config, t *testing.T) {
+		val, err := c.LookupInt32("foo.int32")
+		if err != nil {
+			t.Errorf("Got LookupInt32 error: '%v', expected none\n", err)
+		}
+		if val != 32 {
+			t.Errorf("Got %v, expected 32\n", val)
+		}
+	},
+	// LookupInt64
+	func(c *Config, t *testing.T) {
+		val, err := c.LookupInt64("foo.int64")
+		if err != nil {
+			t.Errorf("Got LookupInt64 error: '%v', expected none\n", err)
+		}
+		if val != 64 {
+			t.Errorf("Got %v, expected 64\n", val)
+		}
+	},
+}
+
+func TestConfigLookups(t *testing.T) {
+	t.Parallel()
+	var (
+		err error
+		c   *Config
+	)
+
+	c, err = setupConfig()
+	defer cleanupConfig()
+
+	if err != nil {
+		t.Errorf("Setup error: '%v'. Expected none\n", err)
+		return
+	}
+	defer c.Free()
+
+	for _, test := range tests {
+		test(c, t)
+	}
+}
diff --git a/credentials.go b/credentials.go
index bb0ec41..4e42b6e 100644
--- a/credentials.go
+++ b/credentials.go
@@ -44,13 +44,15 @@
 	return int(ret), cred
 }
 
-func NewCredSshKey(username string, publickey string, privatekey string, passphrase string) (int, Cred) {
+// NewCredSshKey creates new ssh credentials reading the public and private keys
+// from the file system.
+func NewCredSshKey(username string, publicKeyPath string, privateKeyPath string, passphrase string) (int, Cred) {
 	cred := Cred{}
 	cusername := C.CString(username)
 	defer C.free(unsafe.Pointer(cusername))
-	cpublickey := C.CString(publickey)
+	cpublickey := C.CString(publicKeyPath)
 	defer C.free(unsafe.Pointer(cpublickey))
-	cprivatekey := C.CString(privatekey)
+	cprivatekey := C.CString(privateKeyPath)
 	defer C.free(unsafe.Pointer(cprivatekey))
 	cpassphrase := C.CString(passphrase)
 	defer C.free(unsafe.Pointer(cpassphrase))
@@ -58,6 +60,22 @@
 	return int(ret), cred
 }
 
+// NewCredSshKeyFromMemory creates new ssh credentials using the publicKey and privateKey
+// arguments as the values for the public and private keys.
+func NewCredSshKeyFromMemory(username string, publicKey string, privateKey string, passphrase string) (int, Cred) {
+	cred := Cred{}
+	cusername := C.CString(username)
+	defer C.free(unsafe.Pointer(cusername))
+	cpublickey := C.CString(publicKey)
+	defer C.free(unsafe.Pointer(cpublickey))
+	cprivatekey := C.CString(privateKey)
+	defer C.free(unsafe.Pointer(cprivatekey))
+	cpassphrase := C.CString(passphrase)
+	defer C.free(unsafe.Pointer(cpassphrase))
+	ret := C.git_cred_ssh_key_memory_new(&cred.ptr, cusername, cpublickey, cprivatekey, cpassphrase)
+	return int(ret), cred
+}
+
 func NewCredSshKeyFromAgent(username string) (int, Cred) {
 	cred := Cred{}
 	cusername := C.CString(username)
diff --git a/describe.go b/describe.go
new file mode 100644
index 0000000..d75dbcb
--- /dev/null
+++ b/describe.go
@@ -0,0 +1,222 @@
+package git
+
+/*
+#include <git2.h>
+*/
+import "C"
+import (
+	"runtime"
+	"unsafe"
+)
+
+// DescribeOptions represents the describe operation configuration.
+//
+// You can use DefaultDescribeOptions() to get default options.
+type DescribeOptions struct {
+	// How many tags as candidates to consider to describe the input commit-ish.
+	// Increasing it above 10 will take slightly longer but may produce a more
+	// accurate result. 0 will cause only exact matches to be output.
+	MaxCandidatesTags uint // default: 10
+
+	// By default describe only shows annotated tags. Change this in order
+	// to show all refs from refs/tags or refs/.
+	Strategy DescribeOptionsStrategy // default: DescribeDefault
+
+	// Only consider tags matching the given glob(7) pattern, excluding
+	// the "refs/tags/" prefix. Can be used to avoid leaking private
+	// tags from the repo.
+	Pattern string
+
+	// When calculating the distance from the matching tag or
+	// reference, only walk down the first-parent ancestry.
+	OnlyFollowFirstParent bool
+
+	// If no matching tag or reference is found, the describe
+	// operation would normally fail. If this option is set, it
+	// will instead fall back to showing the full id of the commit.
+	ShowCommitOidAsFallback bool
+}
+
+// DefaultDescribeOptions returns default options for the describe operation.
+func DefaultDescribeOptions() (DescribeOptions, error) {
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	opts := C.git_describe_options{}
+	ecode := C.git_describe_init_options(&opts, C.GIT_DESCRIBE_OPTIONS_VERSION)
+	if ecode < 0 {
+		return DescribeOptions{}, MakeGitError(ecode)
+	}
+
+	return DescribeOptions{
+		MaxCandidatesTags: uint(opts.max_candidates_tags),
+		Strategy:          DescribeOptionsStrategy(opts.describe_strategy),
+	}, nil
+}
+
+// DescribeFormatOptions can be used for formatting the describe string.
+//
+// You can use DefaultDescribeFormatOptions() to get default options.
+type DescribeFormatOptions struct {
+	// Size of the abbreviated commit id to use. This value is the
+	// lower bound for the length of the abbreviated string.
+	AbbreviatedSize uint // default: 7
+
+	// Set to use the long format even when a shorter name could be used.
+	AlwaysUseLongFormat bool
+
+	// If the workdir is dirty and this is set, this string will be
+	// appended to the description string.
+	DirtySuffix string
+}
+
+// DefaultDescribeFormatOptions returns default options for formatting
+// the output.
+func DefaultDescribeFormatOptions() (DescribeFormatOptions, error) {
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	opts := C.git_describe_format_options{}
+	ecode := C.git_describe_init_format_options(&opts, C.GIT_DESCRIBE_FORMAT_OPTIONS_VERSION)
+	if ecode < 0 {
+		return DescribeFormatOptions{}, MakeGitError(ecode)
+	}
+
+	return DescribeFormatOptions{
+		AbbreviatedSize:     uint(opts.abbreviated_size),
+		AlwaysUseLongFormat: opts.always_use_long_format == 1,
+	}, nil
+}
+
+// DescribeOptionsStrategy behaves like the --tags and --all options
+// to git-describe, namely they say to look for any reference in
+// either refs/tags/ or refs/ respectively.
+//
+// By default it only shows annotated tags.
+type DescribeOptionsStrategy uint
+
+// Describe strategy options.
+const (
+	DescribeDefault DescribeOptionsStrategy = C.GIT_DESCRIBE_DEFAULT
+	DescribeTags    DescribeOptionsStrategy = C.GIT_DESCRIBE_TAGS
+	DescribeAll     DescribeOptionsStrategy = C.GIT_DESCRIBE_ALL
+)
+
+// Describe performs the describe operation on the commit.
+func (c *Commit) Describe(opts *DescribeOptions) (*DescribeResult, error) {
+	var resultPtr *C.git_describe_result
+
+	var cDescribeOpts *C.git_describe_options
+	if opts != nil {
+		var cpattern *C.char
+		if len(opts.Pattern) > 0 {
+			cpattern = C.CString(opts.Pattern)
+			defer C.free(unsafe.Pointer(cpattern))
+		}
+
+		cDescribeOpts = &C.git_describe_options{
+			version:                     C.GIT_DESCRIBE_OPTIONS_VERSION,
+			max_candidates_tags:         C.uint(opts.MaxCandidatesTags),
+			describe_strategy:           C.uint(opts.Strategy),
+			pattern:                     cpattern,
+			only_follow_first_parent:    cbool(opts.OnlyFollowFirstParent),
+			show_commit_oid_as_fallback: cbool(opts.ShowCommitOidAsFallback),
+		}
+	}
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ecode := C.git_describe_commit(&resultPtr, c.ptr, cDescribeOpts)
+	if ecode < 0 {
+		return nil, MakeGitError(ecode)
+	}
+
+	return newDescribeResultFromC(resultPtr), nil
+}
+
+// DescribeWorkdir describes the working tree. It means describe HEAD
+// and appends <mark> (-dirty by default) if the working tree is dirty.
+func (repo *Repository) DescribeWorkdir(opts *DescribeOptions) (*DescribeResult, error) {
+	var resultPtr *C.git_describe_result
+
+	var cDescribeOpts *C.git_describe_options
+	if opts != nil {
+		var cpattern *C.char
+		if len(opts.Pattern) > 0 {
+			cpattern = C.CString(opts.Pattern)
+			defer C.free(unsafe.Pointer(cpattern))
+		}
+
+		cDescribeOpts = &C.git_describe_options{
+			version:                     C.GIT_DESCRIBE_OPTIONS_VERSION,
+			max_candidates_tags:         C.uint(opts.MaxCandidatesTags),
+			describe_strategy:           C.uint(opts.Strategy),
+			pattern:                     cpattern,
+			only_follow_first_parent:    cbool(opts.OnlyFollowFirstParent),
+			show_commit_oid_as_fallback: cbool(opts.ShowCommitOidAsFallback),
+		}
+	}
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ecode := C.git_describe_workdir(&resultPtr, repo.ptr, cDescribeOpts)
+	if ecode < 0 {
+		return nil, MakeGitError(ecode)
+	}
+
+	return newDescribeResultFromC(resultPtr), nil
+}
+
+// DescribeResult represents the output from the 'git_describe_commit'
+// and 'git_describe_workdir' functions in libgit2.
+//
+// Use Format() to get a string out of it.
+type DescribeResult struct {
+	ptr *C.git_describe_result
+}
+
+func newDescribeResultFromC(ptr *C.git_describe_result) *DescribeResult {
+	result := &DescribeResult{
+		ptr: ptr,
+	}
+	runtime.SetFinalizer(result, (*DescribeResult).Free)
+	return result
+}
+
+// Format prints the DescribeResult as a string.
+func (result *DescribeResult) Format(opts *DescribeFormatOptions) (string, error) {
+	resultBuf := C.git_buf{}
+
+	var cFormatOpts *C.git_describe_format_options
+	if opts != nil {
+		cDirtySuffix := C.CString(opts.DirtySuffix)
+		defer C.free(unsafe.Pointer(cDirtySuffix))
+
+		cFormatOpts = &C.git_describe_format_options{
+			version:                C.GIT_DESCRIBE_FORMAT_OPTIONS_VERSION,
+			abbreviated_size:       C.uint(opts.AbbreviatedSize),
+			always_use_long_format: cbool(opts.AlwaysUseLongFormat),
+			dirty_suffix:           cDirtySuffix,
+		}
+	}
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ecode := C.git_describe_format(&resultBuf, result.ptr, cFormatOpts)
+	if ecode < 0 {
+		return "", MakeGitError(ecode)
+	}
+	defer C.git_buf_free(&resultBuf)
+
+	return C.GoString(resultBuf.ptr), nil
+}
+
+// Free cleans up the C reference.
+func (result *DescribeResult) Free() {
+	runtime.SetFinalizer(result, nil)
+	C.git_describe_result_free(result.ptr)
+	result.ptr = nil
+}
diff --git a/describe_test.go b/describe_test.go
new file mode 100644
index 0000000..3181f23
--- /dev/null
+++ b/describe_test.go
@@ -0,0 +1,109 @@
+package git
+
+import (
+	"path"
+	"runtime"
+	"strings"
+	"testing"
+)
+
+func TestDescribeCommit(t *testing.T) {
+	t.Parallel()
+	repo := createTestRepo(t)
+	defer cleanupTestRepo(t, repo)
+
+	describeOpts, err := DefaultDescribeOptions()
+	checkFatal(t, err)
+
+	formatOpts, err := DefaultDescribeFormatOptions()
+	checkFatal(t, err)
+
+	commitID, _ := seedTestRepo(t, repo)
+
+	commit, err := repo.LookupCommit(commitID)
+	checkFatal(t, err)
+
+	// No annotated tags can be used to describe master
+	_, err = commit.Describe(&describeOpts)
+	checkDescribeNoRefsFound(t, err)
+
+	// Fallback
+	fallback := describeOpts
+	fallback.ShowCommitOidAsFallback = true
+	result, err := commit.Describe(&fallback)
+	checkFatal(t, err)
+	resultStr, err := result.Format(&formatOpts)
+	checkFatal(t, err)
+	compareStrings(t, "473bf77", resultStr)
+
+	// Abbreviated
+	abbreviated := formatOpts
+	abbreviated.AbbreviatedSize = 2
+	result, err = commit.Describe(&fallback)
+	checkFatal(t, err)
+	resultStr, err = result.Format(&abbreviated)
+	checkFatal(t, err)
+	compareStrings(t, "473b", resultStr)
+
+	createTestTag(t, repo, commit)
+
+	// Exact tag
+	patternOpts := describeOpts
+	patternOpts.Pattern = "v[0-9]*"
+	result, err = commit.Describe(&patternOpts)
+	checkFatal(t, err)
+	resultStr, err = result.Format(&formatOpts)
+	checkFatal(t, err)
+	compareStrings(t, "v0.0.0", resultStr)
+
+	// Pattern no match
+	patternOpts.Pattern = "v[1-9]*"
+	result, err = commit.Describe(&patternOpts)
+	checkDescribeNoRefsFound(t, err)
+
+	commitID, _ = updateReadme(t, repo, "update1")
+	commit, err = repo.LookupCommit(commitID)
+	checkFatal(t, err)
+
+	// Tag-1
+	result, err = commit.Describe(&describeOpts)
+	checkFatal(t, err)
+	resultStr, err = result.Format(&formatOpts)
+	checkFatal(t, err)
+	compareStrings(t, "v0.0.0-1-gd88ef8d", resultStr)
+
+	// Strategy: All
+	describeOpts.Strategy = DescribeAll
+	result, err = commit.Describe(&describeOpts)
+	checkFatal(t, err)
+	resultStr, err = result.Format(&formatOpts)
+	checkFatal(t, err)
+	compareStrings(t, "heads/master", resultStr)
+
+	repo.CreateBranch("hotfix", commit, false)
+
+	// Workdir (branch)
+	result, err = repo.DescribeWorkdir(&describeOpts)
+	checkFatal(t, err)
+	resultStr, err = result.Format(&formatOpts)
+	checkFatal(t, err)
+	compareStrings(t, "heads/hotfix", resultStr)
+}
+
+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(), expectedString) {
+		t.Fatalf(
+			"%s:%v: was expecting error %v, got %v",
+			path.Base(file),
+			line,
+			expectedString,
+			err,
+		)
+	}
+}
diff --git a/diff.go b/diff.go
index de56374..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
@@ -217,6 +220,33 @@
 	return int(C.git_diff_stats_files_changed(stats.ptr))
 }
 
+type DiffStatsFormat int
+
+const (
+	DiffStatsNone           DiffStatsFormat = C.GIT_DIFF_STATS_NONE
+	DiffStatsFull           DiffStatsFormat = C.GIT_DIFF_STATS_FULL
+	DiffStatsShort          DiffStatsFormat = C.GIT_DIFF_STATS_SHORT
+	DiffStatsNumber         DiffStatsFormat = C.GIT_DIFF_STATS_NUMBER
+	DiffStatsIncludeSummary DiffStatsFormat = C.GIT_DIFF_STATS_INCLUDE_SUMMARY
+)
+
+func (stats *DiffStats) String(format DiffStatsFormat,
+	width uint) (string, error) {
+	buf := C.git_buf{}
+	defer C.git_buf_free(&buf)
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ret := C.git_diff_stats_to_buf(&buf,
+		stats.ptr, C.git_diff_stats_format_t(format), C.size_t(width))
+	if ret < 0 {
+		return "", MakeGitError(ret)
+	}
+
+	return C.GoString(buf.ptr), nil
+}
+
 func (diff *Diff) Stats() (*DiffStats, error) {
 	stats := new(DiffStats)
 
@@ -372,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
@@ -550,7 +581,7 @@
 
 		if opts.NotifyCallback != nil {
 			C._go_git_setup_diff_notify_callbacks(copts)
-			copts.notify_payload = pointerHandles.Track(notifyData)
+			copts.payload = pointerHandles.Track(notifyData)
 		}
 	}
 	return
@@ -562,8 +593,8 @@
 		freeStrarray(&cpathspec)
 		C.free(unsafe.Pointer(copts.old_prefix))
 		C.free(unsafe.Pointer(copts.new_prefix))
-		if copts.notify_payload != nil {
-			pointerHandles.Untrack(copts.notify_payload)
+		if copts.payload != nil {
+			pointerHandles.Untrack(copts.payload)
 		}
 	}
 }
@@ -622,6 +653,36 @@
 	return newDiffFromC(diffPtr), nil
 }
 
+func (v *Repository) DiffTreeToIndex(oldTree *Tree, index *Index, opts *DiffOptions) (*Diff, error) {
+	var diffPtr *C.git_diff
+	var oldPtr *C.git_tree
+	var indexPtr *C.git_index
+
+	if oldTree != nil {
+		oldPtr = oldTree.cast_ptr
+	}
+
+	if index != nil {
+		indexPtr = index.ptr
+	}
+
+	copts, notifyData := diffOptionsToC(opts)
+	defer freeDiffOptions(copts)
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ecode := C.git_diff_tree_to_index(&diffPtr, v.ptr, oldPtr, indexPtr, copts)
+	if ecode < 0 {
+		return nil, MakeGitError(ecode)
+	}
+
+	if notifyData != nil && notifyData.Diff != nil {
+		return notifyData.Diff, nil
+	}
+	return newDiffFromC(diffPtr), nil
+}
+
 func (v *Repository) DiffTreeToWorkdirWithIndex(oldTree *Tree, opts *DiffOptions) (*Diff, error) {
 	var diffPtr *C.git_diff
 	var oldPtr *C.git_tree
diff --git a/diff_test.go b/diff_test.go
index 850ed8e..6fbad51 100644
--- a/diff_test.go
+++ b/diff_test.go
@@ -7,6 +7,7 @@
 )
 
 func TestFindSimilar(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
@@ -61,7 +62,7 @@
 }
 
 func TestDiffTreeToTree(t *testing.T) {
-
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
@@ -189,6 +190,7 @@
 }
 
 func TestDiffBlobs(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
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 a44459f..c032b0a 100644
--- a/git.go
+++ b/git.go
@@ -1,9 +1,17 @@
 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 != 25
+# error "Invalid libgit2 version; this git2go supports libgit2 v0.25"
+#endif
+
 */
 import "C"
 import (
@@ -45,6 +53,7 @@
 	ErrClassFilter     ErrorClass = C.GITERR_FILTER
 	ErrClassRevert     ErrorClass = C.GITERR_REVERT
 	ErrClassCallback   ErrorClass = C.GITERR_CALLBACK
+	ErrClassRebase     ErrorClass = C.GITERR_REBASE
 )
 
 type ErrorCode int
@@ -53,6 +62,7 @@
 
 	// No error
 	ErrOk ErrorCode = C.GIT_OK
+
 	// Generic error
 	ErrGeneric ErrorCode = C.GIT_ERROR
 	// Requested object could not be found
@@ -63,10 +73,12 @@
 	ErrAmbigious ErrorCode = C.GIT_EAMBIGUOUS
 	// Output buffer too short to hold data
 	ErrBuffs ErrorCode = C.GIT_EBUFS
+
 	// GIT_EUSER is a special error that is never generated by libgit2
 	// code.  You can return it from a callback (e.g to stop an iteration)
 	// to know that it was generated by the callback and not by libgit2.
 	ErrUser ErrorCode = C.GIT_EUSER
+
 	// Operation not allowed on bare repository
 	ErrBareRepo ErrorCode = C.GIT_EBAREREPO
 	// HEAD refers to branch with no commits
@@ -77,18 +89,33 @@
 	ErrNonFastForward ErrorCode = C.GIT_ENONFASTFORWARD
 	// Name/ref spec was not in a valid format
 	ErrInvalidSpec ErrorCode = C.GIT_EINVALIDSPEC
-	// Merge conflicts prevented operation
-	ErrMergeConflict ErrorCode = C.GIT_EMERGECONFLICT
+	// Checkout conflicts prevented operation
+	ErrConflict ErrorCode = C.GIT_ECONFLICT
 	// Lock file prevented operation
 	ErrLocked ErrorCode = C.GIT_ELOCKED
 	// Reference value does not match expected
 	ErrModified ErrorCode = C.GIT_EMODIFIED
+	// Authentication failed
+	ErrAuth ErrorCode = C.GIT_EAUTH
+	// Server certificate is invalid
+	ErrCertificate ErrorCode = C.GIT_ECERTIFICATE
+	// Patch/merge has already been applied
+	ErrApplied ErrorCode = C.GIT_EAPPLIED
+	// The requested peel operation is not possible
+	ErrPeel ErrorCode = C.GIT_EPEEL
+	// Unexpected EOF
+	ErrEOF ErrorCode = C.GIT_EEOF
+	// Uncommitted changes in index prevented operation
+	ErrUncommitted ErrorCode = C.GIT_EUNCOMMITTED
+	// The operation is not valid for a directory
+	ErrDirectory ErrorCode = C.GIT_EDIRECTORY
+	// A merge conflict exists and cannot continue
+	ErrMergeConflict ErrorCode = C.GIT_EMERGECONFLICT
+
 	// Internal only
 	ErrPassthrough ErrorCode = C.GIT_PASSTHROUGH
 	// Signals end of iteration with iterator
 	ErrIterOver ErrorCode = C.GIT_ITEROVER
-	// Authentication failed
-	ErrAuth ErrorCode = C.GIT_EAUTH
 )
 
 var (
@@ -102,6 +129,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 58caf71..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)
 
@@ -109,6 +113,7 @@
 }
 
 func TestOidZero(t *testing.T) {
+	t.Parallel()
 	var zeroId Oid
 
 	if !zeroId.IsZero() {
@@ -117,6 +122,7 @@
 }
 
 func TestEmptyOid(t *testing.T) {
+	t.Parallel()
 	_, err := NewOid("")
 	if err == nil || !IsErrorCode(err, ErrGeneric) {
 		t.Fatal("Should have returned invalid error")
diff --git a/handles.go b/handles.go
index a062231..d27d3c3 100644
--- a/handles.go
+++ b/handles.go
@@ -1,5 +1,9 @@
 package git
 
+/*
+#include <stdlib.h>
+*/
+import "C"
 import (
 	"fmt"
 	"sync"
@@ -9,77 +13,45 @@
 type HandleList struct {
 	sync.RWMutex
 	// stores the Go pointers
-	handles []interface{}
-	// Indicates which indices are in use, and keeps a pointer to slot int variable (the handle)
-	// in the Go world, so that the Go garbage collector does not free it.
-	set map[int]*int
+	handles map[unsafe.Pointer]interface{}
 }
 
 func NewHandleList() *HandleList {
 	return &HandleList{
-		handles: make([]interface{}, 5),
-		set:     make(map[int]*int),
+		handles: make(map[unsafe.Pointer]interface{}),
 	}
 }
 
-// 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() int {
-	for i := 1; i < len(v.handles); i++ {
-		_, isUsed := v.set[i]
-		if !isUsed {
-			return 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 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 {
+	handle := C.malloc(1)
+
 	v.Lock()
-
-	slot := v.findUnusedSlot()
-	v.handles[slot] = pointer
-	v.set[slot] = &slot // Keep a pointer to slot in Go world, so it's not freed by GC.
-
+	v.handles[handle] = pointer
 	v.Unlock()
 
-	return unsafe.Pointer(&slot)
+	return handle
 }
 
 // Untrack stops tracking the pointer given by the handle
 func (v *HandleList) Untrack(handle unsafe.Pointer) {
-	slot := *(*int)(handle)
-
 	v.Lock()
-
-	v.handles[slot] = nil
-	delete(v.set, slot)
-
+	delete(v.handles, handle)
+	C.free(handle)
 	v.Unlock()
 }
 
 // Get retrieves the pointer from the given handle
 func (v *HandleList) Get(handle unsafe.Pointer) interface{} {
-	slot := *(*int)(handle)
-
 	v.RLock()
+	defer v.RUnlock()
 
-	if _, ok := v.set[slot]; !ok {
+	ptr, ok := v.handles[handle]
+	if !ok {
 		panic(fmt.Sprintf("invalid pointer handle: %p", handle))
 	}
 
-	ptr := v.handles[slot]
-
-	v.RUnlock()
-
 	return ptr
 }
diff --git a/ignore.go b/ignore.go
new file mode 100644
index 0000000..6b12348
--- /dev/null
+++ b/ignore.go
@@ -0,0 +1,51 @@
+package git
+
+/*
+#include <git2.h>
+*/
+import "C"
+import (
+	"runtime"
+	"unsafe"
+)
+
+func (v *Repository) AddIgnoreRule(rules string) error {
+	crules := C.CString(rules)
+	defer C.free(unsafe.Pointer(crules))
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ret := C.git_ignore_add_rule(v.ptr, crules)
+	if ret < 0 {
+		return MakeGitError(ret)
+	}
+	return nil
+}
+
+func (v *Repository) ClearInternalIgnoreRules() error {
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ret := C.git_ignore_clear_internal_rules(v.ptr)
+	if ret < 0 {
+		return MakeGitError(ret)
+	}
+	return nil
+}
+
+func (v *Repository) IsPathIgnored(path string) (bool, error) {
+	var ignored C.int
+
+	cpath := C.CString(path)
+	defer C.free(unsafe.Pointer(cpath))
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ret := C.git_ignore_path_is_ignored(&ignored, v.ptr, cpath)
+	if ret < 0 {
+		return false, MakeGitError(ret)
+	}
+	return ignored == 1, nil
+}
diff --git a/index.go b/index.go
index c1bfb74..2afdcdf 100644
--- a/index.go
+++ b/index.go
@@ -12,7 +12,6 @@
 import (
 	"fmt"
 	"runtime"
-	"time"
 	"unsafe"
 )
 
@@ -27,17 +26,40 @@
 	IndexAddCheckPathspec        IndexAddOpts = C.GIT_INDEX_ADD_CHECK_PATHSPEC
 )
 
+type IndexStageOpts int
+
+const (
+	// IndexStageAny matches any index stage.
+	//
+	// Some index APIs take a stage to match; pass this value to match
+	// any entry matching the path regardless of stage.
+	IndexStageAny IndexStageOpts = C.GIT_INDEX_STAGE_ANY
+	// IndexStageNormal is a normal staged file in the index.
+	IndexStageNormal IndexStageOpts = C.GIT_INDEX_STAGE_NORMAL
+	// IndexStageAncestor is the ancestor side of a conflict.
+	IndexStageAncestor IndexStageOpts = C.GIT_INDEX_STAGE_ANCESTOR
+	// IndexStageOurs is the "ours" side of a conflict.
+	IndexStageOurs IndexStageOpts = C.GIT_INDEX_STAGE_OURS
+	// IndexStageTheirs is the "theirs" side of a conflict.
+	IndexStageTheirs IndexStageOpts = C.GIT_INDEX_STAGE_THEIRS
+)
+
 type Index struct {
 	ptr *C.git_index
 }
 
+type IndexTime struct {
+	seconds     int32
+	nanoseconds uint32
+}
+
 type IndexEntry struct {
-	Ctime time.Time
-	Mtime time.Time
+	Ctime IndexTime
+	Mtime IndexTime
 	Mode  Filemode
-	Uid   uint
-	Gid   uint
-	Size  uint
+	Uid   uint32
+	Gid   uint32
+	Size  uint32
 	Id    *Oid
 	Path  string
 }
@@ -47,26 +69,26 @@
 		return nil
 	}
 	return &IndexEntry{
-		time.Unix(int64(entry.ctime.seconds), int64(entry.ctime.nanoseconds)),
-		time.Unix(int64(entry.mtime.seconds), int64(entry.mtime.nanoseconds)),
+		IndexTime{int32(entry.ctime.seconds), uint32(entry.ctime.nanoseconds)},
+		IndexTime{int32(entry.mtime.seconds), uint32(entry.mtime.nanoseconds)},
 		Filemode(entry.mode),
-		uint(entry.uid),
-		uint(entry.gid),
-		uint(entry.file_size),
+		uint32(entry.uid),
+		uint32(entry.gid),
+		uint32(entry.file_size),
 		newOidFromC(&entry.id),
 		C.GoString(entry.path),
 	}
 }
 
 func populateCIndexEntry(source *IndexEntry, dest *C.git_index_entry) {
-	dest.ctime.seconds = C.git_time_t(source.Ctime.Unix())
-	dest.ctime.nanoseconds = C.uint(source.Ctime.UnixNano())
-	dest.mtime.seconds = C.git_time_t(source.Mtime.Unix())
-	dest.mtime.nanoseconds = C.uint(source.Mtime.UnixNano())
-	dest.mode = C.uint(source.Mode)
-	dest.uid = C.uint(source.Uid)
-	dest.gid = C.uint(source.Gid)
-	dest.file_size = C.git_off_t(source.Size)
+	dest.ctime.seconds = C.int32_t(source.Ctime.seconds)
+	dest.ctime.nanoseconds = C.uint32_t(source.Ctime.nanoseconds)
+	dest.mtime.seconds = C.int32_t(source.Mtime.seconds)
+	dest.mtime.nanoseconds = C.uint32_t(source.Mtime.nanoseconds)
+	dest.mode = C.uint32_t(source.Mode)
+	dest.uid = C.uint32_t(source.Uid)
+	dest.gid = C.uint32_t(source.Gid)
+	dest.file_size = C.uint32_t(source.Size)
 	dest.id = *source.Id.toC()
 	dest.path = C.CString(source.Path)
 }
@@ -93,7 +115,7 @@
 		return nil, MakeGitError(err)
 	}
 
-	return &Index{ptr: ptr}, nil
+	return newIndexFromC(ptr), nil
 }
 
 // OpenIndex creates a new index at the given path. If the file does
@@ -111,7 +133,7 @@
 		return nil, MakeGitError(err)
 	}
 
-	return &Index{ptr: ptr}, nil
+	return newIndexFromC(ptr), nil
 }
 
 // Path returns the index' path on disk or an empty string if it
@@ -256,6 +278,22 @@
 	return nil
 }
 
+// RemoveDirectory removes all entries from the index under a given directory.
+func (v *Index) RemoveDirectory(dir string, stage int) error {
+	cstr := C.CString(dir)
+	defer C.free(unsafe.Pointer(cstr))
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ret := C.git_index_remove_directory(v.ptr, cstr, C.int(stage))
+	if ret < 0 {
+		return MakeGitError(ret)
+	}
+
+	return nil
+}
+
 func (v *Index) WriteTreeTo(repo *Repository) (*Oid, error) {
 	oid := new(Oid)
 
@@ -276,7 +314,7 @@
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	ret := C.git_index_read_tree(v.ptr, tree.cast_ptr);
+	ret := C.git_index_read_tree(v.ptr, tree.cast_ptr)
 	if ret < 0 {
 		return MakeGitError(ret)
 	}
@@ -327,6 +365,50 @@
 	return newIndexEntryFromC(centry), nil
 }
 
+func (v *Index) EntryByPath(path string, stage int) (*IndexEntry, error) {
+	cpath := C.CString(path)
+	defer C.free(unsafe.Pointer(cpath))
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	centry := C.git_index_get_bypath(v.ptr, cpath, C.int(stage))
+	if centry == nil {
+		return nil, MakeGitError(C.GIT_ENOTFOUND)
+	}
+	return newIndexEntryFromC(centry), nil
+}
+
+func (v *Index) Find(path string) (uint, error) {
+	cpath := C.CString(path)
+	defer C.free(unsafe.Pointer(cpath))
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	var pos C.size_t
+	ret := C.git_index_find(&pos, v.ptr, cpath)
+	if ret < 0 {
+		return uint(0), MakeGitError(ret)
+	}
+	return uint(pos), nil
+}
+
+func (v *Index) FindPrefix(prefix string) (uint, error) {
+	cprefix := C.CString(prefix)
+	defer C.free(unsafe.Pointer(cprefix))
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	var pos C.size_t
+	ret := C.git_index_find_prefix(&pos, v.ptr, cprefix)
+	if ret < 0 {
+		return uint(0), MakeGitError(ret)
+	}
+	return uint(pos), nil
+}
+
 func (v *Index) HasConflicts() bool {
 	return C.git_index_has_conflicts(v.ptr) != 0
 }
diff --git a/index_test.go b/index_test.go
index 7c65f4f..f47dace 100644
--- a/index_test.go
+++ b/index_test.go
@@ -8,6 +8,7 @@
 )
 
 func TestCreateRepoAndStage(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
@@ -24,6 +25,7 @@
 }
 
 func TestIndexReadTree(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
@@ -32,10 +34,11 @@
 	ref, err := repo.Head()
 	checkFatal(t, err)
 
-	obj, err := ref.Peel(ObjectTree);
+	obj, err := ref.Peel(ObjectTree)
 	checkFatal(t, err)
 
-	tree := obj.(*Tree)
+	tree, err := obj.AsTree()
+	checkFatal(t, err)
 
 	idx, err := NewIndex()
 	checkFatal(t, err)
@@ -52,6 +55,7 @@
 }
 
 func TestIndexWriteTreeTo(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
@@ -71,6 +75,7 @@
 }
 
 func TestIndexAddAndWriteTreeTo(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
@@ -104,7 +109,48 @@
 	}
 }
 
+func TestIndexRemoveDirectory(t *testing.T) {
+	repo := createTestRepo(t)
+	defer cleanupTestRepo(t, repo)
+
+	odb, err := repo.Odb()
+	checkFatal(t, err)
+
+	blobID, err := odb.Write([]byte("fou\n"), ObjectBlob)
+	checkFatal(t, err)
+
+	idx, err := NewIndex()
+	checkFatal(t, err)
+
+	entryCount := idx.EntryCount()
+	if entryCount != 0 {
+		t.Fatal("Index should count 0 entry")
+	}
+
+	entry := IndexEntry{
+		Path: "path/to/LISEZ_MOI",
+		Id:   blobID,
+		Mode: FilemodeBlob,
+	}
+
+	err = idx.Add(&entry)
+	checkFatal(t, err)
+
+	entryCount = idx.EntryCount()
+	if entryCount != 1 {
+		t.Fatal("Index should count 1 entry")
+	}
+
+	err = idx.RemoveDirectory("path", 0)
+
+	entryCount = idx.EntryCount()
+	if entryCount != 0 {
+		t.Fatal("Index should count 0 entry")
+	}
+}
+
 func TestIndexAddAllNoCallback(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
@@ -126,6 +172,7 @@
 }
 
 func TestIndexAddAllCallback(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
@@ -154,6 +201,7 @@
 }
 
 func TestIndexOpen(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
diff --git a/merge.go b/merge.go
index 183305c..b3fd818 100644
--- a/merge.go
+++ b/merge.go
@@ -6,6 +6,7 @@
 extern git_annotated_commit** _go_git_make_merge_head_array(size_t len);
 extern void _go_git_annotated_commit_array_set(git_annotated_commit** array, git_annotated_commit* ptr, size_t n);
 extern git_annotated_commit* _go_git_annotated_commit_array_get(git_annotated_commit** array, size_t n);
+extern int _go_git_merge_file(git_merge_file_result*, char*, size_t, char*, unsigned int, char*, size_t, char*, unsigned int, char*, size_t, char*, unsigned int, git_merge_file_options*);
 
 */
 import "C"
@@ -81,12 +82,19 @@
 type MergeTreeFlag int
 
 const (
-	MergeTreeFindRenames MergeTreeFlag = C.GIT_MERGE_TREE_FIND_RENAMES
+	// Detect renames that occur between the common ancestor and the "ours"
+	// side or the common ancestor and the "theirs" side.  This will enable
+	// the ability to merge between a modified and renamed file.
+	MergeTreeFindRenames MergeTreeFlag = C.GIT_MERGE_FIND_RENAMES
+	// If a conflict occurs, exit immediately instead of attempting to
+	// continue resolving conflicts.  The merge operation will fail with
+	// GIT_EMERGECONFLICT and no index will be returned.
+	MergeTreeFailOnConflict MergeTreeFlag = C.GIT_MERGE_FAIL_ON_CONFLICT
 )
 
 type MergeOptions struct {
-	Version uint
-	Flags   MergeTreeFlag
+	Version   uint
+	TreeFlags MergeTreeFlag
 
 	RenameThreshold uint
 	TargetLimit     uint
@@ -98,7 +106,7 @@
 func mergeOptionsFromC(opts *C.git_merge_options) MergeOptions {
 	return MergeOptions{
 		Version:         uint(opts.version),
-		Flags:           MergeTreeFlag(opts.flags),
+		TreeFlags:       MergeTreeFlag(opts.flags),
 		RenameThreshold: uint(opts.rename_threshold),
 		TargetLimit:     uint(opts.target_limit),
 		FileFavor:       MergeFileFavor(opts.file_favor),
@@ -124,7 +132,7 @@
 	}
 	return &C.git_merge_options{
 		version:          C.uint(mo.Version),
-		flags:            C.git_merge_tree_flag_t(mo.Flags),
+		flags:            C.git_merge_flag_t(mo.TreeFlags),
 		rename_threshold: C.uint(mo.RenameThreshold),
 		target_limit:     C.uint(mo.TargetLimit),
 		file_favor:       C.git_merge_file_favor_t(mo.FileFavor),
@@ -178,6 +186,9 @@
 	MergePreferenceFastForwardOnly MergePreference = C.GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY
 )
 
+// MergeAnalysis returns the possible actions which could be taken by
+// a 'git-merge' command. There may be multiple answers, so the first
+// return value is a bitmask of MergeAnalysis values.
 func (r *Repository) MergeAnalysis(theirHeads []*AnnotatedCommit) (MergeAnalysis, MergePreference, error) {
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
@@ -259,10 +270,10 @@
 	}
 
 	oids := make([]*Oid, coids.count)
-	hdr := reflect.SliceHeader {
+	hdr := reflect.SliceHeader{
 		Data: uintptr(unsafe.Pointer(coids.ids)),
-		Len: int(coids.count),
-		Cap: int(coids.count),
+		Len:  int(coids.count),
+		Cap:  int(coids.count),
 	}
 
 	goSlice := *(*[]C.git_oid)(unsafe.Pointer(&hdr))
@@ -317,20 +328,6 @@
 	Contents []byte
 }
 
-// populate a C struct with merge file input, make sure to use freeMergeFileInput to clean up allocs
-func populateCMergeFileInput(c *C.git_merge_file_input, input MergeFileInput) {
-	c.path = C.CString(input.Path)
-	if input.Contents != nil {
-		c.ptr = (*C.char)(unsafe.Pointer(&input.Contents[0]))
-		c.size = C.size_t(len(input.Contents))
-	}
-	c.mode = C.uint(input.Mode)
-}
-
-func freeCMergeFileInput(c *C.git_merge_file_input) {
-	C.free(unsafe.Pointer(c.path))
-}
-
 type MergeFileFlags int
 
 const (
@@ -364,7 +361,7 @@
 	c.our_label = C.CString(options.OurLabel)
 	c.their_label = C.CString(options.TheirLabel)
 	c.favor = C.git_merge_file_favor_t(options.Favor)
-	c.flags = C.git_merge_file_flags_t(options.Flags)
+	c.flags = C.git_merge_file_flag_t(options.Flags)
 }
 
 func freeCMergeFileOptions(c *C.git_merge_file_options) {
@@ -375,16 +372,26 @@
 
 func MergeFile(ancestor MergeFileInput, ours MergeFileInput, theirs MergeFileInput, options *MergeFileOptions) (*MergeFileResult, error) {
 
-	var cancestor C.git_merge_file_input
-	var cours C.git_merge_file_input
-	var ctheirs C.git_merge_file_input
+	ancestorPath := C.CString(ancestor.Path)
+	defer C.free(unsafe.Pointer(ancestorPath))
+	var ancestorContents *byte
+	if len(ancestor.Contents) > 0 {
+		ancestorContents = &ancestor.Contents[0]
+	}
 
-	populateCMergeFileInput(&cancestor, ancestor)
-	defer freeCMergeFileInput(&cancestor)
-	populateCMergeFileInput(&cours, ours)
-	defer freeCMergeFileInput(&cours)
-	populateCMergeFileInput(&ctheirs, theirs)
-	defer freeCMergeFileInput(&ctheirs)
+	oursPath := C.CString(ours.Path)
+	defer C.free(unsafe.Pointer(oursPath))
+	var oursContents *byte
+	if len(ours.Contents) > 0 {
+		oursContents = &ours.Contents[0]
+	}
+
+	theirsPath := C.CString(theirs.Path)
+	defer C.free(unsafe.Pointer(theirsPath))
+	var theirsContents *byte
+	if len(theirs.Contents) > 0 {
+		theirsContents = &theirs.Contents[0]
+	}
 
 	var copts *C.git_merge_file_options
 	if options != nil {
@@ -401,7 +408,11 @@
 	defer runtime.UnlockOSThread()
 
 	var result C.git_merge_file_result
-	ecode := C.git_merge_file(&result, &cancestor, &cours, &ctheirs, copts)
+	ecode := C._go_git_merge_file(&result,
+		(*C.char)(unsafe.Pointer(ancestorContents)), C.size_t(len(ancestor.Contents)), ancestorPath, C.uint(ancestor.Mode),
+		(*C.char)(unsafe.Pointer(oursContents)), C.size_t(len(ours.Contents)), oursPath, C.uint(ours.Mode),
+		(*C.char)(unsafe.Pointer(theirsContents)), C.size_t(len(theirs.Contents)), theirsPath, C.uint(theirs.Mode),
+		copts)
 	if ecode < 0 {
 		return nil, MakeGitError(ecode)
 	}
diff --git a/merge_test.go b/merge_test.go
index c09deed..f2c84bc 100644
--- a/merge_test.go
+++ b/merge_test.go
@@ -6,12 +6,13 @@
 )
 
 func TestMergeWithSelf(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
 	seedTestRepo(t, repo)
 
-	master, err := repo.LookupReference("refs/heads/master")
+	master, err := repo.References.Lookup("refs/heads/master")
 	checkFatal(t, err)
 
 	mergeHead, err := repo.AnnotatedCommitFromRef(master)
@@ -24,12 +25,13 @@
 }
 
 func TestMergeAnalysisWithSelf(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
 	seedTestRepo(t, repo)
 
-	master, err := repo.LookupReference("refs/heads/master")
+	master, err := repo.References.Lookup("refs/heads/master")
 	checkFatal(t, err)
 
 	mergeHead, err := repo.AnnotatedCommitFromRef(master)
@@ -46,6 +48,7 @@
 }
 
 func TestMergeSameFile(t *testing.T) {
+	t.Parallel()
 	file := MergeFileInput{
 		Path:     "test",
 		Mode:     33188,
@@ -68,6 +71,7 @@
 
 }
 func TestMergeTreesWithoutAncestor(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
@@ -109,19 +113,23 @@
 	tree, err := repo.LookupTree(treeId)
 	checkFatal(t, err)
 
-	ref, err := repo.LookupReference("HEAD")
+	ref, err := repo.References.Lookup("HEAD")
 	checkFatal(t, err)
 
 	parent, err := ref.Peel(ObjectCommit)
 	checkFatal(t, err)
 
-	commitId, err := repo.CreateCommit("HEAD", sig, sig, message, tree, parent.(*Commit))
+	parentCommit, err := parent.AsCommit()
+	checkFatal(t, err)
+
+	commitId, err := repo.CreateCommit("HEAD", sig, sig, message, tree, parentCommit)
 	checkFatal(t, err)
 
 	return commitId, treeId
 }
 
 func TestMergeBase(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
diff --git a/note.go b/note.go
index 3cdd340..a1b15d8 100644
--- a/note.go
+++ b/note.go
@@ -10,6 +10,127 @@
 	"unsafe"
 )
 
+// This object represents the possible operations which can be
+// performed on the collection of notes for a repository.
+type NoteCollection struct {
+	repo *Repository
+}
+
+// Create adds a note for an object
+func (c *NoteCollection) Create(
+	ref string, author, committer *Signature, id *Oid,
+	note string, force bool) (*Oid, error) {
+
+	oid := new(Oid)
+
+	var cref *C.char
+	if ref == "" {
+		cref = nil
+	} else {
+		cref = C.CString(ref)
+		defer C.free(unsafe.Pointer(cref))
+	}
+
+	authorSig, err := author.toC()
+	if err != nil {
+		return nil, err
+	}
+	defer C.git_signature_free(authorSig)
+
+	committerSig, err := committer.toC()
+	if err != nil {
+		return nil, err
+	}
+	defer C.git_signature_free(committerSig)
+
+	cnote := C.CString(note)
+	defer C.free(unsafe.Pointer(cnote))
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ret := C.git_note_create(
+		oid.toC(), c.repo.ptr, cref, authorSig,
+		committerSig, id.toC(), cnote, cbool(force))
+
+	if ret < 0 {
+		return nil, MakeGitError(ret)
+	}
+	return oid, nil
+}
+
+// Read reads the note for an object
+func (c *NoteCollection) Read(ref string, id *Oid) (*Note, error) {
+	var cref *C.char
+	if ref == "" {
+		cref = nil
+	} else {
+		cref = C.CString(ref)
+		defer C.free(unsafe.Pointer(cref))
+	}
+
+	note := new(Note)
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	if ret := C.git_note_read(&note.ptr, c.repo.ptr, cref, id.toC()); ret < 0 {
+		return nil, MakeGitError(ret)
+	}
+
+	runtime.SetFinalizer(note, (*Note).Free)
+	return note, nil
+}
+
+// Remove removes the note for an object
+func (c *NoteCollection) Remove(ref string, author, committer *Signature, id *Oid) error {
+	var cref *C.char
+	if ref == "" {
+		cref = nil
+	} else {
+		cref = C.CString(ref)
+		defer C.free(unsafe.Pointer(cref))
+	}
+
+	authorSig, err := author.toC()
+	if err != nil {
+		return err
+	}
+	defer C.git_signature_free(authorSig)
+
+	committerSig, err := committer.toC()
+	if err != nil {
+		return err
+	}
+	defer C.git_signature_free(committerSig)
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ret := C.git_note_remove(c.repo.ptr, cref, authorSig, committerSig, id.toC())
+	if ret < 0 {
+		return MakeGitError(ret)
+	}
+	return nil
+}
+
+// DefaultRef returns the default notes reference for a repository
+func (c *NoteCollection) DefaultRef() (string, error) {
+	buf := C.git_buf{}
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	if ret := C.git_note_default_ref(&buf, c.repo.ptr); ret < 0 {
+		return "", MakeGitError(ret)
+	}
+
+	ret := C.GoString(buf.ptr)
+	C.git_buf_free(&buf)
+
+	return ret, nil
+}
+
 // Note
 type Note struct {
 	ptr *C.git_note
diff --git a/note_test.go b/note_test.go
index e6c378d..9f64eb8 100644
--- a/note_test.go
+++ b/note_test.go
@@ -8,6 +8,7 @@
 )
 
 func TestCreateNote(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
@@ -27,6 +28,7 @@
 }
 
 func TestNoteIterator(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
@@ -53,7 +55,7 @@
 			break
 		}
 
-		note, err := repo.ReadNote("", commitId)
+		note, err := repo.Notes.Read("", commitId)
 		checkFatal(t, err)
 
 		if !reflect.DeepEqual(note.Id(), noteId) {
@@ -63,6 +65,7 @@
 }
 
 func TestRemoveNote(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
@@ -73,23 +76,24 @@
 
 	note, _ := createTestNote(t, repo, commit)
 
-	_, err = repo.ReadNote("", commit.Id())
+	_, err = repo.Notes.Read("", commit.Id())
 	checkFatal(t, err)
 
-	err = repo.RemoveNote("", note.Author(), note.Committer(), commitId)
+	err = repo.Notes.Remove("", note.Author(), note.Committer(), commitId)
 	checkFatal(t, err)
 
-	_, err = repo.ReadNote("", commit.Id())
+	_, err = repo.Notes.Read("", commit.Id())
 	if err == nil {
 		t.Fatal("note remove failed")
 	}
 }
 
 func TestDefaultNoteRef(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
-	ref, err := repo.DefaultNoteRef()
+	ref, err := repo.Notes.DefaultRef()
 	checkFatal(t, err)
 
 	compareStrings(t, "refs/notes/commits", ref)
@@ -103,10 +107,10 @@
 		When:  time.Date(2015, 01, 05, 13, 0, 0, 0, loc),
 	}
 
-	noteId, err := repo.CreateNote("", sig, sig, commit.Id(), "I am a note\n", false)
+	noteId, err := repo.Notes.Create("", sig, sig, commit.Id(), "I am a note\n", false)
 	checkFatal(t, err)
 
-	note, err := repo.ReadNote("", commit.Id())
+	note, err := repo.Notes.Read("", commit.Id())
 	checkFatal(t, err)
 
 	return note, noteId
diff --git a/object.go b/object.go
index 20cee85..1981980 100644
--- a/object.go
+++ b/object.go
@@ -4,7 +4,11 @@
 #include <git2.h>
 */
 import "C"
-import "runtime"
+import (
+	"errors"
+	"fmt"
+	"runtime"
+)
 
 type ObjectType int
 
@@ -17,14 +21,7 @@
 	ObjectTag    ObjectType = C.GIT_OBJ_TAG
 )
 
-type Object interface {
-	Free()
-	Id() *Oid
-	Type() ObjectType
-	Owner() *Repository
-}
-
-type gitObject struct {
+type Object struct {
 	ptr  *C.git_object
 	repo *Repository
 }
@@ -48,65 +45,163 @@
 	return ""
 }
 
-func (o gitObject) Id() *Oid {
+func (o *Object) Id() *Oid {
 	return newOidFromC(C.git_object_id(o.ptr))
 }
 
-func (o gitObject) Type() ObjectType {
+func (o *Object) Type() ObjectType {
 	return ObjectType(C.git_object_type(o.ptr))
 }
 
 // Owner returns a weak reference to the repository which owns this
-// object
-func (o gitObject) Owner() *Repository {
+// object. This won't keep the underlying repository alive.
+func (o *Object) Owner() *Repository {
 	return &Repository{
 		ptr: C.git_object_owner(o.ptr),
 	}
 }
 
-func (o *gitObject) Free() {
+func dupObject(obj *Object, kind ObjectType) (*C.git_object, error) {
+	if obj.Type() != kind {
+		return nil, errors.New(fmt.Sprintf("object is not a %v", kind))
+	}
+
+	var cobj *C.git_object
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	if err := C.git_object_dup(&cobj, obj.ptr); err < 0 {
+		return nil, MakeGitError(err)
+	}
+
+	return cobj, nil
+}
+
+func allocTree(ptr *C.git_tree, repo *Repository) *Tree {
+	tree := &Tree{
+		Object: Object{
+			ptr:  (*C.git_object)(ptr),
+			repo: repo,
+		},
+		cast_ptr: ptr,
+	}
+	runtime.SetFinalizer(tree, (*Tree).Free)
+
+	return tree
+}
+
+func (o *Object) AsTree() (*Tree, error) {
+	cobj, err := dupObject(o, ObjectTree)
+	if err != nil {
+		return nil, err
+	}
+
+	return allocTree((*C.git_tree)(cobj), o.repo), nil
+}
+
+func allocCommit(ptr *C.git_commit, repo *Repository) *Commit {
+	commit := &Commit{
+		Object: Object{
+			ptr:  (*C.git_object)(ptr),
+			repo: repo,
+		},
+		cast_ptr: ptr,
+	}
+	runtime.SetFinalizer(commit, (*Commit).Free)
+
+	return commit
+}
+
+func (o *Object) AsCommit() (*Commit, error) {
+	cobj, err := dupObject(o, ObjectCommit)
+	if err != nil {
+		return nil, err
+	}
+
+	return allocCommit((*C.git_commit)(cobj), o.repo), nil
+}
+
+func allocBlob(ptr *C.git_blob, repo *Repository) *Blob {
+	blob := &Blob{
+		Object: Object{
+			ptr:  (*C.git_object)(ptr),
+			repo: repo,
+		},
+		cast_ptr: ptr,
+	}
+	runtime.SetFinalizer(blob, (*Blob).Free)
+
+	return blob
+}
+
+func (o *Object) AsBlob() (*Blob, error) {
+	cobj, err := dupObject(o, ObjectBlob)
+	if err != nil {
+		return nil, err
+	}
+
+	return allocBlob((*C.git_blob)(cobj), o.repo), nil
+}
+
+func allocTag(ptr *C.git_tag, repo *Repository) *Tag {
+	tag := &Tag{
+		Object: Object{
+			ptr:  (*C.git_object)(ptr),
+			repo: repo,
+		},
+		cast_ptr: ptr,
+	}
+	runtime.SetFinalizer(tag, (*Tag).Free)
+
+	return tag
+}
+
+func (o *Object) AsTag() (*Tag, error) {
+	cobj, err := dupObject(o, ObjectTag)
+	if err != nil {
+		return nil, err
+	}
+
+	return allocTag((*C.git_tag)(cobj), o.repo), nil
+}
+
+func (o *Object) Free() {
 	runtime.SetFinalizer(o, nil)
 	C.git_object_free(o.ptr)
 }
 
-func allocObject(cobj *C.git_object, repo *Repository) Object {
-	obj := gitObject{
+// Peel recursively peels an object until an object of the specified type is met.
+//
+// If the query cannot be satisfied due to the object model, ErrInvalidSpec
+// will be returned (e.g. trying to peel a blob to a tree).
+//
+// If you pass ObjectAny as the target type, then the object will be peeled
+// until the type changes. A tag will be peeled until the referenced object
+// is no longer a tag, and a commit will be peeled to a tree. Any other object
+// type will return ErrInvalidSpec.
+//
+// If peeling a tag we discover an object which cannot be peeled to the target
+// type due to the object model, an error will be returned.
+func (o *Object) Peel(t ObjectType) (*Object, error) {
+	var cobj *C.git_object
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	if err := C.git_object_peel(&cobj, o.ptr, C.git_otype(t)); err < 0 {
+		return nil, MakeGitError(err)
+	}
+
+	return allocObject(cobj, o.repo), nil
+}
+
+func allocObject(cobj *C.git_object, repo *Repository) *Object {
+	obj := &Object{
 		ptr:  cobj,
 		repo: repo,
 	}
+	runtime.SetFinalizer(obj, (*Object).Free)
 
-	switch ObjectType(C.git_object_type(cobj)) {
-	case ObjectCommit:
-		commit := &Commit{
-			gitObject: obj,
-			cast_ptr:  (*C.git_commit)(cobj),
-		}
-		runtime.SetFinalizer(commit, (*Commit).Free)
-		return commit
-
-	case ObjectTree:
-		tree := &Tree{
-			gitObject: obj,
-			cast_ptr:  (*C.git_tree)(cobj),
-		}
-		runtime.SetFinalizer(tree, (*Tree).Free)
-		return tree
-
-	case ObjectBlob:
-		blob := &Blob{
-			gitObject: obj,
-			cast_ptr:  (*C.git_blob)(cobj),
-		}
-		runtime.SetFinalizer(blob, (*Blob).Free)
-		return blob
-	case ObjectTag:
-		tag := &Tag{
-			gitObject: obj,
-			cast_ptr:  (*C.git_tag)(cobj),
-		}
-		runtime.SetFinalizer(tag, (*Tag).Free)
-		return tag
-	}
-
-	return nil
+	return obj
 }
diff --git a/object_test.go b/object_test.go
index aa295e5..cb262de 100644
--- a/object_test.go
+++ b/object_test.go
@@ -5,17 +5,18 @@
 )
 
 func TestObjectPoymorphism(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
 	commitId, treeId := seedTestRepo(t, repo)
 
-	var obj Object
+	var obj *Object
 
 	commit, err := repo.LookupCommit(commitId)
 	checkFatal(t, err)
 
-	obj = commit
+	obj = &commit.Object
 	if obj.Type() != ObjectCommit {
 		t.Fatalf("Wrong object type, expected commit, have %v", obj.Type())
 	}
@@ -27,13 +28,13 @@
 	tree, err := repo.LookupTree(treeId)
 	checkFatal(t, err)
 
-	obj = tree
+	obj = &tree.Object
 	if obj.Type() != ObjectTree {
 		t.Fatalf("Wrong object type, expected tree, have %v", obj.Type())
 	}
 
-	tree2, ok := obj.(*Tree)
-	if !ok {
+	tree2, err := obj.AsTree()
+	if err != nil {
 		t.Fatalf("Converting back to *Tree is not ok")
 	}
 
@@ -46,16 +47,16 @@
 		t.Fatal("Wrong filemode for \"README\"")
 	}
 
-	_, ok = obj.(*Commit)
-	if ok {
+	_, err = obj.AsCommit()
+	if err == nil {
 		t.Fatalf("*Tree is somehow the same as *Commit")
 	}
 
 	obj, err = repo.Lookup(tree.Id())
 	checkFatal(t, err)
 
-	_, ok = obj.(*Tree)
-	if !ok {
+	_, err = obj.AsTree()
+	if err != nil {
 		t.Fatalf("Lookup creates the wrong type")
 	}
 
@@ -88,6 +89,7 @@
 }
 
 func TestObjectOwner(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
@@ -99,6 +101,67 @@
 	tree, err := repo.LookupTree(treeId)
 	checkFatal(t, err)
 
-	checkOwner(t, repo, commit)
-	checkOwner(t, repo, tree)
+	checkOwner(t, repo, commit.Object)
+	checkOwner(t, repo, tree.Object)
+}
+
+func TestObjectPeel(t *testing.T) {
+	t.Parallel()
+	repo := createTestRepo(t)
+	defer cleanupTestRepo(t, repo)
+
+	commitID, treeID := seedTestRepo(t, repo)
+
+	var obj *Object
+
+	commit, err := repo.LookupCommit(commitID)
+	checkFatal(t, err)
+
+	obj, err = commit.Peel(ObjectAny)
+	checkFatal(t, err)
+
+	if obj.Type() != ObjectTree {
+		t.Fatalf("Wrong object type when peeling a commit, expected tree, have %v", obj.Type())
+	}
+
+	obj, err = commit.Peel(ObjectTag)
+
+	if !IsErrorCode(err, ErrInvalidSpec) {
+		t.Fatalf("Wrong error when peeling a commit to a tag, expected ErrInvalidSpec, have %v", err)
+	}
+
+	tree, err := repo.LookupTree(treeID)
+	checkFatal(t, err)
+
+	obj, err = tree.Peel(ObjectAny)
+
+	if !IsErrorCode(err, ErrInvalidSpec) {
+		t.Fatalf("Wrong error when peeling a tree, expected ErrInvalidSpec, have %v", err)
+	}
+
+	entry := tree.EntryByName("README")
+
+	blob, err := repo.LookupBlob(entry.Id)
+	checkFatal(t, err)
+
+	obj, err = blob.Peel(ObjectAny)
+
+	if !IsErrorCode(err, ErrInvalidSpec) {
+		t.Fatalf("Wrong error when peeling a blob, expected ErrInvalidSpec, have %v", err)
+	}
+
+	tagID := createTestTag(t, repo, commit)
+
+	tag, err := repo.LookupTag(tagID)
+	checkFatal(t, err)
+
+	obj, err = tag.Peel(ObjectAny)
+	checkFatal(t, err)
+
+	if obj.Type() != ObjectCommit {
+		t.Fatalf("Wrong object type when peeling a tag, expected commit, have %v", obj.Type())
+	}
+
+	// TODO: Should test a tag that annotates a different object than a commit
+	// but it's impossible at the moment to tag such an object.
 }
diff --git a/odb.go b/odb.go
index 6b21329..a728cd3 100644
--- a/odb.go
+++ b/odb.go
@@ -36,8 +36,8 @@
 	return odb, nil
 }
 
-func NewOdbBackendFromC(ptr *C.git_odb_backend) (backend *OdbBackend) {
-	backend = &OdbBackend{ptr}
+func NewOdbBackendFromC(ptr unsafe.Pointer) (backend *OdbBackend) {
+	backend = &OdbBackend{(*C.git_odb_backend)(ptr)}
 	return backend
 }
 
@@ -54,6 +54,21 @@
 	return nil
 }
 
+func (v *Odb) ReadHeader(oid *Oid) (uint64, ObjectType, error) {
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+	
+	var sz C.size_t
+	var cotype C.git_otype 
+
+	ret := C.git_odb_read_header(&sz, &cotype, v.ptr, oid.toC())
+	if ret < 0 {
+		return 0, C.GIT_OBJ_BAD, MakeGitError(ret)
+	}
+
+	return uint64(sz), ObjectType(cotype), nil
+}
+	
 func (v *Odb) Exists(oid *Oid) bool {
 	ret := C.git_odb_exists(v.ptr, oid.toC())
 	return ret != 0
@@ -61,12 +76,15 @@
 
 func (v *Odb) Write(data []byte, otype ObjectType) (oid *Oid, err error) {
 	oid = new(Oid)
-	hdr := (*reflect.SliceHeader)(unsafe.Pointer(&data))
+	var cptr unsafe.Pointer
+	if len(data) > 0 {
+		cptr = unsafe.Pointer(&data[0])
+	}
 
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	ret := C.git_odb_write(oid.toC(), v.ptr, unsafe.Pointer(hdr.Data), C.size_t(hdr.Len), C.git_otype(otype))
+	ret := C.git_odb_write(oid.toC(), v.ptr, cptr, C.size_t(len(data)), C.git_otype(otype))
 
 	if ret < 0 {
 		return nil, MakeGitError(ret)
@@ -172,13 +190,13 @@
 // NewWriteStream opens a write stream to the ODB, which allows you to
 // create a new object in the database. The size and type must be
 // known in advance
-func (v *Odb) NewWriteStream(size int, otype ObjectType) (*OdbWriteStream, error) {
+func (v *Odb) NewWriteStream(size int64, otype ObjectType) (*OdbWriteStream, error) {
 	stream := new(OdbWriteStream)
 
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	ret := C.git_odb_open_wstream(&stream.ptr, v.ptr, C.size_t(size), C.git_otype(otype))
+	ret := C.git_odb_open_wstream(&stream.ptr, v.ptr, C.git_off_t(size), C.git_otype(otype))
 	if ret < 0 {
 		return nil, MakeGitError(ret)
 	}
@@ -208,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/odb_test.go b/odb_test.go
index 2fb6840..3d22fc9 100644
--- a/odb_test.go
+++ b/odb_test.go
@@ -6,7 +6,37 @@
 	"testing"
 )
 
+func TestOdbReadHeader(t *testing.T) {
+	t.Parallel()
+	repo := createTestRepo(t)
+	defer cleanupTestRepo(t, repo)
+
+	_, _ = seedTestRepo(t, repo)
+	odb, err := repo.Odb()
+	if err != nil {
+		t.Fatalf("Odb: %v", err)
+	}
+	data := []byte("hello")
+	id, err := odb.Write(data, ObjectBlob)
+	if err != nil {
+		t.Fatalf("odb.Write: %v", err)
+	}
+
+	sz, typ, err := odb.ReadHeader(id)
+	if err != nil {
+		t.Fatalf("ReadHeader: %v", err)
+	}
+	
+	if sz != uint64(len(data)) {
+		t.Errorf("ReadHeader got size %d, want %d", sz, len(data))
+	}
+	if typ != ObjectBlob {
+		t.Errorf("ReadHeader got object type %s", typ)
+	}
+}
+
 func TestOdbStream(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
@@ -17,7 +47,7 @@
 
 	str := "hello, world!"
 
-	stream, error := odb.NewWriteStream(len(str), ObjectBlob)
+	stream, error := odb.NewWriteStream(int64(len(str)), ObjectBlob)
 	checkFatal(t, error)
 	n, error := io.WriteString(stream, str)
 	checkFatal(t, error)
@@ -36,7 +66,7 @@
 }
 
 func TestOdbHash(t *testing.T) {
-
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
@@ -64,6 +94,7 @@
 }
 
 func TestOdbForeach(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
diff --git a/patch_test.go b/patch_test.go
index 2d52fb4..291c705 100644
--- a/patch_test.go
+++ b/patch_test.go
@@ -6,6 +6,7 @@
 )
 
 func TestPatch(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
diff --git a/push_test.go b/push_test.go
index e36e407..f372882 100644
--- a/push_test.go
+++ b/push_test.go
@@ -5,23 +5,24 @@
 )
 
 func TestRemotePush(t *testing.T) {
+	t.Parallel()
 	repo := createBareTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
 	localRepo := createTestRepo(t)
 	defer cleanupTestRepo(t, localRepo)
 
-	remote, err := localRepo.CreateRemote("test_push", repo.Path())
+	remote, err := localRepo.Remotes.Create("test_push", repo.Path())
 	checkFatal(t, err)
 
 	seedTestRepo(t, localRepo)
 
-	err = remote.Push([]string{"refs/heads/master"}, nil, nil, "")
+	err = remote.Push([]string{"refs/heads/master"}, nil)
 	checkFatal(t, err)
 
-	_, err = localRepo.LookupReference("refs/remotes/test_push/master")
+	_, err = localRepo.References.Lookup("refs/remotes/test_push/master")
 	checkFatal(t, err)
 
-	_, err = repo.LookupReference("refs/heads/master")
+	_, err = repo.References.Lookup("refs/heads/master")
 	checkFatal(t, err)
 }
diff --git a/rebase.go b/rebase.go
new file mode 100644
index 0000000..8553e25
--- /dev/null
+++ b/rebase.go
@@ -0,0 +1,252 @@
+package git
+
+/*
+#include <git2.h>
+*/
+import "C"
+import (
+	"runtime"
+	"unsafe"
+)
+
+// RebaseOperationType is the type of rebase operation
+type RebaseOperationType uint
+
+const (
+	// RebaseOperationPick The given commit is to be cherry-picked.  The client should commit the changes and continue if there are no conflicts.
+	RebaseOperationPick RebaseOperationType = C.GIT_REBASE_OPERATION_PICK
+	// RebaseOperationEdit The given commit is to be cherry-picked, but the client should stop to allow the user to edit the changes before committing them.
+	RebaseOperationEdit RebaseOperationType = C.GIT_REBASE_OPERATION_EDIT
+	// RebaseOperationSquash The given commit is to be squashed into the previous commit.  The commit message will be merged with the previous message.
+	RebaseOperationSquash RebaseOperationType = C.GIT_REBASE_OPERATION_SQUASH
+	// RebaseOperationFixup No commit will be cherry-picked.  The client should run the given command and (if successful) continue.
+	RebaseOperationFixup RebaseOperationType = C.GIT_REBASE_OPERATION_FIXUP
+	// RebaseOperationExec No commit will be cherry-picked.  The client should run the given command and (if successful) continue.
+	RebaseOperationExec RebaseOperationType = C.GIT_REBASE_OPERATION_EXEC
+)
+
+// RebaseOperation describes a single instruction/operation to be performed during the rebase.
+type RebaseOperation struct {
+	Type RebaseOperationType
+	Id   *Oid
+	Exec string
+}
+
+func newRebaseOperationFromC(c *C.git_rebase_operation) *RebaseOperation {
+	operation := &RebaseOperation{}
+	operation.Type = RebaseOperationType(c._type)
+	operation.Id = newOidFromC(&c.id)
+	operation.Exec = C.GoString(c.exec)
+
+	return operation
+}
+
+// RebaseOptions are used to tell the rebase machinery how to operate
+type RebaseOptions struct {
+	Version         uint
+	Quiet           int
+	InMemory        int
+	RewriteNotesRef string
+	MergeOptions    MergeOptions
+	CheckoutOptions CheckoutOpts
+}
+
+// DefaultRebaseOptions returns a RebaseOptions with default values.
+func DefaultRebaseOptions() (RebaseOptions, error) {
+	opts := C.git_rebase_options{}
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ecode := C.git_rebase_init_options(&opts, C.GIT_REBASE_OPTIONS_VERSION)
+	if ecode < 0 {
+		return RebaseOptions{}, MakeGitError(ecode)
+	}
+	return rebaseOptionsFromC(&opts), nil
+}
+
+func rebaseOptionsFromC(opts *C.git_rebase_options) RebaseOptions {
+	return RebaseOptions{
+		Version:         uint(opts.version),
+		Quiet:           int(opts.quiet),
+		InMemory:        int(opts.inmemory),
+		RewriteNotesRef: C.GoString(opts.rewrite_notes_ref),
+		MergeOptions:    mergeOptionsFromC(&opts.merge_options),
+		CheckoutOptions: checkoutOptionsFromC(&opts.checkout_options),
+	}
+}
+
+func (ro *RebaseOptions) toC() *C.git_rebase_options {
+	if ro == nil {
+		return nil
+	}
+	return &C.git_rebase_options{
+		version:           C.uint(ro.Version),
+		quiet:             C.int(ro.Quiet),
+		inmemory:          C.int(ro.InMemory),
+		rewrite_notes_ref: mapEmptyStringToNull(ro.RewriteNotesRef),
+		merge_options:     *ro.MergeOptions.toC(),
+		checkout_options:  *ro.CheckoutOptions.toC(),
+	}
+}
+
+func mapEmptyStringToNull(ref string) *C.char {
+	if ref == "" {
+		return nil
+	}
+	return C.CString(ref)
+}
+
+// Rebase is the struct representing a Rebase object.
+type Rebase struct {
+	ptr *C.git_rebase
+}
+
+// InitRebase initializes a rebase operation to rebase the changes in branch relative to upstream onto another branch.
+func (r *Repository) InitRebase(branch *AnnotatedCommit, upstream *AnnotatedCommit, onto *AnnotatedCommit, opts *RebaseOptions) (*Rebase, error) {
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	if branch == nil {
+		branch = &AnnotatedCommit{ptr: nil}
+	}
+
+	if upstream == nil {
+		upstream = &AnnotatedCommit{ptr: nil}
+	}
+
+	if onto == nil {
+		onto = &AnnotatedCommit{ptr: nil}
+	}
+
+	var ptr *C.git_rebase
+	err := C.git_rebase_init(&ptr, r.ptr, branch.ptr, upstream.ptr, onto.ptr, opts.toC())
+	if err < 0 {
+		return nil, MakeGitError(err)
+	}
+
+	return newRebaseFromC(ptr), nil
+}
+
+// OpenRebase opens an existing rebase that was previously started by either an invocation of InitRebase or by another client.
+func (r *Repository) OpenRebase(opts *RebaseOptions) (*Rebase, error) {
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	var ptr *C.git_rebase
+	err := C.git_rebase_open(&ptr, r.ptr, opts.toC())
+	if err < 0 {
+		return nil, MakeGitError(err)
+	}
+
+	return newRebaseFromC(ptr), nil
+}
+
+// OperationAt gets the rebase operation specified by the given index.
+func (rebase *Rebase) OperationAt(index uint) *RebaseOperation {
+	operation := C.git_rebase_operation_byindex(rebase.ptr, C.size_t(index))
+	
+	return newRebaseOperationFromC(operation)
+}
+
+// CurrentOperationIndex gets the index of the rebase operation that is currently being applied.
+// Returns an error if no rebase operation is currently applied.
+func (rebase *Rebase) CurrentOperationIndex() (uint, error) {
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	operationIndex := int(C.git_rebase_operation_current(rebase.ptr))
+	if operationIndex == C.GIT_REBASE_NO_OPERATION {
+		return 0, MakeGitError(C.GIT_REBASE_NO_OPERATION)
+	}
+
+	return uint(operationIndex), nil
+}
+
+// OperationCount gets the count of rebase operations that are to be applied.
+func (rebase *Rebase) OperationCount() uint {
+	return uint(C.git_rebase_operation_entrycount(rebase.ptr))
+}
+
+// Next performs the next rebase operation and returns the information about it.
+// If the operation is one that applies a patch (which is any operation except RebaseOperationExec)
+// then the patch will be applied and the index and working directory will be updated with the changes.
+// If there are conflicts, you will need to address those before committing the changes.
+func (rebase *Rebase) Next() (*RebaseOperation, error) {
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	var ptr *C.git_rebase_operation
+	err := C.git_rebase_next(&ptr, rebase.ptr)
+	if err < 0 {
+		return nil, MakeGitError(err)
+	}
+
+	return newRebaseOperationFromC(ptr), nil
+}
+
+// Commit commits the current patch.
+// You must have resolved any conflicts that were introduced during the patch application from the Next() invocation.
+func (rebase *Rebase) Commit(ID *Oid, author, committer *Signature, message string) error {
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	authorSig, err := author.toC()
+	if err != nil {
+		return err
+	}
+	defer C.git_signature_free(authorSig)
+
+	committerSig, err := committer.toC()
+	if err != nil {
+		return err
+	}
+	defer C.git_signature_free(committerSig)
+
+	cmsg := C.CString(message)
+	defer C.free(unsafe.Pointer(cmsg))
+
+	cerr := C.git_rebase_commit(ID.toC(), rebase.ptr, authorSig, committerSig, nil, cmsg)
+	if cerr < 0 {
+		return MakeGitError(cerr)
+	}
+
+	return nil
+}
+
+// Finish finishes a rebase that is currently in progress once all patches have been applied.
+func (rebase *Rebase) Finish() error {
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	err := C.git_rebase_finish(rebase.ptr, nil)
+	if err < 0 {
+		return MakeGitError(err)
+	}
+
+	return nil
+}
+
+// Abort aborts a rebase that is currently in progress, resetting the repository and working directory to their state before rebase began.
+func (rebase *Rebase) Abort() error {
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	err := C.git_rebase_abort(rebase.ptr)
+	if err < 0 {
+		return MakeGitError(err)
+	}
+	return nil
+}
+
+// Free frees the Rebase object.
+func (rebase *Rebase) Free() {
+	runtime.SetFinalizer(rebase, nil)
+	C.git_rebase_free(rebase.ptr)
+}
+
+func newRebaseFromC(ptr *C.git_rebase) *Rebase {
+	rebase := &Rebase{ptr: ptr}
+	runtime.SetFinalizer(rebase, (*Rebase).Free)
+	return rebase
+}
diff --git a/rebase_test.go b/rebase_test.go
new file mode 100644
index 0000000..fb88a4e
--- /dev/null
+++ b/rebase_test.go
@@ -0,0 +1,381 @@
+package git
+
+import (
+	"errors"
+	"strconv"
+	"testing"
+	"time"
+)
+
+// Tests
+
+func TestRebaseAbort(t *testing.T) {
+	// TEST DATA
+
+	// Inputs
+	branchName := "emile"
+	masterCommit := "something"
+	emileCommits := []string{
+		"fou",
+		"barre",
+	}
+
+	// Outputs
+	expectedHistory := []string{
+		"Test rebase, Baby! " + emileCommits[1],
+		"Test rebase, Baby! " + emileCommits[0],
+		"This is a commit\n",
+	}
+
+	// TEST
+	repo := createTestRepo(t)
+	defer cleanupTestRepo(t, repo)
+	seedTestRepo(t, repo)
+
+	// Setup a repo with 2 branches and a different tree
+	err := setupRepoForRebase(repo, masterCommit, branchName)
+	checkFatal(t, err)
+
+	// Create several commits in emile
+	for _, commit := range emileCommits {
+		_, err = commitSomething(repo, commit, commit)
+		checkFatal(t, err)
+	}
+
+	// Check history
+	actualHistory, err := commitMsgsList(repo)
+	checkFatal(t, err)
+	assertStringList(t, expectedHistory, actualHistory)
+
+	// Rebase onto master
+	rebase, err := performRebaseOnto(repo, "master")
+	checkFatal(t, err)
+	defer rebase.Free()
+
+	// Abort rebase
+	rebase.Abort()
+
+	// Check history is still the same
+	actualHistory, err = commitMsgsList(repo)
+	checkFatal(t, err)
+	assertStringList(t, expectedHistory, actualHistory)
+}
+
+func TestRebaseNoConflicts(t *testing.T) {
+	// TEST DATA
+
+	// Inputs
+	branchName := "emile"
+	masterCommit := "something"
+	emileCommits := []string{
+		"fou",
+		"barre",
+		"ouich",
+	}
+
+	// Outputs
+	expectedHistory := []string{
+		"Test rebase, Baby! " + emileCommits[2],
+		"Test rebase, Baby! " + emileCommits[1],
+		"Test rebase, Baby! " + emileCommits[0],
+		"Test rebase, Baby! " + masterCommit,
+		"This is a commit\n",
+	}
+
+	// TEST
+	repo := createTestRepo(t)
+	defer cleanupTestRepo(t, repo)
+	seedTestRepo(t, repo)
+
+	// Try to open existing rebase
+	oRebase, err := repo.OpenRebase(nil)
+	if err == nil {
+		t.Fatal("Did not expect to find a rebase in progress")
+	}
+
+	// Setup a repo with 2 branches and a different tree
+	err = setupRepoForRebase(repo, masterCommit, branchName)
+	checkFatal(t, err)
+
+	// Create several commits in emile
+	for _, commit := range emileCommits {
+		_, err = commitSomething(repo, commit, commit)
+		checkFatal(t, err)
+	}
+
+	// Rebase onto master
+	rebase, err := performRebaseOnto(repo, "master")
+	checkFatal(t, err)
+	defer rebase.Free()
+
+	// Open existing rebase
+	oRebase, err = repo.OpenRebase(nil)
+	checkFatal(t, err)
+	defer oRebase.Free()
+	if oRebase == nil {
+		t.Fatal("Expected to find an existing rebase in progress")
+	}
+
+	// Finish the rebase properly
+	err = rebase.Finish()
+	checkFatal(t, err)
+
+	// Check no more rebase is in progress
+	oRebase, err = repo.OpenRebase(nil)
+	if err == nil {
+		t.Fatal("Did not expect to find a rebase in progress")
+	}
+
+	// Check history is in correct order
+	actualHistory, err := commitMsgsList(repo)
+	checkFatal(t, err)
+	assertStringList(t, expectedHistory, actualHistory)
+
+}
+
+// Utils
+func setupRepoForRebase(repo *Repository, masterCommit, branchName string) error {
+	// Create a new branch from master
+	err := createBranch(repo, branchName)
+	if err != nil {
+		return err
+	}
+
+	// Create a commit in master
+	_, err = commitSomething(repo, masterCommit, masterCommit)
+	if err != nil {
+		return err
+	}
+
+	// Switch to emile
+	err = repo.SetHead("refs/heads/" + branchName)
+	if err != nil {
+		return err
+	}
+
+	// Check master commit is not in emile branch
+	if entryExists(repo, masterCommit) {
+		return errors.New(masterCommit + " entry should not exist in " + branchName + " branch.")
+	}
+
+	return nil
+}
+
+func performRebaseOnto(repo *Repository, branch string) (*Rebase, error) {
+	master, err := repo.LookupBranch(branch, BranchLocal)
+	if err != nil {
+		return nil, err
+	}
+	defer master.Free()
+
+	onto, err := repo.AnnotatedCommitFromRef(master.Reference)
+	if err != nil {
+		return nil, err
+	}
+	defer onto.Free()
+
+	// Init rebase
+	rebase, err := repo.InitRebase(nil, nil, onto, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	// Check no operation has been started yet
+	rebaseOperationIndex, err := rebase.CurrentOperationIndex()
+	if err == nil {
+		return nil, errors.New("No operation should have been started yet")
+	}
+
+	// Iterate in rebase operations regarding operation count
+	opCount := int(rebase.OperationCount())
+	for op := 0; op < opCount; op++ {
+		operation, err := rebase.Next()
+		if err != nil {
+			return nil, err
+		}
+
+		// Check operation index is correct
+		rebaseOperationIndex, err = rebase.CurrentOperationIndex()
+		if int(rebaseOperationIndex) != op {
+			return nil, errors.New("Bad operation index")
+		}
+		if !operationsAreEqual(rebase.OperationAt(uint(op)), operation) {
+			return nil, errors.New("Rebase operations should be equal")
+		}
+
+		// Get current rebase operation created commit
+		commit, err := repo.LookupCommit(operation.Id)
+		if err != nil {
+			return nil, err
+		}
+		defer commit.Free()
+
+		// Apply commit
+		err = rebase.Commit(operation.Id, signature(), signature(), commit.Message())
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return rebase, nil
+}
+
+func operationsAreEqual(l, r *RebaseOperation) bool {
+	return l.Exec == r.Exec && l.Type == r.Type && l.Id.String() == r.Id.String()
+}
+
+func createBranch(repo *Repository, branch string) error {
+	commit, err := headCommit(repo)
+	if err != nil {
+		return err
+	}
+	defer commit.Free()
+	_, err = repo.CreateBranch(branch, commit, false)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func signature() *Signature {
+	return &Signature{
+		Name:  "Emile",
+		Email: "emile@emile.com",
+		When:  time.Now(),
+	}
+}
+
+func headCommit(repo *Repository) (*Commit, error) {
+	head, err := repo.Head()
+	if err != nil {
+		return nil, err
+	}
+	defer head.Free()
+
+	commit, err := repo.LookupCommit(head.Target())
+	if err != nil {
+		return nil, err
+	}
+
+	return commit, nil
+}
+
+func headTree(repo *Repository) (*Tree, error) {
+	headCommit, err := headCommit(repo)
+	if err != nil {
+		return nil, err
+	}
+	defer headCommit.Free()
+
+	tree, err := headCommit.Tree()
+	if err != nil {
+		return nil, err
+	}
+
+	return tree, nil
+}
+
+func commitSomething(repo *Repository, something, content string) (*Oid, error) {
+	headCommit, err := headCommit(repo)
+	if err != nil {
+		return nil, err
+	}
+	defer headCommit.Free()
+
+	index, err := NewIndex()
+	if err != nil {
+		return nil, err
+	}
+	defer index.Free()
+
+	blobOID, err := repo.CreateBlobFromBuffer([]byte(content))
+	if err != nil {
+		return nil, err
+	}
+
+	entry := &IndexEntry{
+		Mode: FilemodeBlob,
+		Id:   blobOID,
+		Path: something,
+	}
+
+	if err := index.Add(entry); err != nil {
+		return nil, err
+	}
+
+	newTreeOID, err := index.WriteTreeTo(repo)
+	if err != nil {
+		return nil, err
+	}
+
+	newTree, err := repo.LookupTree(newTreeOID)
+	if err != nil {
+		return nil, err
+	}
+	defer newTree.Free()
+
+	if err != nil {
+		return nil, err
+	}
+	commit, err := repo.CreateCommit("HEAD", signature(), signature(), "Test rebase, Baby! "+something, newTree, headCommit)
+	if err != nil {
+		return nil, err
+	}
+
+	opts := &CheckoutOpts{
+		Strategy: CheckoutRemoveUntracked | CheckoutForce,
+	}
+	err = repo.CheckoutIndex(index, opts)
+	if err != nil {
+		return nil, err
+	}
+
+	return commit, nil
+}
+
+func entryExists(repo *Repository, file string) bool {
+	headTree, err := headTree(repo)
+	if err != nil {
+		return false
+	}
+	defer headTree.Free()
+
+	_, err = headTree.EntryByPath(file)
+
+	return err == nil
+}
+
+func commitMsgsList(repo *Repository) ([]string, error) {
+	head, err := headCommit(repo)
+	if err != nil {
+		return nil, err
+	}
+	defer head.Free()
+
+	var commits []string
+
+	parent := head.Parent(0)
+	defer parent.Free()
+	commits = append(commits, head.Message(), parent.Message())
+
+	for parent.ParentCount() != 0 {
+		parent = parent.Parent(0)
+		defer parent.Free()
+		commits = append(commits, parent.Message())
+	}
+
+	return commits, nil
+}
+
+func assertStringList(t *testing.T, expected, actual []string) {
+	if len(expected) != len(actual) {
+		t.Fatal("Lists are not the same size, expected " + strconv.Itoa(len(expected)) +
+			", got " + strconv.Itoa(len(actual)))
+	}
+	for index, element := range expected {
+		if element != actual[index] {
+			t.Error("Expected element " + strconv.Itoa(index) + " to be " + element + ", got " + actual[index])
+		}
+	}
+}
diff --git a/refdb.go b/refdb.go
index 0d1e241..4e3ebf9 100644
--- a/refdb.go
+++ b/refdb.go
@@ -9,6 +9,7 @@
 import "C"
 import (
 	"runtime"
+	"unsafe"
 )
 
 type Refdb struct {
@@ -34,8 +35,8 @@
 	return refdb, nil
 }
 
-func NewRefdbBackendFromC(ptr *C.git_refdb_backend) (backend *RefdbBackend) {
-	backend = &RefdbBackend{ptr}
+func NewRefdbBackendFromC(ptr unsafe.Pointer) (backend *RefdbBackend) {
+	backend = &RefdbBackend{(*C.git_refdb_backend)(ptr)}
 	return backend
 }
 
diff --git a/reference.go b/reference.go
index ac3580c..463f2fc 100644
--- a/reference.go
+++ b/reference.go
@@ -21,13 +21,137 @@
 	repo *Repository
 }
 
+type ReferenceCollection struct {
+	repo *Repository
+}
+
+func (c *ReferenceCollection) Lookup(name string) (*Reference, error) {
+	cname := C.CString(name)
+	defer C.free(unsafe.Pointer(cname))
+	var ptr *C.git_reference
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ecode := C.git_reference_lookup(&ptr, c.repo.ptr, cname)
+	if ecode < 0 {
+		return nil, MakeGitError(ecode)
+	}
+
+	return newReferenceFromC(ptr, c.repo), nil
+}
+
+func (c *ReferenceCollection) Create(name string, id *Oid, force bool, msg string) (*Reference, error) {
+	cname := C.CString(name)
+	defer C.free(unsafe.Pointer(cname))
+
+	var cmsg *C.char
+	if msg == "" {
+		cmsg = nil
+	} else {
+		cmsg = C.CString(msg)
+		defer C.free(unsafe.Pointer(cmsg))
+	}
+
+	var ptr *C.git_reference
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ecode := C.git_reference_create(&ptr, c.repo.ptr, cname, id.toC(), cbool(force), cmsg)
+	if ecode < 0 {
+		return nil, MakeGitError(ecode)
+	}
+
+	return newReferenceFromC(ptr, c.repo), nil
+}
+
+func (c *ReferenceCollection) CreateSymbolic(name, target string, force bool, msg string) (*Reference, error) {
+	cname := C.CString(name)
+	defer C.free(unsafe.Pointer(cname))
+
+	ctarget := C.CString(target)
+	defer C.free(unsafe.Pointer(ctarget))
+
+	var cmsg *C.char
+	if msg == "" {
+		cmsg = nil
+	} else {
+		cmsg = C.CString(msg)
+		defer C.free(unsafe.Pointer(cmsg))
+	}
+
+	var ptr *C.git_reference
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ecode := C.git_reference_symbolic_create(&ptr, c.repo.ptr, cname, ctarget, cbool(force), cmsg)
+	if ecode < 0 {
+		return nil, MakeGitError(ecode)
+	}
+
+	return newReferenceFromC(ptr, c.repo), nil
+}
+
+// EnsureLog ensures that there is a reflog for the given reference
+// name and creates an empty one if necessary.
+func (c *ReferenceCollection) EnsureLog(name string) error {
+	cname := C.CString(name)
+	defer C.free(unsafe.Pointer(cname))
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ret := C.git_reference_ensure_log(c.repo.ptr, cname)
+	if ret < 0 {
+		return MakeGitError(ret)
+	}
+
+	return nil
+}
+
+// HasLog returns whether there is a reflog for the given reference
+// name
+func (c *ReferenceCollection) HasLog(name string) (bool, error) {
+	cname := C.CString(name)
+	defer C.free(unsafe.Pointer(cname))
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ret := C.git_reference_has_log(c.repo.ptr, cname)
+	if ret < 0 {
+		return false, MakeGitError(ret)
+	}
+
+	return ret == 1, nil
+}
+
+// Dwim looks up a reference by DWIMing its short name
+func (c *ReferenceCollection) Dwim(name string) (*Reference, error) {
+	cname := C.CString(name)
+	defer C.free(unsafe.Pointer(cname))
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	var ptr *C.git_reference
+	ret := C.git_reference_dwim(&ptr, c.repo.ptr, cname)
+	if ret < 0 {
+		return nil, MakeGitError(ret)
+	}
+
+	return newReferenceFromC(ptr, c.repo), nil
+}
+
 func newReferenceFromC(ptr *C.git_reference, repo *Repository) *Reference {
 	ref := &Reference{ptr: ptr, repo: repo}
 	runtime.SetFinalizer(ref, (*Reference).Free)
 	return ref
 }
 
-func (v *Reference) SetSymbolicTarget(target string, sig *Signature, msg string) (*Reference, error) {
+func (v *Reference) SetSymbolicTarget(target string, msg string) (*Reference, error) {
 	var ptr *C.git_reference
 
 	ctarget := C.CString(target)
@@ -36,12 +160,6 @@
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	csig, err := sig.toC()
-	if err != nil {
-		return nil, err
-	}
-	defer C.git_signature_free(csig)
-
 	var cmsg *C.char
 	if msg == "" {
 		cmsg = nil
@@ -50,7 +168,7 @@
 		defer C.free(unsafe.Pointer(cmsg))
 	}
 
-	ret := C.git_reference_symbolic_set_target(&ptr, v.ptr, ctarget, csig, cmsg)
+	ret := C.git_reference_symbolic_set_target(&ptr, v.ptr, ctarget, cmsg)
 	if ret < 0 {
 		return nil, MakeGitError(ret)
 	}
@@ -58,18 +176,12 @@
 	return newReferenceFromC(ptr, v.repo), nil
 }
 
-func (v *Reference) SetTarget(target *Oid, sig *Signature, msg string) (*Reference, error) {
+func (v *Reference) SetTarget(target *Oid, msg string) (*Reference, error) {
 	var ptr *C.git_reference
 
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	csig, err := sig.toC()
-	if err != nil {
-		return nil, err
-	}
-	defer C.git_signature_free(csig)
-
 	var cmsg *C.char
 	if msg == "" {
 		cmsg = nil
@@ -78,7 +190,7 @@
 		defer C.free(unsafe.Pointer(cmsg))
 	}
 
-	ret := C.git_reference_set_target(&ptr, v.ptr, target.toC(), csig, cmsg)
+	ret := C.git_reference_set_target(&ptr, v.ptr, target.toC(), cmsg)
 	if ret < 0 {
 		return nil, MakeGitError(ret)
 	}
@@ -100,17 +212,11 @@
 	return newReferenceFromC(ptr, v.repo), nil
 }
 
-func (v *Reference) Rename(name string, force bool, sig *Signature, msg string) (*Reference, error) {
+func (v *Reference) Rename(name string, force bool, msg string) (*Reference, error) {
 	var ptr *C.git_reference
 	cname := C.CString(name)
 	defer C.free(unsafe.Pointer(cname))
 
-	csig, err := sig.toC()
-	if err != nil {
-		return nil, err
-	}
-	defer C.git_signature_free(csig)
-
 	var cmsg *C.char
 	if msg == "" {
 		cmsg = nil
@@ -122,7 +228,7 @@
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	ret := C.git_reference_rename(&ptr, v.ptr, cname, cbool(force), csig, cmsg)
+	ret := C.git_reference_rename(&ptr, v.ptr, cname, cbool(force), cmsg)
 
 	if ret < 0 {
 		return nil, MakeGitError(ret)
@@ -157,7 +263,7 @@
 	return nil
 }
 
-func (v *Reference) Peel(t ObjectType) (Object, error) {
+func (v *Reference) Peel(t ObjectType) (*Object, error) {
 	var cobj *C.git_object
 
 	runtime.LockOSThread()
@@ -209,6 +315,11 @@
 	return C.git_reference_is_tag(v.ptr) == 1
 }
 
+// IsNote checks if the reference is a note.
+func (v *Reference) IsNote() bool {
+	return C.git_reference_is_note(v.ptr) == 1
+}
+
 func (v *Reference) Free() {
 	runtime.SetFinalizer(v, nil)
 	C.git_reference_free(v.ptr)
@@ -319,3 +430,22 @@
 	runtime.SetFinalizer(v, nil)
 	C.git_reference_iterator_free(v.ptr)
 }
+
+// ReferenceIsValidName ensures the reference name is well-formed.
+//
+// Valid reference names must follow one of two patterns:
+//
+// 1. Top-level names must contain only capital letters and underscores,
+// and must begin and end with a letter. (e.g. "HEAD", "ORIG_HEAD").
+//
+// 2. Names prefixed with "refs/" can be almost anything. You must avoid
+// the characters '~', '^', ':', ' \ ', '?', '[', and '*', and the sequences
+// ".." and " @ {" which have special meaning to revparse.
+func ReferenceIsValidName(name string) bool {
+	cname := C.CString(name)
+	defer C.free(unsafe.Pointer(cname))
+	if C.git_reference_is_valid_name(cname) == 1 {
+		return true
+	}
+	return false
+}
diff --git a/reference_test.go b/reference_test.go
index 5720a66..b6721e1 100644
--- a/reference_test.go
+++ b/reference_test.go
@@ -8,26 +8,20 @@
 )
 
 func TestRefModification(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
 	commitId, treeId := seedTestRepo(t, repo)
 
-	loc, err := time.LoadLocation("Europe/Berlin")
-	checkFatal(t, err)
-	sig := &Signature{
-		Name:  "Rand Om Hacker",
-		Email: "random@hacker.com",
-		When:  time.Date(2013, 03, 06, 14, 30, 0, 0, loc),
-	}
-	_, err = repo.CreateReference("refs/tags/tree", treeId, true, sig, "testTreeTag")
+	_, err := repo.References.Create("refs/tags/tree", treeId, true, "testTreeTag")
 	checkFatal(t, err)
 
-	tag, err := repo.LookupReference("refs/tags/tree")
+	tag, err := repo.References.Lookup("refs/tags/tree")
 	checkFatal(t, err)
 	checkRefType(t, tag, ReferenceOid)
 
-	ref, err := repo.LookupReference("HEAD")
+	ref, err := repo.References.Lookup("HEAD")
 	checkFatal(t, err)
 	checkRefType(t, ref, ReferenceSymbolic)
 
@@ -51,15 +45,16 @@
 		t.Fatalf("Wrong ref target")
 	}
 
-	_, err = tag.Rename("refs/tags/renamed", false, nil, "")
+	_, err = tag.Rename("refs/tags/renamed", false, "")
 	checkFatal(t, err)
-	tag, err = repo.LookupReference("refs/tags/renamed")
+	tag, err = repo.References.Lookup("refs/tags/renamed")
 	checkFatal(t, err)
 	checkRefType(t, ref, ReferenceOid)
 
 }
 
 func TestReferenceIterator(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
@@ -84,13 +79,13 @@
 	commitId, err := repo.CreateCommit("HEAD", sig, sig, message, tree)
 	checkFatal(t, err)
 
-	_, err = repo.CreateReference("refs/heads/one", commitId, true, sig, "headOne")
+	_, err = repo.References.Create("refs/heads/one", commitId, true, "headOne")
 	checkFatal(t, err)
 
-	_, err = repo.CreateReference("refs/heads/two", commitId, true, sig, "headTwo")
+	_, err = repo.References.Create("refs/heads/two", commitId, true, "headTwo")
 	checkFatal(t, err)
 
-	_, err = repo.CreateReference("refs/heads/three", commitId, true, sig, "headThree")
+	_, err = repo.References.Create("refs/heads/three", commitId, true, "headThree")
 	checkFatal(t, err)
 
 	iter, err := repo.NewReferenceIterator()
@@ -138,12 +133,13 @@
 }
 
 func TestReferenceOwner(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
 	commitId, _ := seedTestRepo(t, repo)
 
-	ref, err := repo.CreateReference("refs/heads/foo", commitId, true, nil, "")
+	ref, err := repo.References.Create("refs/heads/foo", commitId, true, "")
 	checkFatal(t, err)
 
 	owner := ref.Owner()
@@ -157,15 +153,16 @@
 }
 
 func TestUtil(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
 	commitId, _ := seedTestRepo(t, repo)
 
-	ref, err := repo.CreateReference("refs/heads/foo", commitId, true, nil, "")
+	ref, err := repo.References.Create("refs/heads/foo", commitId, true, "")
 	checkFatal(t, err)
 
-	ref2, err := repo.DwimReference("foo")
+	ref2, err := repo.References.Dwim("foo")
 	checkFatal(t, err)
 
 	if ref.Cmp(ref2) != 0 {
@@ -176,13 +173,57 @@
 		t.Fatalf("refs/heads/foo has no foo shorthand")
 	}
 
-	hasLog, err := repo.HasLog("refs/heads/foo")
+	hasLog, err := repo.References.HasLog("refs/heads/foo")
 	checkFatal(t, err)
 	if !hasLog {
 		t.Fatalf("branches have logs by default")
 	}
 }
 
+func TestIsNote(t *testing.T) {
+	t.Parallel()
+	repo := createTestRepo(t)
+	defer cleanupTestRepo(t, repo)
+
+	commitID, _ := seedTestRepo(t, repo)
+
+	sig := &Signature{
+		Name:  "Rand Om Hacker",
+		Email: "random@hacker.com",
+		When:  time.Now(),
+	}
+
+	refname, err := repo.Notes.DefaultRef()
+	checkFatal(t, err)
+
+	_, err = repo.Notes.Create(refname, sig, sig, commitID, "This is a note", false)
+	checkFatal(t, err)
+
+	ref, err := repo.References.Lookup(refname)
+	checkFatal(t, err)
+
+	if !ref.IsNote() {
+		t.Fatalf("%s should be a note", ref.Name())
+	}
+
+	ref, err = repo.References.Create("refs/heads/foo", commitID, true, "")
+	checkFatal(t, err)
+
+	if ref.IsNote() {
+		t.Fatalf("%s should not be a note", ref.Name())
+	}
+}
+
+func TestReferenceIsValidName(t *testing.T) {
+	t.Parallel()
+	if !ReferenceIsValidName("HEAD") {
+		t.Errorf("HEAD should be a valid reference name")
+	}
+	if ReferenceIsValidName("HEAD1") {
+		t.Errorf("HEAD1 should not be a valid reference name")
+	}
+}
+
 func compareStringList(t *testing.T, expected, actual []string) {
 	for i, v := range expected {
 		if actual[i] != v {
diff --git a/remote.go b/remote.go
index 0635608..d3f437d 100644
--- a/remote.go
+++ b/remote.go
@@ -69,6 +69,78 @@
 	PushUpdateReferenceCallback
 }
 
+type FetchPrune uint
+
+const (
+	// Use the setting from the configuration
+	FetchPruneUnspecified FetchPrune = C.GIT_FETCH_PRUNE_UNSPECIFIED
+	// Force pruning on
+	FetchPruneOn FetchPrune = C.GIT_FETCH_PRUNE
+	// Force pruning off
+	FetchNoPrune FetchPrune = C.GIT_FETCH_NO_PRUNE
+)
+
+type DownloadTags uint
+
+const (
+
+	// Use the setting from the configuration.
+	DownloadTagsUnspecified DownloadTags = C.GIT_REMOTE_DOWNLOAD_TAGS_UNSPECIFIED
+	// Ask the server for tags pointing to objects we're already
+	// downloading.
+	DownloadTagsAuto DownloadTags = C.GIT_REMOTE_DOWNLOAD_TAGS_AUTO
+
+	// Don't ask for any tags beyond the refspecs.
+	DownloadTagsNone DownloadTags = C.GIT_REMOTE_DOWNLOAD_TAGS_NONE
+
+	// Ask for the all the tags.
+	DownloadTagsAll DownloadTags = C.GIT_REMOTE_DOWNLOAD_TAGS_ALL
+)
+
+type FetchOptions struct {
+	// Callbacks to use for this fetch operation
+	RemoteCallbacks RemoteCallbacks
+	// Whether to perform a prune after the fetch
+	Prune FetchPrune
+	// Whether to write the results to FETCH_HEAD. Defaults to
+	// on. Leave this default in order to behave like git.
+	UpdateFetchhead bool
+
+	// Determines how to behave regarding tags on the remote, such
+	// as auto-downloading tags for objects we're downloading or
+	// downloading all of them.
+	//
+	// The default is to auto-follow tags.
+	DownloadTags DownloadTags
+
+	// Headers are extra headers for the fetch operation.
+	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
@@ -108,7 +180,13 @@
 }
 
 type PushOptions struct {
+	// Callbacks to use for this push operation
+	RemoteCallbacks RemoteCallbacks
+
 	PbParallelism uint
+
+	// Headers are extra headers for the push operation.
+	Headers []string
 }
 
 type RemoteHead struct {
@@ -123,6 +201,12 @@
 	}
 }
 
+func untrackCalbacksPayload(callbacks *C.git_remote_callbacks) {
+	if callbacks != nil && callbacks.payload != nil {
+		pointerHandles.Untrack(callbacks.payload)
+	}
+}
+
 func populateRemoteCallbacks(ptr *C.git_remote_callbacks, callbacks *RemoteCallbacks) {
 	C.git_remote_init_callbacks(ptr, C.GIT_REMOTE_CALLBACKS_VERSION)
 	if callbacks == nil {
@@ -155,12 +239,14 @@
 func credentialsCallback(_cred **C.git_cred, _url *C.char, _username_from_url *C.char, allowed_types uint, data unsafe.Pointer) int {
 	callbacks, _ := pointerHandles.Get(data).(*RemoteCallbacks)
 	if callbacks.CredentialsCallback == nil {
-		return 0
+		return C.GIT_PASSTHROUGH
 	}
 	url := C.GoString(_url)
 	username_from_url := C.GoString(_username_from_url)
 	ret, cred := callbacks.CredentialsCallback(url, username_from_url, (CredType)(allowed_types))
-	*_cred = cred.ptr
+	if cred != nil {
+		*_cred = cred.ptr
+	}
 	return int(ret)
 }
 
@@ -258,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))
@@ -267,41 +367,22 @@
 	return false
 }
 
-func (r *Remote) SetCallbacks(callbacks *RemoteCallbacks) error {
-	r.callbacks = *callbacks
-
-	var ccallbacks C.git_remote_callbacks
-	populateRemoteCallbacks(&ccallbacks, &r.callbacks)
-
-	runtime.LockOSThread()
-	defer runtime.UnlockOSThread()
-
-	ecode := C.git_remote_set_callbacks(r.ptr, &ccallbacks)
-	if ecode < 0 {
-		return MakeGitError(ecode)
-	}
-
-	return nil
-}
-
 func (r *Remote) Free() {
 	runtime.SetFinalizer(r, nil)
-
-	callbacks := C.git_remote_get_callbacks(r.ptr)
-	if callbacks != nil && callbacks.payload != nil {
-		pointerHandles.Untrack(callbacks.payload)
-	}
-
 	C.git_remote_free(r.ptr)
 }
 
-func (repo *Repository) ListRemotes() ([]string, error) {
+type RemoteCollection struct {
+	repo *Repository
+}
+
+func (c *RemoteCollection) List() ([]string, error) {
 	var r C.git_strarray
 
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	ecode := C.git_remote_list(&r, repo.ptr)
+	ecode := C.git_remote_list(&r, c.repo.ptr)
 	if ecode < 0 {
 		return nil, MakeGitError(ecode)
 	}
@@ -311,7 +392,7 @@
 	return remotes, nil
 }
 
-func (repo *Repository) CreateRemote(name string, url string) (*Remote, error) {
+func (c *RemoteCollection) Create(name string, url string) (*Remote, error) {
 	remote := &Remote{}
 
 	cname := C.CString(name)
@@ -322,7 +403,7 @@
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	ret := C.git_remote_create(&remote.ptr, repo.ptr, cname, curl)
+	ret := C.git_remote_create(&remote.ptr, c.repo.ptr, cname, curl)
 	if ret < 0 {
 		return nil, MakeGitError(ret)
 	}
@@ -330,21 +411,21 @@
 	return remote, nil
 }
 
-func (repo *Repository) DeleteRemote(name string) error {
+func (c *RemoteCollection) Delete(name string) error {
 	cname := C.CString(name)
 	defer C.free(unsafe.Pointer(cname))
 
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	ret := C.git_remote_delete(repo.ptr, cname)
+	ret := C.git_remote_delete(c.repo.ptr, cname)
 	if ret < 0 {
 		return MakeGitError(ret)
 	}
 	return nil
 }
 
-func (repo *Repository) CreateRemoteWithFetchspec(name string, url string, fetch string) (*Remote, error) {
+func (c *RemoteCollection) CreateWithFetchspec(name string, url string, fetch string) (*Remote, error) {
 	remote := &Remote{}
 
 	cname := C.CString(name)
@@ -357,7 +438,7 @@
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	ret := C.git_remote_create_with_fetchspec(&remote.ptr, repo.ptr, cname, curl, cfetch)
+	ret := C.git_remote_create_with_fetchspec(&remote.ptr, c.repo.ptr, cname, curl, cfetch)
 	if ret < 0 {
 		return nil, MakeGitError(ret)
 	}
@@ -365,18 +446,16 @@
 	return remote, nil
 }
 
-func (repo *Repository) CreateAnonymousRemote(url, fetch string) (*Remote, error) {
+func (c *RemoteCollection) CreateAnonymous(url string) (*Remote, error) {
 	remote := &Remote{}
 
 	curl := C.CString(url)
 	defer C.free(unsafe.Pointer(curl))
-	cfetch := C.CString(fetch)
-	defer C.free(unsafe.Pointer(cfetch))
 
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	ret := C.git_remote_create_anonymous(&remote.ptr, repo.ptr, curl, cfetch)
+	ret := C.git_remote_create_anonymous(&remote.ptr, c.repo.ptr, curl)
 	if ret < 0 {
 		return nil, MakeGitError(ret)
 	}
@@ -384,7 +463,7 @@
 	return remote, nil
 }
 
-func (repo *Repository) LookupRemote(name string) (*Remote, error) {
+func (c *RemoteCollection) Lookup(name string) (*Remote, error) {
 	remote := &Remote{}
 
 	cname := C.CString(name)
@@ -393,7 +472,7 @@
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	ret := C.git_remote_lookup(&remote.ptr, repo.ptr, cname)
+	ret := C.git_remote_lookup(&remote.ptr, c.repo.ptr, cname)
 	if ret < 0 {
 		return nil, MakeGitError(ret)
 	}
@@ -401,22 +480,6 @@
 	return remote, nil
 }
 
-func (o *Remote) Save() error {
-
-	runtime.LockOSThread()
-	defer runtime.UnlockOSThread()
-
-	ret := C.git_remote_save(o.ptr)
-	if ret < 0 {
-		return MakeGitError(ret)
-	}
-	return nil
-}
-
-func (o *Remote) Owner() Repository {
-	return Repository{C.git_remote_owner(o.ptr)}
-}
-
 func (o *Remote) Name() string {
 	return C.GoString(C.git_remote_name(o.ptr))
 }
@@ -429,42 +492,68 @@
 	return C.GoString(C.git_remote_pushurl(o.ptr))
 }
 
-func (o *Remote) SetUrl(url string) error {
-	curl := C.CString(url)
-	defer C.free(unsafe.Pointer(curl))
+func (c *RemoteCollection) Rename(remote, newname string) ([]string, error) {
+	cproblems := C.git_strarray{}
+	defer freeStrarray(&cproblems)
+	cnewname := C.CString(newname)
+	defer C.free(unsafe.Pointer(cnewname))
+	cremote := C.CString(remote)
+	defer C.free(unsafe.Pointer(cremote))
 
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	ret := C.git_remote_set_url(o.ptr, curl)
+	ret := C.git_remote_rename(&cproblems, c.repo.ptr, cremote, cnewname)
+	if ret < 0 {
+		return []string{}, MakeGitError(ret)
+	}
+
+	problems := makeStringsFromCStrings(cproblems.strings, int(cproblems.count))
+	return problems, nil
+}
+
+func (c *RemoteCollection) SetUrl(remote, url string) error {
+	curl := C.CString(url)
+	defer C.free(unsafe.Pointer(curl))
+	cremote := C.CString(remote)
+	defer C.free(unsafe.Pointer(cremote))
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ret := C.git_remote_set_url(c.repo.ptr, cremote, curl)
 	if ret < 0 {
 		return MakeGitError(ret)
 	}
 	return nil
 }
 
-func (o *Remote) SetPushUrl(url string) error {
+func (c *RemoteCollection) SetPushUrl(remote, url string) error {
 	curl := C.CString(url)
 	defer C.free(unsafe.Pointer(curl))
+	cremote := C.CString(remote)
+	defer C.free(unsafe.Pointer(cremote))
 
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	ret := C.git_remote_set_pushurl(o.ptr, curl)
+	ret := C.git_remote_set_pushurl(c.repo.ptr, cremote, curl)
 	if ret < 0 {
 		return MakeGitError(ret)
 	}
 	return nil
 }
 
-func (o *Remote) AddFetch(refspec string) error {
+func (c *RemoteCollection) AddFetch(remote, refspec string) error {
 	crefspec := C.CString(refspec)
 	defer C.free(unsafe.Pointer(crefspec))
+	cremote := C.CString(remote)
+	defer C.free(unsafe.Pointer(cremote))
 
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	ret := C.git_remote_add_fetch(o.ptr, crefspec)
+	ret := C.git_remote_add_fetch(c.repo.ptr, cremote, crefspec)
 	if ret < 0 {
 		return MakeGitError(ret)
 	}
@@ -525,30 +614,16 @@
 	return refspecs, nil
 }
 
-func (o *Remote) SetFetchRefspecs(refspecs []string) error {
-	crefspecs := C.git_strarray{}
-	crefspecs.count = C.size_t(len(refspecs))
-	crefspecs.strings = makeCStringsFromStrings(refspecs)
-	defer freeStrarray(&crefspecs)
-
-	runtime.LockOSThread()
-	defer runtime.UnlockOSThread()
-
-	ret := C.git_remote_set_fetch_refspecs(o.ptr, &crefspecs)
-	if ret < 0 {
-		return MakeGitError(ret)
-	}
-	return nil
-}
-
-func (o *Remote) AddPush(refspec string) error {
+func (c *RemoteCollection) AddPush(remote, refspec string) error {
 	crefspec := C.CString(refspec)
 	defer C.free(unsafe.Pointer(crefspec))
+	cremote := C.CString(remote)
+	defer C.free(unsafe.Pointer(cremote))
 
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	ret := C.git_remote_add_push(o.ptr, crefspec)
+	ret := C.git_remote_add_push(c.repo.ptr, cremote, crefspec)
 	if ret < 0 {
 		return MakeGitError(ret)
 	}
@@ -570,53 +645,45 @@
 	return refspecs, nil
 }
 
-func (o *Remote) SetPushRefspecs(refspecs []string) error {
-	crefspecs := C.git_strarray{}
-	crefspecs.count = C.size_t(len(refspecs))
-	crefspecs.strings = makeCStringsFromStrings(refspecs)
-	defer freeStrarray(&crefspecs)
-
-	runtime.LockOSThread()
-	defer runtime.UnlockOSThread()
-
-	ret := C.git_remote_set_push_refspecs(o.ptr, &crefspecs)
-	if ret < 0 {
-		return MakeGitError(ret)
-	}
-	return nil
-}
-
-func (o *Remote) ClearRefspecs() {
-	C.git_remote_clear_refspecs(o.ptr)
-}
-
 func (o *Remote) RefspecCount() uint {
 	return uint(C.git_remote_refspec_count(o.ptr))
 }
 
-func (o *Remote) SetUpdateFetchHead(val bool) {
-	C.git_remote_set_update_fetchhead(o.ptr, cbool(val))
+func populateFetchOptions(options *C.git_fetch_options, opts *FetchOptions) {
+	C.git_fetch_init_options(options, C.GIT_FETCH_OPTIONS_VERSION)
+	if opts == nil {
+		return
+	}
+	populateRemoteCallbacks(&options.callbacks, &opts.RemoteCallbacks)
+	options.prune = C.git_fetch_prune_t(opts.Prune)
+	options.update_fetchhead = cbool(opts.UpdateFetchhead)
+	options.download_tags = C.git_remote_autotag_option_t(opts.DownloadTags)
+
+	options.custom_headers = C.git_strarray{}
+	options.custom_headers.count = C.size_t(len(opts.Headers))
+	options.custom_headers.strings = makeCStringsFromStrings(opts.Headers)
 }
 
-func (o *Remote) UpdateFetchHead() bool {
-	return C.git_remote_update_fetchhead(o.ptr) > 0
+func populatePushOptions(options *C.git_push_options, opts *PushOptions) {
+	C.git_push_init_options(options, C.GIT_PUSH_OPTIONS_VERSION)
+	if opts == nil {
+		return
+	}
+
+	options.pb_parallelism = C.uint(opts.PbParallelism)
+
+	options.custom_headers = C.git_strarray{}
+	options.custom_headers.count = C.size_t(len(opts.Headers))
+	options.custom_headers.strings = makeCStringsFromStrings(opts.Headers)
+
+	populateRemoteCallbacks(&options.callbacks, &opts.RemoteCallbacks)
 }
 
 // Fetch performs a fetch operation. refspecs specifies which refspecs
 // to use for this fetch, use an empty list to use the refspecs from
-// the configuration; sig and msg specify what to use for the reflog
-// entries. Leave nil and "" to use defaults.
-func (o *Remote) Fetch(refspecs []string, sig *Signature, msg string) error {
-
-	var csig *C.git_signature = nil
-	if sig != nil {
-		csig, err := sig.toC()
-		if err != nil {
-			return err
-		}
-		defer C.git_signature_free(csig)
-	}
-
+// the configuration; msg specifies what to use for the reflog
+// entries. Leave "" to use defaults.
+func (o *Remote) Fetch(refspecs []string, opts *FetchOptions, msg string) error {
 	var cmsg *C.char = nil
 	if msg != "" {
 		cmsg = C.CString(msg)
@@ -628,34 +695,68 @@
 	crefspecs.strings = makeCStringsFromStrings(refspecs)
 	defer freeStrarray(&crefspecs)
 
+	coptions := (*C.git_fetch_options)(C.calloc(1, C.size_t(unsafe.Sizeof(C.git_fetch_options{}))))
+	defer C.free(unsafe.Pointer(coptions))
+
+	populateFetchOptions(coptions, opts)
+	defer untrackCalbacksPayload(&coptions.callbacks)
+	defer freeStrarray(&coptions.custom_headers)
+
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	ret := C.git_remote_fetch(o.ptr, &crefspecs, csig, cmsg)
+	ret := C.git_remote_fetch(o.ptr, &crefspecs, coptions, cmsg)
 	if ret < 0 {
 		return MakeGitError(ret)
 	}
 	return nil
 }
 
-func (o *Remote) ConnectFetch() error {
-	return o.Connect(ConnectDirectionFetch)
+func (o *Remote) ConnectFetch(callbacks *RemoteCallbacks, proxyOpts *ProxyOptions, headers []string) error {
+	return o.Connect(ConnectDirectionFetch, callbacks, proxyOpts, headers)
 }
 
-func (o *Remote) ConnectPush() error {
-	return o.Connect(ConnectDirectionPush)
+func (o *Remote) ConnectPush(callbacks *RemoteCallbacks, proxyOpts *ProxyOptions, headers []string) error {
+	return o.Connect(ConnectDirectionPush, callbacks, proxyOpts, headers)
 }
 
-func (o *Remote) Connect(direction ConnectDirection) error {
+// Connect opens a connection to a remote.
+//
+// The transport is selected based on the URL. The direction argument
+// is due to a limitation of the git protocol (over TCP or SSH) which
+// 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, 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)); ret != 0 {
+	if ret := C.git_remote_connect(o.ptr, C.git_direction(direction), &ccallbacks, &cproxy, &cheaders); ret != 0 {
 		return MakeGitError(ret)
 	}
 	return nil
 }
 
+func (o *Remote) Disconnect() {
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	C.git_remote_disconnect(o.ptr)
+}
+
 func (o *Remote) Ls(filterRefs ...string) ([]RemoteHead, error) {
 
 	var refs **C.git_remote_head
@@ -702,39 +803,23 @@
 	return heads, nil
 }
 
-func (o *Remote) Push(refspecs []string, opts *PushOptions, sig *Signature, msg string) error {
-	var csig *C.git_signature = nil
-	if sig != nil {
-		csig, err := sig.toC()
-		if err != nil {
-			return err
-		}
-		defer C.git_signature_free(csig)
-	}
-
-	var cmsg *C.char
-	if msg == "" {
-		cmsg = nil
-	} else {
-		cmsg = C.CString(msg)
-		defer C.free(unsafe.Pointer(cmsg))
-	}
-
-	var copts C.git_push_options
-	C.git_push_init_options(&copts, C.GIT_PUSH_OPTIONS_VERSION)
-	if opts != nil {
-		copts.pb_parallelism = C.uint(opts.PbParallelism)
-	}
-
+func (o *Remote) Push(refspecs []string, opts *PushOptions) error {
 	crefspecs := C.git_strarray{}
 	crefspecs.count = C.size_t(len(refspecs))
 	crefspecs.strings = makeCStringsFromStrings(refspecs)
 	defer freeStrarray(&crefspecs)
 
+	coptions := (*C.git_push_options)(C.calloc(1, C.size_t(unsafe.Sizeof(C.git_push_options{}))))
+	defer C.free(unsafe.Pointer(coptions))
+
+	populatePushOptions(coptions, opts)
+	defer untrackCalbacksPayload(&coptions.callbacks)
+	defer freeStrarray(&coptions.custom_headers)
+
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	ret := C.git_remote_push(o.ptr, &crefspecs, &copts, csig, cmsg)
+	ret := C.git_remote_push(o.ptr, &crefspecs, coptions)
 	if ret < 0 {
 		return MakeGitError(ret)
 	}
@@ -745,11 +830,14 @@
 	return C.git_remote_prune_refs(o.ptr) > 0
 }
 
-func (o *Remote) Prune() error {
+func (o *Remote) Prune(callbacks *RemoteCallbacks) error {
+	var ccallbacks C.git_remote_callbacks
+	populateRemoteCallbacks(&ccallbacks, callbacks)
+
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	ret := C.git_remote_prune(o.ptr)
+	ret := C.git_remote_prune(o.ptr, &ccallbacks)
 	if ret < 0 {
 		return MakeGitError(ret)
 	}
diff --git a/remote_test.go b/remote_test.go
index 25ee13d..8b20fd2 100644
--- a/remote_test.go
+++ b/remote_test.go
@@ -3,35 +3,14 @@
 import (
 	"fmt"
 	"testing"
-	"time"
 )
 
-func TestRefspecs(t *testing.T) {
-	repo := createTestRepo(t)
-	defer cleanupTestRepo(t, repo)
-
-	remote, err := repo.CreateAnonymousRemote("git://foo/bar", "refs/heads/*:refs/heads/*")
-	checkFatal(t, err)
-
-	expected := []string{
-		"refs/heads/*:refs/remotes/origin/*",
-		"refs/pull/*/head:refs/remotes/origin/*",
-	}
-
-	err = remote.SetFetchRefspecs(expected)
-	checkFatal(t, err)
-
-	actual, err := remote.FetchRefspecs()
-	checkFatal(t, err)
-
-	compareStringList(t, expected, actual)
-}
-
 func TestListRemotes(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
-	_, err := repo.CreateRemote("test", "git://foo/bar")
+	_, err := repo.Remotes.Create("test", "git://foo/bar")
 
 	checkFatal(t, err)
 
@@ -39,7 +18,7 @@
 		"test",
 	}
 
-	actual, err := repo.ListRemotes()
+	actual, err := repo.Remotes.List()
 	checkFatal(t, err)
 
 	compareStringList(t, expected, actual)
@@ -55,43 +34,46 @@
 }
 
 func TestCertificateCheck(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
-	remote, err := repo.CreateRemote("origin", "https://github.com/libgit2/TestGitRepository")
+	remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository")
 	checkFatal(t, err)
 
-	callbacks := RemoteCallbacks{
-		CertificateCheckCallback: func(cert *Certificate, valid bool, hostname string) ErrorCode {
-			return assertHostname(cert, valid, hostname, t)
+	options := FetchOptions{
+		RemoteCallbacks: RemoteCallbacks{
+			CertificateCheckCallback: func(cert *Certificate, valid bool, hostname string) ErrorCode {
+				return assertHostname(cert, valid, hostname, t)
+			},
 		},
 	}
 
-	err = remote.SetCallbacks(&callbacks)
-	checkFatal(t, err)
-	err = remote.Fetch([]string{}, nil, "")
+	err = remote.Fetch([]string{}, &options, "")
 	checkFatal(t, err)
 }
 
 func TestRemoteConnect(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
-	remote, err := repo.CreateRemote("origin", "https://github.com/libgit2/TestGitRepository")
+	remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository")
 	checkFatal(t, err)
 
-	err = remote.ConnectFetch()
+	err = remote.ConnectFetch(nil, nil, nil)
 	checkFatal(t, err)
 }
 
 func TestRemoteLs(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
-	remote, err := repo.CreateRemote("origin", "https://github.com/libgit2/TestGitRepository")
+	remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository")
 	checkFatal(t, err)
 
-	err = remote.ConnectFetch()
+	err = remote.ConnectFetch(nil, nil, nil)
 	checkFatal(t, err)
 
 	heads, err := remote.Ls()
@@ -103,13 +85,14 @@
 }
 
 func TestRemoteLsFiltering(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
-	remote, err := repo.CreateRemote("origin", "https://github.com/libgit2/TestGitRepository")
+	remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository")
 	checkFatal(t, err)
 
-	err = remote.ConnectFetch()
+	err = remote.ConnectFetch(nil, nil, nil)
 	checkFatal(t, err)
 
 	heads, err := remote.Ls("master")
@@ -129,6 +112,7 @@
 }
 
 func TestRemotePruneRefs(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
@@ -139,10 +123,10 @@
 	err = config.SetBool("remote.origin.prune", true)
 	checkFatal(t, err)
 
-	_, err = repo.CreateRemote("origin", "https://github.com/libgit2/TestGitRepository")
+	_, err = repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository")
 	checkFatal(t, err)
 
-	remote, err := repo.LookupRemote("origin")
+	remote, err := repo.Remotes.Lookup("origin")
 	checkFatal(t, err)
 
 	if !remote.PruneRefs() {
@@ -151,6 +135,7 @@
 }
 
 func TestRemotePrune(t *testing.T) {
+	t.Parallel()
 	remoteRepo := createTestRepo(t)
 	defer cleanupTestRepo(t, remoteRepo)
 
@@ -159,13 +144,7 @@
 	checkFatal(t, err)
 	defer commit.Free()
 
-	sig := &Signature{
-		Name:  "Rand Om Hacker",
-		Email: "random@hacker.com",
-		When:  time.Now(),
-	}
-
-	remoteRef, err := remoteRepo.CreateBranch("test-prune", commit, true, sig, "branch test-prune")
+	remoteRef, err := remoteRepo.CreateBranch("test-prune", commit, true)
 	checkFatal(t, err)
 
 	repo := createTestRepo(t)
@@ -176,13 +155,13 @@
 	defer config.Free()
 
 	remoteUrl := fmt.Sprintf("file://%s", remoteRepo.Workdir())
-	remote, err := repo.CreateRemote("origin", remoteUrl)
+	remote, err := repo.Remotes.Create("origin", remoteUrl)
 	checkFatal(t, err)
 
-	err = remote.Fetch([]string{"test-prune"}, sig, "")
+	err = remote.Fetch([]string{"test-prune"}, nil, "")
 	checkFatal(t, err)
 
-	_, err = repo.CreateReference("refs/remotes/origin/test-prune", head, true, sig, "remote reference")
+	_, err = repo.References.Create("refs/remotes/origin/test-prune", head, true, "remote reference")
 	checkFatal(t, err)
 
 	err = remoteRef.Delete()
@@ -191,16 +170,16 @@
 	err = config.SetBool("remote.origin.prune", true)
 	checkFatal(t, err)
 
-	rr, err := repo.LookupRemote("origin")
+	rr, err := repo.Remotes.Lookup("origin")
 	checkFatal(t, err)
 
-	err = rr.ConnectFetch()
+	err = rr.ConnectFetch(nil, nil, nil)
 	checkFatal(t, err)
 
-	err = rr.Prune()
+	err = rr.Prune(nil)
 	checkFatal(t, err)
 
-	_, err = repo.LookupReference("refs/remotes/origin/test-prune")
+	_, err = repo.References.Lookup("refs/remotes/origin/test-prune")
 	if err == nil {
 		t.Fatal("Expected error getting a pruned reference")
 	}
diff --git a/repository.go b/repository.go
index 996e966..0612b5f 100644
--- a/repository.go
+++ b/repository.go
@@ -13,75 +13,116 @@
 // Repository
 type Repository struct {
 	ptr *C.git_repository
+	// Remotes represents the collection of remotes and can be
+	// used to add, remove and configure remotes for this
+	// repository.
+	Remotes RemoteCollection
+	// Submodules represents the collection of submodules and can
+	// be used to add, remove and configure submodules in this
+	// repostiory.
+	Submodules SubmoduleCollection
+	// References represents the collection of references and can
+	// be used to create, remove or update refernces for this repository.
+	References ReferenceCollection
+	// Notes represents the collection of notes and can be used to
+	// read, write and delete notes from this repository.
+	Notes NoteCollection
+	// 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 {
+	repo := &Repository{ptr: ptr}
+
+	repo.Remotes.repo = repo
+	repo.Submodules.repo = repo
+	repo.References.repo = repo
+	repo.Notes.repo = repo
+	repo.Tags.repo = repo
+	repo.Stashes.repo = repo
+
+	runtime.SetFinalizer(repo, (*Repository).Free)
+
+	return repo
 }
 
 func OpenRepository(path string) (*Repository, error) {
-	repo := new(Repository)
-
 	cpath := C.CString(path)
 	defer C.free(unsafe.Pointer(cpath))
 
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	ret := C.git_repository_open(&repo.ptr, cpath)
+	var ptr *C.git_repository
+	ret := C.git_repository_open(&ptr, cpath)
 	if ret < 0 {
 		return nil, MakeGitError(ret)
 	}
 
-	runtime.SetFinalizer(repo, (*Repository).Free)
-	return repo, nil
+	return newRepositoryFromC(ptr), nil
 }
 
-func OpenRepositoryExtended(path string) (*Repository, error) {
-	repo := new(Repository)
+type RepositoryOpenFlag int
 
+const (
+	RepositoryOpenNoSearch RepositoryOpenFlag = C.GIT_REPOSITORY_OPEN_NO_SEARCH
+	RepositoryOpenCrossFs  RepositoryOpenFlag = C.GIT_REPOSITORY_OPEN_CROSS_FS
+	RepositoryOpenBare     RepositoryOpenFlag = C.GIT_REPOSITORY_OPEN_BARE
+)
+
+func OpenRepositoryExtended(path string, flags RepositoryOpenFlag, ceiling string) (*Repository, error) {
 	cpath := C.CString(path)
 	defer C.free(unsafe.Pointer(cpath))
 
+	var cceiling *C.char = nil
+	if len(ceiling) > 0 {
+		cceiling = C.CString(ceiling)
+		defer C.free(unsafe.Pointer(cceiling))
+	}
+
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	ret := C.git_repository_open_ext(&repo.ptr, cpath, 0, nil)
+	var ptr *C.git_repository
+	ret := C.git_repository_open_ext(&ptr, cpath, C.uint(flags), cceiling)
 	if ret < 0 {
 		return nil, MakeGitError(ret)
 	}
 
-	runtime.SetFinalizer(repo, (*Repository).Free)
-	return repo, nil
+	return newRepositoryFromC(ptr), nil
 }
 
 func InitRepository(path string, isbare bool) (*Repository, error) {
-	repo := new(Repository)
-
 	cpath := C.CString(path)
 	defer C.free(unsafe.Pointer(cpath))
 
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	ret := C.git_repository_init(&repo.ptr, cpath, ucbool(isbare))
+	var ptr *C.git_repository
+	ret := C.git_repository_init(&ptr, cpath, ucbool(isbare))
 	if ret < 0 {
 		return nil, MakeGitError(ret)
 	}
 
-	runtime.SetFinalizer(repo, (*Repository).Free)
-	return repo, nil
+	return newRepositoryFromC(ptr), nil
 }
 
 func NewRepositoryWrapOdb(odb *Odb) (repo *Repository, err error) {
-	repo = new(Repository)
-
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	ret := C.git_repository_wrap_odb(&repo.ptr, odb.ptr)
+	var ptr *C.git_repository
+	ret := C.git_repository_wrap_odb(&ptr, odb.ptr)
 	if ret < 0 {
 		return nil, MakeGitError(ret)
 	}
 
-	runtime.SetFinalizer(repo, (*Repository).Free)
-	return repo, nil
+	return newRepositoryFromC(ptr), nil
 }
 
 func (v *Repository) SetRefdb(refdb *Refdb) {
@@ -122,7 +163,7 @@
 	return newIndexFromC(ptr), nil
 }
 
-func (v *Repository) lookupType(id *Oid, t ObjectType) (Object, error) {
+func (v *Repository) lookupType(id *Oid, t ObjectType) (*Object, error) {
 	var ptr *C.git_object
 
 	runtime.LockOSThread()
@@ -136,7 +177,7 @@
 	return allocObject(ptr, v), nil
 }
 
-func (v *Repository) Lookup(id *Oid) (Object, error) {
+func (v *Repository) Lookup(id *Oid) (*Object, error) {
 	return v.lookupType(id, ObjectAny)
 }
 
@@ -146,7 +187,7 @@
 		return nil, err
 	}
 
-	return obj.(*Tree), nil
+	return obj.AsTree()
 }
 
 func (v *Repository) LookupCommit(id *Oid) (*Commit, error) {
@@ -155,7 +196,7 @@
 		return nil, err
 	}
 
-	return obj.(*Commit), nil
+	return obj.AsCommit()
 }
 
 func (v *Repository) LookupBlob(id *Oid) (*Blob, error) {
@@ -164,7 +205,7 @@
 		return nil, err
 	}
 
-	return obj.(*Blob), nil
+	return obj.AsBlob()
 }
 
 func (v *Repository) LookupTag(id *Oid) (*Tag, error) {
@@ -173,23 +214,7 @@
 		return nil, err
 	}
 
-	return obj.(*Tag), nil
-}
-
-func (v *Repository) LookupReference(name string) (*Reference, error) {
-	cname := C.CString(name)
-	defer C.free(unsafe.Pointer(cname))
-	var ptr *C.git_reference
-
-	runtime.LockOSThread()
-	defer runtime.UnlockOSThread()
-
-	ecode := C.git_reference_lookup(&ptr, v.ptr, cname)
-	if ecode < 0 {
-		return nil, MakeGitError(ecode)
-	}
-
-	return newReferenceFromC(ptr, v), nil
+	return obj.AsTag()
 }
 
 func (v *Repository) Head() (*Reference, error) {
@@ -206,49 +231,25 @@
 	return newReferenceFromC(ptr, v), nil
 }
 
-func (v *Repository) SetHead(refname string, sig *Signature, msg string) error {
+func (v *Repository) SetHead(refname string) error {
 	cname := C.CString(refname)
 	defer C.free(unsafe.Pointer(cname))
 
-	csig, err := sig.toC()
-	if err != nil {
-		return err
-	}
-	defer C.git_signature_free(csig)
-
-	var cmsg *C.char
-	if msg != "" {
-		cmsg = C.CString(msg)
-		defer C.free(unsafe.Pointer(cmsg))
-	}
-
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	ecode := C.git_repository_set_head(v.ptr, cname, csig, cmsg)
+	ecode := C.git_repository_set_head(v.ptr, cname)
 	if ecode != 0 {
 		return MakeGitError(ecode)
 	}
 	return nil
 }
 
-func (v *Repository) SetHeadDetached(id *Oid, sig *Signature, msg string) error {
-	csig, err := sig.toC()
-	if err != nil {
-		return err
-	}
-	defer C.git_signature_free(csig)
-
-	var cmsg *C.char
-	if msg != "" {
-		cmsg = C.CString(msg)
-		defer C.free(unsafe.Pointer(cmsg))
-	}
-
+func (v *Repository) SetHeadDetached(id *Oid) error {
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	ecode := C.git_repository_set_head_detached(v.ptr, id.toC(), csig, cmsg)
+	ecode := C.git_repository_set_head_detached(v.ptr, id.toC())
 	if ecode != 0 {
 		return MakeGitError(ecode)
 	}
@@ -267,69 +268,38 @@
 	return ret != 0, nil
 }
 
-func (v *Repository) CreateReference(name string, id *Oid, force bool, sig *Signature, msg string) (*Reference, error) {
-	cname := C.CString(name)
-	defer C.free(unsafe.Pointer(cname))
-
-	csig, err := sig.toC()
-	if err != nil {
-		return nil, err
-	}
-	defer C.git_signature_free(csig)
-
-	var cmsg *C.char
-	if msg == "" {
-		cmsg = nil
-	} else {
-		cmsg = C.CString(msg)
-		defer C.free(unsafe.Pointer(cmsg))
-	}
-
-	var ptr *C.git_reference
-
+func (v *Repository) IsHeadUnborn() (bool, error) {
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	ecode := C.git_reference_create(&ptr, v.ptr, cname, id.toC(), cbool(force), csig, cmsg)
-	if ecode < 0 {
-		return nil, MakeGitError(ecode)
+	ret := C.git_repository_head_unborn(v.ptr)
+	if ret < 0 {
+		return false, MakeGitError(ret)
 	}
-
-	return newReferenceFromC(ptr, v), nil
+	return ret != 0, nil
 }
 
-func (v *Repository) CreateSymbolicReference(name, target string, force bool, sig *Signature, msg string) (*Reference, error) {
-	cname := C.CString(name)
-	defer C.free(unsafe.Pointer(cname))
-
-	ctarget := C.CString(target)
-	defer C.free(unsafe.Pointer(ctarget))
-
-	csig, err := sig.toC()
-	if err != nil {
-		return nil, err
-	}
-	defer C.git_signature_free(csig)
-
-	var cmsg *C.char
-	if msg == "" {
-		cmsg = nil
-	} else {
-		cmsg = C.CString(msg)
-		defer C.free(unsafe.Pointer(cmsg))
-	}
-
-	var ptr *C.git_reference
-
+func (v *Repository) IsEmpty() (bool, error) {
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	ecode := C.git_reference_symbolic_create(&ptr, v.ptr, cname, ctarget, cbool(force), csig, cmsg)
-	if ecode < 0 {
-		return nil, MakeGitError(ecode)
+	ret := C.git_repository_is_empty(v.ptr)
+	if ret < 0 {
+		return false, MakeGitError(ret)
 	}
 
-	return newReferenceFromC(ptr, v), nil
+	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) {
@@ -403,36 +373,6 @@
 	return oid, nil
 }
 
-func (v *Repository) CreateTag(
-	name string, commit *Commit, tagger *Signature, message string) (*Oid, error) {
-
-	oid := new(Oid)
-
-	cname := C.CString(name)
-	defer C.free(unsafe.Pointer(cname))
-
-	cmessage := C.CString(message)
-	defer C.free(unsafe.Pointer(cmessage))
-
-	taggerSig, err := tagger.toC()
-	if err != nil {
-		return nil, err
-	}
-	defer C.git_signature_free(taggerSig)
-
-	ctarget := commit.gitObject.ptr
-
-	runtime.LockOSThread()
-	defer runtime.UnlockOSThread()
-
-	ret := C.git_tag_create(oid.toC(), v.ptr, cname, ctarget, taggerSig, cmessage, 0)
-	if ret < 0 {
-		return nil, MakeGitError(ret)
-	}
-
-	return oid, nil
-}
-
 func (v *Odb) Free() {
 	runtime.SetFinalizer(v, nil)
 	C.git_odb_free(v.ptr)
@@ -513,169 +453,6 @@
 	return bld, nil
 }
 
-// EnsureLog ensures that there is a reflog for the given reference
-// name and creates an empty one if necessary.
-func (v *Repository) EnsureLog(name string) error {
-	cname := C.CString(name)
-	defer C.free(unsafe.Pointer(cname))
-
-	runtime.LockOSThread()
-	defer runtime.UnlockOSThread()
-
-	ret := C.git_reference_ensure_log(v.ptr, cname)
-	if ret < 0 {
-		return MakeGitError(ret)
-	}
-
-	return nil
-}
-
-// HasLog returns whether there is a reflog for the given reference
-// name
-func (v *Repository) HasLog(name string) (bool, error) {
-	cname := C.CString(name)
-	defer C.free(unsafe.Pointer(cname))
-
-	runtime.LockOSThread()
-	defer runtime.UnlockOSThread()
-
-	ret := C.git_reference_has_log(v.ptr, cname)
-	if ret < 0 {
-		return false, MakeGitError(ret)
-	}
-
-	return ret == 1, nil
-}
-
-// DwimReference looks up a reference by DWIMing its short name
-func (v *Repository) DwimReference(name string) (*Reference, error) {
-	cname := C.CString(name)
-	defer C.free(unsafe.Pointer(cname))
-
-	runtime.LockOSThread()
-	defer runtime.UnlockOSThread()
-
-	var ptr *C.git_reference
-	ret := C.git_reference_dwim(&ptr, v.ptr, cname)
-	if ret < 0 {
-		return nil, MakeGitError(ret)
-	}
-
-	return newReferenceFromC(ptr, v), nil
-}
-
-// CreateNote adds a note for an object
-func (v *Repository) CreateNote(
-	ref string, author, committer *Signature, id *Oid,
-	note string, force bool) (*Oid, error) {
-
-	oid := new(Oid)
-
-	var cref *C.char
-	if ref == "" {
-		cref = nil
-	} else {
-		cref = C.CString(ref)
-		defer C.free(unsafe.Pointer(cref))
-	}
-
-	authorSig, err := author.toC()
-	if err != nil {
-		return nil, err
-	}
-	defer C.git_signature_free(authorSig)
-
-	committerSig, err := committer.toC()
-	if err != nil {
-		return nil, err
-	}
-	defer C.git_signature_free(committerSig)
-
-	cnote := C.CString(note)
-	defer C.free(unsafe.Pointer(cnote))
-
-	runtime.LockOSThread()
-	defer runtime.UnlockOSThread()
-
-	ret := C.git_note_create(
-		oid.toC(), v.ptr, cref, authorSig,
-		committerSig, id.toC(), cnote, cbool(force))
-
-	if ret < 0 {
-		return nil, MakeGitError(ret)
-	}
-	return oid, nil
-}
-
-// ReadNote reads the note for an object
-func (v *Repository) ReadNote(ref string, id *Oid) (*Note, error) {
-	var cref *C.char
-	if ref == "" {
-		cref = nil
-	} else {
-		cref = C.CString(ref)
-		defer C.free(unsafe.Pointer(cref))
-	}
-
-	note := new(Note)
-
-	runtime.LockOSThread()
-	defer runtime.UnlockOSThread()
-
-	if ret := C.git_note_read(&note.ptr, v.ptr, cref, id.toC()); ret < 0 {
-		return nil, MakeGitError(ret)
-	}
-
-	runtime.SetFinalizer(note, (*Note).Free)
-	return note, nil
-}
-
-// RemoveNote removes the note for an object
-func (v *Repository) RemoveNote(ref string, author, committer *Signature, id *Oid) error {
-	var cref *C.char
-	if ref == "" {
-		cref = nil
-	} else {
-		cref = C.CString(ref)
-		defer C.free(unsafe.Pointer(cref))
-	}
-
-	authorSig, err := author.toC()
-	if err != nil {
-		return err
-	}
-	defer C.git_signature_free(authorSig)
-
-	committerSig, err := committer.toC()
-	if err != nil {
-		return err
-	}
-	defer C.git_signature_free(committerSig)
-
-	runtime.LockOSThread()
-	defer runtime.UnlockOSThread()
-
-	ret := C.git_note_remove(v.ptr, cref, authorSig, committerSig, id.toC())
-	if ret < 0 {
-		return MakeGitError(ret)
-	}
-	return nil
-}
-
-// DefaultNoteRef returns the default notes reference for a repository
-func (v *Repository) DefaultNoteRef() (string, error) {
-	var ptr *C.char
-
-	runtime.LockOSThread()
-	defer runtime.UnlockOSThread()
-
-	if ret := C.git_note_default_ref(&ptr, v.ptr); ret < 0 {
-		return "", MakeGitError(ret)
-	}
-
-	return C.GoString(ptr), nil
-}
-
 type RepositoryState int
 
 const (
@@ -708,3 +485,24 @@
 	}
 	return nil
 }
+func (r *Repository) AddGitIgnoreRules(rules string) error {
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	crules := C.CString(rules)
+	defer C.free(unsafe.Pointer(crules))
+	if ret := C.git_ignore_add_rule(r.ptr, crules); ret < 0 {
+		return MakeGitError(ret)
+	}
+	return nil
+}
+
+func (r *Repository) ClearGitIgnoreRules() error {
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	if ret := C.git_ignore_clear_internal_rules(r.ptr); ret < 0 {
+		return MakeGitError(ret)
+	}
+	return nil
+}
diff --git a/reset.go b/reset.go
new file mode 100644
index 0000000..031f5bd
--- /dev/null
+++ b/reset.go
@@ -0,0 +1,42 @@
+package git
+
+/*
+#include <git2.h>
+*/
+import "C"
+import "runtime"
+
+type ResetType int
+
+const (
+	ResetSoft  ResetType = C.GIT_RESET_SOFT
+	ResetMixed ResetType = C.GIT_RESET_MIXED
+	ResetHard  ResetType = C.GIT_RESET_HARD
+)
+
+func (r *Repository) ResetToCommit(commit *Commit, resetType ResetType, opts *CheckoutOpts) error {
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+	ret := C.git_reset(r.ptr, commit.ptr, C.git_reset_t(resetType), opts.toC())
+
+	if ret < 0 {
+		return MakeGitError(ret)
+	}
+	return nil
+}
+
+func (r *Repository) ResetDefaultToCommit(commit *Commit, pathspecs []string) error {
+	cpathspecs := C.git_strarray{}
+	cpathspecs.count = C.size_t(len(pathspecs))
+	cpathspecs.strings = makeCStringsFromStrings(pathspecs)
+	defer freeStrarray(&cpathspecs)
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+	ret := C.git_reset_default(r.ptr, commit.ptr, &cpathspecs)
+
+	if ret < 0 {
+		return MakeGitError(ret)
+	}
+	return nil
+}
diff --git a/reset_test.go b/reset_test.go
new file mode 100644
index 0000000..45777e4
--- /dev/null
+++ b/reset_test.go
@@ -0,0 +1,46 @@
+package git
+
+import (
+	"io/ioutil"
+	"testing"
+)
+
+func TestResetToCommit(t *testing.T) {
+	t.Parallel()
+	repo := createTestRepo(t)
+	seedTestRepo(t, repo)
+	// create commit to reset to
+	commitId, _ := updateReadme(t, repo, "testing reset")
+	// create commit to reset from
+	nextCommitId, _ := updateReadme(t, repo, "will be reset")
+
+	// confirm that we wrote "will be reset" to the readme
+	newBytes, err := ioutil.ReadFile(pathInRepo(repo, "README"))
+	checkFatal(t, err)
+	if string(newBytes) != "will be reset" {
+		t.Fatalf("expected %s to equal 'will be reset'", string(newBytes))
+	}
+
+	// confirm that the head of the repo is the next commit id
+	head, err := repo.Head()
+	checkFatal(t, err)
+	if head.Target().String() != nextCommitId.String() {
+		t.Fatalf(
+			"expected to be at latest commit %s, but was %s",
+			nextCommitId.String(),
+			head.Target().String(),
+		)
+	}
+
+	commitToResetTo, err := repo.LookupCommit(commitId)
+	checkFatal(t, err)
+
+	repo.ResetToCommit(commitToResetTo, ResetHard, &CheckoutOpts{})
+
+	// check that the file now reads "testing reset" like it did before
+	bytes, err := ioutil.ReadFile(pathInRepo(repo, "README"))
+	checkFatal(t, err)
+	if string(bytes) != "testing reset" {
+		t.Fatalf("expected %s to equal 'testing reset'", string(bytes))
+	}
+}
diff --git a/revparse.go b/revparse.go
index 7eb04f1..950932b 100644
--- a/revparse.go
+++ b/revparse.go
@@ -20,16 +20,16 @@
 )
 
 type Revspec struct {
-	to    Object
-	from  Object
+	to    *Object
+	from  *Object
 	flags RevparseFlag
 }
 
-func (rs *Revspec) To() Object {
+func (rs *Revspec) To() *Object {
 	return rs.to
 }
 
-func (rs *Revspec) From() Object {
+func (rs *Revspec) From() *Object {
 	return rs.from
 }
 
@@ -38,8 +38,8 @@
 }
 
 func newRevspecFromC(ptr *C.git_revspec, repo *Repository) *Revspec {
-	var to Object
-	var from Object
+	var to *Object
+	var from *Object
 
 	if ptr.to != nil {
 		to = allocObject(ptr.to, repo)
@@ -73,7 +73,7 @@
 	return newRevspecFromC(&crevspec, r), nil
 }
 
-func (v *Repository) RevparseSingle(spec string) (Object, error) {
+func (v *Repository) RevparseSingle(spec string) (*Object, error) {
 	cspec := C.CString(spec)
 	defer C.free(unsafe.Pointer(cspec))
 
@@ -90,7 +90,7 @@
 	return allocObject(ptr, v), nil
 }
 
-func (r *Repository) RevparseExt(spec string) (Object, *Reference, error) {
+func (r *Repository) RevparseExt(spec string) (*Object, *Reference, error) {
 	cspec := C.CString(spec)
 	defer C.free(unsafe.Pointer(cspec))
 
diff --git a/revparse_test.go b/revparse_test.go
index 2ccdca2..2835434 100644
--- a/revparse_test.go
+++ b/revparse_test.go
@@ -5,6 +5,7 @@
 )
 
 func TestRevparse(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
@@ -17,6 +18,7 @@
 }
 
 func TestRevparseSingle(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
@@ -29,12 +31,13 @@
 }
 
 func TestRevparseExt(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
 	_, treeId := seedTestRepo(t, repo)
 
-	ref, err := repo.CreateReference("refs/heads/master", treeId, true, nil, "")
+	ref, err := repo.References.Create("refs/heads/master", treeId, true, "")
 	checkFatal(t, err)
 
 	obj, ref, err := repo.RevparseExt("master")
@@ -46,7 +49,7 @@
 	}
 }
 
-func checkObject(t *testing.T, obj Object, id *Oid) {
+func checkObject(t *testing.T, obj *Object, id *Oid) {
 	if obj == nil {
 		t.Fatalf("bad object")
 	}
diff --git a/script/build-libgit2-static.sh b/script/build-libgit2-static.sh
new file mode 100755
index 0000000..5723721
--- /dev/null
+++ b/script/build-libgit2-static.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+set -ex
+
+VENDORED_PATH=vendor/libgit2
+
+cd $VENDORED_PATH &&
+mkdir -p install/lib &&
+mkdir -p build &&
+cd build &&
+cmake -DTHREADSAFE=ON \
+      -DBUILD_CLAR=OFF \
+      -DBUILD_SHARED_LIBS=OFF \
+      -DCMAKE_C_FLAGS=-fPIC \
+      -DCMAKE_BUILD_TYPE="RelWithDebInfo" \
+      -DCMAKE_INSTALL_PREFIX=../install \
+      .. &&
+
+cmake --build .
diff --git a/script/check-MakeGitError-thread-lock.go b/script/check-MakeGitError-thread-lock.go
index f6b01b3..77411f7 100644
--- a/script/check-MakeGitError-thread-lock.go
+++ b/script/check-MakeGitError-thread-lock.go
@@ -1,3 +1,5 @@
+// +build ignore
+
 package main
 
 import (
diff --git a/script/install-libgit2.sh b/script/install-libgit2.sh
new file mode 100755
index 0000000..f2468c8
--- /dev/null
+++ b/script/install-libgit2.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+#
+# Install libgit2 to git2go in dynamic mode on Travis
+#
+
+set -ex
+
+# We don't want to build libgit2 on the next branch, as we carry a
+# submodule with the exact version we support
+if [ "x$TRAVIS_BRANCH" = "xnext" ]; then
+    exit 0
+fi
+
+cd "${HOME}"
+LG2VER="0.24.0"
+wget -O libgit2-${LG2VER}.tar.gz https://github.com/libgit2/libgit2/archive/v${LG2VER}.tar.gz
+tar -xzvf libgit2-${LG2VER}.tar.gz
+cd libgit2-${LG2VER} && mkdir build && cd build
+cmake -DTHREADSAFE=ON -DBUILD_CLAR=OFF -DCMAKE_BUILD_TYPE="RelWithDebInfo" .. && make && sudo make install
+sudo ldconfig
+cd "${TRAVIS_BUILD_DIR}"
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/status.go b/status.go
index 3f5a06d..e68e6e9 100644
--- a/status.go
+++ b/status.go
@@ -25,6 +25,7 @@
 	StatusWtTypeChange    Status = C.GIT_STATUS_WT_TYPECHANGE
 	StatusWtRenamed       Status = C.GIT_STATUS_WT_RENAMED
 	StatusIgnored         Status = C.GIT_STATUS_IGNORED
+	StatusConflicted      Status = C.GIT_STATUS_CONFLICTED
 )
 
 type StatusEntry struct {
@@ -126,34 +127,24 @@
 	Pathspec []string
 }
 
-func (opts *StatusOptions) toC() *C.git_status_options {
-	if opts == nil {
-		return nil
-	}
-
-	cpathspec := C.git_strarray{}
-	if opts.Pathspec != nil {
-		cpathspec.count = C.size_t(len(opts.Pathspec))
-		cpathspec.strings = makeCStringsFromStrings(opts.Pathspec)
-		defer freeStrarray(&cpathspec)
-	}
-
-	copts := &C.git_status_options{
-		version:  C.GIT_STATUS_OPTIONS_VERSION,
-		show:     C.git_status_show_t(opts.Show),
-		flags:    C.uint(opts.Flags),
-		pathspec: cpathspec,
-	}
-
-	return copts
-}
-
 func (v *Repository) StatusList(opts *StatusOptions) (*StatusList, error) {
 	var ptr *C.git_status_list
 	var copts *C.git_status_options
 
 	if opts != nil {
-		copts = opts.toC()
+		cpathspec := C.git_strarray{}
+		if opts.Pathspec != nil {
+			cpathspec.count = C.size_t(len(opts.Pathspec))
+			cpathspec.strings = makeCStringsFromStrings(opts.Pathspec)
+			defer freeStrarray(&cpathspec)
+		}
+
+		copts = &C.git_status_options{
+			version:  C.GIT_STATUS_OPTIONS_VERSION,
+			show:     C.git_status_show_t(opts.Show),
+			flags:    C.uint(opts.Flags),
+			pathspec: cpathspec,
+		}
 	} else {
 		copts = &C.git_status_options{}
 		ret := C.git_status_init_options(copts, C.GIT_STATUS_OPTIONS_VERSION)
diff --git a/status_test.go b/status_test.go
index 5b97b00..17ed94f 100644
--- a/status_test.go
+++ b/status_test.go
@@ -7,6 +7,7 @@
 )
 
 func TestStatusFile(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
@@ -27,6 +28,7 @@
 }
 
 func TestStatusList(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
diff --git a/submodule.go b/submodule.go
index 3882462..4a32ce4 100644
--- a/submodule.go
+++ b/submodule.go
@@ -14,9 +14,8 @@
 // SubmoduleUpdateOptions
 type SubmoduleUpdateOptions struct {
 	*CheckoutOpts
-	*RemoteCallbacks
+	*FetchOptions
 	CloneCheckoutStrategy CheckoutStrategy
-	Signature             *Signature
 }
 
 // Submodule
@@ -27,7 +26,6 @@
 type SubmoduleUpdate int
 
 const (
-	SubmoduleUpdateReset    SubmoduleUpdate = C.GIT_SUBMODULE_UPDATE_RESET
 	SubmoduleUpdateCheckout SubmoduleUpdate = C.GIT_SUBMODULE_UPDATE_CHECKOUT
 	SubmoduleUpdateRebase   SubmoduleUpdate = C.GIT_SUBMODULE_UPDATE_REBASE
 	SubmoduleUpdateMerge    SubmoduleUpdate = C.GIT_SUBMODULE_UPDATE_MERGE
@@ -37,7 +35,6 @@
 type SubmoduleIgnore int
 
 const (
-	SubmoduleIgnoreReset     SubmoduleIgnore = C.GIT_SUBMODULE_IGNORE_RESET
 	SubmoduleIgnoreNone      SubmoduleIgnore = C.GIT_SUBMODULE_IGNORE_NONE
 	SubmoduleIgnoreUntracked SubmoduleIgnore = C.GIT_SUBMODULE_IGNORE_UNTRACKED
 	SubmoduleIgnoreDirty     SubmoduleIgnore = C.GIT_SUBMODULE_IGNORE_DIRTY
@@ -71,13 +68,17 @@
 	SubmoduleRecurseOndemand SubmoduleRecurse = C.GIT_SUBMODULE_RECURSE_ONDEMAND
 )
 
+type SubmoduleCollection struct {
+	repo *Repository
+}
+
 func SubmoduleStatusIsUnmodified(status int) bool {
 	o := SubmoduleStatus(status) & ^(SubmoduleStatusInHead | SubmoduleStatusInIndex |
 		SubmoduleStatusInConfig | SubmoduleStatusInWd)
 	return o == 0
 }
 
-func (repo *Repository) LookupSubmodule(name string) (*Submodule, error) {
+func (c *SubmoduleCollection) Lookup(name string) (*Submodule, error) {
 	cname := C.CString(name)
 	defer C.free(unsafe.Pointer(cname))
 
@@ -86,7 +87,7 @@
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	ret := C.git_submodule_lookup(&sub.ptr, repo.ptr, cname)
+	ret := C.git_submodule_lookup(&sub.ptr, c.repo.ptr, cname)
 	if ret < 0 {
 		return nil, MakeGitError(ret)
 	}
@@ -107,21 +108,21 @@
 	}
 }
 
-func (repo *Repository) ForeachSubmodule(cbk SubmoduleCbk) error {
+func (c *SubmoduleCollection) Foreach(cbk SubmoduleCbk) error {
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
 	handle := pointerHandles.Track(cbk)
 	defer pointerHandles.Untrack(handle)
 
-	ret := C._go_git_visit_submodule(repo.ptr, handle)
+	ret := C._go_git_visit_submodule(c.repo.ptr, handle)
 	if ret < 0 {
 		return MakeGitError(ret)
 	}
 	return nil
 }
 
-func (repo *Repository) AddSubmodule(url, path string, use_git_link bool) (*Submodule, error) {
+func (c *SubmoduleCollection) Add(url, path string, use_git_link bool) (*Submodule, error) {
 	curl := C.CString(url)
 	defer C.free(unsafe.Pointer(curl))
 	cpath := C.CString(path)
@@ -132,7 +133,7 @@
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	ret := C.git_submodule_add_setup(&sub.ptr, repo.ptr, curl, cpath, cbool(use_git_link))
+	ret := C.git_submodule_add_setup(&sub.ptr, c.repo.ptr, curl, cpath, cbool(use_git_link))
 	if ret < 0 {
 		return nil, MakeGitError(ret)
 	}
@@ -161,23 +162,6 @@
 	return nil
 }
 
-func (sub *Submodule) Save() error {
-	runtime.LockOSThread()
-	defer runtime.UnlockOSThread()
-
-	ret := C.git_submodule_save(sub.ptr)
-	if ret < 0 {
-		return MakeGitError(ret)
-	}
-	return nil
-}
-
-func (sub *Submodule) Owner() *Repository {
-	repo := C.git_submodule_owner(sub.ptr)
-	//FIXME: how to handle dangling references ?
-	return &Repository{repo}
-}
-
 func (sub *Submodule) Name() string {
 	n := C.git_submodule_name(sub.ptr)
 	return C.GoString(n)
@@ -193,14 +177,16 @@
 	return C.GoString(n)
 }
 
-func (sub *Submodule) SetUrl(url string) error {
+func (c *SubmoduleCollection) SetUrl(submodule, url string) error {
+	csubmodule := C.CString(submodule)
+	defer C.free(unsafe.Pointer(csubmodule))
 	curl := C.CString(url)
 	defer C.free(unsafe.Pointer(curl))
 
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	ret := C.git_submodule_set_url(sub.ptr, curl)
+	ret := C.git_submodule_set_url(c.repo.ptr, csubmodule, curl)
 	if ret < 0 {
 		return MakeGitError(ret)
 	}
@@ -236,9 +222,19 @@
 	return SubmoduleIgnore(o)
 }
 
-func (sub *Submodule) SetIgnore(ignore SubmoduleIgnore) SubmoduleIgnore {
-	o := C.git_submodule_set_ignore(sub.ptr, C.git_submodule_ignore_t(ignore))
-	return SubmoduleIgnore(o)
+func (c *SubmoduleCollection) SetIgnore(submodule string, ignore SubmoduleIgnore) error {
+	csubmodule := C.CString(submodule)
+	defer C.free(unsafe.Pointer(csubmodule))
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ret := C.git_submodule_set_ignore(c.repo.ptr, csubmodule, C.git_submodule_ignore_t(ignore))
+	if ret < 0 {
+		return MakeGitError(ret)
+	}
+
+	return nil
 }
 
 func (sub *Submodule) UpdateStrategy() SubmoduleUpdate {
@@ -246,20 +242,33 @@
 	return SubmoduleUpdate(o)
 }
 
-func (sub *Submodule) SetUpdate(update SubmoduleUpdate) SubmoduleUpdate {
-	o := C.git_submodule_set_update(sub.ptr, C.git_submodule_update_t(update))
-	return SubmoduleUpdate(o)
+func (c *SubmoduleCollection) SetUpdate(submodule string, update SubmoduleUpdate) error {
+	csubmodule := C.CString(submodule)
+	defer C.free(unsafe.Pointer(csubmodule))
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ret := C.git_submodule_set_update(c.repo.ptr, csubmodule, C.git_submodule_update_t(update))
+	if ret < 0 {
+		return MakeGitError(ret)
+	}
+
+	return nil
 }
 
 func (sub *Submodule) FetchRecurseSubmodules() SubmoduleRecurse {
 	return SubmoduleRecurse(C.git_submodule_fetch_recurse_submodules(sub.ptr))
 }
 
-func (sub *Submodule) SetFetchRecurseSubmodules(recurse SubmoduleRecurse) error {
+func (c *SubmoduleCollection) SetFetchRecurseSubmodules(submodule string, recurse SubmoduleRecurse) error {
+	csubmodule := C.CString(submodule)
+	defer C.free(unsafe.Pointer(csubmodule))
+
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	ret := C.git_submodule_set_fetch_recurse_submodules(sub.ptr, C.git_submodule_recurse_t(recurse))
+	ret := C.git_submodule_set_fetch_recurse_submodules(c.repo.ptr, csubmodule, C.git_submodule_recurse_t(recurse))
 	if ret < 0 {
 		return MakeGitError(C.int(ret))
 	}
@@ -289,38 +298,15 @@
 }
 
 func (sub *Submodule) Open() (*Repository, error) {
-	repo := new(Repository)
-
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	ret := C.git_submodule_open(&repo.ptr, sub.ptr)
+	var ptr *C.git_repository
+	ret := C.git_submodule_open(&ptr, sub.ptr)
 	if ret < 0 {
 		return nil, MakeGitError(ret)
 	}
-	return repo, nil
-}
-
-func (sub *Submodule) Reload(force bool) error {
-	runtime.LockOSThread()
-	defer runtime.UnlockOSThread()
-
-	ret := C.git_submodule_reload(sub.ptr, cbool(force))
-	if ret < 0 {
-		return MakeGitError(ret)
-	}
-	return nil
-}
-
-func (repo *Repository) ReloadAllSubmodules(force bool) error {
-	runtime.LockOSThread()
-	defer runtime.UnlockOSThread()
-
-	ret := C.git_submodule_reload_all(repo.ptr, cbool(force))
-	if ret < 0 {
-		return MakeGitError(ret)
-	}
-	return nil
+	return newRepositoryFromC(ptr), nil
 }
 
 func (sub *Submodule) Update(init bool, opts *SubmoduleUpdateOptions) error {
@@ -349,14 +335,8 @@
 	}
 
 	populateCheckoutOpts(&ptr.checkout_opts, opts.CheckoutOpts)
-	populateRemoteCallbacks(&ptr.remote_callbacks, opts.RemoteCallbacks)
+	populateFetchOptions(&ptr.fetch_opts, opts.FetchOptions)
 	ptr.clone_checkout_strategy = C.uint(opts.CloneCheckoutStrategy)
 
-	sig, err := opts.Signature.toC()
-	if err != nil {
-		return err
-	}
-	ptr.signature = sig
-
 	return nil
 }
diff --git a/submodule_test.go b/submodule_test.go
index 27bc193..fa2e98c 100644
--- a/submodule_test.go
+++ b/submodule_test.go
@@ -5,16 +5,17 @@
 )
 
 func TestSubmoduleForeach(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
 	seedTestRepo(t, repo)
 
-	_, err := repo.AddSubmodule("http://example.org/submodule", "submodule", true)
+	_, err := repo.Submodules.Add("http://example.org/submodule", "submodule", true)
 	checkFatal(t, err)
 
 	i := 0
-	err = repo.ForeachSubmodule(func(sub *Submodule, name string) int {
+	err = repo.Submodules.Foreach(func(sub *Submodule, name string) int {
 		i++
 		return 0
 	})
diff --git a/tag.go b/tag.go
index 89ac8bd..81d7258 100644
--- a/tag.go
+++ b/tag.go
@@ -2,12 +2,18 @@
 
 /*
 #include <git2.h>
+
+extern int _go_git_tag_foreach(git_repository *repo, void *payload);
 */
 import "C"
+import (
+	"runtime"
+	"unsafe"
+)
 
 // Tag
 type Tag struct {
-	gitObject
+	Object
 	cast_ptr *C.git_tag
 }
 
@@ -24,7 +30,7 @@
 	return newSignatureFromC(cast_ptr)
 }
 
-func (t Tag) Target() Object {
+func (t Tag) Target() *Object {
 	var ptr *C.git_object
 	ret := C.git_tag_target(&ptr, t.cast_ptr)
 
@@ -42,3 +48,179 @@
 func (t Tag) TargetType() ObjectType {
 	return ObjectType(C.git_tag_target_type(t.cast_ptr))
 }
+
+type TagsCollection struct {
+	repo *Repository
+}
+
+func (c *TagsCollection) Create(
+	name string, commit *Commit, tagger *Signature, message string) (*Oid, error) {
+
+	oid := new(Oid)
+
+	cname := C.CString(name)
+	defer C.free(unsafe.Pointer(cname))
+
+	cmessage := C.CString(message)
+	defer C.free(unsafe.Pointer(cmessage))
+
+	taggerSig, err := tagger.toC()
+	if err != nil {
+		return nil, err
+	}
+	defer C.git_signature_free(taggerSig)
+
+	ctarget := commit.ptr
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ret := C.git_tag_create(oid.toC(), c.repo.ptr, cname, ctarget, taggerSig, cmessage, 0)
+	if ret < 0 {
+		return nil, MakeGitError(ret)
+	}
+
+	return oid, nil
+}
+
+func (c *TagsCollection) Remove(name string) error {
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	cname := C.CString(name)
+	defer C.free(unsafe.Pointer(cname))
+
+	ret := C.git_tag_delete(c.repo.ptr, cname)
+	if ret < 0 {
+		return MakeGitError(ret)
+	}
+
+	return nil
+}
+
+// CreateLightweight creates a new lightweight tag pointing to a commit
+// and returns the id of the target object.
+//
+// The name of the tag is validated for consistency (see git_tag_create() for the rules
+// https://libgit2.github.com/libgit2/#HEAD/group/tag/git_tag_create) and should
+// not conflict with an already existing tag name.
+//
+// If force is true and a reference already exists with the given name, it'll be replaced.
+//
+// The created tag is a simple reference and can be queried using
+// repo.References.Lookup("refs/tags/<name>"). The name of the tag (eg "v1.0.0")
+// is queried with ref.Shorthand().
+func (c *TagsCollection) CreateLightweight(name string, commit *Commit, force bool) (*Oid, error) {
+
+	oid := new(Oid)
+
+	cname := C.CString(name)
+	defer C.free(unsafe.Pointer(cname))
+
+	ctarget := commit.ptr
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	err := C.git_tag_create_lightweight(oid.toC(), c.repo.ptr, cname, ctarget, cbool(force))
+	if err < 0 {
+		return nil, MakeGitError(err)
+	}
+
+	return oid, nil
+}
+
+// List returns the names of all the tags in the repository,
+// eg: ["v1.0.1", "v2.0.0"].
+func (c *TagsCollection) List() ([]string, error) {
+	var strC C.git_strarray
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ecode := C.git_tag_list(&strC, c.repo.ptr)
+	if ecode < 0 {
+		return nil, MakeGitError(ecode)
+	}
+	defer C.git_strarray_free(&strC)
+
+	tags := makeStringsFromCStrings(strC.strings, int(strC.count))
+	return tags, nil
+}
+
+// ListWithMatch returns the names of all the tags in the repository
+// that match a given pattern.
+//
+// The pattern is a standard fnmatch(3) pattern http://man7.org/linux/man-pages/man3/fnmatch.3.html
+func (c *TagsCollection) ListWithMatch(pattern string) ([]string, error) {
+	var strC C.git_strarray
+
+	patternC := C.CString(pattern)
+	defer C.free(unsafe.Pointer(patternC))
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ecode := C.git_tag_list_match(&strC, patternC, c.repo.ptr)
+	if ecode < 0 {
+		return nil, MakeGitError(ecode)
+	}
+	defer C.git_strarray_free(&strC)
+
+	tags := makeStringsFromCStrings(strC.strings, int(strC.count))
+	return tags, nil
+}
+
+// TagForeachCallback is called for each tag in the repository.
+//
+// The name is the full ref name eg: "refs/tags/v1.0.0".
+//
+// Note that the callback is called for lightweight tags as well,
+// so repo.LookupTag() will return an error for these tags. Use
+// repo.References.Lookup() instead.
+type TagForeachCallback func(name string, id *Oid) error
+type tagForeachData struct {
+	callback TagForeachCallback
+	err      error
+}
+
+//export gitTagForeachCb
+func gitTagForeachCb(name *C.char, id *C.git_oid, handle unsafe.Pointer) int {
+	payload := pointerHandles.Get(handle)
+	data, ok := payload.(*tagForeachData)
+	if !ok {
+		panic("could not retrieve tag foreach CB handle")
+	}
+
+	err := data.callback(C.GoString(name), newOidFromC(id))
+	if err != nil {
+		data.err = err
+		return C.GIT_EUSER
+	}
+
+	return 0
+}
+
+// Foreach calls the callback for each tag in the repository.
+func (c *TagsCollection) Foreach(callback TagForeachCallback) error {
+	data := tagForeachData{
+		callback: callback,
+		err:      nil,
+	}
+
+	handle := pointerHandles.Track(&data)
+	defer pointerHandles.Untrack(handle)
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	err := C._go_git_tag_foreach(c.repo.ptr, handle)
+	if err == C.GIT_EUSER {
+		return data.err
+	}
+	if err < 0 {
+		return MakeGitError(err)
+	}
+
+	return nil
+}
diff --git a/tag_test.go b/tag_test.go
index 74f9fec..3404923 100644
--- a/tag_test.go
+++ b/tag_test.go
@@ -1,11 +1,13 @@
 package git
 
 import (
+	"errors"
 	"testing"
 	"time"
 )
 
 func TestCreateTag(t *testing.T) {
+	t.Parallel()
 	repo := createTestRepo(t)
 	defer cleanupTestRepo(t, repo)
 
@@ -24,6 +26,150 @@
 	compareStrings(t, commitId.String(), tag.TargetId().String())
 }
 
+func TestCreateTagLightweight(t *testing.T) {
+	t.Parallel()
+	repo := createTestRepo(t)
+	defer cleanupTestRepo(t, repo)
+
+	commitID, _ := seedTestRepo(t, repo)
+
+	commit, err := repo.LookupCommit(commitID)
+	checkFatal(t, err)
+
+	tagID, err := repo.Tags.CreateLightweight("v0.1.0", commit, false)
+	checkFatal(t, err)
+
+	_, err = repo.Tags.CreateLightweight("v0.1.0", commit, true)
+	checkFatal(t, err)
+
+	ref, err := repo.References.Lookup("refs/tags/v0.1.0")
+	checkFatal(t, err)
+
+	compareStrings(t, "refs/tags/v0.1.0", ref.Name())
+	compareStrings(t, "v0.1.0", ref.Shorthand())
+	compareStrings(t, tagID.String(), commitID.String())
+	compareStrings(t, commitID.String(), ref.Target().String())
+}
+
+func TestListTags(t *testing.T) {
+	t.Parallel()
+	repo := createTestRepo(t)
+	defer cleanupTestRepo(t, repo)
+
+	commitID, _ := seedTestRepo(t, repo)
+
+	commit, err := repo.LookupCommit(commitID)
+	checkFatal(t, err)
+
+	createTag(t, repo, commit, "v1.0.1", "Release v1.0.1")
+
+	commitID, _ = updateReadme(t, repo, "Release version 2")
+
+	commit, err = repo.LookupCommit(commitID)
+	checkFatal(t, err)
+
+	createTag(t, repo, commit, "v2.0.0", "Release v2.0.0")
+
+	expected := []string{
+		"v1.0.1",
+		"v2.0.0",
+	}
+
+	actual, err := repo.Tags.List()
+	checkFatal(t, err)
+
+	compareStringList(t, expected, actual)
+}
+
+func TestListTagsWithMatch(t *testing.T) {
+	t.Parallel()
+	repo := createTestRepo(t)
+	defer cleanupTestRepo(t, repo)
+
+	commitID, _ := seedTestRepo(t, repo)
+
+	commit, err := repo.LookupCommit(commitID)
+	checkFatal(t, err)
+
+	createTag(t, repo, commit, "v1.0.1", "Release v1.0.1")
+
+	commitID, _ = updateReadme(t, repo, "Release version 2")
+
+	commit, err = repo.LookupCommit(commitID)
+	checkFatal(t, err)
+
+	createTag(t, repo, commit, "v2.0.0", "Release v2.0.0")
+
+	expected := []string{
+		"v2.0.0",
+	}
+
+	actual, err := repo.Tags.ListWithMatch("v2*")
+	checkFatal(t, err)
+
+	compareStringList(t, expected, actual)
+
+	expected = []string{
+		"v1.0.1",
+	}
+
+	actual, err = repo.Tags.ListWithMatch("v1*")
+	checkFatal(t, err)
+
+	compareStringList(t, expected, actual)
+}
+
+func TestTagForeach(t *testing.T) {
+	t.Parallel()
+	repo := createTestRepo(t)
+	defer cleanupTestRepo(t, repo)
+
+	commitID, _ := seedTestRepo(t, repo)
+
+	commit, err := repo.LookupCommit(commitID)
+	checkFatal(t, err)
+
+	tag1 := createTag(t, repo, commit, "v1.0.1", "Release v1.0.1")
+
+	commitID, _ = updateReadme(t, repo, "Release version 2")
+
+	commit, err = repo.LookupCommit(commitID)
+	checkFatal(t, err)
+
+	tag2 := createTag(t, repo, commit, "v2.0.0", "Release v2.0.0")
+
+	expectedNames := []string{
+		"refs/tags/v1.0.1",
+		"refs/tags/v2.0.0",
+	}
+	actualNames := []string{}
+	expectedOids := []string{
+		tag1.String(),
+		tag2.String(),
+	}
+	actualOids := []string{}
+
+	err = repo.Tags.Foreach(func(name string, id *Oid) error {
+		actualNames = append(actualNames, name)
+		actualOids = append(actualOids, id.String())
+		return nil
+	})
+	checkFatal(t, err)
+
+	compareStringList(t, expectedNames, actualNames)
+	compareStringList(t, expectedOids, actualOids)
+
+	fakeErr := errors.New("fake error")
+
+	err = repo.Tags.Foreach(func(name string, id *Oid) error {
+		return fakeErr
+	})
+
+	if err != fakeErr {
+		t.Fatalf("Tags.Foreach() did not return the expected error, got %v", err)
+	}
+}
+
 func compareStrings(t *testing.T, expected, value string) {
 	if value != expected {
 		t.Fatalf("expected '%v', actual '%v'", expected, value)
@@ -39,7 +185,21 @@
 		When:  time.Date(2013, 03, 06, 14, 30, 0, 0, loc),
 	}
 
-	tagId, err := repo.CreateTag("v0.0.0", commit, sig, "This is a tag")
+	tagId, err := repo.Tags.Create("v0.0.0", commit, sig, "This is a tag")
+	checkFatal(t, err)
+	return tagId
+}
+
+func createTag(t *testing.T, repo *Repository, commit *Commit, name, message string) *Oid {
+	loc, err := time.LoadLocation("Europe/Bucharest")
+	checkFatal(t, err)
+	sig := &Signature{
+		Name:  "Rand Om Hacker",
+		Email: "random@hacker.com",
+		When:  time.Date(2013, 03, 06, 14, 30, 0, 0, loc),
+	}
+
+	tagId, err := repo.Tags.Create(name, commit, sig, message)
 	checkFatal(t, err)
 	return tagId
 }
diff --git a/tree.go b/tree.go
index cdb5581..27dd63d 100644
--- a/tree.go
+++ b/tree.go
@@ -23,7 +23,7 @@
 )
 
 type Tree struct {
-	gitObject
+	Object
 	cast_ptr *C.git_tree
 }
 
@@ -55,6 +55,24 @@
 	return newTreeEntry(entry)
 }
 
+// EntryById performs a lookup for a tree entry with the given SHA value.
+//
+// It returns a *TreeEntry that is owned by the Tree. You don't have to
+// free it, but you must not use it after the Tree is freed.
+//
+// Warning: this must examine every entry in the tree, so it is not fast.
+func (t Tree) EntryById(id *Oid) *TreeEntry {
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	entry := C.git_tree_entry_byid(t.cast_ptr, id.toC())
+	if entry == nil {
+		return nil
+	}
+
+	return newTreeEntry(entry)
+}
+
 // EntryByPath looks up an entry by its full path, recursing into
 // deeper trees if necessary (i.e. if there are slashes in the path)
 func (t Tree) EntryByPath(path string) (*TreeEntry, error) {
@@ -132,7 +150,7 @@
 	C.git_treebuilder_free(v.ptr)
 }
 
-func (v *TreeBuilder) Insert(filename string, id *Oid, filemode int) error {
+func (v *TreeBuilder) Insert(filename string, id *Oid, filemode Filemode) error {
 	cfilename := C.CString(filename)
 	defer C.free(unsafe.Pointer(cfilename))
 
diff --git a/tree_test.go b/tree_test.go
new file mode 100644
index 0000000..f5b6822
--- /dev/null
+++ b/tree_test.go
@@ -0,0 +1,65 @@
+package git
+
+import "testing"
+
+func TestTreeEntryById(t *testing.T) {
+	t.Parallel()
+	repo := createTestRepo(t)
+	defer cleanupTestRepo(t, repo)
+
+	_, treeID := seedTestRepo(t, repo)
+
+	tree, err := repo.LookupTree(treeID)
+	checkFatal(t, err)
+
+	id, err := NewOid("257cc5642cb1a054f08cc83f2d943e56fd3ebe99")
+	checkFatal(t, err)
+
+	entry := tree.EntryById(id)
+
+	if entry == nil {
+		t.Fatalf("entry id %v was not found", id)
+	}
+}
+
+func TestTreeBuilderInsert(t *testing.T) {
+	t.Parallel()
+	repo := createTestRepo(t)
+	defer cleanupTestRepo(t, repo)
+
+	subTree, err := repo.TreeBuilder()
+	if err != nil {
+		t.Fatalf("TreeBuilder: %v", err)
+	}
+	defer subTree.Free()
+
+	odb, err := repo.Odb()
+	if err != nil {
+		t.Fatalf("repo.Odb: %v", err)
+	}
+	blobId, err := odb.Write([]byte("hello"), ObjectBlob)
+	if err != nil {
+		t.Fatalf("odb.Write: %v", err)
+	}
+	if err = subTree.Insert("subfile", blobId, FilemodeBlobExecutable); err != nil {
+		t.Fatalf("TreeBuilder.Insert: %v", err)
+	}
+	treeID, err := subTree.Write()
+	if err != nil {
+		t.Fatalf("TreeBuilder.Write: %v", err)
+	}
+
+	tree, err := repo.LookupTree(treeID)
+	if err != nil {
+		t.Fatalf("LookupTree: %v", err)
+	}
+
+	entry, err := tree.EntryByPath("subfile")
+	if err != nil {
+		t.Fatalf("tree.EntryByPath(%q): %v", "subfile", err)
+	}
+
+	if !entry.Id.Equal(blobId) {
+		t.Fatalf("got oid %v, want %v", entry.Id, blobId)
+	}
+}
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/walk.go b/walk.go
index d02044a..ab1de61 100644
--- a/walk.go
+++ b/walk.go
@@ -190,6 +190,10 @@
 	return nil
 }
 
+func (v *RevWalk) SimplifyFirstParent() {
+	C.git_revwalk_simplify_first_parent(v.ptr)
+}
+
 func (v *RevWalk) Sorting(sm SortType) {
 	C.git_revwalk_sorting(v.ptr, C.uint(sm))
 }
diff --git a/wrapper.c b/wrapper.c
index 017168d..11c2f32 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -5,6 +5,17 @@
 
 typedef int (*gogit_submodule_cbk)(git_submodule *sm, const char *name, void *payload);
 
+void _go_git_populate_remote_cb(git_clone_options *opts)
+{
+	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);
@@ -59,7 +70,7 @@
 		lcb = (git_diff_line_cb)&diffForEachLineCb;
 	}
 
-	return git_diff_foreach(diff, fcb, hcb, lcb, payload);
+	return git_diff_foreach(diff, fcb, NULL, hcb, lcb, payload);
 }
 
 int _go_git_diff_blobs(git_blob *old, const char *old_path, git_blob *new, const char *new_path, git_diff_options *opts, int eachFile, int eachHunk, int eachLine, void *payload)
@@ -80,7 +91,7 @@
 		lcb = (git_diff_line_cb)&diffForEachLineCb;
 	}
 
-	return git_diff_blobs(old, old_path, new, new_path, opts, fcb, hcb, lcb, payload);
+	return git_diff_blobs(old, old_path, new, new_path, opts, fcb, NULL, hcb, lcb, payload);
 }
 
 void _go_git_setup_diff_notify_callbacks(git_diff_options *opts) {
@@ -103,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);
@@ -131,4 +129,55 @@
 	return git_index_remove_all(index, pathspec, cb, callback);
 }
 
+int _go_git_tag_foreach(git_repository *repo, void *payload)
+{
+    return git_tag_foreach(repo, (git_tag_foreach_cb)&gitTagForeachCb, payload);
+}
+
+int _go_git_merge_file(git_merge_file_result* out, char* ancestorContents, size_t ancestorLen, char* ancestorPath, unsigned int ancestorMode, char* oursContents, size_t oursLen, char* oursPath, unsigned int oursMode, char* theirsContents, size_t theirsLen, char* theirsPath, unsigned int theirsMode, git_merge_file_options* copts) {
+	git_merge_file_input ancestor = GIT_MERGE_FILE_INPUT_INIT;
+	git_merge_file_input ours = GIT_MERGE_FILE_INPUT_INIT;
+	git_merge_file_input theirs = GIT_MERGE_FILE_INPUT_INIT;
+
+	ancestor.ptr = ancestorContents;
+	ancestor.size = ancestorLen;
+	ancestor.path = ancestorPath;
+	ancestor.mode = ancestorMode;
+
+	ours.ptr = oursContents;
+	ours.size = oursLen;
+	ours.path = oursPath;
+	ours.mode = oursMode;
+
+	theirs.ptr = theirsContents;
+	theirs.size = theirsLen;
+	theirs.path = theirsPath;
+	theirs.mode = theirsMode;
+
+	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 */