Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions pkg/gitfs/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,10 @@ func (f gitFS) statEntry(name string, entry *goGitPlumbingObject.TreeEntry, foll
fi.entry = entry
fi.name = path.Join(fi.name, name)

if fi.isSubmodule() {
return fi, nil
}

if fi.IsDir() {
fi.tree, err = goGitPlumbingObject.GetTree(f.storer, entry.Hash) // see https://github.com/go-git/go-git/blob/v5.11.0/plumbing/object/tree.go#L103
if err != nil {
Expand Down Expand Up @@ -309,6 +313,16 @@ func (f gitFS) ReadDir(n int) ([]fs.DirEntry, error) {
return nil, fmt.Errorf("%q not open (or not a directory)", f.name)
}
ret := []fs.DirEntry{}
if f.isSubmodule() {
// https://pkg.go.dev/io/fs#ReadDirFile
// "If n > 0, ... At the end of a directory, the error is io.EOF."
// "If n <= 0, ... it returns the slice and a nil error."
err := io.EOF
if n <= 0 {
err = nil
}
return ret, err
}
for i := 0; n <= 0 || i < n; i++ {
name, entry, err := f.walker.Next()
if err != nil {
Expand Down Expand Up @@ -354,10 +368,18 @@ func (f gitFS) Mode() fs.FileMode {
return 0775
case goGitPlumbingFileMode.Dir:
return 0775 | fs.ModeDir
case goGitPlumbingFileMode.Submodule:
// TODO handle submodules better / more explicitly ("git archive" presents them as empty directories, so we do too, for now)
return 0775 | fs.ModeDir
}
return 0 | fs.ModeIrregular // TODO what to do for files whose types we don't support? 😬
}

func (f gitFS) isSubmodule() bool {
// TODO handle submodules better / more explicitly ("git archive" presents them as empty directories, so we do too, for now)
return f.entry != nil && f.entry.Mode == goGitPlumbingFileMode.Submodule
}

// https://pkg.go.dev/io/fs#FileInfo: modification time
func (f gitFS) ModTime() time.Time {
return f.Mod
Expand Down
68 changes: 68 additions & 0 deletions pkg/gitfs/fs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package gitfs_test

import (
"io"
"io/fs"
"testing"
"testing/fstest"

Expand Down Expand Up @@ -171,3 +172,70 @@ func TestSubdirSymlinkFS(t *testing.T) {
}
})
}

func TestSubmoduleFS(t *testing.T) {
// TODO instead of cloning a remote repository, synthesize a very simple Git repository right in the test here (benefit of the remote repository is that it's much larger, so fstest.TestFS has a lot more data to test against)
// Init + CreateRemoteAnonymous + Fetch because Clone doesn't support fetch-by-commit
repo, err := git.Init(memory.NewStorage(), nil)
if err != nil {
t.Fatal(err)
}
remote, err := repo.CreateRemoteAnonymous(&goGitConfig.RemoteConfig{
Name: "anonymous",
URLs: []string{"https://github.com/debuerreotype/debuerreotype.git"}, // just a repository with a known submodule (`./validate/`)
})
if err != nil {
t.Fatal(err)
}
commit := "d12af8e5556e39f82082b44628288e2eb27d4c34"
err = remote.Fetch(&git.FetchOptions{
RefSpecs: []goGitConfig.RefSpec{goGitConfig.RefSpec(commit + ":FETCH_HEAD")},
Tags: git.NoTags,
})
if err != nil {
t.Fatal(err)
}
f, err := gitfs.CommitHash(repo, commit)
if err != nil {
t.Fatal(err)
}

t.Run("Stat", func(t *testing.T) {
fi, err := fs.Stat(f, "validate")
if err != nil {
t.Fatal(err)
}
if fi.Mode().IsRegular() {
t.Fatal("validate should not be a regular file")
}
if !fi.IsDir() {
t.Fatal("validate should be a directory but isn't")
}
})
t.Run("ReadDir", func(t *testing.T) {
entries, err := fs.ReadDir(f, "validate")
if err != nil {
t.Fatal(err)
}
if len(entries) != 0 {
t.Fatalf("validate should have 0 entries, not %d\n\n%#v", len(entries), entries)
}
})

// might as well run fstest again, now that we have a new filesystem tree 😅
t.Run("fstest.TestFS", func(t *testing.T) {
if err := fstest.TestFS(f, "Dockerfile"); err != nil {
t.Fatal(err)
}
})
t.Run("Sub+fstest.TestFS", func(t *testing.T) {
sub, err := fs.Sub(f, "validate")
if err != nil {
t.Fatal(err)
}
// "As a special case, if no expected files are listed, fsys must be empty."
if err := fstest.TestFS(sub); err != nil {
t.Fatal(err)
}
})
}
Loading