Add gitiles host FS.
This creates a tree representing all repos on a gitiles host.
Change-Id: Ida8f7b88fc4c4f4b8503ea696838c6b44e125fca
diff --git a/cmd/slothfs-hostfs/main.go b/cmd/slothfs-hostfs/main.go
new file mode 100644
index 0000000..68a80b6
--- /dev/null
+++ b/cmd/slothfs-hostfs/main.go
@@ -0,0 +1,71 @@
+// 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"
+ "path/filepath"
+ "time"
+
+ "github.com/google/slothfs/cache"
+ "github.com/google/slothfs/fs"
+ "github.com/google/slothfs/gitiles"
+ "github.com/hanwen/go-fuse/fuse/nodefs"
+)
+
+func main() {
+ debug := flag.Bool("debug", false, "Print FUSE debug info.")
+ cacheDir := flag.String("cache", filepath.Join(os.Getenv("HOME"), ".cache", "slothfs"),
+ "Set directory for file system cache.")
+ gitilesOptions := gitiles.DefineFlags()
+ flag.Parse()
+
+ if *cacheDir == "" {
+ log.Fatal("must set --cache")
+ }
+ if len(flag.Args()) < 1 {
+ log.Fatal("usage: main MOUNT-POINT")
+ }
+
+ mntDir := flag.Arg(0)
+ cache, err := cache.NewCache(*cacheDir, cache.Options{})
+ if err != nil {
+ log.Fatalf("NewCache: %v", err)
+ }
+
+ service, err := gitiles.NewService(*gitilesOptions)
+ if err != nil {
+ log.Fatalf("NewService: %v", err)
+ }
+
+ root, err := fs.NewHostFS(cache, service, nil)
+ if err != nil {
+ log.Fatalf("NewService: %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/gitilesconfigfs.go b/fs/gitilesconfigfs.go
index 73728e1..ce4fda1 100644
--- a/fs/gitilesconfigfs.go
+++ b/fs/gitilesconfigfs.go
@@ -43,16 +43,20 @@
return nil, fuse.ENOENT
}
+ if ch := r.Inode().GetChild(name); ch != nil {
+ return ch, fuse.OK
+ }
+
tree, err := r.cache.Tree.Get(id)
if err != nil {
tree, err = r.service.GetTree(id.String(), "/", true)
if err != nil {
- log.Println("GetTree(%s): %v", id, err)
+ log.Printf("GetTree(%s): %v", id, err)
return nil, fuse.EIO
}
if err := r.cache.Tree.Add(id, tree); err != nil {
- log.Println("TreeCache.Add(%s): %v", id, err)
+ log.Printf("TreeCache.Add(%s): %v", id, err)
}
}
diff --git a/fs/gitilesfs_test.go b/fs/gitilesfs_test.go
index 3c033e3..f444292 100644
--- a/fs/gitilesfs_test.go
+++ b/fs/gitilesfs_test.go
@@ -93,6 +93,15 @@
var testGitiles = map[string]string{
"/platform/manifest/+show/master/default.xml?format=TEXT": testManifestXML,
+ "/?format=JSON": `)]}'
+{
+ "platform/build/kati": {
+ "name": "platform/build/kati",
+ "clone_url": "https://android.googlesource.com/platform/build/kati",
+ "description": "Description."
+ }
+}
+`,
"/platform/build/kati/+/master?format=JSON": `)]}'
{
"commit": "ce34badf691d36e8048b63f89d1a86ee5fa4325c",
@@ -585,3 +594,26 @@
t.Errorf("blob for %s differs", fn)
}
}
+
+func TestGitilesHostFS(t *testing.T) {
+ fix, err := newTestFixture()
+ if err != nil {
+ t.Fatal("newTestFixture", err)
+ }
+ defer fix.cleanup()
+
+ if fs, err := NewHostFS(fix.cache, fix.service, nil); err != nil {
+ t.Fatalf("NewHostFS: %v", err)
+ } else if err := fix.mount(fs); err != nil {
+ t.Fatalf("mount: %v", err)
+ }
+
+ fn := filepath.Join(fix.mntDir, "platform/build/kati", "ce34badf691d36e8048b63f89d1a86ee5fa4325c", "AUTHORS")
+ content, err := ioutil.ReadFile(fn)
+ if err != nil {
+ t.Fatalf("ReadFile: %v", err)
+ }
+ if bytes.Compare(content, testBlob) != 0 {
+ t.Errorf("blob for %s differs", fn)
+ }
+}
diff --git a/fs/gitileshostfs.go b/fs/gitileshostfs.go
new file mode 100644
index 0000000..7c83e6d
--- /dev/null
+++ b/fs/gitileshostfs.go
@@ -0,0 +1,121 @@
+// 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"
+ "path/filepath"
+ "sort"
+ "strings"
+
+ "github.com/google/slothfs/cache"
+ "github.com/google/slothfs/gitiles"
+ "github.com/hanwen/go-fuse/fuse/nodefs"
+)
+
+type hostFS struct {
+ nodefs.Node
+
+ cache *cache.Cache
+ service *gitiles.Service
+ projects map[string]*gitiles.Project
+ cloneOptions []CloneOption
+}
+
+func parents(projMap map[string]*gitiles.Project) map[string]struct{} {
+ dirs := map[string]struct{}{}
+ for nm := range projMap {
+ for nm != "" && nm != "." {
+ next := filepath.Dir(nm)
+ dirs[next] = struct{}{}
+ nm = next
+ }
+ }
+ return dirs
+}
+
+func NewHostFS(cache *cache.Cache, service *gitiles.Service, cloneOptions []CloneOption) (*hostFS, error) {
+ projMap, err := service.List(nil)
+ if err != nil {
+ return nil, err
+ }
+
+ dirs := parents(projMap)
+ for p := range projMap {
+ if _, ok := dirs[p]; ok {
+ return nil, fmt.Errorf("%q is a dir and a project", p)
+ }
+ }
+
+ return &hostFS{
+ Node: nodefs.NewDefaultNode(),
+ projects: projMap,
+ cloneOptions: cloneOptions,
+ service: service,
+ cache: cache,
+ }, nil
+}
+
+func (h *hostFS) OnMount(fsConn *nodefs.FileSystemConnector) {
+ var keys []string
+ for k := range parents(h.projects) {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+
+ nodes := map[string]*nodefs.Inode{
+ "": h.Inode(),
+ }
+
+ for _, k := range keys {
+ if k == "." {
+ continue
+ }
+ d, nm := filepath.Split(k)
+ d = strings.TrimSuffix(d, "/")
+ parent := nodes[d]
+
+ var node nodefs.Node
+ if p := h.projects[k]; p != nil {
+ node = h.newProjectNode(parent, p)
+ delete(h.projects, k)
+ node.OnMount(fsConn)
+ } else {
+ node = newDirNode()
+ }
+ ch := parent.NewChild(nm, true, node)
+ nodes[k] = ch
+ }
+
+ for k, p := range h.projects {
+ d, nm := filepath.Split(k)
+ d = strings.TrimSuffix(d, "/")
+
+ parent := nodes[d]
+ node := h.newProjectNode(parent, p)
+ node.OnMount(fsConn)
+
+ parent.NewChild(nm, true, node)
+ }
+}
+
+func (h *hostFS) newProjectNode(parent *nodefs.Inode, proj *gitiles.Project) nodefs.Node {
+ repoService := h.service.NewRepoService(proj.Name)
+ opts := GitilesOptions{
+ CloneURL: proj.CloneURL,
+ CloneOption: h.cloneOptions,
+ }
+ return NewGitilesConfigFSRoot(h.cache, repoService, &opts)
+}
diff --git a/gitiles/client.go b/gitiles/client.go
index 8bc070c..90c4f54 100644
--- a/gitiles/client.go
+++ b/gitiles/client.go
@@ -194,6 +194,12 @@
projects := map[string]*Project{}
err := s.getJSON(&listURL, &projects)
+ for k, v := range projects {
+ if k != v.Name {
+ return nil, fmt.Errorf("gitiles: key %q had project name %q", k, v.Name)
+ }
+ }
+
return projects, err
}