| package filesystem |
| |
| import ( |
| "io" |
| "os" |
| "path" |
| |
| "gopkg.in/src-d/go-billy.v3" |
| "gopkg.in/src-d/go-git.v4/plumbing" |
| "gopkg.in/src-d/go-git.v4/plumbing/filemode" |
| "gopkg.in/src-d/go-git.v4/utils/merkletrie/noder" |
| ) |
| |
| var ignore = map[string]bool{ |
| ".git": true, |
| } |
| |
| // The node represents a file or a directory in a billy.Filesystem. It |
| // implements the interface noder.Noder of merkletrie package. |
| // |
| // This implementation implements a "standard" hash method being able to be |
| // compared with any other noder.Noder implementation inside of go-git. |
| type node struct { |
| fs billy.Filesystem |
| submodules map[string]plumbing.Hash |
| |
| path string |
| hash []byte |
| children []noder.Noder |
| isDir bool |
| } |
| |
| // NewRootNode returns the root node based on a given billy.Filesystem. |
| // |
| // In order to provide the submodule hash status, a map[string]plumbing.Hash |
| // should be provided where the key is the path of the submodule and the commit |
| // of the submodule HEAD |
| func NewRootNode( |
| fs billy.Filesystem, |
| submodules map[string]plumbing.Hash, |
| ) noder.Noder { |
| return &node{fs: fs, submodules: submodules, isDir: true} |
| } |
| |
| // Hash the hash of a filesystem is the result of concatenating the computed |
| // plumbing.Hash of the file as a Blob and its plumbing.FileMode; that way the |
| // difftree algorithm will detect changes in the contents of files and also in |
| // their mode. |
| // |
| // The hash of a directory is always a 24-bytes slice of zero values |
| func (n *node) Hash() []byte { |
| return n.hash |
| } |
| |
| func (n *node) Name() string { |
| return path.Base(n.path) |
| } |
| |
| func (n *node) IsDir() bool { |
| return n.isDir |
| } |
| |
| func (n *node) Children() ([]noder.Noder, error) { |
| if err := n.calculateChildren(); err != nil { |
| return nil, err |
| } |
| |
| return n.children, nil |
| } |
| |
| func (n *node) NumChildren() (int, error) { |
| if err := n.calculateChildren(); err != nil { |
| return -1, err |
| } |
| |
| return len(n.children), nil |
| } |
| |
| func (n *node) calculateChildren() error { |
| if len(n.children) != 0 { |
| return nil |
| } |
| |
| files, err := n.fs.ReadDir(n.path) |
| if err != nil { |
| if os.IsNotExist(err) { |
| return nil |
| } |
| |
| return nil |
| } |
| |
| for _, file := range files { |
| if _, ok := ignore[file.Name()]; ok { |
| continue |
| } |
| |
| c, err := n.newChildNode(file) |
| if err != nil { |
| return err |
| } |
| |
| n.children = append(n.children, c) |
| } |
| |
| return nil |
| } |
| |
| func (n *node) newChildNode(file os.FileInfo) (*node, error) { |
| path := path.Join(n.path, file.Name()) |
| |
| hash, err := n.calculateHash(path, file) |
| if err != nil { |
| return nil, err |
| } |
| |
| node := &node{ |
| fs: n.fs, |
| submodules: n.submodules, |
| |
| path: path, |
| hash: hash, |
| isDir: file.IsDir(), |
| } |
| |
| if hash, isSubmodule := n.submodules[path]; isSubmodule { |
| node.hash = append(hash[:], filemode.Submodule.Bytes()...) |
| node.isDir = false |
| } |
| |
| return node, nil |
| } |
| |
| func (n *node) calculateHash(path string, file os.FileInfo) ([]byte, error) { |
| if file.IsDir() { |
| return make([]byte, 24), nil |
| } |
| |
| var hash plumbing.Hash |
| var err error |
| if file.Mode()&os.ModeSymlink != 0 { |
| hash, err = n.doCalculateHashForSymlink(path, file) |
| } else { |
| hash, err = n.doCalculateHashForRegular(path, file) |
| } |
| |
| if err != nil { |
| return nil, err |
| } |
| |
| mode, err := filemode.NewFromOSFileMode(file.Mode()) |
| if err != nil { |
| return nil, err |
| } |
| |
| return append(hash[:], mode.Bytes()...), nil |
| } |
| |
| func (n *node) doCalculateHashForRegular(path string, file os.FileInfo) (plumbing.Hash, error) { |
| f, err := n.fs.Open(path) |
| if err != nil { |
| return plumbing.ZeroHash, err |
| } |
| |
| defer f.Close() |
| |
| h := plumbing.NewHasher(plumbing.BlobObject, file.Size()) |
| if _, err := io.Copy(h, f); err != nil { |
| return plumbing.ZeroHash, err |
| } |
| |
| return h.Sum(), nil |
| } |
| |
| func (n *node) doCalculateHashForSymlink(path string, file os.FileInfo) (plumbing.Hash, error) { |
| target, err := n.fs.Readlink(path) |
| if err != nil { |
| return plumbing.ZeroHash, err |
| } |
| |
| h := plumbing.NewHasher(plumbing.BlobObject, file.Size()) |
| if _, err := h.Write([]byte(target)); err != nil { |
| return plumbing.ZeroHash, err |
| } |
| |
| return h.Sum(), nil |
| } |
| |
| func (n *node) String() string { |
| return n.path |
| } |