package fsnoder
import (
"bytes"
"fmt"
"hash/fnv"
"sort"
"strings"
"github.com/go-git/go-git/v5/utils/merkletrie/noder"
)
// Dir values implement directory-like noders.
type dir struct {
name string // relative
children []noder.Noder // sorted by name
hash []byte // memoized
}
type byName []noder.Noder
func (a byName) Len() int { return len(a) }
func (a byName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a byName) Less(i, j int) bool {
return strings.Compare(a[i].Name(), a[j].Name()) < 0
}
// copies the children slice, so nobody can modify the order of its
// elements from the outside.
func newDir(name string, children []noder.Noder) (*dir, error) {
cloned := make([]noder.Noder, len(children))
_ = copy(cloned, children)
sort.Sort(byName(cloned))
if hasChildrenWithNoName(cloned) {
return nil, fmt.Errorf("non-root inner nodes cannot have empty names")
}
if hasDuplicatedNames(cloned) {
return nil, fmt.Errorf("children cannot have duplicated names")
}
return &dir{
name: name,
children: cloned,
}, nil
}
func hasChildrenWithNoName(children []noder.Noder) bool {
for _, c := range children {
if c.Name() == "" {
return true
}
}
return false
}
func hasDuplicatedNames(children []noder.Noder) bool {
if len(children) < 2 {
return false
}
for i := 1; i < len(children); i++ {
if children[i].Name() == children[i-1].Name() {
return true
}
}
return false
}
func (d *dir) Hash() []byte {
if d.hash == nil {
d.calculateHash()
}
return d.hash
}
// hash is calculated as the hash of "dir " plus the concatenation, for
// each child, of its name, a space and its hash. Children are sorted
// alphabetically before calculating the hash, so the result is unique.
func (d *dir) calculateHash() {
h := fnv.New64a()
h.Write([]byte("dir "))
for _, c := range d.children {
h.Write([]byte(c.Name()))
h.Write([]byte(" "))
h.Write(c.Hash())
}
d.hash = h.Sum([]byte{})
}
func (d *dir) Name() string {
return d.name
}
func (d *dir) IsDir() bool {
return true
}
// returns a copy so nobody can alter the order of its elements from the
// outside.
func (d *dir) Children() ([]noder.Noder, error) {
clon := make([]noder.Noder, len(d.children))
_ = copy(clon, d.children)
return clon, nil
}
func (d *dir) NumChildren() (int, error) {
return len(d.children), nil
}
const (
dirStartMark = '('
dirEndMark = ')'
dirElementSep = ' '
)
// The string generated by this method is unique for each tree, as the
// children of each node are sorted alphabetically by name when
// generating the string.
func (d *dir) String() string {
var buf bytes.Buffer
buf.WriteString(d.name)
buf.WriteRune(dirStartMark)
for i, c := range d.children {
if i != 0 {
buf.WriteRune(dirElementSep)
}
buf.WriteString(c.String())
}
buf.WriteRune(dirEndMark)
return buf.String()
}