diff --git a/filechangeinfo.go b/filechangeinfo.go new file mode 100644 index 0000000..8a4360a --- /dev/null +++ b/filechangeinfo.go @@ -0,0 +1,62 @@ +package main + +import ( + "fmt" + "bytes" +) + +type FileChangeInfo struct { + path string + typeflag byte + md5sum [16]byte + diffType DiffType +} + +type DiffType int + +// enum to show whether a file has changed +const ( + Unchanged DiffType = iota + Changed + Added + Removed +) + +func (d DiffType) String() string { + switch d { + case Unchanged: + return "Unchanged" + case Changed: + return "Changed" + case Added: + return "Added" + case Removed: + return "Removed" + default: + return fmt.Sprintf("%d", int(d)) + } +} + +func (a DiffType) merge(b DiffType) DiffType { + if a == b { + return a + } + return Changed +} + +func (a *FileChangeInfo) getDiffType(b *FileChangeInfo) DiffType { + if a == nil && b == nil { + return Unchanged + } + if a == nil || b == nil { + return Changed + } + if a.typeflag == b.typeflag { + if bytes.Compare(a.md5sum[:], b.md5sum[:]) == 0 { + return Unchanged + } + } + return Changed +} + + diff --git a/filechangeinfo_test.go b/filechangeinfo_test.go new file mode 100644 index 0000000..38cc2c9 --- /dev/null +++ b/filechangeinfo_test.go @@ -0,0 +1,64 @@ +package main + +import ( + "testing" + "fmt" +) + +func TestAssignDiffType(t *testing.T) { + tree := NewTree() + tree.AddPath("/usr", BlankFileChangeInfo("/usr", Changed)) + if tree.root.children["usr"].data.diffType != Changed { + t.Fail() + } +} + +func TestMergeDiffTypes(t *testing.T) { + a := Unchanged + b := Unchanged + merged := a.merge(b) + if merged != Unchanged { + t.Errorf("Expected Unchaged (0) but got %v", merged) + } + a = Changed + b = Unchanged + merged = a.merge(b) + if merged != Changed { + t.Errorf("Expected Unchaged (0) but got %v", merged) + } +} + +func TestDiffTypeFromChildren(t *testing.T) { + tree := NewTree() + tree.AddPath("/usr", BlankFileChangeInfo("/usr", Unchanged)) + info1 := BlankFileChangeInfo("/usr/bin", Added) + tree.AddPath("/usr/bin", info1) + info2 := BlankFileChangeInfo("/usr/bin2", Removed) + tree.AddPath("/usr/bin2", info2) + tree.root.children["usr"].deriveDiffType(Unchanged) + if tree.root.children["usr"].data.diffType != Changed { + t.Errorf("Expected Changed but got %v", tree.root.children["usr"].data.diffType) + } +} + +func AssertDiffType(node *FileNode, expectedDiffType DiffType, t *testing.T) error { + if node.data == nil { + t.Errorf("Expected *FileChangeInfo but got nil at path %s", node.Path()) + return fmt.Errorf("expected *FileChangeInfo but got nil at path %s", node.Path()) + } + if node.data.diffType != expectedDiffType { + t.Errorf("Expecting node at %s to have DiffType %v, but had %v", node.Path(), expectedDiffType, node.data.diffType) + return fmt.Errorf("Assertion failed") + } + return nil +} + +func BlankFileChangeInfo(path string, diffType DiffType) (f *FileChangeInfo) { + result := FileChangeInfo{ + path: path, + typeflag: 1, + md5sum: [16]byte{1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0}, + diffType: diffType, + } + return &result +} diff --git a/filenode.go b/filenode.go new file mode 100644 index 0000000..1d78d18 --- /dev/null +++ b/filenode.go @@ -0,0 +1,189 @@ +package main + +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) +} diff --git a/filenode_test.go b/filenode_test.go new file mode 100644 index 0000000..2477773 --- /dev/null +++ b/filenode_test.go @@ -0,0 +1,109 @@ +package main + +import "testing" + + +func TestAddChild(t *testing.T) { + var expected, actual int + tree := NewTree() + + payload := FileChangeInfo{ + path: "stufffffs", + } + + one := tree.Root().AddChild("first node!", &payload) + + two := tree.Root().AddChild("nil node!", nil) + + tree.Root().AddChild("third node!", nil) + two.AddChild("forth, one level down...", nil) + two.AddChild("fifth, one level down...", nil) + two.AddChild("fifth, one level down...", nil) + + expected, actual = 5, tree.size + if expected != actual { + t.Errorf("Expected a tree size of %d got %d.", expected, actual) + } + + expected, actual = 2, len(two.children) + if expected != actual { + t.Errorf("Expected 'twos' number of children to be %d got %d.", expected, actual) + } + + expected, actual = 3, len(tree.Root().children) + if expected != actual { + t.Errorf("Expected 'twos' number of children to be %d got %d.", expected, actual) + } + + expectedFC := &FileChangeInfo{ + path: "stufffffs", + } + actualFC := one.data + if *expectedFC != *actualFC { + t.Errorf("Expected 'ones' payload to be %+v got %+v.", expectedFC, actualFC) + } + + if *two.data != *new(FileChangeInfo) { + t.Errorf("Expected 'twos' payload to be nil got %d.", two.data) + } + +} + +func TestRemoveChild(t *testing.T) { + var expected, actual int + + tree := NewTree() + tree.Root().AddChild("first", nil) + two := tree.Root().AddChild("nil", nil) + tree.Root().AddChild("third", nil) + forth := two.AddChild("forth", nil) + two.AddChild("fifth", nil) + + forth.Remove() + + expected, actual = 4, tree.size + if expected != actual { + t.Errorf("Expected a tree size of %d got %d.", expected, actual) + } + + if tree.Root().children["forth"] != nil { + t.Errorf("Expected 'forth' node to be deleted.") + } + + two.Remove() + + expected, actual = 2, tree.size + if expected != actual { + t.Errorf("Expected a tree size of %d got %d.", expected, actual) + } + + if tree.Root().children["nil"] != nil { + t.Errorf("Expected 'nil' node to be deleted.") + } + +} + +func TestPath(t *testing.T) { + expected := "/etc/nginx/nginx.conf" + tree := NewTree() + node, _ := tree.AddPath(expected, nil) + + actual := node.Path() + if expected != actual { + t.Errorf("Expected path '%s' got '%s'", expected, actual) + } +} + +func TestIsWhiteout(t *testing.T) { + tree1 := NewTree() + p1, _ := tree1.AddPath("/etc/nginx/public1", nil) + p2, _ := tree1.AddPath("/etc/nginx/.wh.public2", nil) + + if p1.IsWhiteout() != false { + t.Errorf("Expected path '%s' to **not** be a whiteout file", p1.name) + } + + if p2.IsWhiteout() != true { + t.Errorf("Expected path '%s' to be a whiteout file", p2.name) + } +} \ No newline at end of file diff --git a/tree.go b/filetree.go similarity index 54% rename from tree.go rename to filetree.go index 41543a0..b46812f 100644 --- a/tree.go +++ b/filetree.go @@ -20,76 +20,27 @@ const ( type FileTree struct { - root *Node + root *FileNode size int name string } -type Node struct { - tree *FileTree - parent *Node - name string - collapsed bool - data *FileChangeInfo - children map[string]*Node -} - func NewTree() (tree *FileTree) { tree = new(FileTree) tree.size = 0 - tree.root = new(Node) + tree.root = new(FileNode) tree.root.tree = tree - tree.root.children = make(map[string]*Node) + tree.root.children = make(map[string]*FileNode) return tree } -func NewNode(parent *Node, name string, data *FileChangeInfo) (node *Node) { - node = new(Node) - node.name = name - if data == nil { - data = &FileChangeInfo{} - } - node.data = data - node.children = make(map[string]*Node) - node.parent = parent - if parent != nil { - node.tree = parent.tree - } - return node -} - -func (tree *FileTree) Root() *Node { +func (tree *FileTree) Root() *FileNode { return tree.root } -func (node *Node) AddChild(name string, data *FileChangeInfo) (child *Node) { - 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 *Node) Remove() error { - for _, child := range node.children { - child.Remove() - } - delete(node.parent.children, node.name) - node.tree.size-- - return nil -} - -func (node *Node) String() string { - return node.name -} - func (tree *FileTree) String() string { var renderLine func(string, []bool, bool, bool) string - var walkTree func(*Node, []bool, int) string + var walkTree func(*FileNode, []bool, int) string renderLine = func(nodeText string, spaces []bool, last bool, collapsed bool) string { var otherBranches string @@ -114,7 +65,7 @@ func (tree *FileTree) String() string { return otherBranches + thisBranch + collapsedIndicator + nodeText + newLine } - walkTree = func(node *Node, spaces []bool, depth int) string { + walkTree = func(node *FileNode, spaces []bool, depth int) string { var result string var keys []string for key := range node.children { @@ -137,16 +88,6 @@ func (tree *FileTree) String() string { return "." + newLine + walkTree(tree.Root(), []bool{}, 0) } -func (node *Node) Copy() *Node { - // newNode := new(Node) - // *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 (tree *FileTree) Copy() *FileTree { newTree := NewTree() @@ -156,8 +97,8 @@ func (tree *FileTree) Copy() *FileTree { return newTree } -type Visiter func(*Node) error -type VisitEvaluator func(*Node) bool +type Visiter func(*FileNode) error +type VisitEvaluator func(*FileNode) bool func (tree *FileTree) Visit(visiter Visiter) error { return tree.root.Visit(visiter) @@ -167,72 +108,8 @@ func (tree *FileTree) VisitDepthParentFirst(visiter Visiter, evaluator VisitEval return tree.root.VisitDepthParentFirst(visiter, evaluator) } -func (node *Node) 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 *Node) 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 *Node) IsWhiteout() bool { - return strings.HasPrefix(node.name, whiteoutPrefix) -} - -func (node *Node) 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 (tree *FileTree) Stack(upper *FileTree) error { - graft := func(node *Node) error { + graft := func(node *FileNode) error { if node.IsWhiteout() { err := tree.RemovePath(node.Path()) if err != nil { @@ -249,7 +126,7 @@ func (tree *FileTree) Stack(upper *FileTree) error { return upper.Visit(graft) } -func (tree *FileTree) GetNode(path string) (*Node, error) { +func (tree *FileTree) GetNode(path string) (*FileNode, error) { nodeNames := strings.Split(path, "/") node := tree.Root() for _, name := range nodeNames { @@ -264,7 +141,7 @@ func (tree *FileTree) GetNode(path string) (*Node, error) { return node, nil } -func (tree *FileTree) AddPath(path string, data *FileChangeInfo) (*Node, error) { +func (tree *FileTree) AddPath(path string, data *FileChangeInfo) (*FileNode, error) { nodeNames := strings.Split(path, "/") node := tree.Root() for idx, name := range nodeNames { @@ -296,3 +173,39 @@ func (tree *FileTree) RemovePath(path string) error { } return node.Remove() } + + +func (tree *FileTree) compare(upper *FileTree) error { + graft := func(node *FileNode) error { + if node.IsWhiteout() { + err := tree.MarkRemoved(node.Path()) + if err != nil { + return fmt.Errorf("Cannot remove node %s: %v", node.Path(), err.Error()) + } + } else { + existingNode, _ := tree.GetNode(node.Path()) + if existingNode == nil { + newNode, err := tree.AddPath(node.Path(), node.data) + fmt.Printf("added new node at %s\n", newNode.Path()) + if err != nil { + return fmt.Errorf("Cannot add new node %s: %v", node.Path(), err.Error()) + } + newNode.AssignDiffType(Added) + } else { + diffType := existingNode.compare(node) + fmt.Printf("found existing node at %s\n", existingNode.Path()) + existingNode.deriveDiffType(diffType) + } + } + return nil + } + return upper.Visit(graft) +} + +func (tree *FileTree) MarkRemoved(path string) error { + node, err := tree.GetNode(path) + if err != nil { + return err + } + return node.AssignDiffType(Removed) +} diff --git a/tree_test.go b/filetree_test.go similarity index 55% rename from tree_test.go rename to filetree_test.go index 095d1cd..6c4d849 100644 --- a/tree_test.go +++ b/filetree_test.go @@ -1,88 +1,9 @@ package main -import "testing" - -func TestAddChild(t *testing.T) { - var expected, actual int - tree := NewTree() - - payload := FileChangeInfo{ - path: "stufffffs", - } - - one := tree.Root().AddChild("first node!", &payload) - - two := tree.Root().AddChild("nil node!", nil) - - tree.Root().AddChild("third node!", nil) - two.AddChild("forth, one level down...", nil) - two.AddChild("fifth, one level down...", nil) - two.AddChild("fifth, one level down...", nil) - - expected, actual = 5, tree.size - if expected != actual { - t.Errorf("Expected a tree size of %d got %d.", expected, actual) - } - - expected, actual = 2, len(two.children) - if expected != actual { - t.Errorf("Expected 'twos' number of children to be %d got %d.", expected, actual) - } - - expected, actual = 3, len(tree.Root().children) - if expected != actual { - t.Errorf("Expected 'twos' number of children to be %d got %d.", expected, actual) - } - - expectedFC := &FileChangeInfo{ - path: "stufffffs", - } - actualFC := one.data - if *expectedFC != *actualFC { - t.Errorf("Expected 'ones' payload to be %+v got %+v.", expectedFC, actualFC) - } - - if *two.data != *new(FileChangeInfo) { - t.Errorf("Expected 'twos' payload to be nil got %d.", two.data) - } - - - -} - -func TestRemoveChild(t *testing.T) { - var expected, actual int - - tree := NewTree() - tree.Root().AddChild("first", nil) - two := tree.Root().AddChild("nil", nil) - tree.Root().AddChild("third", nil) - forth := two.AddChild("forth", nil) - two.AddChild("fifth", nil) - - forth.Remove() - - expected, actual = 4, tree.size - if expected != actual { - t.Errorf("Expected a tree size of %d got %d.", expected, actual) - } - - if tree.Root().children["forth"] != nil { - t.Errorf("Expected 'forth' node to be deleted.") - } - - two.Remove() - - expected, actual = 2, tree.size - if expected != actual { - t.Errorf("Expected a tree size of %d got %d.", expected, actual) - } - - if tree.Root().children["nil"] != nil { - t.Errorf("Expected 'nil' node to be deleted.") - } - -} +import ( + "testing" + "fmt" +) func TestPrintTree(t *testing.T) { tree := NewTree() @@ -163,31 +84,6 @@ func TestRemovePath(t *testing.T) { } -func TestPath(t *testing.T) { - expected := "/etc/nginx/nginx.conf" - tree := NewTree() - node, _ := tree.AddPath(expected, nil) - - actual := node.Path() - if expected != actual { - t.Errorf("Expected path '%s' got '%s'", expected, actual) - } -} - -func TestIsWhiteout(t *testing.T) { - tree1 := NewTree() - p1, _ := tree1.AddPath("/etc/nginx/public1", nil) - p2, _ := tree1.AddPath("/etc/nginx/.wh.public2", nil) - - if p1.IsWhiteout() != false { - t.Errorf("Expected path '%s' to **not** be a whiteout file", p1.name) - } - - if p2.IsWhiteout() != true { - t.Errorf("Expected path '%s' to be a whiteout file", p2.name) - } -} - func TestStack(t *testing.T) { payloadKey := "/var/run/systemd" payloadValue := FileChangeInfo{ @@ -274,3 +170,132 @@ func TestCopy(t *testing.T) { } } + + + +func TestCompareWithNoChanges(t *testing.T) { + lowerTree := NewTree() + upperTree := NewTree() + paths := [...]string{"/etc", "/etc/sudoers", "/etc/hosts", "/usr/bin", "/usr/bin/bash", "/usr"} + + for _, value := range paths { + fakeData := FileChangeInfo{ + path: value, + typeflag: 1, + md5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + diffType: Unchanged, + } + lowerTree.AddPath(value, &fakeData) + upperTree.AddPath(value, &fakeData) + } + lowerTree.compare(upperTree) + asserter := func(n *FileNode) error { + if n.Path() == "/" { + return nil + } + if n.data == nil { + t.Errorf("Expected *FileChangeInfo but got nil") + return fmt.Errorf("expected *FileChangeInfo but got nil") + } + if (n.data.diffType) != Unchanged { + t.Errorf("Expecting node at %s to have DiffType unchanged, but had %v", n.Path(), n.data.diffType) + } + return nil + } + err := lowerTree.Visit(asserter) + if err != nil { + t.Error(err) + } +} + +func TestCompareWithAdds(t *testing.T) { + lowerTree := NewTree() + upperTree := NewTree() + lowerPaths := [...]string{"/etc", "/etc/sudoers", "/usr", "/etc/hosts", "/usr/bin"} + upperPaths := [...]string{"/etc", "/etc/sudoers", "/usr", "/etc/hosts", "/usr/bin", "/usr/bin/bash"} + + for _, value := range lowerPaths { + fakeData := FileChangeInfo{ + path: value, + typeflag: 1, + md5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + diffType: Unchanged, + } + lowerTree.AddPath(value, &fakeData) + } + + for _, value := range upperPaths { + fakeData := FileChangeInfo{ + path: value, + typeflag: 1, + md5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + diffType: Unchanged, + } + upperTree.AddPath(value, &fakeData) + } + + lowerTree.compare(upperTree) + asserter := func(n *FileNode) error { + + p := n.Path() + if p == "/" { + return nil + } + // Adding a file changes the folders it's in + if p == "/usr/bin/bash" { + return AssertDiffType(n, Added, t) + } + if p == "/usr/bin" { + return AssertDiffType(n, Changed, t) + } + if p == "/usr" { + return AssertDiffType(n, Changed, t) + } + return AssertDiffType(n, Unchanged, t) + } + err := lowerTree.Visit(asserter) + if err != nil { + t.Error(err) + } +} + +func TestCompareWithChanges(t *testing.T) { + lowerTree := NewTree() + upperTree := NewTree() + lowerPaths := [...]string{"/etc", "/usr", "/etc/hosts", "/etc/sudoers", "/usr/bin"} + upperPaths := [...]string{"/etc", "/usr", "/etc/hosts", "/etc/sudoers", "/usr/bin"} + + for _, value := range lowerPaths { + fakeData := FileChangeInfo{ + path: value, + typeflag: 1, + md5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + diffType: Unchanged, + } + lowerTree.AddPath(value, &fakeData) + } + + for _, value := range upperPaths { + fakeData := FileChangeInfo{ + path: value, + typeflag: 1, + md5sum: [16]byte{1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0}, + diffType: Unchanged, + } + upperTree.AddPath(value, &fakeData) + } + + lowerTree.compare(upperTree) + asserter := func(n *FileNode) error { + p := n.Path() + if p == "/" { + return nil + } + return AssertDiffType(n, Changed, t) + } + err := lowerTree.Visit(asserter) + if err != nil { + t.Error(err) + } +} + diff --git a/main.go b/main.go index 6ddb9d5..5300971 100644 --- a/main.go +++ b/main.go @@ -124,12 +124,12 @@ func demo() { -func getAbsPositionNode() (node *Node) { - var visiter func(*Node) error - var evaluator func(*Node) bool +func getAbsPositionNode() (node *FileNode) { + var visiter func(*FileNode) error + var evaluator func(*FileNode) bool var dfsCounter uint - visiter = func(curNode *Node) error { + visiter = func(curNode *FileNode) error { if dfsCounter == data.absPosition { node = curNode } @@ -137,7 +137,7 @@ func getAbsPositionNode() (node *Node) { return nil } - evaluator = func(curNode *Node) bool { + evaluator = func(curNode *FileNode) bool { return !curNode.collapsed } @@ -154,7 +154,7 @@ func showCurNodeInSideBar(g *gocui.Gui, v *gocui.View) error { v, _ := g.View("side") // todo: handle above error. v.Clear() - _, err := fmt.Fprintf(v, "Node:\n%+v\n", getAbsPositionNode()) + _, err := fmt.Fprintf(v, "FileNode:\n%+v\n", getAbsPositionNode()) return err }) // todo: blerg diff --git a/tar-read.go b/tar-read.go index 02c8c2d..f436760 100644 --- a/tar-read.go +++ b/tar-read.go @@ -163,12 +163,7 @@ func makeEntry(r *tar.Reader, h *tar.Header, path string) FileChangeInfo { var zeros = [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} -type FileChangeInfo struct { - path string - typeflag byte - md5sum [16]byte - diffType DiffType -} + type Manifest struct { Config string diff --git a/tree_compare.go b/tree_compare.go deleted file mode 100644 index 4f2034d..0000000 --- a/tree_compare.go +++ /dev/null @@ -1,161 +0,0 @@ -package main - -import ( - "bytes" - "fmt" -) - -func differ() func(int) int { - sum := 0 - return func(x int) int { - sum += x - return sum - } -} - -type DiffType int - -// enum to show whether a file has changed -const ( - Unchanged DiffType = iota - Changed - Added - Removed -) - -func (d DiffType) String() string { - switch d { - case Unchanged: - return "Unchanged" - case Changed: - return "Changed" - case Added: - return "Added" - case Removed: - return "Removed" - default: - return fmt.Sprintf("%d", int(d)) - } -} - -func compareNodes(a *Node, b *Node) 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 getDiffType(a.data, b.data) -} - -func getDiffType(a *FileChangeInfo, b *FileChangeInfo) DiffType { - if a == nil && b == nil { - return Unchanged - } - if a == nil || b == nil { - return Changed - } - if a.typeflag == b.typeflag { - if bytes.Compare(a.md5sum[:], b.md5sum[:]) == 0 { - return Unchanged - } - } - return Changed -} - -func mergeDiffTypes(a DiffType, b DiffType) DiffType { - if a == b { - return a - } - return Changed -} - -func (tree *FileTree) compareTo(upper *FileTree) error { - - // TODO mark all as unchanged - markAllUnchanged := func(node *Node) error { - return node.AssignDiffType(Unchanged) - } - err := tree.Visit(markAllUnchanged) - if err != nil { - return err - } - graft := func(node *Node) error { - if node.IsWhiteout() { - err := tree.MarkRemoved(node.Path()) - if err != nil { - return fmt.Errorf("Cannot remove node %s: %v", node.Path(), err.Error()) - } - } else { - existingNode, _ := tree.GetNode(node.Path()) - if existingNode == nil { - newNode, err := tree.AddPath(node.Path(), node.data) - fmt.Printf("added new node at %s\n", newNode.Path()) - if err != nil { - return fmt.Errorf("Cannot add new node %s: %v", node.Path(), err.Error()) - } - newNode.AssignDiffType(Added) - } else { - diffType := compareNodes(existingNode, node) - fmt.Printf("found existing node at %s\n", existingNode.Path()) - existingNode.DiffTypeFromChildren(diffType) - } - } - return nil - } - return upper.Visit(graft) -} - -// 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 - -func (tree *FileTree) MarkRemoved(path string) error { - node, err := tree.GetNode(path) - if err != nil { - return err - } - return node.AssignDiffType(Removed) -} - -func (node *Node) IsLeaf() bool { - return len(node.children) == 0 -} - -func (node *Node) DiffTypeFromChildren(diffType DiffType) error { - if node.IsLeaf() { - node.AssignDiffType(diffType) - return nil - } - myDiffType := diffType - - for _, v := range node.children { - vData := v.data - myDiffType = mergeDiffTypes(myDiffType, vData.diffType) - - } - node.AssignDiffType(myDiffType) - return nil -} - -func (node *Node) AssignDiffType(diffType DiffType) error { - if node.Path() == "/" { - return nil - } - node.data.diffType = diffType - return nil -} diff --git a/tree_compare_test.go b/tree_compare_test.go deleted file mode 100644 index 64cc76f..0000000 --- a/tree_compare_test.go +++ /dev/null @@ -1,190 +0,0 @@ -package main - -import ( - "fmt" - "testing" -) - -func TestCompareWithNoChanges(t *testing.T) { - lowerTree := NewTree() - upperTree := NewTree() - paths := [...]string{"/etc", "/etc/sudoers", "/etc/hosts", "/usr/bin", "/usr/bin/bash", "/usr"} - - for _, value := range paths { - fakeData := FileChangeInfo{ - path: value, - typeflag: 1, - md5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - diffType: Unchanged, - } - lowerTree.AddPath(value, &fakeData) - upperTree.AddPath(value, &fakeData) - } - lowerTree.compareTo(upperTree) - asserter := func(n *Node) error { - if n.Path() == "/" { - return nil - } - if n.data == nil { - t.Errorf("Expected *FileChangeInfo but got nil") - return fmt.Errorf("expected *FileChangeInfo but got nil") - } - if (n.data.diffType) != Unchanged { - t.Errorf("Expecting node at %s to have DiffType unchanged, but had %v", n.Path(), n.data.diffType) - } - return nil - } - err := lowerTree.Visit(asserter) - if err != nil { - t.Error(err) - } -} - -func TestCompareWithAdds(t *testing.T) { - lowerTree := NewTree() - upperTree := NewTree() - lowerPaths := [...]string{"/etc", "/etc/sudoers", "/usr", "/etc/hosts", "/usr/bin"} - upperPaths := [...]string{"/etc", "/etc/sudoers", "/usr", "/etc/hosts", "/usr/bin", "/usr/bin/bash"} - - for _, value := range lowerPaths { - fakeData := FileChangeInfo{ - path: value, - typeflag: 1, - md5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - diffType: Unchanged, - } - lowerTree.AddPath(value, &fakeData) - } - - for _, value := range upperPaths { - fakeData := FileChangeInfo{ - path: value, - typeflag: 1, - md5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - diffType: Unchanged, - } - upperTree.AddPath(value, &fakeData) - } - - lowerTree.compareTo(upperTree) - asserter := func(n *Node) error { - - p := n.Path() - if p == "/" { - return nil - } - // Adding a file changes the folders it's in - if p == "/usr/bin/bash" { - return AssertDiffType(n, Added, t) - } - if p == "/usr/bin" { - return AssertDiffType(n, Changed, t) - } - if p == "/usr" { - return AssertDiffType(n, Changed, t) - } - return AssertDiffType(n, Unchanged, t) - } - err := lowerTree.Visit(asserter) - if err != nil { - t.Error(err) - } -} - -func TestCompareWithChanges(t *testing.T) { - lowerTree := NewTree() - upperTree := NewTree() - lowerPaths := [...]string{"/etc", "/usr", "/etc/hosts", "/etc/sudoers", "/usr/bin"} - upperPaths := [...]string{"/etc", "/usr", "/etc/hosts", "/etc/sudoers", "/usr/bin"} - - for _, value := range lowerPaths { - fakeData := FileChangeInfo{ - path: value, - typeflag: 1, - md5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - diffType: Unchanged, - } - lowerTree.AddPath(value, &fakeData) - } - - for _, value := range upperPaths { - fakeData := FileChangeInfo{ - path: value, - typeflag: 1, - md5sum: [16]byte{1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0}, - diffType: Unchanged, - } - upperTree.AddPath(value, &fakeData) - } - - lowerTree.compareTo(upperTree) - asserter := func(n *Node) error { - p := n.Path() - if p == "/" { - return nil - } - return AssertDiffType(n, Changed, t) - } - err := lowerTree.Visit(asserter) - if err != nil { - t.Error(err) - } -} - -func TestAssignDiffType(t *testing.T) { - tree := NewTree() - tree.AddPath("/usr", BlankFileChangeInfo("/usr", Changed)) - if tree.root.children["usr"].data.diffType != Changed { - t.Fail() - } -} - -func TestMergeDiffTypes(t *testing.T) { - a := Unchanged - b := Unchanged - merged := mergeDiffTypes(a, b) - if merged != Unchanged { - t.Errorf("Expected Unchaged (0) but got %v", merged) - } - a = Changed - b = Unchanged - merged = mergeDiffTypes(a, b) - if merged != Changed { - t.Errorf("Expected Unchaged (0) but got %v", merged) - } -} - -func TestDiffTypeFromChildren(t *testing.T) { - tree := NewTree() - tree.AddPath("/usr", BlankFileChangeInfo("/usr", Unchanged)) - info1 := BlankFileChangeInfo("/usr/bin", Added) - tree.AddPath("/usr/bin", info1) - info2 := BlankFileChangeInfo("/usr/bin2", Removed) - tree.AddPath("/usr/bin2", info2) - tree.root.children["usr"].DiffTypeFromChildren(Unchanged) - if tree.root.children["usr"].data.diffType != Changed { - t.Errorf("Expected Changed but got %v", tree.root.children["usr"].data.diffType) - } -} - -func AssertDiffType(node *Node, expectedDiffType DiffType, t *testing.T) error { - if node.data == nil { - t.Errorf("Expected *FileChangeInfo but got nil at path %s", node.Path()) - return fmt.Errorf("expected *FileChangeInfo but got nil at path %s", node.Path()) - } - if node.data.diffType != expectedDiffType { - t.Errorf("Expecting node at %s to have DiffType %v, but had %v", node.Path(), expectedDiffType, node.data.diffType) - return fmt.Errorf("Assertion failed") - } - return nil -} - -func BlankFileChangeInfo(path string, diffType DiffType) (f *FileChangeInfo) { - result := FileChangeInfo{ - path: path, - typeflag: 1, - md5sum: [16]byte{1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0}, - diffType: diffType, - } - return &result -}