Use github.com/src-d/go-git for improved deployability.

Change-Id: I3aa4299af2f430ba6890cfe3e374fdc61e6ba554
diff --git a/cache/cas.go b/cache/cas.go
index f5100fb..32c01ab 100644
--- a/cache/cas.go
+++ b/cache/cas.go
@@ -20,7 +20,7 @@
 	"os"
 	"path/filepath"
 
-	git "github.com/libgit2/git2go"
+	"gopkg.in/src-d/go-git.v4/plumbing"
 )
 
 // CAS is a content addressable storage. It is intended to be used
@@ -41,19 +41,19 @@
 	}, nil
 }
 
-func (c *CAS) path(id git.Oid) string {
+func (c *CAS) path(id plumbing.Hash) string {
 	str := id.String()
 	return fmt.Sprintf("%s/%s/%s", c.dir, str[:3], str[3:])
 }
 
 // Open returns a file corresponding to the blob, opened for reading.
-func (c *CAS) Open(id git.Oid) (*os.File, bool) {
+func (c *CAS) Open(id plumbing.Hash) (*os.File, bool) {
 	f, err := os.Open(c.path(id))
 	return f, err == nil
 }
 
 // Write writes the given data under the given ID atomically.
-func (c *CAS) Write(id git.Oid, data []byte) error {
+func (c *CAS) Write(id plumbing.Hash, data []byte) error {
 	// TODO(hanwen): we should run data through the git hash to
 	// verify that it is what it says it is.
 	f, err := ioutil.TempFile(c.dir, "tmp")
diff --git a/cache/gitcache.go b/cache/gitcache.go
index 8625e4f..112cd66 100644
--- a/cache/gitcache.go
+++ b/cache/gitcache.go
@@ -27,7 +27,7 @@
 	"strings"
 	"time"
 
-	git "github.com/libgit2/git2go"
+	git "gopkg.in/src-d/go-git.v4"
 )
 
 // gitCache manages a set of bare git repositories.  Repositories are
@@ -170,7 +170,7 @@
 	if err != nil {
 		return nil
 	}
-	repo, err := git.OpenRepository(p)
+	repo, err := git.PlainOpen(p)
 	if err != nil {
 		return nil
 	}
@@ -197,6 +197,6 @@
 			return nil, err
 		}
 	}
-	repo, err := git.OpenRepository(p)
+	repo, err := git.PlainOpen(p)
 	return repo, err
 }
diff --git a/cache/gitcache_test.go b/cache/gitcache_test.go
index 06618b6..209ac76 100644
--- a/cache/gitcache_test.go
+++ b/cache/gitcache_test.go
@@ -22,7 +22,7 @@
 	"testing"
 	"time"
 
-	git "github.com/libgit2/git2go"
+	git "gopkg.in/src-d/go-git.v4"
 )
 
 func TestGitCache(t *testing.T) {
@@ -103,7 +103,7 @@
 	done := make(chan res, 10)
 	for i := 0; i < cap(done); i++ {
 		go func() {
-			repo, err := git.OpenRepository(dir + "/.git")
+			repo, err := git.PlainOpen(dir)
 			done <- res{err, repo}
 		}()
 	}
@@ -111,8 +111,7 @@
 	for i := 0; i < cap(done); i++ {
 		r := <-done
 		if r.err != nil {
-			t.Errorf("OpenRepository: %v", err)
+			t.Errorf("OpenRepository: %v", r.err)
 		}
-		defer r.repo.Free()
 	}
 }
diff --git a/cache/lazyrepo.go b/cache/lazyrepo.go
index 18aacdc..85414f6 100644
--- a/cache/lazyrepo.go
+++ b/cache/lazyrepo.go
@@ -18,7 +18,7 @@
 	"log"
 	"sync"
 
-	git "github.com/libgit2/git2go"
+	git "gopkg.in/src-d/go-git.v4"
 )
 
 // LazyRepo represents a git repository that might be fetched on
diff --git a/cache/treecache.go b/cache/treecache.go
index a712d32..6943d34 100644
--- a/cache/treecache.go
+++ b/cache/treecache.go
@@ -15,14 +15,20 @@
 package cache
 
 import (
+	"encoding/hex"
 	"encoding/json"
 	"fmt"
+	"io"
 	"io/ioutil"
 	"os"
 	"path/filepath"
 
 	"github.com/google/slothfs/gitiles"
-	git "github.com/libgit2/git2go"
+	"gopkg.in/src-d/go-git.v4/plumbing"
+	"gopkg.in/src-d/go-git.v4/plumbing/filemode"
+	"gopkg.in/src-d/go-git.v4/plumbing/object"
+
+	git "gopkg.in/src-d/go-git.v4"
 )
 
 // A TreeCache caches recursively expanded trees by their git commit and tree IDs.
@@ -38,13 +44,13 @@
 	return &TreeCache{dir: d}, nil
 }
 
-func (c *TreeCache) path(id *git.Oid) string {
+func (c *TreeCache) path(id *plumbing.Hash) string {
 	str := id.String()
 	return fmt.Sprintf("%s/%s/%s", c.dir, str[:3], str[3:])
 }
 
 // Get returns a tree, if available.
-func (c *TreeCache) Get(id *git.Oid) (*gitiles.Tree, error) {
+func (c *TreeCache) Get(id *plumbing.Hash) (*gitiles.Tree, error) {
 	content, err := ioutil.ReadFile(c.path(id))
 	if err != nil {
 		return nil, err
@@ -57,14 +63,26 @@
 	return &t, nil
 }
 
+func parseID(s string) (*plumbing.Hash, error) {
+	b, err := hex.DecodeString(s)
+	if err != nil || len(b) != 20 {
+		return nil, fmt.Errorf("NewOid(%q): %v", s, err)
+	}
+
+	var h plumbing.Hash
+	copy(h[:], b)
+	return &h, nil
+}
+
 // Add adds a Tree to the cache
-func (c *TreeCache) Add(id *git.Oid, tree *gitiles.Tree) error {
+func (c *TreeCache) Add(id *plumbing.Hash, tree *gitiles.Tree) error {
 	if err := c.add(id, tree); err != nil {
 		return err
 	}
 
 	if id.String() != tree.ID {
-		treeID, err := git.NewOid(tree.ID)
+		// Ugh: error handling?
+		treeID, err := parseID(tree.ID)
 		if err != nil {
 			return err
 		}
@@ -73,7 +91,7 @@
 	return nil
 }
 
-func (c *TreeCache) add(id *git.Oid, tree *gitiles.Tree) error {
+func (c *TreeCache) add(id *plumbing.Hash, tree *gitiles.Tree) error {
 	f, err := ioutil.TempFile(c.dir, "tmp")
 	if err != nil {
 		return err
@@ -103,88 +121,77 @@
 }
 
 // GetTree loads the Tree from an on-disk Git repository.
-func GetTree(repo *git.Repository, id *git.Oid) (*gitiles.Tree, error) {
-	obj, err := repo.Lookup(id)
-	if err != nil {
-		return nil, err
+func GetTree(repo *git.Repository, id *plumbing.Hash) (*gitiles.Tree, error) {
+	treeObj, err := repo.TreeObject(*id)
+	if treeObj == nil {
+		commit, e2 := repo.CommitObject(*id)
+		if e2 != nil {
+			return nil, e2
+		}
+		treeObj, err = repo.TreeObject(commit.TreeHash)
 	}
-	defer obj.Free()
-
-	peeledObj, err := obj.Peel(git.ObjectTree)
-	if err != nil {
-		return nil, err
-	}
-	if peeledObj != obj {
-		defer peeledObj.Free()
-	}
-
-	asTree, err := peeledObj.AsTree()
 	if err != nil {
 		return nil, err
 	}
 
 	var tree gitiles.Tree
-	tree.ID = obj.Id().String()
 
-	odb, err := repo.Odb()
-	if err != nil {
-		return nil, err
-	}
-	defer odb.Free()
+	tree.ID = id.String()
+	walker := object.NewTreeWalker(treeObj, true, map[plumbing.Hash]bool{})
+	defer walker.Close()
+loop:
+	for {
+		name, entry, err := walker.Next()
+		if err == io.EOF {
+			break
+		}
 
-	cb := func(n string, e *git.TreeEntry) int {
-		t := ""
+		if err != nil {
+			return nil, err
+		}
+
 		var size *int
-		switch e.Type {
-		case git.ObjectTree:
-			return 0
-		case git.ObjectCommit:
+		var t string
+		var blob *object.Blob
+		switch entry.Mode {
+		case filemode.Dir:
+			continue loop
+		case filemode.Submodule:
 			t = "commit"
-		case git.ObjectBlob:
+		case filemode.Symlink, filemode.Regular, filemode.Executable:
 			t = "blob"
-			sz, _, rhErr := odb.ReadHeader(e.Id)
-			if rhErr != nil {
-				err = rhErr
-				return -1
+			blob, err = repo.BlobObject(entry.Hash)
+			if err != nil {
+				return nil, err
 			}
 			size = new(int)
-			*size = int(sz)
-
+			*size = int(blob.Size)
 		default:
-			err = fmt.Errorf("illegal object %d for %s", e.Type, n)
+			err = fmt.Errorf("illegal mode %d for %s", entry.Mode, name)
 		}
 
 		gEntry := gitiles.TreeEntry{
-			Name: filepath.Join(n, e.Name),
-			ID:   e.Id.String(),
-			Mode: int(e.Filemode),
+			Name: name,
+			ID:   entry.Hash.String(),
+			Mode: int(entry.Mode),
 			Size: size,
 			Type: t,
 		}
-		if e.Filemode == git.FilemodeLink {
-			obj, lookErr := repo.Lookup(e.Id)
+		if entry.Mode == filemode.Symlink {
+			r, err := blob.Reader()
 			if err != nil {
-				err = lookErr
-				return -1
+				return nil, err
 			}
-			defer obj.Free()
-
-			blob, blobErr := obj.AsBlob()
-			if blobErr != nil {
-				err = blobErr
-				return -1
+			c, err := ioutil.ReadAll(r)
+			r.Close()
+			if err != nil {
+				return nil, err
 			}
-
-			target := string(blob.Contents())
+			target := string(c)
 			gEntry.Target = &target
 		}
 
 		tree.Entries = append(tree.Entries, gEntry)
-		return 0
-	}
-
-	if err := asTree.Walk(cb); err != nil {
-		return nil, err
 	}
 
 	return &tree, nil
diff --git a/cache/treecache_test.go b/cache/treecache_test.go
index ba307c6..9c80903 100644
--- a/cache/treecache_test.go
+++ b/cache/treecache_test.go
@@ -21,7 +21,10 @@
 	"testing"
 
 	"github.com/google/slothfs/gitiles"
-	git "github.com/libgit2/git2go"
+	git "gopkg.in/src-d/go-git.v4"
+	"gopkg.in/src-d/go-git.v4/plumbing"
+	"gopkg.in/src-d/go-git.v4/plumbing/filemode"
+	"gopkg.in/src-d/go-git.v4/plumbing/object"
 )
 
 func newInt(i int) *int          { return &i }
@@ -95,16 +98,12 @@
 		t.Fatalf("getTree: %v", err)
 	}
 
-	randomID, err := git.NewOid("abcd1234abcd1234abcd1234abcd1234abcd1234")
-	if err != nil {
-		t.Fatalf("NewOid: %v", err)
-	}
-
-	if err := cache.Add(randomID, treeResp); err != nil {
+	randomID := plumbing.NewHash("abcd1234abcd1234abcd1234abcd1234abcd1234")
+	if err := cache.Add(&randomID, treeResp); err != nil {
 		t.Fatalf("cache.add %v", err)
 	}
 
-	roundtrip, err := cache.Get(randomID)
+	roundtrip, err := cache.Get(&randomID)
 	if err != nil {
 		t.Fatalf("cache.get: %v", err)
 	}
@@ -123,8 +122,8 @@
 
 type testRepo struct {
 	dir       string
-	subTreeID *git.Oid
-	treeID    *git.Oid
+	subTreeID *plumbing.Hash
+	treeID    *plumbing.Hash
 	repo      *git.Repository
 }
 
@@ -132,13 +131,45 @@
 	os.RemoveAll(r.dir)
 }
 
+func writeBlob(repo *git.Repository, content []byte) (*plumbing.Hash, error) {
+	obj := repo.Storer.NewEncodedObject()
+	obj.SetType(plumbing.BlobObject)
+
+	w, err := obj.Writer()
+	if err != nil {
+		return nil, err
+	}
+	if _, err := w.Write(content); err != nil {
+		return nil, err
+	}
+
+	if err := w.Close(); err != nil {
+		return nil, err
+	}
+
+	h, err := repo.Storer.SetEncodedObject(obj)
+	return &h, err
+}
+
+func writeTree(repo *git.Repository, entries []object.TreeEntry) (*plumbing.Hash, error) {
+	t := object.Tree{
+		Entries: entries,
+	}
+
+	obj := repo.Storer.NewEncodedObject()
+	t.Encode(obj)
+
+	h, err := repo.Storer.SetEncodedObject(obj)
+	return &h, err
+}
+
 func initTest() (*testRepo, error) {
 	d, err := ioutil.TempDir("", "tmpgit")
 	if err != nil {
 		return nil, err
 	}
 
-	repo, err := git.InitRepository(d, true)
+	repo, err := git.PlainInit(d, true)
 	if err != nil {
 		return nil, err
 	}
@@ -146,47 +177,27 @@
 	c1 := []byte("hello")
 	c2 := []byte("goedemiddag")
 
-	id1, err := repo.CreateBlobFromBuffer(c1)
+	id1, err := writeBlob(repo, c1)
 	if err != nil {
 		return nil, err
 	}
 
-	id2, err := repo.CreateBlobFromBuffer(c2)
+	id2, err := writeBlob(repo, c2)
 	if err != nil {
 		return nil, err
 	}
-	b, err := repo.TreeBuilder()
 
-	if err != nil {
-		return nil, err
-	}
-	defer b.Free()
+	subTreeID, err := writeTree(repo,
+		[]object.TreeEntry{
+			{Name: "f1", Hash: *id1, Mode: filemode.Regular},
+			{Name: "f2", Hash: *id2, Mode: filemode.Executable},
+		})
 
-	if err = b.Insert("f1", id1, git.FilemodeBlob); err != nil {
-		return nil, err
-	}
-
-	if err := b.Insert("f2", id2, git.FilemodeBlobExecutable); err != nil {
-		return nil, err
-	}
-	subTreeID, err := b.Write()
-
-	b, err = repo.TreeBuilder()
-	if err != nil {
-		return nil, err
-	}
-	defer b.Free()
-
-	if err = b.Insert("dir", subTreeID, git.FilemodeTree); err != nil {
-		return nil, err
-	}
-	if err = b.Insert("link", id1, git.FilemodeLink); err != nil {
-		return nil, err
-	}
-	treeID, err := b.Write()
-	if err != nil {
-		return nil, err
-	}
+	treeID, err := writeTree(repo,
+		[]object.TreeEntry{
+			{Name: "dir", Hash: *subTreeID, Mode: filemode.Dir},
+			{Name: "link", Hash: *id1, Mode: filemode.Symlink},
+		})
 
 	return &testRepo{
 		dir:       d,
diff --git a/fs/gitilesconfigfs.go b/fs/gitilesconfigfs.go
index ce4fda1..19ae65a 100644
--- a/fs/gitilesconfigfs.go
+++ b/fs/gitilesconfigfs.go
@@ -15,13 +15,16 @@
 package fs
 
 import (
+	"encoding/hex"
+	"fmt"
 	"log"
 
+	"gopkg.in/src-d/go-git.v4/plumbing"
+
 	"github.com/google/slothfs/cache"
 	"github.com/google/slothfs/gitiles"
 	"github.com/hanwen/go-fuse/fuse"
 	"github.com/hanwen/go-fuse/fuse/nodefs"
-	"github.com/libgit2/git2go"
 )
 
 type gitilesConfigFSRoot struct {
@@ -37,8 +40,19 @@
 	r.fsConn = fsConn
 }
 
+func parseID(s string) (*plumbing.Hash, error) {
+	b, err := hex.DecodeString(s)
+	if err != nil || len(b) != 20 {
+		return nil, fmt.Errorf("NewOid(%q): %v", s, err)
+	}
+
+	var h plumbing.Hash
+	copy(h[:], b)
+	return &h, nil
+}
+
 func (r *gitilesConfigFSRoot) Lookup(out *fuse.Attr, name string, context *fuse.Context) (*nodefs.Inode, fuse.Status) {
-	id, err := git.NewOid(name)
+	id, err := parseID(name)
 	if err != nil {
 		return nil, fuse.ENOENT
 	}
diff --git a/fs/gitilesfs.go b/fs/gitilesfs.go
index 40c22e3..dd96b08 100644
--- a/fs/gitilesfs.go
+++ b/fs/gitilesfs.go
@@ -18,6 +18,7 @@
 	"encoding/json"
 	"fmt"
 	"io"
+	"io/ioutil"
 	"log"
 	"os"
 	"path/filepath"
@@ -26,11 +27,13 @@
 	"syscall"
 	"time"
 
+	"gopkg.in/src-d/go-git.v4/plumbing"
+	"gopkg.in/src-d/go-git.v4/plumbing/object"
+
 	"github.com/google/slothfs/cache"
 	"github.com/google/slothfs/gitiles"
 	"github.com/hanwen/go-fuse/fuse"
 	"github.com/hanwen/go-fuse/fuse/nodefs"
-	git "github.com/libgit2/git2go"
 )
 
 // gitilesRoot is the root for a FUSE filesystem backed by a Gitiles
@@ -48,12 +51,12 @@
 	handleLessIO bool
 
 	// OID => path
-	shaMap map[git.Oid]string
+	shaMap map[plumbing.Hash]string
 
 	lazyRepo *cache.LazyRepo
 
 	fetchingCond *sync.Cond
-	fetching     map[git.Oid]bool
+	fetching     map[plumbing.Hash]bool
 }
 
 type linkNode struct {
@@ -93,7 +96,7 @@
 	// Data from Git metadata.
 	mode       uint32
 	size       int64
-	id         git.Oid
+	id         plumbing.Hash
 	linkTarget []byte
 
 	// if set, clone the repo on reading this file.
@@ -196,7 +199,7 @@
 
 // openFile returns a file handle for the given blob. If `clone` is
 // given, we may try a clone of the git repository
-func (r *gitilesRoot) openFile(id git.Oid, clone bool) (*os.File, error) {
+func (r *gitilesRoot) openFile(id plumbing.Hash, clone bool) (*os.File, error) {
 	f, ok := r.cache.Blob.Open(id)
 	if ok {
 		return f, nil
@@ -211,7 +214,7 @@
 	return f, nil
 }
 
-func (r *gitilesRoot) fetchFile(id git.Oid, clone bool) (*os.File, error) {
+func (r *gitilesRoot) fetchFile(id plumbing.Hash, clone bool) (*os.File, error) {
 	r.fetchingCond.L.Lock()
 	defer r.fetchingCond.L.Unlock()
 
@@ -242,7 +245,17 @@
 	return nil, err
 }
 
-func (r *gitilesRoot) fetchFileExpensive(id git.Oid, clone bool) error {
+func readBlob(blob *object.Blob) ([]byte, error) {
+	r, err := blob.Reader()
+	if err != nil {
+		return nil, err
+	}
+	defer r.Close()
+
+	return ioutil.ReadAll(r)
+}
+
+func (r *gitilesRoot) fetchFileExpensive(id plumbing.Hash, clone bool) error {
 	repo := r.lazyRepo.Repository()
 	if clone && repo == nil {
 		r.lazyRepo.Clone()
@@ -250,10 +263,12 @@
 
 	var content []byte
 	if repo != nil {
-		blob, err := repo.LookupBlob(&id)
+		blob, err := repo.BlobObject(id)
 		if err == nil {
-			content = blob.Contents()
-			blob.Free()
+			content, err = readBlob(blob)
+			if err != nil {
+				content = nil
+			}
 		}
 	}
 
@@ -309,12 +324,12 @@
 		service:      service,
 		nodeCache:    newNodeCache(),
 		cache:        c,
-		shaMap:       map[git.Oid]string{},
+		shaMap:       map[plumbing.Hash]string{},
 		tree:         tree,
 		opts:         options,
 		lazyRepo:     cache.NewLazyRepo(options.CloneURL, c),
 		fetchingCond: sync.NewCond(&sync.Mutex{}),
-		fetching:     map[git.Oid]bool{},
+		fetching:     map[plumbing.Hash]bool{},
 	}
 
 	return r
@@ -387,7 +402,7 @@
 		dir, base := filepath.Split(p)
 
 		parent := r.pathTo(fsConn, dir)
-		id, err := git.NewOid(e.ID)
+		id, err := parseID(e.ID)
 		if err != nil {
 			return err
 		}
diff --git a/fs/manifestfs.go b/fs/manifestfs.go
index 1c96928..018efcf 100644
--- a/fs/manifestfs.go
+++ b/fs/manifestfs.go
@@ -26,7 +26,6 @@
 	"github.com/google/slothfs/manifest"
 	"github.com/hanwen/go-fuse/fuse"
 	"github.com/hanwen/go-fuse/fuse/nodefs"
-	git "github.com/libgit2/git2go"
 )
 
 type manifestFSRoot struct {
@@ -68,7 +67,7 @@
 	}
 
 	for _, p := range opts.Manifest.Project {
-		if _, err := git.NewOid(p.Revision); err != nil {
+		if _, err := parseID(p.Revision); err != nil {
 			return nil, fmt.Errorf("project %s revision %q does not parse: %v", p.Name, p.Revision, err)
 		}
 	}
@@ -226,7 +225,7 @@
 	out := make(chan resultT, len(mf.Project))
 	for _, p := range mf.Project {
 		go func(p manifest.Project) {
-			revID, err := git.NewOid(p.Revision)
+			revID, err := parseID(p.Revision)
 			if err != nil {
 				out <- resultT{p.GetPath(), nil, err}
 				return
@@ -236,7 +235,6 @@
 			cached := (err == nil && tree != nil)
 			if err != nil {
 				if repo := c.Git.OpenLocal(p.CloneURL); repo != nil {
-					defer repo.Free()
 					tree, err = cache.GetTree(repo, revID)
 				}
 			}
diff --git a/fs/manifestfs_test.go b/fs/manifestfs_test.go
index 4f0822b..b17ddf9 100644
--- a/fs/manifestfs_test.go
+++ b/fs/manifestfs_test.go
@@ -32,8 +32,6 @@
 	"github.com/google/slothfs/manifest"
 	"github.com/hanwen/go-fuse/fuse"
 	"github.com/hanwen/go-fuse/fuse/nodefs"
-
-	git "github.com/libgit2/git2go"
 )
 
 func newManifestTestFixture(mf *manifest.Manifest) (*testFixture, error) {
@@ -105,9 +103,9 @@
 		t.Fatal("mount", err)
 	}
 
-	headID, err := git.NewOid(headSHA1)
+	headID, err := parseID(headSHA1)
 	if err != nil {
-		t.Fatalf("NewOid(%q): %v", headSHA1, err)
+		t.Fatalf("parseID(%s): %v", headSHA1, err)
 	}
 
 	tree, err := fix.cache.Tree.Get(headID)
diff --git a/fs/nodecache.go b/fs/nodecache.go
index ad7e206..daa84bf 100644
--- a/fs/nodecache.go
+++ b/fs/nodecache.go
@@ -17,11 +17,11 @@
 import (
 	"sync"
 
-	git "github.com/libgit2/git2go"
+	"gopkg.in/src-d/go-git.v4/plumbing"
 )
 
 type nodeCacheKey struct {
-	ID   git.Oid
+	ID   plumbing.Hash
 	xbit bool
 }
 
@@ -44,7 +44,7 @@
 	}
 }
 
-func (c *nodeCache) get(id *git.Oid, xbit bool) *gitilesNode {
+func (c *nodeCache) get(id *plumbing.Hash, xbit bool) *gitilesNode {
 	c.mu.RLock()
 	defer c.mu.RUnlock()
 
diff --git a/populate/deref.go b/populate/deref.go
index 7e82553..a7e619f 100644
--- a/populate/deref.go
+++ b/populate/deref.go
@@ -15,14 +15,38 @@
 package populate
 
 import (
+	"encoding/hex"
 	"fmt"
+	"log"
+
+	"gopkg.in/src-d/go-git.v4/plumbing"
 
 	"github.com/google/slothfs/gitiles"
 	"github.com/google/slothfs/manifest"
-
-	git "github.com/libgit2/git2go"
 )
 
+func parseID(s string) (*plumbing.Hash, error) {
+	b, err := hex.DecodeString(s)
+	if err != nil {
+		return nil, fmt.Errorf("parseID(%q): %v", s, err)
+	}
+	if len(b) != 20 {
+		return nil, fmt.Errorf("hash must be 20 hex bytes")
+	}
+
+	var h plumbing.Hash
+	copy(h[:], b)
+	return &h, nil
+}
+
+func gitID(s string) *plumbing.Hash {
+	h, err := parseID(s)
+	if err != nil {
+		log.Panic(err)
+	}
+	return h
+}
+
 // FetchManifest gets the default manifest file from a Gitiles server.
 func FetchManifest(service *gitiles.Service, repo, branch string) (*manifest.Manifest, error) {
 	project := service.NewRepoService(repo)
@@ -56,7 +80,7 @@
 		// According to the repo doc, the revision should be a branch,
 		// either like 'refs/heads/master' or 'master'. We abuse this field by
 		// also allowing commit SHA1s.
-		if _, err := git.NewOid(rev); err == nil {
+		if _, err := parseID(rev); err == nil {
 			// Already a SHA1, don't change.
 			continue
 		}
diff --git a/populate/e2e_test.go b/populate/e2e_test.go
index 505c3fb..67ea461 100644
--- a/populate/e2e_test.go
+++ b/populate/e2e_test.go
@@ -24,14 +24,14 @@
 	"reflect"
 	"testing"
 
+	"gopkg.in/src-d/go-git.v4/plumbing"
+
 	"github.com/google/slothfs/cache"
 	"github.com/google/slothfs/fs"
 	"github.com/google/slothfs/gitiles"
 	"github.com/google/slothfs/manifest"
 	"github.com/hanwen/go-fuse/fuse"
 	"github.com/hanwen/go-fuse/fuse/nodefs"
-
-	git "github.com/libgit2/git2go"
 )
 
 // a bunch of random sha1s.
@@ -42,12 +42,12 @@
 	"7ba00d0407ed4467c874ab45bb47fcb82fe63fac",
 }
 
-func gitID(s string) *git.Oid {
-	i, err := git.NewOid(s)
+func getID(s string) *plumbing.Hash {
+	h, err := parseID(s)
 	if err != nil {
-		log.Panicf("NewOid(%q): %v", i, err)
+		log.Panic(err)
 	}
-	return i
+	return h
 }
 
 func newInt(i int) *int {
diff --git a/populate/repotree.go b/populate/repotree.go
index ede6d6b..bbbaa69 100644
--- a/populate/repotree.go
+++ b/populate/repotree.go
@@ -25,7 +25,7 @@
 	"sort"
 	"strings"
 
-	git "github.com/libgit2/git2go"
+	"gopkg.in/src-d/go-git.v4/plumbing"
 
 	"github.com/google/slothfs/gitiles"
 	"github.com/google/slothfs/manifest"
@@ -35,7 +35,7 @@
 // repoTree node.
 type fileInfo struct {
 	// the SHA1 of the file. This can be nil if getting it was too expensive.
-	sha1 *git.Oid
+	sha1 *plumbing.Hash
 }
 
 // repoTree is a nested set of Git repositories.
@@ -128,7 +128,7 @@
 
 	for _, e := range tree.Entries {
 		fi := &fileInfo{}
-		fi.sha1, err = git.NewOid(e.ID)
+		fi.sha1, err = parseID(e.ID)
 		if err != nil {
 			return err
 		}