Forráskód Böngészése

Merge pull request #79 from vmarkovtsev/master

Add support for merging files
Vadim Markovtsev 6 éve
szülő
commit
b8790001ba
4 módosított fájl, 418 hozzáadás és 121 törlés
  1. 4 3
      OCTOPUS.md
  2. 84 7
      internal/burndown/file.go
  3. 184 0
      internal/burndown/file_test.go
  4. 146 111
      internal/rbtree/rbtree.go

+ 4 - 3
OCTOPUS.md

@@ -7,16 +7,17 @@ thinking how to include them into the analysis.
 
 ### Plan
 
-* Commits must sorted by time.
+* Commits must be sorted by time.
 * When a fork is hit, clone the pipeline. Assign the old instance to the main branch and new
 instances to the sprouts. BurndownAnalysis should share the same counters for efficiency
 and simplicity, but the files must be copied.
 * Follow each branch independently. Clone side pipelines as needed.
 * Join pipelines on merge commits. Side pipelines are killed, the main instance survives.
 This will be tricky for Burndown because we need to join the files together while preserving
-the line annotations.
+the line annotations. The plan is to calculate the separate line annotations for each branch and blend them,
+the oldest timestamp winning.
 * Merge commits should have diffs which correspond to CGit diffs. So far they represent the diff
-with the previous commits in the main branch.
+with the previous commit in the main branch.
 * The sequence of commits must be the analysis scenario: it must inform when to fork and to merge,
 which pipeline instance to apply.
 

+ 84 - 7
internal/burndown/file.go

@@ -36,9 +36,18 @@ func NewStatus(data interface{}, update func(interface{}, int, int, int)) Status
 }
 
 // TreeEnd denotes the value of the last leaf in the tree.
-const TreeEnd int = -1
+const TreeEnd = -1
+// TreeMaxBinPower is the binary power value which corresponds to the maximum day which
+// can be stored in the tree.
+const TreeMaxBinPower = 14
+// TreeMergeMark is the special day which disables the status updates and is used in File.Merge().
+const TreeMergeMark = (1 << TreeMaxBinPower) - 1
 
 func (file *File) updateTime(currentTime int, previousTime int, delta int) {
+	if currentTime & TreeMergeMark == TreeMergeMark {
+		// merge mode
+		return
+	}
 	for _, status := range file.statuses {
 		status.update(status.data, currentTime, previousTime, delta)
 	}
@@ -53,9 +62,7 @@ func (file *File) updateTime(currentTime int, previousTime int, delta int) {
 //
 // statuses are the attached interval length mappings.
 func NewFile(time int, length int, statuses ...Status) *File {
-	file := new(File)
-	file.statuses = statuses
-	file.tree = new(rbtree.RBTree)
+	file := &File{tree: new(rbtree.RBTree), statuses: statuses}
 	if length > 0 {
 		file.updateTime(time, time, length)
 		file.tree.Insert(rbtree.Item{Key: 0, Value: time})
@@ -73,9 +80,7 @@ func NewFile(time int, length int, statuses ...Status) *File {
 //
 // statuses are the attached interval length mappings.
 func NewFileFromTree(keys []int, vals []int, statuses ...Status) *File {
-	file := new(File)
-	file.statuses = statuses
-	file.tree = new(rbtree.RBTree)
+	file := &File{tree: new(rbtree.RBTree), statuses: statuses}
 	if len(keys) != len(vals) {
 		panic("keys and vals must be of equal length")
 	}
@@ -86,6 +91,20 @@ func NewFileFromTree(keys []int, vals []int, statuses ...Status) *File {
 	return file
 }
 
+// Clone copies the file. It performs a deep copy of the tree;
+// depending on `clearStatuses` the original statuses are removed or not.
+// Any new `statuses` are appended.
+func (file *File) Clone(clearStatuses bool, statuses ...Status) *File {
+	clone := &File{tree: file.tree.Clone(), statuses: file.statuses}
+	if clearStatuses {
+		clone.statuses = []Status{}
+	}
+	for _, status := range statuses {
+		clone.statuses = append(clone.statuses, status)
+	}
+	return clone
+}
+
 // Len returns the File's size - that is, the maximum key in the tree of line
 // intervals.
 func (file *File) Len() int {
@@ -218,6 +237,51 @@ func (file *File) Update(time int, pos int, insLength int, delLength int) {
 	}
 }
 
+func (file *File) Merge(day int, others... *File) {
+	myself := file.flatten()
+	for _, other := range others {
+		lines := other.flatten()
+		if len(myself) != len(lines) {
+			panic("file corruption, lines number mismatch during merge")
+		}
+		for i, l := range myself {
+			ol := lines[i]
+			if ol & TreeMergeMark == TreeMergeMark {
+				continue
+			}
+			if l & TreeMergeMark == TreeMergeMark {
+				myself[i] = ol
+			} else if l != ol {
+				// the same line introduced in different branches
+				// consider the oldest version as the ground truth
+				if l > ol {
+					myself[i] = ol
+					// subtract from the newer day l
+					file.updateTime(ol, l, -1)
+				} else {
+					// subtract from the newer day ol
+					file.updateTime(l, ol, -1)
+				}
+			}
+		}
+	}
+	for i, l := range myself {
+		if l & TreeMergeMark == TreeMergeMark {
+			myself[i] = day
+			file.updateTime(day, day, 1)
+		}
+	}
+	// now we need to reconstruct the tree from the discrete values
+	tree := &rbtree.RBTree{}
+	for i, v := range myself {
+		if i == 0 || v != myself[i - 1] {
+			tree.Insert(rbtree.Item{Key: i, Value: v})
+		}
+	}
+	tree.Insert(rbtree.Item{Key: len(myself), Value: TreeEnd})
+	file.tree = tree
+}
+
 // Status returns the bound status object by the specified index.
 func (file *File) Status(index int) interface{} {
 	if index < 0 || index >= len(file.statuses) {
@@ -263,3 +327,16 @@ func (file *File) Validate() {
 		prevKey = node.Key
 	}
 }
+
+// flatten represents the file as a slice of lines, each line's value being the corresponding day.
+func (file *File) flatten() []int {
+	lines := make([]int, 0, file.Len())
+	val := -1
+	for iter := file.tree.Min(); !iter.Limit(); iter = iter.Next() {
+		for i := len(lines); i < iter.Item().Key; i++ {
+			lines = append(lines, val)
+		}
+		val = iter.Item().Value
+	}
+	return lines
+}

+ 184 - 0
internal/burndown/file_test.go

@@ -5,6 +5,7 @@ import (
 
 	"github.com/stretchr/testify/assert"
 	"gopkg.in/src-d/hercules.v4/internal/rbtree"
+	"fmt"
 )
 
 func updateStatusFile(
@@ -54,6 +55,97 @@ func TestBullshitFile(t *testing.T) {
 	assert.Equal(t, int64(0), status[1])
 }
 
+func TestCloneFile(t *testing.T) {
+	file, status := fixtureFile()
+	// 0 0 | 100 -1                             [0]: 100
+	file.Update(1, 20, 30, 0)
+	// 0 0 | 20 1 | 50 0 | 130 -1               [0]: 100, [1]: 30
+	file.Update(2, 20, 0, 5)
+	// 0 0 | 20 1 | 45 0 | 125 -1               [0]: 100, [1]: 25
+	file.Update(3, 20, 0, 5)
+	// 0 0 | 20 1 | 40 0 | 120 -1               [0]: 100, [1]: 20
+	file.Update(4, 20, 10, 0)
+	// 0 0 | 20 4 | 30 1 | 50 0 | 130 -1        [0]: 100, [1]: 20, [4]: 10
+	clone := file.Clone(false)
+	clone.Update(5, 45, 0, 10)
+	// 0 0 | 20 4 | 30 1 | 45 0 | 120 -1        [0]: 95, [1]: 15, [4]: 10
+	clone.Update(6, 45, 5, 0)
+	// 0 0 | 20 4 | 30 1 | 45 6 | 50 0 | 125 -1 [0]: 95, [1]: 15, [4]: 10, [6]: 5
+	assert.Equal(t, int64(95), status[0])
+	assert.Equal(t, int64(15), status[1])
+	assert.Equal(t, int64(0), status[2])
+	assert.Equal(t, int64(0), status[3])
+	assert.Equal(t, int64(10), status[4])
+	assert.Equal(t, int64(0), status[5])
+	assert.Equal(t, int64(5), status[6])
+	dump := file.Dump()
+	// Output:
+	// 0 0
+	// 20 4
+	// 30 1
+	// 50 0
+	// 130 -1
+	assert.Equal(t, "0 0\n20 4\n30 1\n50 0\n130 -1\n", dump)
+	dump = clone.Dump()
+	// Output:
+	// 0 0
+	// 20 4
+	// 30 1
+	// 45 6
+	// 50 0
+	// 125 -1
+	assert.Equal(t, "0 0\n20 4\n30 1\n45 6\n50 0\n125 -1\n", dump)
+
+}
+
+func TestCloneFileClearStatus(t *testing.T) {
+	file, status := fixtureFile()
+	// 0 0 | 100 -1                             [0]: 100
+	file.Update(1, 20, 30, 0)
+	// 0 0 | 20 1 | 50 0 | 130 -1               [0]: 100, [1]: 30
+	file.Update(2, 20, 0, 5)
+	// 0 0 | 20 1 | 45 0 | 125 -1               [0]: 100, [1]: 25
+	file.Update(3, 20, 0, 5)
+	// 0 0 | 20 1 | 40 0 | 120 -1               [0]: 100, [1]: 20
+	file.Update(4, 20, 10, 0)
+	// 0 0 | 20 4 | 30 1 | 50 0 | 130 -1        [0]: 100, [1]: 20, [4]: 10
+	newStatus := map[int]int64{}
+	clone := file.Clone(true, NewStatus(newStatus, updateStatusFile))
+	clone.Update(5, 45, 0, 10)
+	// 0 0 | 20 4 | 30 1 | 45 0 | 120 -1        [0]: -5, [1]: -5
+	clone.Update(6, 45, 5, 0)
+	// 0 0 | 20 4 | 30 1 | 45 6 | 50 0 | 125 -1 [0]: -5, [1]: -5, [6]: 5
+	assert.Equal(t, int64(100), status[0])
+	assert.Equal(t, int64(20), status[1])
+	assert.Equal(t, int64(0), status[2])
+	assert.Equal(t, int64(0), status[3])
+	assert.Equal(t, int64(10), status[4])
+	assert.Equal(t, int64(-5), newStatus[0])
+	assert.Equal(t, int64(-5), newStatus[1])
+	assert.Equal(t, int64(0), newStatus[2])
+	assert.Equal(t, int64(0), newStatus[3])
+	assert.Equal(t, int64(0), newStatus[4])
+	assert.Equal(t, int64(0), newStatus[5])
+	assert.Equal(t, int64(5), newStatus[6])
+	dump := file.Dump()
+	// Output:
+	// 0 0
+	// 20 4
+	// 30 1
+	// 50 0
+	// 130 -1
+	assert.Equal(t, "0 0\n20 4\n30 1\n50 0\n130 -1\n", dump)
+	dump = clone.Dump()
+	// Output:
+	// 0 0
+	// 20 4
+	// 30 1
+	// 45 6
+	// 50 0
+	// 125 -1
+	assert.Equal(t, "0 0\n20 4\n30 1\n45 6\n50 0\n125 -1\n", dump)
+}
+
 func TestLenFile(t *testing.T) {
 	file, _ := fixtureFile()
 	assert.Equal(t, 100, file.Len())
@@ -418,3 +510,95 @@ func TestFileValidate(t *testing.T) {
 	file.tree.FindGE(2).Item().Key = 1
 	assert.Panics(t, func() { file.Validate() })
 }
+
+func TestFileFlatten(t *testing.T) {
+	file, _ := fixtureFile()
+	// 0 0 | 100 -1                             [0]: 100
+	file.Update(1, 20, 30, 0)
+	// 0 0 | 20 1 | 50 0 | 130 -1               [0]: 100, [1]: 30
+	file.Update(2, 20, 0, 5)
+	// 0 0 | 20 1 | 45 0 | 125 -1               [0]: 100, [1]: 25
+	file.Update(3, 20, 0, 5)
+	// 0 0 | 20 1 | 40 0 | 120 -1               [0]: 100, [1]: 20
+	file.Update(4, 20, 10, 0)
+	// 0 0 | 20 4 | 30 1 | 50 0 | 130 -1        [0]: 100, [1]: 20, [4]: 10
+	lines := file.flatten()
+	for i := 0; i < 20; i++ {
+		assert.Equal(t, 0, lines[i], fmt.Sprintf("line %d", i))
+	}
+	for i := 20; i < 30; i++ {
+		assert.Equal(t, 4, lines[i], fmt.Sprintf("line %d", i))
+	}
+	for i := 30; i < 50; i++ {
+		assert.Equal(t, 1, lines[i], fmt.Sprintf("line %d", i))
+	}
+	for i := 50; i < 130; i++ {
+		assert.Equal(t, 0, lines[i], fmt.Sprintf("line %d", i))
+	}
+	assert.Len(t, lines, 130)
+}
+
+func TestFileMergeMark(t *testing.T) {
+	file, status := fixtureFile()
+	// 0 0 | 100 -1                             [0]: 100
+	file.Update(1, 20, 30, 0)
+	// 0 0 | 20 1 | 50 0 | 130 -1               [0]: 100, [1]: 30
+	file.Update(2, 20, 0, 5)
+	// 0 0 | 20 1 | 45 0 | 125 -1               [0]: 100, [1]: 25
+	file.Update(3, 20, 0, 5)
+	// 0 0 | 20 1 | 40 0 | 120 -1               [0]: 100, [1]: 20
+	file.Update(4, 20, 10, 0)
+	// 0 0 | 20 4 | 30 1 | 50 0 | 130 -1        [0]: 100, [1]: 20, [4]: 10
+	file.Update(TreeMergeMark, 60, 20, 20)
+	// 0 0 | 20 4 | 30 1 | 50 0 | 60 M | 80 0 | 130 -1
+	// [0]: 100, [1]: 20, [4]: 10
+	dump := file.Dump()
+	assert.Equal(t, "0 0\n20 4\n30 1\n50 0\n60 16383\n80 0\n130 -1\n", dump)
+	assert.Contains(t, status, 0)
+	assert.Equal(t, int64(100), status[0])
+	assert.Equal(t, int64(20), status[1])
+	assert.Equal(t, int64(0), status[2])
+	assert.Equal(t, int64(0), status[3])
+	assert.Equal(t, int64(10), status[4])
+	assert.NotContains(t, status, TreeMergeMark)
+}
+
+
+func TestFileMerge(t *testing.T) {
+	file1, status := fixtureFile()
+	// 0 0 | 100 -1                             [0]: 100
+	file1.Update(1, 20, 30, 0)
+	// 0 0 | 20 1 | 50 0 | 130 -1               [0]: 100, [1]: 30
+	file1.Update(2, 20, 0, 5)
+	// 0 0 | 20 1 | 45 0 | 125 -1               [0]: 100, [1]: 25
+	file1.Update(3, 20, 0, 5)
+	// 0 0 | 20 1 | 40 0 | 120 -1               [0]: 100, [1]: 20
+	file1.Update(4, 20, 10, 0)
+	// 0 0 | 20 4 | 30 1 | 50 0 | 130 -1        [0]: 100, [1]: 20, [4]: 10
+	file2 := file1.Clone(false)
+	file1.Update(TreeMergeMark, 60, 30, 30)
+	// 0 0 | 20 4 | 30 1 | 50 0 | 60 M | 90 0 | 130 -1
+	// [0]: 70, [1]: 20, [4]: 10
+	file2.Update(5, 60, 20, 20)
+	// 0 0 | 20 4 | 30 1 | 50 0 | 60 5 | 80 0 | 130 -1
+	// [0]: 80, [1]: 20, [4]: 10, [5]: 20
+	file2.Update(TreeMergeMark, 80, 10, 10)
+	// 0 0 | 20 4 | 30 1 | 50 0 | 60 5 | 80 M | 90 0 | 130 -1
+	// [0]: 70, [1]: 20, [4]: 10, [5]: 20
+	file2.Update(6, 0, 10, 10)
+	// 0 6 | 10 0 | 20 4 | 30 1 | 50 0 | 60 5 | 80 M | 90 0 | 130 -1
+	// [0]: 60, [1]: 20, [4]: 10, [5]: 20, [6]: 10
+	file1.Merge(7, file2)
+	// 0 0 | 20 4 | 30 1 | 50 0 | 60 5 | 80 7 | 90 0 | 130 -1
+	// [0]: 70, [1]: 20, [4]: 10, [5]: 20, [6]: 0, [7]: 10
+	dump := file1.Dump()
+	assert.Equal(t, "0 0\n20 4\n30 1\n50 0\n60 5\n80 7\n90 0\n130 -1\n", dump)
+	assert.Equal(t, int64(70), status[0])
+	assert.Equal(t, int64(20), status[1])
+	assert.Equal(t, int64(0), status[2])
+	assert.Equal(t, int64(0), status[3])
+	assert.Equal(t, int64(10), status[4])
+	assert.Equal(t, int64(20), status[5])
+	assert.Equal(t, int64(0), status[6])
+	assert.Equal(t, int64(10), status[7])
+}

+ 146 - 111
internal/rbtree/rbtree.go

@@ -22,7 +22,7 @@ type RBTree struct {
 	// Root of the tree
 	root *node
 
-	// The minimum and maximum nodes under the root.
+	// The minimum and maximum nodes under the tree.
 	minNode, maxNode *node
 
 	// Number of nodes under root, including the root
@@ -30,14 +30,49 @@ type RBTree struct {
 }
 
 // Len returns the number of elements in the tree.
-func (root *RBTree) Len() int {
-	return root.count
+func (tree *RBTree) Len() int {
+	return tree.count
+}
+
+// Clone performs a deep copy of the tree.
+func (tree *RBTree) Clone() *RBTree {
+	clone := &RBTree{}
+	clone.count = tree.count
+	nodeMap := map[*node]*node{}
+	queue := []*node{tree.root}
+	for len(queue) > 0 {
+		head := queue[len(queue)-1]
+		queue = queue[:len(queue)-1]
+		headCopy := *head
+		nodeMap[head] = &headCopy
+		if head.left != nil {
+			queue = append(queue, head.left)
+		}
+		if head.right != nil {
+			queue = append(queue, head.right)
+		}
+	}
+	for _, mapped := range nodeMap {
+		if mapped.parent != nil {
+			mapped.parent = nodeMap[mapped.parent]
+		}
+		if mapped.left != nil {
+			mapped.left = nodeMap[mapped.left]
+		}
+		if mapped.right != nil {
+			mapped.right = nodeMap[mapped.right]
+		}
+	}
+	clone.root = nodeMap[tree.root]
+	clone.minNode = nodeMap[tree.minNode]
+	clone.maxNode = nodeMap[tree.maxNode]
+	return clone
 }
 
 // Get is a convenience function for finding an element equal to Key. Returns
 // nil if not found.
-func (root *RBTree) Get(key int) *int {
-	n, exact := root.findGE(key)
+func (tree *RBTree) Get(key int) *int {
+	n, exact := tree.findGE(key)
 	if exact {
 		return &n.item.Value
 	}
@@ -46,60 +81,60 @@ func (root *RBTree) Get(key int) *int {
 
 // Min creates an iterator that points to the minimum item in the tree.
 // If the tree is empty, returns Limit()
-func (root *RBTree) Min() Iterator {
-	return Iterator{root, root.minNode}
+func (tree *RBTree) Min() Iterator {
+	return Iterator{tree, tree.minNode}
 }
 
 // Max creates an iterator that points at the maximum item in the tree.
 //
 // If the tree is empty, returns NegativeLimit().
-func (root *RBTree) Max() Iterator {
-	if root.maxNode == nil {
-		return Iterator{root, negativeLimitNode}
+func (tree *RBTree) Max() Iterator {
+	if tree.maxNode == nil {
+		return Iterator{tree, negativeLimitNode}
 	}
-	return Iterator{root, root.maxNode}
+	return Iterator{tree, tree.maxNode}
 }
 
 // Limit creates an iterator that points beyond the maximum item in the tree.
-func (root *RBTree) Limit() Iterator {
-	return Iterator{root, nil}
+func (tree *RBTree) Limit() Iterator {
+	return Iterator{tree, nil}
 }
 
 // NegativeLimit creates an iterator that points before the minimum item in the tree.
-func (root *RBTree) NegativeLimit() Iterator {
-	return Iterator{root, negativeLimitNode}
+func (tree *RBTree) NegativeLimit() Iterator {
+	return Iterator{tree, negativeLimitNode}
 }
 
 // FindGE finds the smallest element N such that N >= Key, and returns the
 // iterator pointing to the element. If no such element is found,
-// returns root.Limit().
-func (root *RBTree) FindGE(key int) Iterator {
-	n, _ := root.findGE(key)
-	return Iterator{root, n}
+// returns tree.Limit().
+func (tree *RBTree) FindGE(key int) Iterator {
+	n, _ := tree.findGE(key)
+	return Iterator{tree, n}
 }
 
 // FindLE finds the largest element N such that N <= Key, and returns the
 // iterator pointing to the element. If no such element is found,
 // returns iter.NegativeLimit().
-func (root *RBTree) FindLE(key int) Iterator {
-	n, exact := root.findGE(key)
+func (tree *RBTree) FindLE(key int) Iterator {
+	n, exact := tree.findGE(key)
 	if exact {
-		return Iterator{root, n}
+		return Iterator{tree, n}
 	}
 	if n != nil {
-		return Iterator{root, n.doPrev()}
+		return Iterator{tree, n.doPrev()}
 	}
-	if root.maxNode == nil {
-		return Iterator{root, negativeLimitNode}
+	if tree.maxNode == nil {
+		return Iterator{tree, negativeLimitNode}
 	}
-	return Iterator{root, root.maxNode}
+	return Iterator{tree, tree.maxNode}
 }
 
 // Insert an item. If the item is already in the tree, do nothing and
 // return false. Else return true.
-func (root *RBTree) Insert(item Item) (bool, Iterator) {
+func (tree *RBTree) Insert(item Item) (bool, Iterator) {
 	// TODO: delay creating n until it is found to be inserted
-	n := root.doInsert(item)
+	n := tree.doInsert(item)
 	if n == nil {
 		return false, Iterator{}
 	}
@@ -139,12 +174,12 @@ func (root *RBTree) Insert(item Item) (bool, Iterator) {
 
 		// Case 4: parent is red, uncle is black (1)
 		if n.isRightChild() && n.parent.isLeftChild() {
-			root.rotateLeft(n.parent)
+			tree.rotateLeft(n.parent)
 			n = n.left
 			continue
 		}
 		if n.isLeftChild() && n.parent.isRightChild() {
-			root.rotateRight(n.parent)
+			tree.rotateRight(n.parent)
 			n = n.right
 			continue
 		}
@@ -153,21 +188,21 @@ func (root *RBTree) Insert(item Item) (bool, Iterator) {
 		n.parent.color = black
 		grandparent.color = red
 		if n.isLeftChild() {
-			root.rotateRight(grandparent)
+			tree.rotateRight(grandparent)
 		} else {
-			root.rotateLeft(grandparent)
+			tree.rotateLeft(grandparent)
 		}
 		break
 	}
-	return true, Iterator{root, insN}
+	return true, Iterator{tree, insN}
 }
 
 // DeleteWithKey deletes an item with the given Key. Returns true iff the item was
 // found.
-func (root *RBTree) DeleteWithKey(key int) bool {
-	iter := root.FindGE(key)
+func (tree *RBTree) DeleteWithKey(key int) bool {
+	iter := tree.FindGE(key)
 	if iter.node != nil {
-		root.DeleteWithIterator(iter)
+		tree.DeleteWithIterator(iter)
 		return true
 	}
 	return false
@@ -176,9 +211,9 @@ func (root *RBTree) DeleteWithKey(key int) bool {
 // DeleteWithIterator deletes the current item.
 //
 // REQUIRES: !iter.Limit() && !iter.NegativeLimit()
-func (root *RBTree) DeleteWithIterator(iter Iterator) {
+func (tree *RBTree) DeleteWithIterator(iter Iterator) {
 	doAssert(!iter.Limit() && !iter.NegativeLimit())
-	root.doDelete(iter.node)
+	tree.doDelete(iter.node)
 }
 
 // Iterator allows scanning tree elements in sort order.
@@ -188,7 +223,7 @@ func (root *RBTree) DeleteWithIterator(iter Iterator) {
 // iterator becomes invalid. For other operation types, the iterator
 // remains valid.
 type Iterator struct {
-	root *RBTree
+	tree *RBTree
 	node *node
 }
 
@@ -204,12 +239,12 @@ func (iter Iterator) Limit() bool {
 
 // Min checks if the iterator points to the minimum element in the tree.
 func (iter Iterator) Min() bool {
-	return iter.node == iter.root.minNode
+	return iter.node == iter.tree.minNode
 }
 
 // Max checks if the iterator points to the maximum element in the tree.
 func (iter Iterator) Max() bool {
-	return iter.node == iter.root.maxNode
+	return iter.node == iter.tree.maxNode
 }
 
 // NegativeLimit checks if the iterator points before the minimum element in the tree.
@@ -231,9 +266,9 @@ func (iter Iterator) Item() *Item {
 func (iter Iterator) Next() Iterator {
 	doAssert(!iter.Limit())
 	if iter.NegativeLimit() {
-		return Iterator{iter.root, iter.root.minNode}
+		return Iterator{iter.tree, iter.tree.minNode}
 	}
-	return Iterator{iter.root, iter.node.doNext()}
+	return Iterator{iter.tree, iter.node.doNext()}
 }
 
 // Prev creates a new iterator that points to the predecessor of the current
@@ -243,12 +278,12 @@ func (iter Iterator) Next() Iterator {
 func (iter Iterator) Prev() Iterator {
 	doAssert(!iter.NegativeLimit())
 	if !iter.Limit() {
-		return Iterator{iter.root, iter.node.doPrev()}
+		return Iterator{iter.tree, iter.node.doPrev()}
 	}
-	if iter.root.maxNode == nil {
-		return Iterator{iter.root, negativeLimitNode}
+	if iter.tree.maxNode == nil {
+		return Iterator{iter.tree, negativeLimitNode}
 	}
-	return Iterator{iter.root, iter.root.maxNode}
+	return Iterator{iter.tree, iter.tree.maxNode}
 }
 
 func doAssert(b bool) {
@@ -356,54 +391,54 @@ func maxPredecessor(n *node) *node {
 // Private methods
 //
 
-func (root *RBTree) recomputeMinNode() {
-	root.minNode = root.root
-	if root.minNode != nil {
-		for root.minNode.left != nil {
-			root.minNode = root.minNode.left
+func (tree *RBTree) recomputeMinNode() {
+	tree.minNode = tree.root
+	if tree.minNode != nil {
+		for tree.minNode.left != nil {
+			tree.minNode = tree.minNode.left
 		}
 	}
 }
 
-func (root *RBTree) recomputeMaxNode() {
-	root.maxNode = root.root
-	if root.maxNode != nil {
-		for root.maxNode.right != nil {
-			root.maxNode = root.maxNode.right
+func (tree *RBTree) recomputeMaxNode() {
+	tree.maxNode = tree.root
+	if tree.maxNode != nil {
+		for tree.maxNode.right != nil {
+			tree.maxNode = tree.maxNode.right
 		}
 	}
 }
 
-func (root *RBTree) maybeSetMinNode(n *node) {
-	if root.minNode == nil {
-		root.minNode = n
-		root.maxNode = n
-	} else if n.item.Key < root.minNode.item.Key {
-		root.minNode = n
+func (tree *RBTree) maybeSetMinNode(n *node) {
+	if tree.minNode == nil {
+		tree.minNode = n
+		tree.maxNode = n
+	} else if n.item.Key < tree.minNode.item.Key {
+		tree.minNode = n
 	}
 }
 
-func (root *RBTree) maybeSetMaxNode(n *node) {
-	if root.maxNode == nil {
-		root.minNode = n
-		root.maxNode = n
-	} else if n.item.Key > root.maxNode.item.Key {
-		root.maxNode = n
+func (tree *RBTree) maybeSetMaxNode(n *node) {
+	if tree.maxNode == nil {
+		tree.minNode = n
+		tree.maxNode = n
+	} else if n.item.Key > tree.maxNode.item.Key {
+		tree.maxNode = n
 	}
 }
 
 // Try inserting "item" into the tree. Return nil if the item is
 // already in the tree. Otherwise return a new (leaf) node.
-func (root *RBTree) doInsert(item Item) *node {
-	if root.root == nil {
+func (tree *RBTree) doInsert(item Item) *node {
+	if tree.root == nil {
 		n := &node{item: item}
-		root.root = n
-		root.minNode = n
-		root.maxNode = n
-		root.count++
+		tree.root = n
+		tree.minNode = n
+		tree.maxNode = n
+		tree.count++
 		return n
 	}
-	parent := root.root
+	parent := tree.root
 	for true {
 		comp := item.Key - parent.item.Key
 		if comp == 0 {
@@ -412,8 +447,8 @@ func (root *RBTree) doInsert(item Item) *node {
 			if parent.left == nil {
 				n := &node{item: item, parent: parent}
 				parent.left = n
-				root.count++
-				root.maybeSetMinNode(n)
+				tree.count++
+				tree.maybeSetMinNode(n)
 				return n
 			}
 			parent = parent.left
@@ -421,8 +456,8 @@ func (root *RBTree) doInsert(item Item) *node {
 			if parent.right == nil {
 				n := &node{item: item, parent: parent}
 				parent.right = n
-				root.count++
-				root.maybeSetMaxNode(n)
+				tree.count++
+				tree.maybeSetMaxNode(n)
 				return n
 			}
 			parent = parent.right
@@ -434,8 +469,8 @@ func (root *RBTree) doInsert(item Item) *node {
 // Find a node whose item >= Key. The 2nd return Value is true iff the
 // node.item==Key. Returns (nil, false) if all nodes in the tree are <
 // Key.
-func (root *RBTree) findGE(key int) (*node, bool) {
-	n := root.root
+func (tree *RBTree) findGE(key int) (*node, bool) {
+	n := tree.root
 	for true {
 		if n == nil {
 			return nil, false
@@ -465,10 +500,10 @@ func (root *RBTree) findGE(key int) (*node, bool) {
 }
 
 // Delete N from the tree.
-func (root *RBTree) doDelete(n *node) {
+func (tree *RBTree) doDelete(n *node) {
 	if n.left != nil && n.right != nil {
 		pred := maxPredecessor(n)
-		root.swapNodes(n, pred)
+		tree.swapNodes(n, pred)
 	}
 
 	doAssert(n.left == nil || n.right == nil)
@@ -478,33 +513,33 @@ func (root *RBTree) doDelete(n *node) {
 	}
 	if n.color == black {
 		n.color = getColor(child)
-		root.deleteCase1(n)
+		tree.deleteCase1(n)
 	}
-	root.replaceNode(n, child)
+	tree.replaceNode(n, child)
 	if n.parent == nil && child != nil {
 		child.color = black
 	}
-	root.count--
-	if root.count == 0 {
-		root.minNode = nil
-		root.maxNode = nil
+	tree.count--
+	if tree.count == 0 {
+		tree.minNode = nil
+		tree.maxNode = nil
 	} else {
-		if root.minNode == n {
-			root.recomputeMinNode()
+		if tree.minNode == n {
+			tree.recomputeMinNode()
 		}
-		if root.maxNode == n {
-			root.recomputeMaxNode()
+		if tree.maxNode == n {
+			tree.recomputeMaxNode()
 		}
 	}
 }
 
 // Move n to the pred's place, and vice versa
 //
-func (root *RBTree) swapNodes(n, pred *node) {
+func (tree *RBTree) swapNodes(n, pred *node) {
 	doAssert(pred != n)
 	isLeft := pred.isLeftChild()
 	tmp := *pred
-	root.replaceNode(n, pred)
+	tree.replaceNode(n, pred)
 	pred.color = n.color
 
 	if tmp.parent == n {
@@ -561,16 +596,16 @@ func (root *RBTree) swapNodes(n, pred *node) {
 	n.color = tmp.color
 }
 
-func (root *RBTree) deleteCase1(n *node) {
+func (tree *RBTree) deleteCase1(n *node) {
 	for true {
 		if n.parent != nil {
 			if getColor(n.sibling()) == red {
 				n.parent.color = red
 				n.sibling().color = black
 				if n == n.parent.left {
-					root.rotateLeft(n.parent)
+					tree.rotateLeft(n.parent)
 				} else {
-					root.rotateRight(n.parent)
+					tree.rotateRight(n.parent)
 				}
 			}
 			if getColor(n.parent) == black &&
@@ -589,7 +624,7 @@ func (root *RBTree) deleteCase1(n *node) {
 					n.sibling().color = red
 					n.parent.color = black
 				} else {
-					root.deleteCase5(n)
+					tree.deleteCase5(n)
 				}
 			}
 		}
@@ -597,21 +632,21 @@ func (root *RBTree) deleteCase1(n *node) {
 	}
 }
 
-func (root *RBTree) deleteCase5(n *node) {
+func (tree *RBTree) deleteCase5(n *node) {
 	if n == n.parent.left &&
 		getColor(n.sibling()) == black &&
 		getColor(n.sibling().left) == red &&
 		getColor(n.sibling().right) == black {
 		n.sibling().color = red
 		n.sibling().left.color = black
-		root.rotateRight(n.sibling())
+		tree.rotateRight(n.sibling())
 	} else if n == n.parent.right &&
 		getColor(n.sibling()) == black &&
 		getColor(n.sibling().right) == red &&
 		getColor(n.sibling().left) == black {
 		n.sibling().color = red
 		n.sibling().right.color = black
-		root.rotateLeft(n.sibling())
+		tree.rotateLeft(n.sibling())
 	}
 
 	// case 6
@@ -620,17 +655,17 @@ func (root *RBTree) deleteCase5(n *node) {
 	if n == n.parent.left {
 		doAssert(getColor(n.sibling().right) == red)
 		n.sibling().right.color = black
-		root.rotateLeft(n.parent)
+		tree.rotateLeft(n.parent)
 	} else {
 		doAssert(getColor(n.sibling().left) == red)
 		n.sibling().left.color = black
-		root.rotateRight(n.parent)
+		tree.rotateRight(n.parent)
 	}
 }
 
-func (root *RBTree) replaceNode(oldn, newn *node) {
+func (tree *RBTree) replaceNode(oldn, newn *node) {
 	if oldn.parent == nil {
-		root.root = newn
+		tree.root = newn
 	} else {
 		if oldn == oldn.parent.left {
 			oldn.parent.left = newn
@@ -648,7 +683,7 @@ func (root *RBTree) replaceNode(oldn, newn *node) {
   A   Y	    =>     X   C
      B C 	  A B
 */
-func (root *RBTree) rotateLeft(x *node) {
+func (tree *RBTree) rotateLeft(x *node) {
 	y := x.right
 	x.right = y.left
 	if y.left != nil {
@@ -656,7 +691,7 @@ func (root *RBTree) rotateLeft(x *node) {
 	}
 	y.parent = x.parent
 	if x.parent == nil {
-		root.root = y
+		tree.root = y
 	} else {
 		if x.isLeftChild() {
 			x.parent.left = y
@@ -673,7 +708,7 @@ func (root *RBTree) rotateLeft(x *node) {
    X   C  =>   A   Y
   A B             B C
 */
-func (root *RBTree) rotateRight(y *node) {
+func (tree *RBTree) rotateRight(y *node) {
 	x := y.left
 
 	// Move "B"
@@ -684,7 +719,7 @@ func (root *RBTree) rotateRight(y *node) {
 
 	x.parent = y.parent
 	if y.parent == nil {
-		root.root = x
+		tree.root = x
 	} else {
 		if y.isLeftChild() {
 			y.parent.left = x