package filetree import ( "sort" "strings" ) type FileNode struct { Tree *FileTree Parent *FileNode Name string Collapsed bool Data *FileChangeInfo Children map[string]*FileNode } func NewNode(parent *FileNode, name string, data *FileChangeInfo) (node *FileNode) { node = new(FileNode) node.Name = name if data == nil { data = &FileChangeInfo{} } node.Data = data node.Children = make(map[string]*FileNode) node.Parent = parent if parent != nil { node.Tree = parent.Tree } return node } func (node *FileNode) Copy() *FileNode { // newNode := new(FileNode) // *newNode = *node // return newNode newNode := NewNode(node.Parent, node.Name, node.Data) for name, child := range node.Children { newNode.Children[name] = child.Copy() } return newNode } func (node *FileNode) AddChild(name string, data *FileChangeInfo) (child *FileNode) { child = NewNode(node, name, data) if node.Children[name] != nil { // tree node already exists, replace the payload, keep the children node.Children[name].Data = data } else { node.Children[name] = child node.Tree.Size++ } return child } func (node *FileNode) Remove() error { for _, child := range node.Children { child.Remove() } delete(node.Parent.Children, node.Name) node.Tree.Size-- return nil } func (node *FileNode) String() string { return node.Name } func (node *FileNode) Visit(visiter Visiter) error { var keys []string for key := range node.Children { keys = append(keys, key) } sort.Strings(keys) for _, name := range keys { child := node.Children[name] err := child.Visit(visiter) if err != nil { return err } } return visiter(node) } func (node *FileNode) VisitDepthParentFirst(visiter Visiter, evaluator VisitEvaluator) error { err := visiter(node) if err != nil { return err } var keys []string for key := range node.Children { keys = append(keys, key) } sort.Strings(keys) for _, name := range keys { child := node.Children[name] if evaluator == nil || !evaluator(node) { continue } err = child.VisitDepthParentFirst(visiter, evaluator) if err != nil { return err } } return err } func (node *FileNode) IsWhiteout() bool { return strings.HasPrefix(node.Name, whiteoutPrefix) } func (node *FileNode) Path() string { path := []string{} curNode := node for { if curNode.Parent == nil { break } name := curNode.Name if curNode == node { // white out prefixes are fictitious on leaf nodes name = strings.TrimPrefix(name, whiteoutPrefix) } path = append([]string{name}, path...) curNode = curNode.Parent } return "/" + strings.Join(path, "/") } func (node *FileNode) IsLeaf() bool { return len(node.Children) == 0 } func (node *FileNode) deriveDiffType(diffType DiffType) error { // THE DIFF_TYPE OF A NODE IS ALWAYS THE DIFF_TYPE OF ITS ATTRIBUTES AND ITS CONTENTS // THE CONTENTS ARE THE BYTES OF A FILE OR THE CHILDREN OF A DIRECTORY if node.IsLeaf() { node.AssignDiffType(diffType) return nil } myDiffType := diffType for _, v := range node.Children { vData := v.Data myDiffType = myDiffType.merge(vData.DiffType) } node.AssignDiffType(myDiffType) return nil } func (node *FileNode) AssignDiffType(diffType DiffType) error { if node.Path() == "/" { return nil } node.Data.DiffType = diffType return nil } func (a *FileNode) compare(b *FileNode) DiffType { if a == nil && b == nil { return Unchanged } // a is nil but not b if a == nil && b != nil { return Added } // b is nil but not a if a != nil && b == nil { return Removed } if b.IsWhiteout() { return Removed } if a.Name != b.Name { panic("comparing mismatched nodes") } // TODO: fails on nil return a.Data.getDiffType(b.Data) }