// Package fs implements abstractions for interacting with a filesystem, either // via a git tree, a staged index, or directly. package fs import ( "bytes" "fmt" "io" "io/ioutil" "os" "gopkg.in/src-d/go-billy.v4" "gopkg.in/src-d/go-git.v4" "gopkg.in/src-d/go-git.v4/plumbing/object" ) // FS is a simple interface for reading off a snapshot of a filesystem. type FS interface { Open(path string) (io.ReadCloser, error) } type treeFS struct { tree *object.Tree } // FromTree wraps a git tree object to implement the FS interface. All paths // will be relative to the root of the tree. func FromTree(t *object.Tree) FS { return treeFS{tree: t} } func (gt treeFS) Open(path string) (io.ReadCloser, error) { f, err := gt.tree.File(path) if err != nil { return nil, err } return f.Blob.Reader() } type billyFS struct { fs billy.Filesystem } // FromBillyFilesystem wraps a billy.Filesystem to implement the FS interface. // All paths will be relative to the filesystem's root. func FromBillyFilesystem(bfs billy.Filesystem) FS { return billyFS{fs: bfs} } func (bfs billyFS) Open(path string) (io.ReadCloser, error) { return bfs.fs.Open(path) } // FromStagedChangesTree processes the current set of staged changes into a tree // object, and returns an FS for that tree. All paths will be relative to the // root of the git repo. func FromStagedChangesTree(repo *git.Repository) (FS, *object.Tree, error) { w, err := repo.Worktree() if err != nil { return nil, nil, fmt.Errorf("could not open git worktree: %w", err) } storer := repo.Storer idx, err := storer.Index() if err != nil { return nil, nil, fmt.Errorf("could not open git staging index: %w", err) } th := &buildTreeHelper{ fs: w.Filesystem, s: storer, } treeHash, err := th.BuildTree(idx) if err != nil { return nil, nil, fmt.Errorf("could not build staging index tree: %w", err) } tree, err := repo.TreeObject(treeHash) if err != nil { return nil, nil, fmt.Errorf("could not get staged tree object (%q): %w", treeHash, err) } return FromTree(tree), tree, nil } // Stub is an implementation of FS based on a map of paths to the file contents // at that path. Paths should be "clean" or they will not match with anything. type Stub map[string][]byte // Open implements the method for the FS interface. func (s Stub) Open(path string) (io.ReadCloser, error) { body, ok := s[path] if !ok { return nil, os.ErrNotExist } return ioutil.NopCloser(bytes.NewReader(body)), nil }