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)