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 }