Remove obsolete manifestfs

Change-Id: Ifa460f8773b5d55080e00dca337a5d4e93bf63e9
diff --git a/cmd/slothfs-deref-manifest/main.go b/cmd/slothfs-deref-manifest/main.go
deleted file mode 100644
index 00965f4..0000000
--- a/cmd/slothfs-deref-manifest/main.go
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2016 Google Inc. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//    http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package main
-
-import (
-	"flag"
-	"log"
-	"os"
-
-	"github.com/google/slothfs/gitiles"
-	"github.com/google/slothfs/populate"
-)
-
-func main() {
-	gitilesOptions := gitiles.DefineFlags()
-	branch := flag.String("branch", "master", "Specify branch of the manifest repository to use.")
-	repo := flag.String("repo", "platform/manifest", "Set repository name holding manifest file.")
-	flag.Parse()
-
-	service, err := gitiles.NewService(*gitilesOptions)
-	if err != nil {
-		log.Fatalf("NewService: %v", err)
-	}
-
-	mf, err := populate.FetchManifest(service, *repo, *branch)
-	if err != nil {
-		log.Fatalf("FetchManifest: %v", err)
-	}
-
-	mf.Filter()
-
-	if err := populate.DerefManifest(service, mf); err != nil {
-		log.Fatalf("DerefManifest: %v", err)
-	}
-
-	xml, err := mf.MarshalXML()
-	if err != nil {
-		log.Fatalf("MarshalXML: %v", err)
-	}
-
-	os.Stdout.Write(xml)
-}
diff --git a/cmd/slothfs-deref-repo/main.go b/cmd/slothfs-deref-repo/main.go
deleted file mode 100644
index 74140d2..0000000
--- a/cmd/slothfs-deref-repo/main.go
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright 2016 Google Inc. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//    http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// This program creates a manifest file with revisions filled in from
-// a normal repo checkout. This can be used for comparing slothfs and
-// actual repo checkouts.
-package main
-
-import (
-	"flag"
-	"log"
-	"os"
-	"path/filepath"
-
-	"github.com/google/slothfs/manifest"
-	git "github.com/libgit2/git2go"
-)
-
-func main() {
-	flag.Parse()
-	if len(flag.Args()) == 0 {
-		log.Fatal("must state the repo toplevel directory.")
-	}
-
-	top := flag.Arg(0)
-	mf, err := manifest.ParseFile(filepath.Join(top, ".repo", "manifest.xml"))
-	if err != nil {
-		log.Fatal(err)
-	}
-
-	mf.Filter()
-	for i, p := range mf.Project {
-		repoPath := filepath.Join(top, p.GetPath(), ".git")
-		repo, err := git.OpenRepository(repoPath)
-		if err != nil {
-			continue
-		}
-
-		h, err := repo.Head()
-		if err != nil {
-			log.Println("head", p.Name, err)
-		}
-
-		obj, err := h.Peel(git.ObjectCommit)
-		if err != nil {
-			log.Println("peel", p.Name, err)
-		}
-
-		mf.Project[i].Revision = obj.Id().String()
-	}
-
-	xml, err := mf.MarshalXML()
-	if err != nil {
-		log.Fatal("MarshalXML", err)
-	}
-
-	os.Stdout.Write(xml)
-}
diff --git a/cmd/slothfs-manifestfs/main.go b/cmd/slothfs-manifestfs/main.go
deleted file mode 100644
index 549dfd7..0000000
--- a/cmd/slothfs-manifestfs/main.go
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright 2016 Google Inc. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//    http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package main
-
-import (
-	"flag"
-	"io/ioutil"
-	"log"
-	"os"
-	"path/filepath"
-	"time"
-
-	"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/nodefs"
-)
-
-func main() {
-	manifestPath := flag.String("manifest", "", "Set path to the expanded manifest file.")
-	cacheDir := flag.String("cache", filepath.Join(os.Getenv("HOME"), ".cache", "slothfs"),
-		"Set the directory holding the file system cache.")
-	debug := flag.Bool("debug", false, "Print FUSE debug info.")
-	config := flag.String("config", "", "Set path to clone configuration JSON file.")
-	gitilesOptions := gitiles.DefineFlags()
-	flag.Parse()
-
-	if *manifestPath == "" {
-		log.Fatal("must set --manifest")
-	}
-	if *cacheDir == "" {
-		log.Fatal("must set --cache")
-	}
-
-	if len(flag.Args()) < 1 {
-		log.Fatal("mountpoint argument missing.")
-	}
-	mntDir := flag.Arg(0)
-
-	cache, err := cache.NewCache(*cacheDir, cache.Options{})
-	if err != nil {
-		log.Printf("NewCache: %v", err)
-	}
-
-	service, err := gitiles.NewService(*gitilesOptions)
-	if err != nil {
-		log.Printf("NewService: %v", err)
-	}
-
-	mf, err := manifest.ParseFile(*manifestPath)
-	if err != nil {
-		log.Fatal(err)
-	}
-
-	opts := fs.ManifestOptions{
-		Manifest: mf,
-	}
-
-	if *config != "" {
-		configContents, err := ioutil.ReadFile(*config)
-		if err != nil {
-			log.Fatal(err)
-		}
-		opts.RepoCloneOption, opts.FileCloneOption, err = fs.ReadConfig(configContents)
-		if err != nil {
-			log.Fatal(err)
-		}
-	}
-
-	root, err := fs.NewManifestFS(service, cache, opts)
-	if err != nil {
-		log.Fatalf("NewManifestFS: %v", err)
-	}
-
-	server, _, err := nodefs.MountRoot(mntDir, root, &nodefs.Options{
-		EntryTimeout:    time.Hour,
-		NegativeTimeout: time.Hour,
-		AttrTimeout:     time.Hour,
-		Debug:           *debug,
-	})
-	if err != nil {
-		log.Fatalf("MountFileSystem: %v", err)
-	}
-	log.Printf("Started gitiles fs FUSE on %s", mntDir)
-	server.Serve()
-}
diff --git a/fs/fixture_test.go b/fs/fixture_test.go
new file mode 100644
index 0000000..92d41f7
--- /dev/null
+++ b/fs/fixture_test.go
@@ -0,0 +1,103 @@
+// Copyright 2016 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package fs
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"time"
+
+	"github.com/google/slothfs/cache"
+	"github.com/google/slothfs/gitiles"
+	"github.com/hanwen/go-fuse/fuse"
+	"github.com/hanwen/go-fuse/fuse/nodefs"
+)
+
+type testFixture struct {
+	dir        string
+	mntDir     string
+	server     *fuse.Server
+	cache      *cache.Cache
+	testServer *testServer
+	service    *gitiles.Service
+	root       nodefs.Node
+}
+
+func (f *testFixture) cleanup() {
+	if f.testServer != nil {
+		f.testServer.listener.Close()
+	}
+	if f.server != nil {
+		f.server.Unmount()
+	}
+	os.RemoveAll(f.dir)
+}
+
+func newTestFixture() (*testFixture, error) {
+	d, err := ioutil.TempDir("", "slothfs")
+	if err != nil {
+		return nil, err
+	}
+
+	fixture := &testFixture{dir: d}
+
+	fixture.cache, err = cache.NewCache(filepath.Join(d, "/cache"), cache.Options{})
+	if err != nil {
+		return nil, err
+	}
+
+	fixture.testServer, err = newTestServer()
+	if err != nil {
+		return nil, err
+	}
+
+	fixture.service, err = gitiles.NewService(gitiles.Options{
+		Address: fmt.Sprintf("http://%s", fixture.testServer.addr),
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	return fixture, nil
+}
+
+func (f *testFixture) mount(root nodefs.Node) error {
+	f.mntDir = filepath.Join(f.dir, "mnt")
+	if err := os.Mkdir(f.mntDir, 0755); err != nil {
+		return err
+	}
+
+	fuseOpts := &nodefs.Options{
+		EntryTimeout:    time.Hour,
+		NegativeTimeout: time.Hour,
+		AttrTimeout:     time.Hour,
+	}
+
+	var err error
+	f.server, _, err = nodefs.MountRoot(f.mntDir, root, fuseOpts)
+	if err != nil {
+		return err
+	}
+
+	if fuseDebug {
+		f.server.SetDebug(true)
+	}
+	go f.server.Serve()
+
+	f.root = root
+	return nil
+}
diff --git a/fs/manifestfs.go b/fs/manifestfs.go
deleted file mode 100644
index 018efcf..0000000
--- a/fs/manifestfs.go
+++ /dev/null
@@ -1,274 +0,0 @@
-// Copyright 2016 Google Inc. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//    http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package fs
-
-import (
-	"encoding/json"
-	"fmt"
-	"log"
-	"path/filepath"
-	"strings"
-
-	"github.com/google/slothfs/cache"
-	"github.com/google/slothfs/gitiles"
-	"github.com/google/slothfs/manifest"
-	"github.com/hanwen/go-fuse/fuse"
-	"github.com/hanwen/go-fuse/fuse/nodefs"
-)
-
-type manifestFSRoot struct {
-	nodefs.Node
-
-	service *gitiles.Service
-
-	cache     *cache.Cache
-	nodeCache *nodeCache
-
-	// trees is Path => Tree map.
-	trees map[string]*gitiles.Tree
-
-	options ManifestOptions
-
-	// XML data for the manifest.
-	manifestXML []byte
-}
-
-func (r *manifestFSRoot) Deletable() bool { return false }
-
-func (r *manifestFSRoot) GetXAttr(attribute string, context *fuse.Context) (data []byte, code fuse.Status) {
-	return nil, fuse.ENODATA
-}
-
-// NewManifestFS creates a Manifest FS root node.
-func NewManifestFS(service *gitiles.Service, cache *cache.Cache, opts ManifestOptions) (nodefs.Node, error) {
-	xml, err := opts.Manifest.MarshalXML()
-	if err != nil {
-		return nil, err
-	}
-	root := &manifestFSRoot{
-		Node:        newDirNode(),
-		nodeCache:   newNodeCache(),
-		cache:       cache,
-		service:     service,
-		options:     opts,
-		manifestXML: xml,
-	}
-
-	for _, p := range opts.Manifest.Project {
-		if _, err := parseID(p.Revision); err != nil {
-			return nil, fmt.Errorf("project %s revision %q does not parse: %v", p.Name, p.Revision, err)
-		}
-	}
-
-	root.trees, err = fetchTreeMap(cache, service, opts.Manifest)
-	if err != nil {
-		return nil, err
-	}
-	return root, nil
-}
-
-func (r *manifestFSRoot) OnMount(fsConn *nodefs.FileSystemConnector) {
-	if err := r.onMount(fsConn); err != nil {
-		log.Printf("onMount: %v", err)
-		for k := range r.Inode().Children() {
-			r.Inode().RmChild(k)
-		}
-
-		r.Inode().NewChild("ERROR", false, newDataNode([]byte(err.Error())))
-	}
-
-	// Don't need the trees anymore.
-	r.trees = nil
-}
-
-func (r *manifestFSRoot) onMount(fsConn *nodefs.FileSystemConnector) error {
-	var byDepth [][]string
-	for p := range r.trees {
-		d := len(strings.Split(p, "/"))
-		for len(byDepth) <= d {
-			byDepth = append(byDepth, nil)
-		}
-
-		byDepth[d] = append(byDepth[d], p)
-	}
-
-	clonablePaths := map[string]bool{}
-	revmap := map[string]*manifest.Project{}
-	for i, p := range r.options.Manifest.Project {
-		revmap[p.GetPath()] = &r.options.Manifest.Project[i]
-
-		if p.CloneDepth == "" {
-			clonablePaths[p.GetPath()] = true
-		}
-	}
-
-	// TODO(hanwen): use parallelism here.
-
-	for _, ps := range byDepth {
-		for _, p := range ps {
-			dir, base := filepath.Split(p)
-			parent, left := fsConn.Node(r.Inode(), dir)
-			for _, c := range left {
-				ch := parent.NewChild(c, true, newDirNode())
-				parent = ch
-			}
-
-			clone, ok := clonablePaths[p]
-			if !ok {
-				for _, e := range r.options.RepoCloneOption {
-					if e.RE.FindString(p) != "" {
-						clone = e.Clone
-						break
-					}
-				}
-			}
-
-			cloneURL := revmap[p].CloneURL
-			if !clone {
-				cloneURL = ""
-			}
-
-			repoService := r.service.NewRepoService(revmap[p].Name)
-
-			opts := GitilesRevisionOptions{
-				Revision: revmap[p].Revision,
-				GitilesOptions: GitilesOptions{
-					CloneURL:    cloneURL,
-					CloneOption: r.options.FileCloneOption,
-				},
-			}
-
-			subRoot := NewGitilesRoot(r.cache, r.trees[p], repoService, opts)
-			subRoot.(*gitilesRoot).nodeCache = r.nodeCache
-			parent.NewChild(base, true, subRoot)
-			if err := subRoot.(*gitilesRoot).onMount(fsConn); err != nil {
-				return fmt.Errorf("onMount(%s): %v", p, err)
-			}
-		}
-	}
-
-	// Do Linkfile, Copyfile after setting up the repos, so we
-	// have directories to attach the copy/link nodes to.
-	for _, p := range r.options.Manifest.Project {
-		for _, cp := range p.Copyfile {
-			srcNode, left := fsConn.Node(r.Inode(), filepath.Join(p.GetPath(), cp.Src))
-			if len(left) > 0 {
-				return fmt.Errorf("Copyfile(%s): source %s does not exist", p.Name, cp.Src)
-			}
-
-			dir, left := fsConn.Node(r.Inode(), cp.Dest)
-			switch len(left) {
-			case 0:
-				return fmt.Errorf("Copyfile(%s): dest %s already exists.", p.Name, cp.Dest)
-			case 1:
-			default:
-				return fmt.Errorf("Copyfile(%s): directory for dest %s does not exist.", p.Name, cp.Dest)
-			}
-
-			dir.AddChild(left[0], srcNode)
-		}
-
-		for _, lf := range p.Linkfile {
-			dir, left := fsConn.Node(r.Inode(), lf.Dest)
-			switch len(left) {
-			case 0:
-				return fmt.Errorf("Linkfile(%s): dest %s already exists.", p.Name, lf.Dest)
-			case 1:
-			default:
-				return fmt.Errorf("Linkfile(%s): directory for dest %s does not exist.", p.Name, lf.Dest)
-			}
-
-			src := filepath.Join(p.GetPath(), lf.Src)
-			rel, err := filepath.Rel(filepath.Dir(lf.Dest), src)
-			if err != nil {
-				return err
-			}
-
-			node := newLinkNode(filepath.Join(rel))
-			dir.NewChild(left[0], false, node)
-		}
-	}
-
-	metaNode := r.Inode().NewChild(".slothfs", true, newDirNode())
-	metaNode.NewChild("manifest.xml", false, newDataNode(r.manifestXML))
-
-	var tree gitiles.Tree
-	treeContent, err := json.Marshal(tree)
-	if err != nil {
-		log.Panicf("json.Marshal: %v", err)
-	}
-	metaNode.NewChild("tree.json", false, newDataNode(treeContent))
-
-	return nil
-}
-
-func fetchTreeMap(c *cache.Cache, service *gitiles.Service, mf *manifest.Manifest) (map[string]*gitiles.Tree, error) {
-	type resultT struct {
-		path string
-		resp *gitiles.Tree
-		err  error
-	}
-
-	// Fetch all the trees in parallel.
-	out := make(chan resultT, len(mf.Project))
-	for _, p := range mf.Project {
-		go func(p manifest.Project) {
-			revID, err := parseID(p.Revision)
-			if err != nil {
-				out <- resultT{p.GetPath(), nil, err}
-				return
-			}
-
-			tree, err := c.Tree.Get(revID)
-			cached := (err == nil && tree != nil)
-			if err != nil {
-				if repo := c.Git.OpenLocal(p.CloneURL); repo != nil {
-					tree, err = cache.GetTree(repo, revID)
-				}
-			}
-
-			if err != nil {
-				repoService := service.NewRepoService(p.Name)
-
-				tree, err = repoService.GetTree(p.Revision, "", true)
-			}
-
-			if !cached && tree != nil && err == nil {
-				if err := c.Tree.Add(revID, tree); err != nil {
-					log.Printf("treeCache.Add: %v", err)
-				}
-			}
-
-			out <- resultT{p.GetPath(), tree, err}
-		}(p)
-	}
-
-	// drain goroutines
-	var result []resultT
-	for range mf.Project {
-		r := <-out
-		result = append(result, r)
-	}
-
-	resmap := map[string]*gitiles.Tree{}
-	for _, r := range result {
-		if r.err != nil {
-			return nil, fmt.Errorf("Tree(%s): %v", r.path, r.err)
-		}
-
-		resmap[r.path] = r.resp
-	}
-	return resmap, nil
-}
diff --git a/fs/manifestfs_test.go b/fs/manifestfs_test.go
deleted file mode 100644
index b17ddf9..0000000
--- a/fs/manifestfs_test.go
+++ /dev/null
@@ -1,468 +0,0 @@
-// Copyright 2016 Google Inc. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//    http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package fs
-
-import (
-	"fmt"
-	"io/ioutil"
-	"os"
-	"os/exec"
-	"path/filepath"
-	"reflect"
-	"sort"
-	"strings"
-	"syscall"
-	"testing"
-	"time"
-
-	"github.com/google/slothfs/cache"
-	"github.com/google/slothfs/gitiles"
-	"github.com/google/slothfs/manifest"
-	"github.com/hanwen/go-fuse/fuse"
-	"github.com/hanwen/go-fuse/fuse/nodefs"
-)
-
-func newManifestTestFixture(mf *manifest.Manifest) (*testFixture, error) {
-	fix, err := newTestFixture()
-	if err != nil {
-		return nil, err
-	}
-
-	opts := ManifestOptions{
-		Manifest: mf,
-	}
-
-	fs, err := NewManifestFS(fix.service, fix.cache, opts)
-	if err != nil {
-		return nil, err
-	}
-	if err := fix.mount(fs); err != nil {
-		return nil, err
-	}
-
-	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 := parseID(headSHA1)
-	if err != nil {
-		t.Fatalf("parseID(%s): %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 {
-		mf.Project[i].CloneDepth = "1"
-	}
-
-	fix, err := newManifestTestFixture(&mf)
-	if err != nil {
-		t.Fatalf("newManifestTestFixture: %v", err)
-	}
-	defer fix.cleanup()
-
-	fs := fix.root.(*manifestFSRoot)
-	ch := fs.Inode()
-	for _, n := range []string{"build", "kati", "AUTHORS"} {
-		newCh := ch.GetChild(n)
-		if ch == nil {
-			t.Fatalf("node for %q not found. Have %s", n, ch.Children())
-		}
-		ch = newCh
-	}
-
-	giNode, ok := ch.Node().(*gitilesNode)
-	if !ok {
-		t.Fatalf("got node type %T, want *gitilesNode", ch.Node())
-	}
-
-	if giNode.clone {
-		t.Errorf("file had clone set.")
-	}
-}
-
-func TestManifestFSTimestamps(t *testing.T) {
-	fix, err := newManifestTestFixture(testManifest)
-	if err != nil {
-		t.Fatal("newTestFixture", err)
-	}
-	defer fix.cleanup()
-
-	var zeroFiles []string
-	if err := filepath.Walk(fix.mntDir, func(n string, fi os.FileInfo, err error) error {
-		if fi != nil && fi.ModTime().UnixNano() == 0 {
-			r, _ := filepath.Rel(fix.mntDir, n)
-			zeroFiles = append(zeroFiles, r)
-		}
-		return nil
-	}); err != nil {
-		t.Fatalf("Walk: %v", err)
-	}
-	if len(zeroFiles) > 0 {
-		sort.Strings(zeroFiles)
-		t.Errorf("found files with zero timestamps: %v", zeroFiles)
-	}
-}
-
-func TestManifestFSBasic(t *testing.T) {
-	fix, err := newManifestTestFixture(testManifest)
-	if err != nil {
-		t.Fatal("newTestFixture", err)
-	}
-	defer fix.cleanup()
-
-	fn := filepath.Join(fix.mntDir, "build", "kati", "AUTHORS")
-	fi, err := os.Lstat(fn)
-	if err != nil {
-		t.Fatalf("Lstat(%s): %v", fn, err)
-	}
-	if fi.Size() != 373 {
-		t.Errorf("got size %d want %d", fi.Size(), 373)
-	}
-
-	contents, err := ioutil.ReadFile(fn)
-	if err != nil {
-		t.Fatalf("ReadFile(%s): %v", fn, err)
-	}
-
-	want := testGitiles["/platform/build/kati/+show/ce34badf691d36e8048b63f89d1a86ee5fa4325c/AUTHORS?format=TEXT"]
-	if string(contents) != want {
-		t.Fatalf("got %q, want %q", contents, want)
-	}
-
-	copyPath := filepath.Join(fix.mntDir, "build", "copydest")
-	if copyFI, err := os.Lstat(copyPath); err != nil {
-		t.Errorf("Lstat(%s): %v", copyPath, err)
-	} else {
-		copyStat := copyFI.Sys().(*syscall.Stat_t)
-		origStat := fi.Sys().(*syscall.Stat_t)
-
-		if !reflect.DeepEqual(copyStat, origStat) {
-			t.Errorf("got stat %v, want %v", copyStat, origStat)
-		}
-	}
-
-	linkPath := filepath.Join(fix.mntDir, "build", "linkdest")
-	if got, err := os.Readlink(linkPath); err != nil {
-		t.Errorf("Readlink(%s): %v", linkPath, err)
-	} else if want := "kati/AUTHORS"; got != want {
-		t.Errorf("Readlink(%s) = %q, want %q", linkPath, got, want)
-	}
-}
-
-func TestManifestFSXMLFile(t *testing.T) {
-	fix, err := newManifestTestFixture(testManifest)
-	if err != nil {
-		t.Fatal("newTestFixture", err)
-	}
-	defer fix.cleanup()
-
-	xmlPath := filepath.Join(fix.mntDir, ".slothfs", "manifest.xml")
-	fuseMF, err := manifest.ParseFile(xmlPath)
-	if err != nil {
-		t.Fatalf("ParseFile(%s): %v", xmlPath, err)
-	}
-
-	if !reflect.DeepEqual(fuseMF, testManifest) {
-		t.Errorf("read back manifest %v, want %v", fuseMF, testManifest)
-	}
-}
-
-type testFixture struct {
-	dir        string
-	mntDir     string
-	server     *fuse.Server
-	cache      *cache.Cache
-	testServer *testServer
-	service    *gitiles.Service
-	root       nodefs.Node
-}
-
-func (f *testFixture) cleanup() {
-	if f.testServer != nil {
-		f.testServer.listener.Close()
-	}
-	if f.server != nil {
-		f.server.Unmount()
-	}
-	os.RemoveAll(f.dir)
-}
-
-func newTestFixture() (*testFixture, error) {
-	d, err := ioutil.TempDir("", "slothfs")
-	if err != nil {
-		return nil, err
-	}
-
-	fixture := &testFixture{dir: d}
-
-	fixture.cache, err = cache.NewCache(filepath.Join(d, "/cache"), cache.Options{})
-	if err != nil {
-		return nil, err
-	}
-
-	fixture.testServer, err = newTestServer()
-	if err != nil {
-		return nil, err
-	}
-
-	fixture.service, err = gitiles.NewService(gitiles.Options{
-		Address: fmt.Sprintf("http://%s", fixture.testServer.addr),
-	})
-	if err != nil {
-		return nil, err
-	}
-
-	return fixture, nil
-}
-
-func (f *testFixture) mount(root nodefs.Node) error {
-	f.mntDir = filepath.Join(f.dir, "mnt")
-	if err := os.Mkdir(f.mntDir, 0755); err != nil {
-		return err
-	}
-
-	fuseOpts := &nodefs.Options{
-		EntryTimeout:    time.Hour,
-		NegativeTimeout: time.Hour,
-		AttrTimeout:     time.Hour,
-	}
-
-	var err error
-	f.server, _, err = nodefs.MountRoot(f.mntDir, root, fuseOpts)
-	if err != nil {
-		return err
-	}
-
-	if fuseDebug {
-		f.server.SetDebug(true)
-	}
-	go f.server.Serve()
-
-	f.root = root
-	return nil
-}
-
-func TestMultiManifestFSBrokenXML(t *testing.T) {
-	fix, err := newTestFixture()
-	if err != nil {
-		t.Fatalf("newTestFixture: %v", err)
-	}
-	defer fix.cleanup()
-
-	brokenXMLFile := filepath.Join(fix.dir, "broken.xml")
-	if err := ioutil.WriteFile(brokenXMLFile, []byte("I'm not XML."), 0644); err != nil {
-		t.Errorf("WriteFile(%s): %v", brokenXMLFile, err)
-	}
-
-	opts := MultiManifestFSOptions{}
-	fs := NewMultiManifestFS(fix.service, fix.cache, opts)
-
-	if err := fix.mount(fs); err != nil {
-		t.Fatalf("mount: %v", err)
-	}
-
-	if err := os.Symlink(brokenXMLFile, filepath.Join(fix.mntDir, "config", "ws")); err == nil {
-		t.Fatalf("want error for broken XML file")
-	}
-}
-
-func TestMultiManifestFSBasic(t *testing.T) {
-	fix, err := newTestFixture()
-	if err != nil {
-		t.Fatalf("newTestFixture: %v", err)
-	}
-	defer fix.cleanup()
-
-	xmlFile := filepath.Join(fix.dir, "manifest.xml")
-	if err := ioutil.WriteFile(xmlFile, []byte(testManifestXML), 0644); err != nil {
-		t.Errorf("WriteFile(%s): %v", xmlFile, err)
-	}
-
-	opts := MultiManifestFSOptions{}
-	fs := NewMultiManifestFS(fix.service, fix.cache, opts)
-
-	if err := fix.mount(fs); err != nil {
-		t.Fatalf("mount: %v", err)
-	}
-
-	wsDir := filepath.Join(fix.mntDir, "ws")
-	if fi, err := os.Lstat(wsDir); err == nil {
-		t.Fatalf("got %v, want non-existent workspace dir", fi)
-	}
-
-	configName := filepath.Join(fix.mntDir, "config", "ws")
-	if err := os.Symlink(xmlFile, configName); err != nil {
-		t.Fatalf("Symlink(%s):  %v", xmlFile, err)
-	}
-
-	if _, err := os.Lstat(wsDir); err != nil {
-		t.Fatalf("Lstat(%s): %v", wsDir, err)
-	}
-
-	if got, err := os.Readlink(configName); err != nil {
-		t.Fatalf("Readlink(%s): %v", configName, err)
-	} else if want := "../ws/.slothfs/manifest.xml"; got != want {
-		t.Errorf("got link %s, want %s", got, want)
-	}
-
-	if _, err := manifest.ParseFile(configName); err != nil {
-		t.Fatalf("ParseFile(%s): %v", configName, err)
-	}
-
-	fn := filepath.Join(wsDir, "build", "kati", "AUTHORS")
-	if fi, err := os.Lstat(fn); err != nil {
-		t.Fatalf("Lstat(%s): %v", fn, err)
-	} else if fi.Size() != 373 {
-		t.Errorf("got %d, want size 373", fi.Size())
-	}
-
-	if err := os.Remove(configName); err != nil {
-		t.Fatalf("Delete(%s): %v", configName, err)
-	}
-
-	if fi, err := os.Lstat(wsDir); err == nil {
-		t.Errorf("Lstat(%s): got %v, want error", wsDir, fi)
-	}
-}
-
-func TestMultiManifestFSManifestDir(t *testing.T) {
-	fix, err := newTestFixture()
-	if err != nil {
-		t.Fatalf("newTestFixture: %v", err)
-	}
-	defer fix.cleanup()
-
-	mfDir := filepath.Join(fix.dir, "manifests")
-	if err := os.MkdirAll(mfDir, 0755); err != nil {
-		t.Fatalf("MkdirAll: %v", err)
-	}
-
-	xmlFile := filepath.Join(mfDir, "ws")
-	if err := ioutil.WriteFile(xmlFile, []byte(testManifestXML), 0644); err != nil {
-		t.Errorf("WriteFile(%s): %v", xmlFile, err)
-	}
-
-	opts := MultiManifestFSOptions{
-		ManifestDir: mfDir,
-	}
-	fs := NewMultiManifestFS(fix.service, fix.cache, opts)
-
-	if err := fix.mount(fs); err != nil {
-		t.Fatalf("mount: %v", err)
-	}
-
-	wsDir := filepath.Join(fix.mntDir, "ws")
-	if _, err := os.Lstat(wsDir); err != nil {
-		t.Fatalf("Lstat(%s): %v", wsDir, err)
-	}
-
-	if err := os.Remove(filepath.Join(fix.mntDir, "config", "ws")); err != nil {
-		t.Fatalf("Remove(config link): %v", err)
-	}
-
-	if fi, err := os.Lstat(filepath.Join(mfDir, "ws")); err == nil {
-		t.Errorf("'ws' still in manifest dir: %v", fi)
-	}
-
-	f, err := ioutil.TempFile("", "")
-	if err != nil {
-		t.Fatalf("TempFile: %v", err)
-	}
-	if err := ioutil.WriteFile(f.Name(), []byte(testManifestXML), 0644); err != nil {
-		t.Errorf("WriteFile(%s): %v", xmlFile, err)
-	}
-
-	configName := filepath.Join(fix.mntDir, "config", "ws2")
-	if err := os.Symlink(f.Name(), configName); err != nil {
-		t.Fatalf("Symlink(%s):  %v", xmlFile, err)
-	}
-
-	// XML file appears again.
-	xmlFile = filepath.Join(mfDir, "ws2")
-	if _, err := os.Stat(xmlFile); err != nil {
-		t.Errorf("Stat(%s): %v", xmlFile, err)
-	}
-}
diff --git a/fs/multimanifestfs.go b/fs/multimanifestfs.go
deleted file mode 100644
index f9fa084..0000000
--- a/fs/multimanifestfs.go
+++ /dev/null
@@ -1,222 +0,0 @@
-// Copyright 2016 Google Inc. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//    http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package fs
-
-import (
-	"io/ioutil"
-	"log"
-	"os"
-	"path/filepath"
-	"sync"
-	"syscall"
-
-	"github.com/google/slothfs/cache"
-	"github.com/google/slothfs/gitiles"
-	"github.com/google/slothfs/manifest"
-	"github.com/hanwen/go-fuse/fuse"
-	"github.com/hanwen/go-fuse/fuse/nodefs"
-)
-
-type multiManifestFSRoot struct {
-	nodefs.Node
-	nodeCache *nodeCache
-	cache     *cache.Cache
-	fsConn    *nodefs.FileSystemConnector
-	options   MultiManifestFSOptions
-	gitiles   *gitiles.Service
-}
-
-func (r *multiManifestFSRoot) StatFs() *fuse.StatfsOut {
-	var s syscall.Statfs_t
-	err := syscall.Statfs(r.cache.Root(), &s)
-	if err == nil {
-		out := &fuse.StatfsOut{}
-		out.FromStatfsT(&s)
-		return out
-	}
-	return nil
-}
-
-func (c *configNode) configureWorkspaces() error {
-	if c.root.options.ManifestDir == "" {
-		return nil
-	}
-	fs, err := filepath.Glob(filepath.Join(c.root.options.ManifestDir, "*"))
-	if err != nil || len(fs) == 0 {
-		return err
-	}
-
-	log.Println("configuring workspaces...")
-	var wg sync.WaitGroup
-	wg.Add(len(fs))
-	for _, f := range fs {
-		go func(n string) {
-			_, code := c.Symlink(filepath.Base(n), n, nil)
-			log.Printf("manifest %s: %v", n, code)
-			wg.Done()
-		}(f)
-	}
-	wg.Wait()
-
-	return nil
-}
-
-func (r *multiManifestFSRoot) OnMount(fsConn *nodefs.FileSystemConnector) {
-	r.fsConn = fsConn
-
-	cfg := &configNode{
-		Node: nodefs.NewDefaultNode(),
-		root: r,
-	}
-	r.Inode().NewChild("config", true, cfg)
-
-	if err := cfg.configureWorkspaces(); err != nil {
-		log.Printf("configureWorkspaces: %v", err)
-	}
-}
-
-func (c *configNode) Deletable() bool { return false }
-
-func NewMultiManifestFS(service *gitiles.Service, c *cache.Cache, options MultiManifestFSOptions) *multiManifestFSRoot {
-	r := &multiManifestFSRoot{
-		Node:      nodefs.NewDefaultNode(),
-		nodeCache: newNodeCache(),
-		cache:     c,
-		options:   options,
-		gitiles:   service,
-	}
-	return r
-}
-
-func (r *multiManifestFSRoot) Deletable() bool { return false }
-
-func (r *multiManifestFSRoot) GetXAttr(attribute string, context *fuse.Context) (data []byte, code fuse.Status) {
-	return nil, fuse.ENODATA
-}
-
-type configNode struct {
-	nodefs.Node
-	root *multiManifestFSRoot
-}
-
-type configEntryNode struct {
-	nodefs.Node
-	link []byte
-}
-
-func (c *configEntryNode) GetAttr(out *fuse.Attr, f nodefs.File, ctx *fuse.Context) fuse.Status {
-	out.Mode = fuse.S_IFLNK
-	return fuse.OK
-}
-
-func (c *configEntryNode) Readlink(ctx *fuse.Context) ([]byte, fuse.Status) {
-	return c.link, fuse.OK
-}
-
-func (c *configEntryNode) Deletable() bool { return false }
-
-func (c *configNode) Unlink(name string, ctx *fuse.Context) fuse.Status {
-	child := c.root.Inode().RmChild(name)
-	if child == nil {
-		return fuse.ENOENT
-	}
-
-	// Notify the kernel this part of the tree disappeared.
-	c.root.fsConn.DeleteNotify(c.root.Inode(), child, name)
-
-	c.Inode().RmChild(name)
-
-	// No need to notify for the removed symlink. Since we're in
-	// the Unlink method, will VFS already knows about the
-	// deletion once we return OK.
-
-	if dir := c.root.options.ManifestDir; dir != "" {
-		os.Remove(filepath.Join(dir, name))
-	}
-
-	return fuse.OK
-}
-
-func (c *configNode) Symlink(name, content string, ctx *fuse.Context) (*nodefs.Inode, fuse.Status) {
-	mfBytes, err := ioutil.ReadFile(content)
-	if err != nil {
-		return nil, fuse.ToStatus(err)
-	}
-
-	mf, err := manifest.Parse(mfBytes)
-	if err != nil {
-		log.Printf("Parse(%s): %v", content, err)
-		return nil, fuse.EINVAL
-	}
-
-	options := ManifestOptions{
-		Manifest:        mf,
-		RepoCloneOption: c.root.options.RepoCloneOption,
-		FileCloneOption: c.root.options.FileCloneOption,
-	}
-
-	fs, err := NewManifestFS(c.root.gitiles, c.root.cache, options)
-	if err != nil {
-		log.Printf("NewManifestFS(%s): %v", string(content), err)
-		return nil, fuse.EIO
-	}
-	fs.(*manifestFSRoot).nodeCache = c.root.nodeCache
-
-	child := c.root.Inode().NewChild(name, true, fs)
-	if child == nil {
-		// TODO(hanwen): can this ever happen?
-		return nil, fuse.EINVAL
-	}
-
-	config := c.Inode().NewChild(name, false, &configEntryNode{
-		Node: nodefs.NewDefaultNode(),
-		// This is sneaky, but it appears to work.
-		link: []byte(filepath.Join("..", name, ".slothfs", "manifest.xml")),
-	})
-
-	if err := fs.(*manifestFSRoot).onMount(c.root.fsConn); err != nil {
-		log.Printf("onMount(%s): %v", name, err)
-		for k := range child.Children() {
-			child.RmChild(k)
-		}
-
-		child.NewChild("ERROR", false, &dataNode{nodefs.NewDefaultNode(), []byte(err.Error())})
-	} else {
-		if dir := c.root.options.ManifestDir; dir != "" {
-			for {
-				f, err := ioutil.TempFile(dir, "")
-				if err != nil {
-					break
-				}
-
-				_, err = f.Write(mfBytes)
-				if err != nil {
-					break
-				}
-
-				if err := f.Close(); err != nil {
-					break
-				}
-
-				os.Rename(f.Name(), filepath.Join(dir, name))
-				break
-			}
-		}
-	}
-
-	c.root.fsConn.EntryNotify(c.root.Inode(), name)
-
-	return config, fuse.OK
-}