Add NewGitilesConfigFSRoot. This exposes a git repository at multiple commit/tree IDs. Change-Id: I10df27e74037d762e12ee3fbeaf2a929b89bb398
diff --git a/cmd/slothfs-gitilesfs/main.go b/cmd/slothfs-gitilesfs/main.go index d08d362..273b890 100644 --- a/cmd/slothfs-gitilesfs/main.go +++ b/cmd/slothfs-gitilesfs/main.go
@@ -28,7 +28,6 @@ ) func main() { - branch := flag.String("branch", "master", "Set the branch name.") repo := flag.String("repo", "", "Set the repository name.") debug := flag.Bool("debug", false, "Print FUSE debug info.") cacheDir := flag.String("cache", filepath.Join(os.Getenv("HOME"), ".cache", "slothfs"), @@ -40,7 +39,7 @@ log.Fatal("must set --cache") } if len(flag.Args()) < 1 { - log.Fatal("usage: main -gitiles URL -repo REPO [-branch BRANCH] MOUNT-POINT") + log.Fatal("usage: main -repo REPO MOUNT-POINT") } mntDir := flag.Arg(0) @@ -60,17 +59,11 @@ log.Fatalf("GetProject(%s): %v", *repo, err) } - tree, err := repoService.GetTree(*branch, "", true) - if err != nil { - log.Fatal(err) - } - opts := fs.GitilesOptions{ - Revision: *branch, CloneURL: project.CloneURL, } - root := fs.NewGitilesRoot(cache, tree, repoService, opts) + root := fs.NewGitilesConfigFSRoot(cache, repoService, &opts) server, _, err := nodefs.MountRoot(mntDir, root, &nodefs.Options{ EntryTimeout: time.Hour, NegativeTimeout: time.Hour,
diff --git a/fs/api.go b/fs/api.go index c1ba8e8..5393755 100644 --- a/fs/api.go +++ b/fs/api.go
@@ -26,10 +26,16 @@ Clone bool } -// GitilesOptions configures the Gitiles filesystem. -type GitilesOptions struct { +// GitilesOptions configures the Gitiles filesystem (ie. Gitiles +// backed FS) at a certain revision. +type GitilesRevisionOptions struct { Revision string + GitilesOptions +} + +// GitilesOptions sets options for NewGitilesConfigRoot. +type GitilesOptions struct { // If set, clone the repo on reads from here. CloneURL string
diff --git a/fs/gitilesconfigfs.go b/fs/gitilesconfigfs.go new file mode 100644 index 0000000..73728e1 --- /dev/null +++ b/fs/gitilesconfigfs.go
@@ -0,0 +1,87 @@ +// 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 ( + "log" + + "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 { + nodefs.Node + + fsConn *nodefs.FileSystemConnector + cache *cache.Cache + service *gitiles.RepoService + options GitilesOptions +} + +func (r *gitilesConfigFSRoot) OnMount(fsConn *nodefs.FileSystemConnector) { + r.fsConn = fsConn +} + +func (r *gitilesConfigFSRoot) Lookup(out *fuse.Attr, name string, context *fuse.Context) (*nodefs.Inode, fuse.Status) { + id, err := git.NewOid(name) + if err != nil { + return nil, fuse.ENOENT + } + + 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) + return nil, fuse.EIO + } + + if err := r.cache.Tree.Add(id, tree); err != nil { + log.Println("TreeCache.Add(%s): %v", id, err) + } + } + + gro := GitilesRevisionOptions{ + Revision: id.String(), + GitilesOptions: r.options, + } + newRoot := NewGitilesRoot(r.cache, tree, r.service, gro) + ch := r.Inode().NewChild(id.String(), true, newRoot) + out.Mode = fuse.S_IFDIR | 0755 + + newRoot.OnMount(r.fsConn) + return ch, fuse.OK +} + +// NewGitilesConfigFSRoot returns a root node for a filesystem that lazily +// instantiates a repository if you access any subdirectory named by a +// 40-byte hex SHA1. +func NewGitilesConfigFSRoot(c *cache.Cache, service *gitiles.RepoService, options *GitilesOptions) nodefs.Node { + // TODO(hanwen): nodefs.Node has an OnForget(), but it will + // never trigger for directories that have children. That + // means that we effectively never drop old trees. We can fix + // this by either: 1) reconsidering OnForget in go-fuse 2) do + // a periodic removal of all subtrees trees. Since the FS is + // read-only that should cause no ill effects. + return &gitilesConfigFSRoot{ + Node: nodefs.NewDefaultNode(), + cache: c, + service: service, + options: *options, + } +}
diff --git a/fs/gitilesfs.go b/fs/gitilesfs.go index 8f4287d..40c22e3 100644 --- a/fs/gitilesfs.go +++ b/fs/gitilesfs.go
@@ -43,7 +43,7 @@ cache *cache.Cache service *gitiles.RepoService tree *gitiles.Tree - opts GitilesOptions + opts GitilesRevisionOptions handleLessIO bool @@ -303,7 +303,7 @@ } // NewGitilesRoot returns the root node for a file system. -func NewGitilesRoot(c *cache.Cache, tree *gitiles.Tree, service *gitiles.RepoService, options GitilesOptions) nodefs.Node { +func NewGitilesRoot(c *cache.Cache, tree *gitiles.Tree, service *gitiles.RepoService, options GitilesRevisionOptions) nodefs.Node { r := &gitilesRoot{ Node: newDirNode(), service: service,
diff --git a/fs/gitilesfs_test.go b/fs/gitilesfs_test.go index 88a826c..3c033e3 100644 --- a/fs/gitilesfs_test.go +++ b/fs/gitilesfs_test.go
@@ -15,6 +15,7 @@ package fs import ( + "bytes" "encoding/base64" "fmt" "io/ioutil" @@ -38,11 +39,15 @@ const fuseDebug = false +const testEncodedBlob = `IyBUaGlzIGlzIHRoZSBvZmZpY2lhbCBsaXN0IG9mIGdsb2cgYXV0aG9ycyBmb3IgY29weXJpZ2h0IHB1cnBvc2VzLgojIFRoaXMgZmlsZSBpcyBkaXN0aW5jdCBmcm9tIHRoZSBDT05UUklCVVRPUlMgZmlsZXMuCiMgU2VlIHRoZSBsYXR0ZXIgZm9yIGFuIGV4cGxhbmF0aW9uLgojCiMgTmFtZXMgc2hvdWxkIGJlIGFkZGVkIHRvIHRoaXMgZmlsZSBhczoKIwlOYW1lIG9yIE9yZ2FuaXphdGlvbiA8ZW1haWwgYWRkcmVzcz4KIyBUaGUgZW1haWwgYWRkcmVzcyBpcyBub3QgcmVxdWlyZWQgZm9yIG9yZ2FuaXphdGlvbnMuCiMKIyBQbGVhc2Uga2VlcCB0aGUgbGlzdCBzb3J0ZWQuCgpLb3VoZWkgU3V0b3UgPGtvdUBjb3ptaXhuZy5vcmc+Ckdvb2dsZSBJbmMuCg==` + +var testBlob []byte + func init() { enc := map[string]string{ - "/platform/build/kati/+show/ce34badf691d36e8048b63f89d1a86ee5fa4325c/AUTHORS?format=TEXT": `IyBUaGlzIGlzIHRoZSBvZmZpY2lhbCBsaXN0IG9mIGdsb2cgYXV0aG9ycyBmb3IgY29weXJpZ2h0IHB1cnBvc2VzLgojIFRoaXMgZmlsZSBpcyBkaXN0aW5jdCBmcm9tIHRoZSBDT05UUklCVVRPUlMgZmlsZXMuCiMgU2VlIHRoZSBsYXR0ZXIgZm9yIGFuIGV4cGxhbmF0aW9uLgojCiMgTmFtZXMgc2hvdWxkIGJlIGFkZGVkIHRvIHRoaXMgZmlsZSBhczoKIwlOYW1lIG9yIE9yZ2FuaXphdGlvbiA8ZW1haWwgYWRkcmVzcz4KIyBUaGUgZW1haWwgYWRkcmVzcyBpcyBub3QgcmVxdWlyZWQgZm9yIG9yZ2FuaXphdGlvbnMuCiMKIyBQbGVhc2Uga2VlcCB0aGUgbGlzdCBzb3J0ZWQuCgpLb3VoZWkgU3V0b3UgPGtvdUBjb3ptaXhuZy5vcmc+Ckdvb2dsZSBJbmMuCg==`, - "/platform/build/kati/+show/ce34badf691d36e8048b63f89d1a86ee5fa4325c/AUTHORSx?format=TEXT": `IyBUaGlzIGlzIHRoZSBvZmZpY2lhbCBsaXN0IG9mIGdsb2cgYXV0aG9ycyBmb3IgY29weXJpZ2h0IHB1cnBvc2VzLgojIFRoaXMgZmlsZSBpcyBkaXN0aW5jdCBmcm9tIHRoZSBDT05UUklCVVRPUlMgZmlsZXMuCiMgU2VlIHRoZSBsYXR0ZXIgZm9yIGFuIGV4cGxhbmF0aW9uLgojCiMgTmFtZXMgc2hvdWxkIGJlIGFkZGVkIHRvIHRoaXMgZmlsZSBhczoKIwlOYW1lIG9yIE9yZ2FuaXphdGlvbiA8ZW1haWwgYWRkcmVzcz4KIyBUaGUgZW1haWwgYWRkcmVzcyBpcyBub3QgcmVxdWlyZWQgZm9yIG9yZ2FuaXphdGlvbnMuCiMKIyBQbGVhc2Uga2VlcCB0aGUgbGlzdCBzb3J0ZWQuCgpLb3VoZWkgU3V0b3UgPGtvdUBjb3ptaXhuZy5vcmc+Ckdvb2dsZSBJbmMuCg==`, - "/platform/build/kati/+show/ce34badf691d36e8048b63f89d1a86ee5fa4325c/AUTHORS2?format=TEXT": `IyBUaGlzIGlzIHRoZSBvZmZpY2lhbCBsaXN0IG9mIGdsb2cgYXV0aG9ycyBmb3IgY29weXJpZ2h0IHB1cnBvc2VzLgojIFRoaXMgZmlsZSBpcyBkaXN0aW5jdCBmcm9tIHRoZSBDT05UUklCVVRPUlMgZmlsZXMuCiMgU2VlIHRoZSBsYXR0ZXIgZm9yIGFuIGV4cGxhbmF0aW9uLgojCiMgTmFtZXMgc2hvdWxkIGJlIGFkZGVkIHRvIHRoaXMgZmlsZSBhczoKIwlOYW1lIG9yIE9yZ2FuaXphdGlvbiA8ZW1haWwgYWRkcmVzcz4KIyBUaGUgZW1haWwgYWRkcmVzcyBpcyBub3QgcmVxdWlyZWQgZm9yIG9yZ2FuaXphdGlvbnMuCiMKIyBQbGVhc2Uga2VlcCB0aGUgbGlzdCBzb3J0ZWQuCgpLb3VoZWkgU3V0b3UgPGtvdUBjb3ptaXhuZy5vcmc+Ckdvb2dsZSBJbmMuCg==`, + "/platform/build/kati/+show/ce34badf691d36e8048b63f89d1a86ee5fa4325c/AUTHORS?format=TEXT": testEncodedBlob, + "/platform/build/kati/+show/ce34badf691d36e8048b63f89d1a86ee5fa4325c/AUTHORSx?format=TEXT": testEncodedBlob, + "/platform/build/kati/+show/ce34badf691d36e8048b63f89d1a86ee5fa4325c/AUTHORS2?format=TEXT": testEncodedBlob, "/platform/build/kati/+/ce34badf691d36e8048b63f89d1a86ee5fa4325c/testcase/addprefix.mk": "dGVzdDoKCWVjaG8gJChhZGRwcmVmaXggc3JjLyxmb28gYmFyKQo=", } for k, v := range enc { @@ -53,6 +58,10 @@ } c = c[:n] + if v == testEncodedBlob { + testBlob = c + } + testGitiles[k] = string(c) } } @@ -241,9 +250,11 @@ t.Fatal("Tree:", err) } - options := GitilesOptions{ + options := GitilesRevisionOptions{ Revision: "ce34badf691d36e8048b63f89d1a86ee5fa4325c", - CloneURL: fmt.Sprintf("http://%s/platform/build/kati", fix.testServer.addr), + GitilesOptions: GitilesOptions{ + CloneURL: fmt.Sprintf("http://%s/platform/build/kati", fix.testServer.addr), + }, } fs := NewGitilesRoot(fix.cache, treeResp, repoService, options) @@ -269,7 +280,7 @@ t.Fatal("Tree:", err) } - options := GitilesOptions{} + options := GitilesRevisionOptions{} fs := NewGitilesRoot(fix.cache, treeResp, repoService, options) if err := fix.mount(fs); err != nil { @@ -308,7 +319,7 @@ t.Fatal("Tree:", err) } - options := GitilesOptions{} + options := GitilesRevisionOptions{} fs := NewGitilesRoot(fix.cache, treeResp, repoService, options) if err := fix.mount(fs); err != nil { @@ -358,7 +369,7 @@ ID: "ce34badf691d36e8048b63f89d1a86ee5fa4325c", }}, } - fs := NewGitilesRoot(fix.cache, tree, repoService, GitilesOptions{}) + fs := NewGitilesRoot(fix.cache, tree, repoService, GitilesRevisionOptions{}) if err := fix.mount(fs); err != nil { t.Fatal("mount", err) } @@ -392,9 +403,8 @@ t.Fatal("Tree:", err) } - options := GitilesOptions{ - CloneOption: fileOpts, - } + options := GitilesRevisionOptions{} + options.CloneOption = fileOpts fs := NewGitilesRoot(fix.cache, treeResp, repoService, options) if err := fix.mount(fs); err != nil { @@ -443,7 +453,7 @@ t.Fatal("Tree:", err) } - options := GitilesOptions{ + options := GitilesRevisionOptions{ Revision: "ce34badf691d36e8048b63f89d1a86ee5fa4325c", } @@ -486,7 +496,7 @@ t.Fatal("Tree:", err) } - fs := NewGitilesRoot(fix.cache, treeResp, repoService, GitilesOptions{}) + fs := NewGitilesRoot(fix.cache, treeResp, repoService, GitilesRevisionOptions{}) if err := fix.mount(fs); err != nil { t.Fatal("mount", err) } @@ -521,7 +531,7 @@ t.Fatal("Tree:", err) } - options := GitilesOptions{ + options := GitilesRevisionOptions{ Revision: "ce34badf691d36e8048b63f89d1a86ee5fa4325c", } @@ -548,3 +558,30 @@ } } } + +func TestGitilesConfigFSTest(t *testing.T) { + fix, err := newTestFixture() + if err != nil { + t.Fatal("newTestFixture", err) + } + defer fix.cleanup() + + repoService := fix.service.NewRepoService("platform/build/kati") + if err != nil { + t.Fatal("Tree:", err) + } + + fs := NewGitilesConfigFSRoot(fix.cache, repoService, &GitilesOptions{}) + if err := fix.mount(fs); err != nil { + t.Fatal("mount", err) + } + + fn := filepath.Join(fix.mntDir, "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/manifestfs.go b/fs/manifestfs.go index 3f60986..1c96928 100644 --- a/fs/manifestfs.go +++ b/fs/manifestfs.go
@@ -143,10 +143,12 @@ repoService := r.service.NewRepoService(revmap[p].Name) - opts := GitilesOptions{ - Revision: revmap[p].Revision, - CloneURL: cloneURL, - CloneOption: r.options.FileCloneOption, + opts := GitilesRevisionOptions{ + Revision: revmap[p].Revision, + GitilesOptions: GitilesOptions{ + CloneURL: cloneURL, + CloneOption: r.options.FileCloneOption, + }, } subRoot := NewGitilesRoot(r.cache, r.trees[p], repoService, opts)