Also insert trees into cache if they came from a Git repository.

If Git repositories are on spinning storage, the trees are relatively
expensive to construct.

Change-Id: I49680a5b254f0f2b0c0190e439c512ce2625a3fb
diff --git a/fs/manifestfs.go b/fs/manifestfs.go
index 2450c02..b5b6ebc 100644
--- a/fs/manifestfs.go
+++ b/fs/manifestfs.go
@@ -231,6 +231,7 @@
 			}
 
 			tree, err := c.Tree.Get(revID)
+			cached := (err == nil && tree != nil)
 			if err != nil {
 				if repo := c.Git.OpenLocal(p.CloneURL); repo != nil {
 					defer repo.Free()
@@ -242,10 +243,11 @@
 				repoService := service.NewRepoService(p.Name)
 
 				tree, err = repoService.GetTree(p.Revision, "", true)
-				if err == nil {
-					if err := c.Tree.Add(revID, tree); err != nil {
-						log.Printf("treeCache.Add: %v", err)
-					}
+			}
+
+			if !cached && tree != nil && err == nil {
+				if err := c.Tree.Add(revID, tree); err != nil {
+					log.Printf("treeCache.Add: %v", err)
 				}
 			}
 
diff --git a/fs/manifestfs_test.go b/fs/manifestfs_test.go
index 05c1ca9..2f3aa86 100644
--- a/fs/manifestfs_test.go
+++ b/fs/manifestfs_test.go
@@ -18,9 +18,11 @@
 	"fmt"
 	"io/ioutil"
 	"os"
+	"os/exec"
 	"path/filepath"
 	"reflect"
 	"sort"
+	"strings"
 	"syscall"
 	"testing"
 	"time"
@@ -30,6 +32,8 @@
 	"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) {
@@ -53,6 +57,82 @@
 	return fix, nil
 }
 
+func TestManifestFSGitRepoSeedsTreeCache(t *testing.T) {
+	fix, err := newTestFixture()
+	if err != nil {
+		t.Fatal("newTestFixture", err)
+	}
+	defer fix.cleanup()
+
+	// Add a git repo.
+	cmd := exec.Command("/bin/sh", "-c",
+		strings.Join([]string{
+			"mkdir -p localhost/platform/build/kati.git",
+			"cd localhost/platform/build/kati.git",
+			"git init",
+			"touch file",
+			"git add file",
+			"git commit -m msg -a",
+			"git show --no-patch --pretty=format:'%H' HEAD | head",
+		}, " && "))
+	cmd.Dir = filepath.Join(fix.dir, "cache", "git")
+
+	headSHA1 := ""
+	if out, err := cmd.CombinedOutput(); err != nil {
+		t.Fatalf("create repo: %v, out: %s", err, string(out))
+	} else {
+		lines := strings.Split(string(out), "\n")
+		headSHA1 = lines[len(lines)-1]
+	}
+
+	p := testManifest.Project[0]
+	p.CloneURL = "file:///localhost/platform/build/kati"
+	p.Revision = headSHA1
+
+	mf := *testManifest
+	mf.Project = nil
+	mf.Project = append(mf.Project, p)
+
+	opts := ManifestOptions{
+		Manifest: &mf,
+	}
+
+	fs, err := NewManifestFS(fix.service, fix.cache, opts)
+	if err != nil {
+		t.Fatal("NewManifestFS", err)
+	}
+	if err := fix.mount(fs); err != nil {
+		t.Fatal("mount", err)
+	}
+
+	headID, err := git.NewOid(headSHA1)
+	if err != nil {
+		t.Fatal("NewOid(%q): %v", headSHA1, err)
+	}
+
+	tree, err := fix.cache.Tree.Get(headID)
+	if err != nil {
+		t.Fatalf("treeCache.Get(%s): %v", headSHA1, err)
+	}
+
+	newInt := func(n int) *int { return &n }
+	tree.ID = ""
+	want := &gitiles.Tree{
+		Entries: []gitiles.TreeEntry{
+			{
+				Name: "file",
+				Mode: 0100644,
+				Type: "blob",
+				ID:   "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391",
+				Size: newInt(0),
+			},
+		},
+	}
+	if !reflect.DeepEqual(tree, want) {
+		t.Errorf("got cached tree %v, want %v", tree, want)
+	}
+}
+
 func TestManifestFSCloneOption(t *testing.T) {
 	mf := *testManifest
 	for i := range mf.Project {
diff --git a/gitiles/types.go b/gitiles/types.go
index 0b9a593..56a3da5 100644
--- a/gitiles/types.go
+++ b/gitiles/types.go
@@ -14,7 +14,10 @@
 
 package gitiles
 
-import "fmt"
+import (
+	"bytes"
+	"fmt"
+)
 
 // Project describes a repository
 type Project struct {
@@ -103,3 +106,14 @@
 	ID      string
 	Entries []TreeEntry
 }
+
+func (t *Tree) String() string {
+	var buf bytes.Buffer
+	fmt.Fprintf(&buf, "tree %s {\n", t.ID)
+	for _, e := range t.Entries {
+		fmt.Fprintf(&buf, "  %s\n", e.String())
+	}
+	fmt.Fprintf(&buf, "}\n")
+	return buf.String()
+
+}