瀏覽代碼

Implement fused insert+delete

Vadim Markovtsev 8 年之前
父節點
當前提交
6772bb8b4b
共有 3 個文件被更改,包括 134 次插入12 次删除
  1. 34 4
      analyser.go
  2. 24 8
      file.go
  3. 76 0
      file_test.go

+ 34 - 4
analyser.go

@@ -108,7 +108,6 @@ func (analyser *Analyser) handleModification(
 	str_to := str(blob_to)
 	file, exists := files[change.From.Name]
 	if !exists {
-		// fmt.Fprintf(os.Stderr, "warning: file %s does not exist\n", change.From.Name)
 		analyser.handleInsertion(change, day, status, files)
 		return
 	}
@@ -126,6 +125,18 @@ func (analyser *Analyser) handleModification(
 	// we do not call RunesToDiffLines so the number of lines equals
 	// to the rune count
 	position := 0
+	pending := diffmatchpatch.Diff{Text: ""}
+
+	apply := func(edit diffmatchpatch.Diff) {
+		length := utf8.RuneCountInString(edit.Text)
+		if edit.Type == diffmatchpatch.DiffInsert {
+			file.Update(day, position, length, 0)
+			position += length
+		} else {
+			file.Update(day, position, 0, length)
+		}
+	}
+
 	for _, edit := range diffs {
 		length := utf8.RuneCountInString(edit.Text)
 		func() {
@@ -143,17 +154,36 @@ func (analyser *Analyser) handleModification(
 			}()
 			switch edit.Type {
 			case diffmatchpatch.DiffEqual:
+				if pending.Text != "" {
+					apply(pending)
+					pending.Text = ""
+				}
 				position += length
 			case diffmatchpatch.DiffInsert:
-				file.Update(day, position, length, 0)
-				position += length
+				if pending.Text != "" {
+					if pending.Type == diffmatchpatch.DiffInsert {
+						panic("DiffInsert may not appear after DiffInsert")
+					}
+					file.Update(day, position, length, utf8.RuneCountInString(pending.Text))
+					position += length
+					pending.Text = ""
+				} else {
+					pending = edit
+				}
 			case diffmatchpatch.DiffDelete:
-				file.Update(day, position, 0, length)
+				if pending.Text != "" {
+					panic("DiffDelete may not appear after DiffInsert/DiffDelete")
+				}
+				pending = edit
 			default:
 				panic(fmt.Sprintf("diff operation is not supported: %d", edit.Type))
 			}
 		}()
 	}
+	if pending.Text != "" {
+		apply(pending)
+		pending.Text = ""
+	}
 	if file.Len() != len(dst) {
 		panic(fmt.Sprintf("%s: internal integrity error dst %d != %d",
 			change.To.Name, len(dst), file.Len()))

+ 24 - 8
file.go

@@ -57,21 +57,26 @@ func (file *File) Update(time int, pos int, ins_length int, del_length int) {
 		panic(fmt.Sprintf("attempt to insert after the end of the file: %d < %d",
 			tree.Max().Item().key, pos))
 	}
+	if tree.Len() < 2 && tree.Min().Item().key != 0 {
+		panic("invalid tree state")
+	}
 	status := file.status
 	iter := tree.FindLE(pos)
 	origin := *iter.Item()
 	status[time] += int64(ins_length)
 	if del_length == 0 {
 		// simple case with insertions only
-		if origin.key < pos {
+		if origin.key < pos || (origin.value == time && pos == 0) {
 			iter = iter.Next()
 		}
 		for ; !iter.Limit(); iter = iter.Next() {
 			iter.Item().key += ins_length
 		}
-		tree.Insert(Item{key: pos, value: time})
-		if origin.key < pos {
-			tree.Insert(Item{key: pos + ins_length, value: origin.value})
+		if origin.value != time {
+			tree.Insert(Item{key: pos, value: time})
+			if origin.key < pos {
+				tree.Insert(Item{key: pos + ins_length, value: origin.value})
+			}
 		}
 		return
 	}
@@ -100,9 +105,18 @@ func (file *File) Update(time int, pos int, ins_length int, del_length int) {
 
 	// prepare for the keys update
 	var previous *Item
-	if ins_length > 0 {
-		if origin.value != time {
-			// insert our new interval
+	if ins_length > 0 && (origin.value != time || origin.key == pos) {
+		// insert our new interval
+		if iter.Item().value == time {
+			if iter.Prev().Item().value != time {
+				iter.Item().key = pos
+			} else {
+				next_iter := iter.Next()
+				tree.DeleteWithIterator(iter)
+				iter = next_iter
+			}
+			origin.value = time // cancels the insertion after applying the delta
+		} else {
 			_, iter = tree.Insert(Item{key: pos, value: time})
 		}
 	} else {
@@ -124,7 +138,9 @@ func (file *File) Update(time int, pos int, ins_length int, del_length int) {
 		}
 	}
 	if ins_length > 0 {
-		tree.Insert(Item{pos + ins_length, origin.value})
+		if origin.value != time {
+			tree.Insert(Item{pos + ins_length, origin.value})
+		}
 	} else if (pos > origin.key && previous.value != origin.value) || pos == origin.key {
 		// continue the original interval
 		tree.Insert(Item{pos, origin.value})

+ 76 - 0
file_test.go

@@ -110,6 +110,17 @@ func TestFused(t *testing.T) {
 	assert.Equal(t, int64(6), status[1])
 }
 
+func TestInsertSameTime(t *testing.T) {
+	file, status := fixture()
+	file.Update(0, 5, 10, 0)
+	dump := file.Dump()
+	// Output:
+	// 0 0
+	// 110 -1
+	assert.Equal(t, "0 0\n110 -1\n", dump)
+	assert.Equal(t, int64(110), status[0])
+}
+
 func TestInsertSameStart(t *testing.T) {
 	file, status := fixture()
 	file.Update(1, 10, 10, 0)
@@ -241,3 +252,68 @@ func TestTorture(t *testing.T) {
 	assert.Equal(t, int64(0), status[7])
 	assert.Equal(t, int64(10), status[8])
 }
+
+func TestInsertDeleteSameTime(t *testing.T) {
+	file, status := fixture()
+	file.Update(0, 10, 10, 20)
+	dump := file.Dump()
+	assert.Equal(t, "0 0\n90 -1\n", dump)
+	assert.Equal(t, int64(90), status[0])
+	file.Update(0, 10, 20, 10)
+	dump = file.Dump()
+	assert.Equal(t, "0 0\n100 -1\n", dump)
+	assert.Equal(t, int64(100), status[0])
+}
+
+func TestBug1(t *testing.T) {
+	file, status := fixture()
+	file.Update(316, 1, 86, 0)
+	file.Update(316, 87, 0, 99)
+	file.Update(251, 0, 1, 0)
+	file.Update(251, 1, 0, 1)
+	dump := file.Dump()
+	assert.Equal(t, "0 251\n1 316\n87 -1\n", dump)
+	assert.Equal(t, int64(1), status[251])
+	assert.Equal(t, int64(86), status[316])
+	file.Update(316, 0, 0, 1)
+	file.Update(316, 0, 1, 0)
+	dump = file.Dump()
+	assert.Equal(t, "0 316\n87 -1\n", dump)
+	assert.Equal(t, int64(0), status[251])
+	assert.Equal(t, int64(87), status[316])
+}
+
+func TestBug2(t *testing.T) {
+	file, status := fixture()
+	file.Update(316, 1, 86, 0)
+	file.Update(316, 87, 0, 99)
+	file.Update(251, 0, 1, 0)
+	file.Update(251, 1, 0, 1)
+	dump := file.Dump()
+	assert.Equal(t, "0 251\n1 316\n87 -1\n", dump)
+	file.Update(316, 0, 1, 1)
+	dump = file.Dump()
+	assert.Equal(t, "0 316\n87 -1\n", dump)
+	assert.Equal(t, int64(0), status[251])
+	assert.Equal(t, int64(87), status[316])
+}
+
+func TestJoin(t *testing.T) {
+	file, status := fixture()
+	file.Update(1, 10, 10, 0)
+	file.Update(1, 30, 10, 0)
+	file.Update(1, 20, 10, 10)
+	dump := file.Dump()
+	assert.Equal(t, "0 0\n10 1\n40 0\n120 -1\n", dump)
+	assert.Equal(t, int64(90), status[0])
+	assert.Equal(t, int64(30), status[1])
+}
+
+func TestBug3(t *testing.T) {
+	file, status := fixture()
+	file.Update(0, 1, 0, 99)
+	file.Update(0, 0, 1, 1)
+	dump := file.Dump()
+	assert.Equal(t, "0 0\n1 -1\n", dump)
+	assert.Equal(t, int64(1), status[0])
+}