Quellcode durchsuchen

Merge pull request #146 from vmarkovtsev/alloc

RBTree allocators
Vadim Markovtsev vor 6 Jahren
Ursprung
Commit
cc0b759957
6 geänderte Dateien mit 899 neuen und 457 gelöschten Zeilen
  1. 1 1
      cmd/hercules/root.go
  2. 80 55
      internal/burndown/file.go
  3. 66 110
      internal/burndown/file_test.go
  4. 399 288
      internal/rbtree/rbtree.go
  5. 340 0
      internal/rbtree/rbtree_test.go
  6. 13 3
      leaves/burndown.go

+ 1 - 1
cmd/hercules/root.go

@@ -265,7 +265,7 @@ func printResults(
 	commonResult := results[nil].(*hercules.CommonAnalysisResult)
 
 	fmt.Println("hercules:")
-	fmt.Println("  version: 3")
+	fmt.Printf("  version: %d\n", hercules.BinaryVersion)
 	fmt.Println("  hash:", hercules.BinaryGitHash)
 	fmt.Println("  repository:", uri)
 	fmt.Println("  begin_unix_time:", commonResult.BeginTime)

+ 80 - 55
internal/burndown/file.go

@@ -3,6 +3,7 @@ package burndown
 import (
 	"fmt"
 	"log"
+	"math"
 
 	"gopkg.in/src-d/hercules.v6/internal"
 	"gopkg.in/src-d/hercules.v6/internal/rbtree"
@@ -28,7 +29,7 @@ type File struct {
 }
 
 // TreeEnd denotes the value of the last leaf in the tree.
-const TreeEnd = -1
+const TreeEnd = math.MaxUint32
 
 // TreeMaxBinPower is the binary power value which corresponds to the maximum day which
 // can be stored in the tree.
@@ -61,13 +62,19 @@ func (file *File) updateTime(currentTime, previousTime, delta int) {
 // last node);
 //
 // updaters are the attached interval length mappings.
-func NewFile(time int, length int, updaters ...Updater) *File {
-	file := &File{tree: new(rbtree.RBTree), updaters: updaters}
+func NewFile(time int, length int, allocator *rbtree.Allocator, updaters ...Updater) *File {
+	file := &File{tree: rbtree.NewRBTree(allocator), updaters: updaters}
 	file.updateTime(time, time, length)
+	if time < 0 || time > math.MaxUint32 {
+		log.Panicf("time is out of allowed range: %d", time)
+	}
+	if length > math.MaxUint32 {
+		log.Panicf("length is out of allowed range: %d", length)
+	}
 	if length > 0 {
-		file.tree.Insert(rbtree.Item{Key: 0, Value: time})
+		file.tree.Insert(rbtree.Item{Key: 0, Value: uint32(time)})
 	}
-	file.tree.Insert(rbtree.Item{Key: length, Value: TreeEnd})
+	file.tree.Insert(rbtree.Item{Key: uint32(length), Value: TreeEnd})
 	return file
 }
 
@@ -79,13 +86,20 @@ func NewFile(time int, length int, updaters ...Updater) *File {
 // vals is a slice with the starting tree values. Must match the size of keys.
 //
 // updaters are the attached interval length mappings.
-func NewFileFromTree(keys []int, vals []int, updaters ...Updater) *File {
-	file := &File{tree: new(rbtree.RBTree), updaters: updaters}
+func NewFileFromTree(keys []int, vals []int, allocator *rbtree.Allocator, updaters ...Updater) *File {
+	file := &File{tree: rbtree.NewRBTree(allocator), updaters: updaters}
 	if len(keys) != len(vals) {
 		panic("keys and vals must be of equal length")
 	}
-	for i := 0; i < len(keys); i++ {
-		file.tree.Insert(rbtree.Item{Key: keys[i], Value: vals[i]})
+	for i, key := range keys {
+		val := vals[i]
+		if key < 0 || key >= math.MaxUint32 {
+			log.Panicf("key is out of allowed range: [%d]=%d", i, key)
+		}
+		if val < 0 || val > math.MaxUint32 {
+			log.Panicf("val is out of allowed range: [%d]=%d", i, val)
+		}
+		file.tree.Insert(rbtree.Item{Key: uint32(key), Value: uint32(val)})
 	}
 	file.Validate()
 	return file
@@ -94,21 +108,19 @@ func NewFileFromTree(keys []int, vals []int, updaters ...Updater) *File {
 // Clone copies the file. It performs a deep copy of the tree;
 // depending on `clearStatuses` the original updaters are removed or not.
 // Any new `updaters` are appended.
-func (file *File) Clone(clearStatuses bool, updaters ...Updater) *File {
-	clone := &File{tree: file.tree.Clone(), updaters: file.updaters}
-	if clearStatuses {
-		clone.updaters = []Updater{}
-	}
-	for _, updater := range updaters {
-		clone.updaters = append(clone.updaters, updater)
-	}
-	return clone
+func (file *File) Clone(allocator *rbtree.Allocator) *File {
+	return &File{tree: file.tree.Clone(allocator), updaters: file.updaters}
+}
+
+// Delete deallocates the file.
+func (file *File) Delete() {
+	file.tree.Erase()
 }
 
 // Len returns the File's size - that is, the maximum key in the tree of line
 // intervals.
 func (file *File) Len() int {
-	return file.tree.Max().Item().Key
+	return int(file.tree.Max().Item().Key)
 }
 
 // Update modifies the underlying tree to adapt to the specified line changes.
@@ -130,9 +142,15 @@ func (file *File) Update(time int, pos int, insLength int, delLength int) {
 	if time < 0 {
 		panic("time may not be negative")
 	}
+	if time >= math.MaxUint32 {
+		panic("time may not be >= MaxUint32")
+	}
 	if pos < 0 {
 		panic("attempt to insert/delete at a negative position")
 	}
+	if pos > math.MaxUint32 {
+		panic("pos may not be > MaxUint32")
+	}
 	if insLength < 0 || delLength < 0 {
 		panic("insLength and delLength must be non-negative")
 	}
@@ -143,11 +161,11 @@ func (file *File) Update(time int, pos int, insLength int, delLength int) {
 	if tree.Len() < 2 && tree.Min().Item().Key != 0 {
 		panic("invalid tree state")
 	}
-	if pos > tree.Max().Item().Key {
+	if uint32(pos) > tree.Max().Item().Key {
 		panic(fmt.Sprintf("attempt to insert after the end of the file: %d < %d",
 			tree.Max().Item().Key, pos))
 	}
-	iter := tree.FindLE(pos)
+	iter := tree.FindLE(uint32(pos))
 	origin := *iter.Item()
 	prevOrigin := origin
 	{
@@ -161,16 +179,16 @@ func (file *File) Update(time int, pos int, insLength int, delLength int) {
 	}
 	if delLength == 0 {
 		// simple case with insertions only
-		if origin.Key < pos || (origin.Value == time && (pos == 0 || pos == origin.Key)) {
+		if origin.Key < uint32(pos) || (origin.Value == uint32(time) && (pos == 0 || uint32(pos) == origin.Key)) {
 			iter = iter.Next()
 		}
 		for ; !iter.Limit(); iter = iter.Next() {
-			iter.Item().Key += insLength
+			iter.Item().Key += uint32(insLength)
 		}
-		if origin.Value != time {
-			tree.Insert(rbtree.Item{Key: pos, Value: time})
-			if origin.Key < pos {
-				tree.Insert(rbtree.Item{Key: pos + insLength, Value: origin.Value})
+		if origin.Value != uint32(time) {
+			tree.Insert(rbtree.Item{Key: uint32(pos), Value: uint32(time)})
+			if origin.Key < uint32(pos) {
+				tree.Insert(rbtree.Item{Key: uint32(pos + insLength), Value: origin.Value})
 			}
 		}
 		return
@@ -181,13 +199,13 @@ func (file *File) Update(time int, pos int, insLength int, delLength int) {
 		node := iter.Item()
 		nextIter := iter.Next()
 		if nextIter.Limit() {
-			if pos+delLength > node.Key {
+			if uint32(pos+delLength) > node.Key {
 				panic("attempt to delete after the end of the file")
 			}
 			break
 		}
-		delta := internal.Min(nextIter.Item().Key, pos+delLength) - internal.Max(node.Key, pos)
-		if delta == 0 && insLength == 0 && origin.Key == pos && prevOrigin.Value == node.Value {
+		delta := internal.Min(int(nextIter.Item().Key), pos+delLength) - internal.Max(int(node.Key), pos)
+		if delta == 0 && insLength == 0 && origin.Key == uint32(pos) && prevOrigin.Value == node.Value {
 			origin = *node
 			tree.DeleteWithIterator(iter)
 			iter = nextIter
@@ -195,8 +213,8 @@ func (file *File) Update(time int, pos int, insLength int, delLength int) {
 		if delta <= 0 {
 			break
 		}
-		file.updateTime(time, node.Value, -delta)
-		if node.Key >= pos {
+		file.updateTime(time, int(node.Value), -delta)
+		if node.Key >= uint32(pos) {
 			origin = *node
 			tree.DeleteWithIterator(iter)
 		}
@@ -205,19 +223,19 @@ func (file *File) Update(time int, pos int, insLength int, delLength int) {
 
 	// prepare for the keys update
 	var previous *rbtree.Item
-	if insLength > 0 && (origin.Value != time || origin.Key == pos) {
+	if insLength > 0 && (origin.Value != uint32(time) || origin.Key == uint32(pos)) {
 		// insert our new interval
-		if iter.Item().Value == time && iter.Item().Key-delLength == pos {
+		if iter.Item().Value == uint32(time) && int(iter.Item().Key)-delLength == pos {
 			prev := iter.Prev()
-			if prev.NegativeLimit() || prev.Item().Value != time {
-				iter.Item().Key = pos
+			if prev.NegativeLimit() || prev.Item().Value != uint32(time) {
+				iter.Item().Key = uint32(pos)
 			} else {
 				tree.DeleteWithIterator(iter)
 				iter = prev
 			}
-			origin.Value = time // cancels the insertion after applying the delta
+			origin.Value = uint32(time) // cancels the insertion after applying the delta
 		} else {
-			_, iter = tree.Insert(rbtree.Item{Key: pos, Value: time})
+			_, iter = tree.Insert(rbtree.Item{Key: uint32(pos), Value: uint32(time)})
 		}
 	} else {
 		// rollback 1 position back, see "for true" deletion cycle ^
@@ -230,26 +248,26 @@ func (file *File) Update(time int, pos int, insLength int, delLength int) {
 	if delta != 0 {
 		for iter = iter.Next(); !iter.Limit(); iter = iter.Next() {
 			// we do not need to re-balance the tree
-			iter.Item().Key += delta
+			iter.Item().Key = uint32(int(iter.Item().Key)+delta)
 		}
 		// have to adjust origin in case insLength == 0
-		if origin.Key > pos {
-			origin.Key += delta
+		if origin.Key > uint32(pos) {
+			origin.Key = uint32(int(origin.Key)+delta)
 		}
 	}
 
 	if insLength > 0 {
-		if origin.Value != time {
-			tree.Insert(rbtree.Item{Key: pos + insLength, Value: origin.Value})
+		if origin.Value != uint32(time) {
+			tree.Insert(rbtree.Item{Key: uint32(pos + insLength), Value: origin.Value})
 		} else if pos == 0 {
 			// recover the beginning
-			tree.Insert(rbtree.Item{Key: pos, Value: time})
+			tree.Insert(rbtree.Item{Key: uint32(pos), Value: uint32(time)})
 		}
-	} else if (pos > origin.Key && previous != nil && previous.Value != origin.Value) ||
-		(pos == origin.Key && origin.Value != prevOrigin.Value) ||
+	} else if (uint32(pos) > origin.Key && previous != nil && previous.Value != origin.Value) ||
+		(uint32(pos) == origin.Key && origin.Value != prevOrigin.Value) ||
 		pos == 0 {
 		// continue the original interval
-		tree.Insert(rbtree.Item{Key: pos, Value: origin.Value})
+		tree.Insert(rbtree.Item{Key: uint32(pos), Value: origin.Value})
 	}
 }
 
@@ -287,13 +305,14 @@ func (file *File) Merge(day int, others ...*File) {
 		}
 	}
 	// now we need to reconstruct the tree from the discrete values
-	tree := &rbtree.RBTree{}
+	file.tree.Erase()
+	tree := rbtree.NewRBTree(file.tree.Allocator())
 	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: uint32(i), Value: uint32(v)})
 		}
 	}
-	tree.Insert(rbtree.Item{Key: len(myself), Value: TreeEnd})
+	tree.Insert(rbtree.Item{Key: uint32(len(myself)), Value: TreeEnd})
 	file.tree = tree
 }
 
@@ -303,7 +322,13 @@ func (file *File) Dump() string {
 	buffer := ""
 	for iter := file.tree.Min(); !iter.Limit(); iter = iter.Next() {
 		node := iter.Item()
-		buffer += fmt.Sprintf("%d %d\n", node.Key, node.Value)
+		var val int
+		if node.Value == math.MaxUint32 {
+			val = -1
+		} else {
+			val = int(node.Value)
+		}
+		buffer += fmt.Sprintf("%d %d\n", node.Key, val)
 	}
 	return buffer
 }
@@ -324,7 +349,7 @@ func (file *File) Validate() {
 	if file.tree.Max().Item().Value != TreeEnd {
 		log.Panicf("the last value in the tree must be %d", TreeEnd)
 	}
-	prevKey := -1
+	prevKey := uint32(math.MaxUint32)
 	for iter := file.tree.Min(); !iter.Limit(); iter = iter.Next() {
 		node := iter.Item()
 		if node.Key == prevKey {
@@ -340,10 +365,10 @@ func (file *File) Validate() {
 // 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
+	val := uint32(math.MaxUint32)
 	for iter := file.tree.Min(); !iter.Limit(); iter = iter.Next() {
-		for i := len(lines); i < iter.Item().Key; i++ {
-			lines = append(lines, val)
+		for i := uint32(len(lines)); i < iter.Item().Key; i++ {
+			lines = append(lines, int(val))
 		}
 		val = iter.Item().Value
 	}

+ 66 - 110
internal/burndown/file_test.go

@@ -2,6 +2,7 @@ package burndown
 
 import (
 	"fmt"
+	"math"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -12,16 +13,17 @@ func updateStatusFile(status map[int]int64, _, previousTime, delta int) {
 	status[previousTime] += int64(delta)
 }
 
-func fixtureFile() (*File, map[int]int64) {
+func fixtureFile() (*File, map[int]int64, *rbtree.Allocator) {
 	status := map[int]int64{}
-	file := NewFile(0, 100, func(a, b, c int) {
+	alloc := rbtree.NewAllocator()
+	file := NewFile(0, 100, alloc, func(a, b, c int) {
 		updateStatusFile(status, a, b, c)
 	})
-	return file, status
+	return file, status, alloc
 }
 
 func TestInitializeFile(t *testing.T) {
-	file, status := fixtureFile()
+	file, status, _ := fixtureFile()
 	dump := file.Dump()
 	// Output:
 	// 0 0
@@ -37,7 +39,7 @@ func testPanicFile(t *testing.T, method func(*File), msg string) {
 		assert.IsType(t, "", r)
 		assert.Contains(t, r.(string), msg)
 	}()
-	file, _ := fixtureFile()
+	file, _, _ := fixtureFile()
 	method(file)
 }
 
@@ -50,14 +52,15 @@ func TestBullshitFile(t *testing.T) {
 	testPanicFile(t, func(file *File) { file.Update(1, 0, 0, -10) }, "Length")
 	testPanicFile(t, func(file *File) { file.Update(1, 0, -10, -10) }, "Length")
 	testPanicFile(t, func(file *File) { file.Update(-1, 0, 10, 10) }, "time")
-	file, status := fixtureFile()
+	file, status, alloc := fixtureFile()
 	file.Update(1, 10, 0, 0)
 	assert.Equal(t, int64(100), status[0])
 	assert.Equal(t, int64(0), status[1])
+	assert.Equal(t, alloc.Size(), 3)  // 1 + 2 nodes
 }
 
 func TestCloneFile(t *testing.T) {
-	file, status := fixtureFile()
+	file, status, alloc := 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
@@ -67,7 +70,8 @@ func TestCloneFile(t *testing.T) {
 	// 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)
+	assert.Equal(t, alloc.Size(), 6)
+	clone := file.Clone(alloc.Clone())
 	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)
@@ -99,63 +103,13 @@ func TestCloneFile(t *testing.T) {
 
 }
 
-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, func(a, b, c int) {
-		updateStatusFile(newStatus, a, b, c)
-	})
-	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()
+	file, _, _ := fixtureFile()
 	assert.Equal(t, 100, file.Len())
 }
 
 func TestInsertFile(t *testing.T) {
-	file, status := fixtureFile()
+	file, status, _ := fixtureFile()
 	file.Update(1, 10, 10, 0)
 	dump := file.Dump()
 	// Output:
@@ -170,7 +124,7 @@ func TestInsertFile(t *testing.T) {
 
 func TestZeroInitializeFile(t *testing.T) {
 	status := map[int]int64{}
-	file := NewFile(0, 0, func(a, b, c int) {
+	file := NewFile(0, 0, rbtree.NewAllocator(), func(a, b, c int) {
 		updateStatusFile(status, a, b, c)
 	})
 	assert.Contains(t, status, 0)
@@ -188,7 +142,7 @@ func TestZeroInitializeFile(t *testing.T) {
 }
 
 func TestDeleteFile(t *testing.T) {
-	file, status := fixtureFile()
+	file, status, alloc := fixtureFile()
 	file.Update(1, 10, 0, 10)
 	dump := file.Dump()
 	// Output:
@@ -197,10 +151,11 @@ func TestDeleteFile(t *testing.T) {
 	assert.Equal(t, "0 0\n90 -1\n", dump)
 	assert.Equal(t, int64(90), status[0])
 	assert.Equal(t, int64(0), status[1])
+	assert.Equal(t, alloc.Size(), 3)
 }
 
 func TestFusedFile(t *testing.T) {
-	file, status := fixtureFile()
+	file, status, alloc := fixtureFile()
 	file.Update(1, 10, 6, 7)
 	dump := file.Dump()
 	// Output:
@@ -214,10 +169,15 @@ func TestFusedFile(t *testing.T) {
 	file.Update(3, 10, 0, 6)
 	dump = file.Dump()
 	assert.Equal(t, "0 0\n93 -1\n", dump)
+	assert.Equal(t, alloc.Size(), 5)
+	file.Update(3, 10, 6, 0) // +2 nodes
+	assert.Equal(t, alloc.Size(), 5)  // using gaps
+	file.Update(4, 10, 6, 0)
+	assert.Equal(t, alloc.Size(), 6)
 }
 
 func TestDeleteSameBeginning(t *testing.T) {
-	file, _ := fixtureFile()
+	file, _, _ := fixtureFile()
 	file.Update(1, 0, 5, 0)
 	dump := file.Dump()
 	// Output:
@@ -232,7 +192,7 @@ func TestDeleteSameBeginning(t *testing.T) {
 }
 
 func TestInsertSameTimeFile(t *testing.T) {
-	file, status := fixtureFile()
+	file, status, _ := fixtureFile()
 	file.Update(0, 5, 10, 0)
 	dump := file.Dump()
 	// Output:
@@ -243,7 +203,7 @@ func TestInsertSameTimeFile(t *testing.T) {
 }
 
 func TestInsertSameStartFile(t *testing.T) {
-	file, status := fixtureFile()
+	file, status, _ := fixtureFile()
 	file.Update(1, 10, 10, 0)
 	file.Update(2, 10, 10, 0)
 	dump := file.Dump()
@@ -260,7 +220,7 @@ func TestInsertSameStartFile(t *testing.T) {
 }
 
 func TestInsertEndFile(t *testing.T) {
-	file, status := fixtureFile()
+	file, status, _ := fixtureFile()
 	file.Update(1, 100, 10, 0)
 	dump := file.Dump()
 	// Output:
@@ -273,7 +233,7 @@ func TestInsertEndFile(t *testing.T) {
 }
 
 func TestDeleteSameStart0File(t *testing.T) {
-	file, status := fixtureFile()
+	file, status, _ := fixtureFile()
 	file.Update(1, 0, 0, 10)
 	dump := file.Dump()
 	// Output:
@@ -285,7 +245,7 @@ func TestDeleteSameStart0File(t *testing.T) {
 }
 
 func TestDeleteSameStartMiddleFile(t *testing.T) {
-	file, status := fixtureFile()
+	file, status, _ := fixtureFile()
 	file.Update(1, 10, 10, 0)
 	file.Update(2, 10, 0, 5)
 	dump := file.Dump()
@@ -300,7 +260,7 @@ func TestDeleteSameStartMiddleFile(t *testing.T) {
 }
 
 func TestDeleteIntersectionFile(t *testing.T) {
-	file, status := fixtureFile()
+	file, status, _ := fixtureFile()
 	file.Update(1, 10, 10, 0)
 	file.Update(2, 15, 0, 10)
 	dump := file.Dump()
@@ -315,7 +275,7 @@ func TestDeleteIntersectionFile(t *testing.T) {
 }
 
 func TestDeleteAllFile(t *testing.T) {
-	file, status := fixtureFile()
+	file, status, _ := fixtureFile()
 	file.Update(1, 0, 0, 100)
 	// Output:
 	// 0 -1
@@ -326,7 +286,7 @@ func TestDeleteAllFile(t *testing.T) {
 }
 
 func TestFusedIntersectionFile(t *testing.T) {
-	file, status := fixtureFile()
+	file, status, _ := fixtureFile()
 	file.Update(1, 10, 10, 0)
 	file.Update(2, 15, 3, 10)
 	dump := file.Dump()
@@ -343,7 +303,7 @@ func TestFusedIntersectionFile(t *testing.T) {
 }
 
 func TestTortureFile(t *testing.T) {
-	file, status := fixtureFile()
+	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
@@ -375,7 +335,7 @@ func TestTortureFile(t *testing.T) {
 }
 
 func TestInsertDeleteSameTimeFile(t *testing.T) {
-	file, status := fixtureFile()
+	file, status, _ := fixtureFile()
 	file.Update(0, 10, 10, 20)
 	dump := file.Dump()
 	assert.Equal(t, "0 0\n90 -1\n", dump)
@@ -387,7 +347,7 @@ func TestInsertDeleteSameTimeFile(t *testing.T) {
 }
 
 func TestBug1File(t *testing.T) {
-	file, status := fixtureFile()
+	file, status, _ := fixtureFile()
 	file.Update(316, 1, 86, 0)
 	file.Update(316, 87, 0, 99)
 	file.Update(251, 0, 1, 0)
@@ -405,7 +365,7 @@ func TestBug1File(t *testing.T) {
 }
 
 func TestBug2File(t *testing.T) {
-	file, status := fixtureFile()
+	file, status, _ := fixtureFile()
 	file.Update(316, 1, 86, 0)
 	file.Update(316, 87, 0, 99)
 	file.Update(251, 0, 1, 0)
@@ -420,7 +380,7 @@ func TestBug2File(t *testing.T) {
 }
 
 func TestJoinFile(t *testing.T) {
-	file, status := fixtureFile()
+	file, status, _ := fixtureFile()
 	file.Update(1, 10, 10, 0)
 	file.Update(1, 30, 10, 0)
 	file.Update(1, 20, 10, 10)
@@ -431,7 +391,7 @@ func TestJoinFile(t *testing.T) {
 }
 
 func TestBug3File(t *testing.T) {
-	file, status := fixtureFile()
+	file, status, _ := fixtureFile()
 	file.Update(0, 1, 0, 99)
 	file.Update(0, 0, 1, 1)
 	dump := file.Dump()
@@ -441,7 +401,7 @@ func TestBug3File(t *testing.T) {
 
 func TestBug4File(t *testing.T) {
 	status := map[int]int64{}
-	file := NewFile(0, 10, func(a, b, c int) {
+	file := NewFile(0, 10, rbtree.NewAllocator(), func(a, b, c int) {
 		updateStatusFile(status, a, b, c)
 	})
 	// 0 0 | 10 -1
@@ -504,8 +464,8 @@ func TestBug4File(t *testing.T) {
 func TestBug5File(t *testing.T) {
 	status := map[int]int64{}
 	keys := []int{0, 2, 4, 7, 10}
-	vals := []int{24, 28, 24, 28, -1}
-	file := NewFileFromTree(keys, vals, func(a, b, c int) {
+	vals := []int{24, 28, 24, 28, math.MaxUint32}
+	file := NewFileFromTree(keys, vals, rbtree.NewAllocator(), func(a, b, c int) {
 		updateStatusFile(status, a, b, c)
 	})
 	file.Update(28, 0, 1, 3)
@@ -513,8 +473,8 @@ func TestBug5File(t *testing.T) {
 	assert.Equal(t, "0 28\n2 24\n5 28\n8 -1\n", dump)
 
 	keys = []int{0, 1, 16, 18}
-	vals = []int{305, 0, 157, -1}
-	file = NewFileFromTree(keys, vals, func(a, b, c int) {
+	vals = []int{305, 0, 157, math.MaxUint32}
+	file = NewFileFromTree(keys, vals, rbtree.NewAllocator(), func(a, b, c int) {
 		updateStatusFile(status, a, b, c)
 	})
 	file.Update(310, 0, 0, 2)
@@ -525,34 +485,30 @@ func TestBug5File(t *testing.T) {
 func TestNewFileFromTreeInvalidSize(t *testing.T) {
 	keys := [...]int{1, 2, 3}
 	vals := [...]int{4, 5}
-	assert.Panics(t, func() { NewFileFromTree(keys[:], vals[:]) })
+	assert.Panics(t, func() { NewFileFromTree(keys[:], vals[:], rbtree.NewAllocator()) })
 }
 
 func TestUpdatePanic(t *testing.T) {
 	keys := [...]int{0}
-	vals := [...]int{-1}
-	file := NewFileFromTree(keys[:], vals[:])
+	vals := [...]int{math.MaxUint32}
+	file := NewFileFromTree(keys[:], vals[:], rbtree.NewAllocator())
 	file.tree.DeleteWithKey(0)
-	file.tree.Insert(rbtree.Item{Key: -1, Value: -1})
-	var paniced interface{}
-	func() {
-		defer func() {
-			paniced = recover()
-		}()
-		file.Update(1, 0, 1, 0)
-	}()
-	assert.Contains(t, paniced, "invalid tree state")
+	file.tree.Insert(rbtree.Item{Key: 1, Value: math.MaxUint32})
+	assert.PanicsWithValue(t, "invalid tree state", func(){file.Update(1, 0, 1, 0)})
+	file.tree.Insert(rbtree.Item{Key: 0, Value: math.MaxUint32})
+	assert.PanicsWithValue(
+		t, "time may not be >= MaxUint32", func(){file.Update(math.MaxUint32, 0, 1, 0)})
 }
 
 func TestFileValidate(t *testing.T) {
 	keys := [...]int{0}
-	vals := [...]int{-1}
-	file := NewFileFromTree(keys[:], vals[:])
+	vals := [...]int{math.MaxUint32}
+	file := NewFileFromTree(keys[:], vals[:], rbtree.NewAllocator())
 	file.tree.DeleteWithKey(0)
-	file.tree.Insert(rbtree.Item{Key: -1, Value: -1})
+	file.tree.Insert(rbtree.Item{Key: 1, Value: math.MaxUint32})
 	assert.Panics(t, func() { file.Validate() })
-	file.tree.DeleteWithKey(-1)
-	file.tree.Insert(rbtree.Item{Key: 0, Value: -1})
+	file.tree.DeleteWithKey(1)
+	file.tree.Insert(rbtree.Item{Key: 0, Value: math.MaxUint32})
 	file.Validate()
 	file.tree.DeleteWithKey(0)
 	file.tree.Insert(rbtree.Item{Key: 0, Value: 0})
@@ -560,14 +516,14 @@ func TestFileValidate(t *testing.T) {
 	file.tree.DeleteWithKey(0)
 	file.tree.Insert(rbtree.Item{Key: 0, Value: 1})
 	file.tree.Insert(rbtree.Item{Key: 1, Value: 1})
-	file.tree.Insert(rbtree.Item{Key: 2, Value: -1})
+	file.tree.Insert(rbtree.Item{Key: 2, Value: math.MaxUint32})
 	file.Validate()
 	file.tree.FindGE(2).Item().Key = 1
 	assert.Panics(t, func() { file.Validate() })
 }
 
 func TestFileFlatten(t *testing.T) {
-	file, _ := fixtureFile()
+	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
@@ -594,7 +550,7 @@ func TestFileFlatten(t *testing.T) {
 }
 
 func TestFileMergeMark(t *testing.T) {
-	file, status := fixtureFile()
+	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
@@ -619,7 +575,7 @@ func TestFileMergeMark(t *testing.T) {
 }
 
 func TestFileMerge(t *testing.T) {
-	file1, status := fixtureFile()
+	file1, status, alloc := 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
@@ -629,7 +585,7 @@ func TestFileMerge(t *testing.T) {
 	// 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)
+	file2 := file1.Clone(alloc.Clone())
 	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
@@ -658,13 +614,13 @@ func TestFileMerge(t *testing.T) {
 }
 
 func TestFileMergeNoop(t *testing.T) {
-	file1, _ := fixtureFile()
+	file1, _, _ := fixtureFile()
 	// 0 0 | 100 -1                             [0]: 100
 	assert.Panics(t, func() { file1.Merge(3, nil) })
 }
 
 func TestFileMergeNil(t *testing.T) {
-	file, _ := fixtureFile()
+	file, _, _ := fixtureFile()
 	assert.Panics(t, func() {
 		file.Merge(1, nil)
 	})
@@ -673,8 +629,8 @@ func TestFileMergeNil(t *testing.T) {
 func TestBug6File(t *testing.T) {
 	status := map[int]int64{}
 	keys := []int{0, 113, 153, 154}
-	vals := []int{7, 10, 7, -1}
-	file := NewFileFromTree(keys, vals, func(a, b, c int) {
+	vals := []int{7, 10, 7, math.MaxUint32}
+	file := NewFileFromTree(keys, vals, rbtree.NewAllocator(), func(a, b, c int) {
 		updateStatusFile(status, a, b, c)
 	})
 	// 0 7 | 113 10 | 153 7 | 154 -1
@@ -694,7 +650,7 @@ func TestBug6File(t *testing.T) {
 	dump := file.Dump()
 	assert.Equal(t, "0 7\n99 10\n100 7\n104 10\n105 7\n106 10\n107 7\n108 10\n109 7\n113 10\n157 7\n158 -1\n", dump)
 
-	file = NewFileFromTree(keys, vals, func(a, b, c int) {
+	file = NewFileFromTree(keys, vals, rbtree.NewAllocator(), func(a, b, c int) {
 		updateStatusFile(status, a, b, c)
 	})
 	// 0 7 | 113 10 | 153 7 | 154 -1

Datei-Diff unterdrückt, da er zu groß ist
+ 399 - 288
internal/rbtree/rbtree.go


+ 340 - 0
internal/rbtree/rbtree_test.go

@@ -0,0 +1,340 @@
+package rbtree
+
+import (
+	"fmt"
+	"math/rand"
+	"sort"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+
+// Create a tree storing a set of integers
+func testNewIntSet() *RBTree {
+	return NewRBTree(NewAllocator())
+}
+
+func testAssert(t *testing.T, b bool, message string) {
+	assert.True(t, b, message)
+}
+
+func boolInsert(tree *RBTree, item int) bool {
+	status, _ := tree.Insert(Item{uint32(item), uint32(item)})
+	return status
+}
+
+func TestEmpty(t *testing.T) {
+	tree := testNewIntSet()
+	testAssert(t, tree.Len() == 0, "len!=0")
+	testAssert(t, tree.Max().NegativeLimit(), "neglimit")
+	testAssert(t, tree.Min().Limit(), "limit")
+	testAssert(t, tree.FindGE(10).Limit(), "Not empty")
+	testAssert(t, tree.FindLE(10).NegativeLimit(), "Not empty")
+	testAssert(t, tree.Get(10) == nil, "Not empty")
+	testAssert(t, tree.Limit().Equal(tree.Min()), "iter")
+}
+
+func TestFindGE(t *testing.T) {
+	tree := testNewIntSet()
+	testAssert(t, boolInsert(tree, 10), "Insert1")
+	testAssert(t, !boolInsert(tree, 10), "Insert2")
+	testAssert(t, tree.Len() == 1, "len==1")
+	testAssert(t, tree.FindGE(10).Item().Key == 10, "FindGE 10")
+	testAssert(t, tree.FindGE(11).Limit(), "FindGE 11")
+	assert.Equal(t, tree.FindGE(9).Item().Key, uint32(10), "FindGE 10")
+}
+
+func TestFindLE(t *testing.T) {
+	tree := testNewIntSet()
+	testAssert(t, boolInsert(tree, 10), "insert1")
+	testAssert(t, tree.FindLE(10).Item().Key == 10, "FindLE 10")
+	testAssert(t, tree.FindLE(11).Item().Key == 10, "FindLE 11")
+	testAssert(t, tree.FindLE(9).NegativeLimit(), "FindLE 9")
+}
+
+func TestGet(t *testing.T) {
+	tree := testNewIntSet()
+	testAssert(t, boolInsert(tree, 10), "insert1")
+	assert.Equal(t, *tree.Get(10), uint32(10), "Get 10")
+	testAssert(t, tree.Get(9) == nil, "Get 9")
+	testAssert(t, tree.Get(11) == nil, "Get 11")
+}
+
+func TestDelete(t *testing.T) {
+	tree := testNewIntSet()
+	testAssert(t, !tree.DeleteWithKey(10), "del")
+	testAssert(t, tree.Len() == 0, "dellen")
+	testAssert(t, boolInsert(tree, 10), "ins")
+	testAssert(t, tree.DeleteWithKey(10), "del")
+	testAssert(t, tree.Len() == 0, "dellen")
+
+	// delete was deleting after the request if request not found
+	// ensure this does not regress:
+	testAssert(t, boolInsert(tree, 10), "ins")
+	testAssert(t, !tree.DeleteWithKey(9), "del")
+	testAssert(t, tree.Len() == 1, "dellen")
+
+}
+
+func iterToString(i Iterator) string {
+	s := ""
+	for ; !i.Limit(); i = i.Next() {
+		if s != "" { s = s + ","}
+		s = s + fmt.Sprintf("%d", i.Item().Key)
+	}
+	return s
+}
+
+func reverseIterToString(i Iterator) string {
+	s := ""
+	for ; !i.NegativeLimit(); i = i.Prev() {
+		if s != "" { s = s + ","}
+		s = s + fmt.Sprintf("%d", i.Item().Key)
+	}
+	return s
+}
+
+func TestIterator(t *testing.T) {
+	tree := testNewIntSet()
+	for i := 0; i < 10; i = i + 2 {
+		boolInsert(tree, i)
+	}
+	assert.Equal(t, iterToString(tree.FindGE(3)), "4,6,8")
+	assert.Equal(t, iterToString(tree.FindGE(4)), "4,6,8")
+	assert.Equal(t, iterToString(tree.FindGE(8)), "8")
+	assert.Equal(t, iterToString(tree.FindGE(9)), "")
+	assert.Equal(t, reverseIterToString(tree.FindLE(3)), "2,0")
+	assert.Equal(t, reverseIterToString(tree.FindLE(2)), "2,0")
+	assert.Equal(t, reverseIterToString(tree.FindLE(0)), "0")
+}
+
+//
+// Randomized tests
+//
+
+// oracle stores provides an interface similar to rbtree, but stores
+// data in an sorted array
+type oracle struct {
+	data []int
+}
+
+func newOracle() *oracle {
+	return &oracle{data: make([]int, 0)}
+}
+
+func (o *oracle) Len() int {
+	return len(o.data)
+}
+
+// interface needed for sorting
+func (o *oracle) Less(i, j int) bool {
+	return o.data[i] < o.data[j]
+}
+
+func (o *oracle) Swap(i, j int) {
+	e := o.data[j]
+	o.data[j] = o.data[i]
+	o.data[i] = e
+}
+
+func (o *oracle) Insert(key int) bool {
+	for _, e := range o.data {
+		if e == key {
+			return false
+		}
+	}
+
+	n := len(o.data) + 1
+	newData := make([]int, n)
+	copy(newData, o.data)
+	newData[n-1] = key
+	o.data = newData
+	sort.Sort(o)
+	return true
+}
+
+func (o *oracle) RandomExistingKey(rand *rand.Rand) int {
+	index := rand.Int31n(int32(len(o.data)))
+	return o.data[index]
+}
+
+func (o *oracle) FindGE(t *testing.T, key int) oracleIterator {
+	prev := int(-1)
+	for i, e := range o.data {
+		if e <= prev {
+			t.Fatal("Nonsorted oracle ", e, prev)
+		}
+		if e >= key {
+			return oracleIterator{o: o, index: i}
+		}
+	}
+	return oracleIterator{o: o, index: len(o.data)}
+}
+
+func (o *oracle) FindLE(t *testing.T, key int) oracleIterator {
+	iter := o.FindGE(t, key)
+	if !iter.Limit() && o.data[iter.index] == key {
+		return iter
+	}
+	return oracleIterator{o, iter.index - 1}
+}
+
+func (o *oracle) Delete(key int) bool {
+	for i, e := range o.data {
+		if e == key {
+			newData := make([]int, len(o.data)-1)
+			copy(newData, o.data[0:i])
+			copy(newData[i:], o.data[i+1:])
+			o.data = newData
+			return true
+		}
+	}
+	return false
+}
+
+//
+// Test iterator
+//
+type oracleIterator struct {
+	o     *oracle
+	index int
+}
+
+func (oiter oracleIterator) Limit() bool {
+	return oiter.index >= len(oiter.o.data)
+}
+
+func (oiter oracleIterator) Min() bool {
+	return oiter.index == 0
+}
+
+func (oiter oracleIterator) NegativeLimit() bool {
+	return oiter.index < 0
+}
+
+func (oiter oracleIterator) Max() bool {
+	return oiter.index == len(oiter.o.data) - 1
+}
+
+func (oiter oracleIterator) Item() int {
+	return oiter.o.data[oiter.index]
+}
+
+func (oiter oracleIterator) Next() oracleIterator {
+	return oracleIterator{oiter.o, oiter.index + 1}
+}
+
+func (oiter oracleIterator) Prev() oracleIterator {
+	return oracleIterator{oiter.o, oiter.index - 1}
+}
+
+func compareContents(t *testing.T, oiter oracleIterator, titer Iterator) {
+	oi := oiter
+	ti := titer
+
+	// Test forward iteration
+	testAssert(t, oi.NegativeLimit() == ti.NegativeLimit(), "rend")
+	if oi.NegativeLimit() {
+		oi = oi.Next()
+		ti = ti.Next()
+	}
+
+	for !oi.Limit() && !ti.Limit() {
+		// log.Print("Item: ", oi.Item(), ti.Item())
+		if ti.Item().Key != uint32(oi.Item()) {
+			t.Fatal("Wrong item", ti.Item(), oi.Item())
+		}
+		oi = oi.Next()
+		ti = ti.Next()
+	}
+	if !ti.Limit() {
+		t.Fatal("!ti.done", ti.Item())
+	}
+	if !oi.Limit() {
+		t.Fatal("!oi.done", oi.Item())
+	}
+
+	// Test reverse iteration
+	oi = oiter
+	ti = titer
+	testAssert(t, oi.Limit() == ti.Limit(), "end")
+	if oi.Limit() {
+		oi = oi.Prev()
+		ti = ti.Prev()
+	}
+
+	for !oi.NegativeLimit() && !ti.NegativeLimit() {
+		if ti.Item().Key != uint32(oi.Item()) {
+			t.Fatal("Wrong item", ti.Item(), oi.Item())
+		}
+		oi = oi.Prev()
+		ti = ti.Prev()
+	}
+	if !ti.NegativeLimit() {
+		t.Fatal("!ti.done", ti.Item())
+	}
+	if !oi.NegativeLimit() {
+		t.Fatal("!oi.done", oi.Item())
+	}
+}
+
+func compareContentsFull(t *testing.T, o *oracle, tree *RBTree) {
+	compareContents(t, o.FindGE(t, -1), tree.FindGE(0))
+}
+
+func TestRandomized(t *testing.T) {
+	const numKeys = 1000
+
+	o := newOracle()
+	tree := testNewIntSet()
+	r := rand.New(rand.NewSource(0))
+	for i := 0; i < 10000; i++ {
+		op := r.Int31n(100)
+		if op < 50 {
+			key := r.Int31n(numKeys)
+			o.Insert(int(key))
+			boolInsert(tree, int(key))
+			compareContentsFull(t, o, tree)
+		} else if op < 90 && o.Len() > 0 {
+			key := o.RandomExistingKey(r)
+			o.Delete(key)
+			if !tree.DeleteWithKey(uint32(key)) {
+				t.Fatal("DeleteExisting", key)
+			}
+			compareContentsFull(t, o, tree)
+		} else if op < 95 {
+			key := int(r.Int31n(numKeys))
+			compareContents(t, o.FindGE(t, key), tree.FindGE(uint32(key)))
+		} else {
+			key := int(r.Int31n(numKeys))
+			compareContents(t, o.FindLE(t, key), tree.FindLE(uint32(key)))
+		}
+	}
+}
+
+func TestClone(t *testing.T) {
+	alloc1 := NewAllocator()
+	alloc1.malloc()
+	tree := NewRBTree(alloc1)
+	tree.Insert(Item{7, 7})
+	assert.Equal(t, alloc1.storage, []node{{}, {}, {color: black, item: Item{7, 7}}})
+	assert.Equal(t, tree.minNode, uint32(2))
+	assert.Equal(t, tree.maxNode, uint32(2))
+	alloc2 := NewAllocator()
+	clone := tree.Clone(alloc2)
+	assert.Equal(t, alloc2.storage, []node{{}, {color: black, item: Item{7, 7}}})
+	assert.Equal(t, clone.minNode, uint32(1))
+	assert.Equal(t, clone.maxNode, uint32(1))
+	assert.Equal(t, alloc2.Size(), 2)
+	tree.Insert(Item{10, 10})
+	alloc2 = NewAllocator()
+	clone = tree.Clone(alloc2)
+	assert.Equal(t, alloc2.storage, []node{
+		{},
+		{right: 2, color: black, item: Item{7, 7}},
+		{parent: 1, color: red, item: Item{10, 10}}})
+	assert.Equal(t, clone.minNode, uint32(1))
+	assert.Equal(t, clone.maxNode, uint32(2))
+	assert.Equal(t, alloc2.Size(), 3)
+}

+ 13 - 3
leaves/burndown.go

@@ -20,6 +20,7 @@ import (
 	"gopkg.in/src-d/hercules.v6/internal/pb"
 	items "gopkg.in/src-d/hercules.v6/internal/plumbing"
 	"gopkg.in/src-d/hercules.v6/internal/plumbing/identity"
+	"gopkg.in/src-d/hercules.v6/internal/rbtree"
 	"gopkg.in/src-d/hercules.v6/internal/yaml"
 )
 
@@ -66,6 +67,8 @@ type BurndownAnalysis struct {
 	peopleHistories []sparseHistory
 	// files is the mapping <file path> -> *File.
 	files map[string]*burndown.File
+	// fileAllocator is the allocator for RBTree-s in `files`.
+	fileAllocator *rbtree.Allocator
 	// mergedFiles is used during merges to record the real file hashes
 	mergedFiles map[string]bool
 	// mergedAuthor of the processed merge commit
@@ -257,6 +260,7 @@ func (analyser *BurndownAnalysis) Initialize(repository *git.Repository) error {
 	}
 	analyser.peopleHistories = make([]sparseHistory, analyser.PeopleNumber)
 	analyser.files = map[string]*burndown.File{}
+	analyser.fileAllocator = rbtree.NewAllocator()
 	analyser.mergedFiles = map[string]bool{}
 	analyser.mergedAuthor = identity.AuthorMissing
 	analyser.renames = map[string]string{}
@@ -314,8 +318,9 @@ func (analyser *BurndownAnalysis) Fork(n int) []core.PipelineItem {
 	for i := range result {
 		clone := *analyser
 		clone.files = map[string]*burndown.File{}
+		clone.fileAllocator = clone.fileAllocator.Clone()
 		for key, file := range analyser.files {
-			clone.files[key] = file.Clone(false)
+			clone.files[key] = file.Clone(clone.fileAllocator)
 		}
 		result[i] = &clone
 	}
@@ -342,6 +347,9 @@ func (analyser *BurndownAnalysis) Merge(branches []core.PipelineItem) {
 	for key, val := range keys {
 		if !val {
 			for _, burn := range all {
+				if f, exists := burn.files[key]; exists {
+					f.Delete()
+				}
 				delete(burn.files, key)
 			}
 			continue
@@ -364,7 +372,8 @@ func (analyser *BurndownAnalysis) Merge(branches []core.PipelineItem) {
 		}
 		for _, burn := range all {
 			if burn.files[key] != files[0] {
-				burn.files[key] = files[0].Clone(false)
+				burn.files[key].Delete()
+				burn.files[key] = files[0].Clone(burn.fileAllocator)
 			}
 		}
 	}
@@ -1011,7 +1020,7 @@ func (analyser *BurndownAnalysis) newFile(
 		updaters = append(updaters, analyser.updateMatrix)
 		day = analyser.packPersonWithDay(author, day)
 	}
-	return burndown.NewFile(day, size, updaters...), nil
+	return burndown.NewFile(day, size, analyser.fileAllocator, updaters...), nil
 }
 
 func (analyser *BurndownAnalysis) handleInsertion(
@@ -1054,6 +1063,7 @@ func (analyser *BurndownAnalysis) handleDeletion(
 		return nil
 	}
 	file.Update(analyser.packPersonWithDay(author, analyser.day), 0, 0, lines)
+	file.Delete()
 	delete(analyser.files, name)
 	delete(analyser.fileHistories, name)
 	analyser.renames[name] = ""