Browse Source
Merging Git notes branches has several differences from merging "normal" branches. Although Git notes are initially stored as one flat tree the tree may fanout when the number of notes becomes too large for efficient access. In this case the first two hex digits of the note name will be used as a subdirectory name and the rest 38 hex digits as the file name under that directory. Similarly, when number of notes decreases a fanout tree may collapse back into a flat tree. The Git notes merge algorithm must take into account possibly different tree structures in different note branches and must properly match them against each other. Any conflict on a Git note is, by default, resolved by concatenating the two conflicting versions of the note. A delete-edit conflict is, by default, resolved by keeping the edit version. The note merge logic is pluggable and the caller may provide custom note merger that will perform different merging strategy. Additionally, it is possible to have non-note entries inside a notes tree. The merge algorithm must also take this fact into account and will try to merge such non-note entries. However, in case of any merge conflicts the merge operation will fail. Git notes merge algorithm is currently not trying to do content merge of non-note entries. Thanks to Shawn Pearce for patiently answering my questions related to this topic, giving hints and providing code snippets. Change-Id: I3b2335c76c766fd7ea25752e54087f9b19d69c88 Signed-off-by: Sasa Zivkov <sasa.zivkov@sap.com> Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>stable-0.11
Sasa Zivkov
14 years ago
committed by
Matthias Sohn
15 changed files with 1439 additions and 20 deletions
@ -0,0 +1,149 @@
|
||||
/* |
||||
* Copyright (C) 2010, Sasa Zivkov <sasa.zivkov@sap.com> |
||||
* and other copyright owners as documented in the project's IP log. |
||||
* |
||||
* This program and the accompanying materials are made available |
||||
* under the terms of the Eclipse Distribution License v1.0 which |
||||
* accompanies this distribution, is reproduced below, and is |
||||
* available at http://www.eclipse.org/org/documents/edl-v10.php
|
||||
* |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or |
||||
* without modification, are permitted provided that the following |
||||
* conditions are met: |
||||
* |
||||
* - Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* |
||||
* - Redistributions in binary form must reproduce the above |
||||
* copyright notice, this list of conditions and the following |
||||
* disclaimer in the documentation and/or other materials provided |
||||
* with the distribution. |
||||
* |
||||
* - Neither the name of the Eclipse Foundation, Inc. nor the |
||||
* names of its contributors may be used to endorse or promote |
||||
* products derived from this software without specific prior |
||||
* written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
||||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
package org.eclipse.jgit.notes; |
||||
|
||||
import static org.junit.Assert.assertEquals; |
||||
import static org.junit.Assert.assertNull; |
||||
import static org.junit.Assert.assertSame; |
||||
|
||||
import org.eclipse.jgit.junit.TestRepository; |
||||
import org.eclipse.jgit.lib.ObjectInserter; |
||||
import org.eclipse.jgit.lib.ObjectReader; |
||||
import org.eclipse.jgit.lib.Repository; |
||||
import org.eclipse.jgit.lib.RepositoryTestCase; |
||||
import org.eclipse.jgit.revwalk.RevBlob; |
||||
import org.junit.After; |
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
|
||||
public class DefaultNoteMergerTest extends RepositoryTestCase { |
||||
|
||||
private TestRepository<Repository> tr; |
||||
|
||||
private ObjectReader reader; |
||||
|
||||
private ObjectInserter inserter; |
||||
|
||||
private DefaultNoteMerger merger; |
||||
|
||||
private Note baseNote; |
||||
|
||||
private RevBlob noteOn; |
||||
|
||||
@Override |
||||
@Before |
||||
public void setUp() throws Exception { |
||||
super.setUp(); |
||||
tr = new TestRepository<Repository>(db); |
||||
reader = db.newObjectReader(); |
||||
inserter = db.newObjectInserter(); |
||||
merger = new DefaultNoteMerger(); |
||||
noteOn = tr.blob("a"); |
||||
baseNote = newNote("data"); |
||||
} |
||||
|
||||
@Override |
||||
@After |
||||
public void tearDown() throws Exception { |
||||
reader.release(); |
||||
inserter.release(); |
||||
super.tearDown(); |
||||
} |
||||
|
||||
@Test |
||||
public void testDeleteDelete() throws Exception { |
||||
assertNull(merger.merge(baseNote, null, null, null, null)); |
||||
} |
||||
|
||||
@Test |
||||
public void testEditDelete() throws Exception { |
||||
Note edit = newNote("edit"); |
||||
assertSame(merger.merge(baseNote, edit, null, null, null), edit); |
||||
assertSame(merger.merge(baseNote, null, edit, null, null), edit); |
||||
} |
||||
|
||||
@Test |
||||
public void testIdenticalEdit() throws Exception { |
||||
Note edit = newNote("edit"); |
||||
assertSame(merger.merge(baseNote, edit, edit, null, null), edit); |
||||
} |
||||
|
||||
@Test |
||||
public void testEditEdit() throws Exception { |
||||
Note edit1 = newNote("edit1"); |
||||
Note edit2 = newNote("edit2"); |
||||
|
||||
Note result = merger.merge(baseNote, edit1, edit2, reader, inserter); |
||||
assertEquals(result, noteOn); // same note
|
||||
assertEquals(result.getData(), tr.blob("edit1edit2")); |
||||
|
||||
result = merger.merge(baseNote, edit2, edit1, reader, inserter); |
||||
assertEquals(result, noteOn); // same note
|
||||
assertEquals(result.getData(), tr.blob("edit2edit1")); |
||||
} |
||||
|
||||
@Test |
||||
public void testIdenticalAdd() throws Exception { |
||||
Note add = newNote("add"); |
||||
assertSame(merger.merge(null, add, add, null, null), add); |
||||
} |
||||
|
||||
@Test |
||||
public void testAddAdd() throws Exception { |
||||
Note add1 = newNote("add1"); |
||||
Note add2 = newNote("add2"); |
||||
|
||||
Note result = merger.merge(null, add1, add2, reader, inserter); |
||||
assertEquals(result, noteOn); // same note
|
||||
assertEquals(result.getData(), tr.blob("add1add2")); |
||||
|
||||
result = merger.merge(null, add2, add1, reader, inserter); |
||||
assertEquals(result, noteOn); // same note
|
||||
assertEquals(result.getData(), tr.blob("add2add1")); |
||||
} |
||||
|
||||
private Note newNote(String data) throws Exception { |
||||
return new Note(noteOn, tr.blob(data)); |
||||
} |
||||
} |
@ -0,0 +1,509 @@
|
||||
/* |
||||
* Copyright (C) 2010, Sasa Zivkov <sasa.zivkov@sap.com> |
||||
* and other copyright owners as documented in the project's IP log. |
||||
* |
||||
* This program and the accompanying materials are made available |
||||
* under the terms of the Eclipse Distribution License v1.0 which |
||||
* accompanies this distribution, is reproduced below, and is |
||||
* available at http://www.eclipse.org/org/documents/edl-v10.php
|
||||
* |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or |
||||
* without modification, are permitted provided that the following |
||||
* conditions are met: |
||||
* |
||||
* - Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* |
||||
* - Redistributions in binary form must reproduce the above |
||||
* copyright notice, this list of conditions and the following |
||||
* disclaimer in the documentation and/or other materials provided |
||||
* with the distribution. |
||||
* |
||||
* - Neither the name of the Eclipse Foundation, Inc. nor the |
||||
* names of its contributors may be used to endorse or promote |
||||
* products derived from this software without specific prior |
||||
* written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
||||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
package org.eclipse.jgit.notes; |
||||
|
||||
import static org.junit.Assert.assertEquals; |
||||
import static org.junit.Assert.assertTrue; |
||||
import static org.junit.Assert.fail; |
||||
|
||||
import java.io.IOException; |
||||
import java.util.Iterator; |
||||
|
||||
import org.eclipse.jgit.junit.TestRepository; |
||||
import org.eclipse.jgit.lib.ObjectInserter; |
||||
import org.eclipse.jgit.lib.ObjectReader; |
||||
import org.eclipse.jgit.lib.Repository; |
||||
import org.eclipse.jgit.lib.RepositoryTestCase; |
||||
import org.eclipse.jgit.merge.MergeStrategy; |
||||
import org.eclipse.jgit.revwalk.RevBlob; |
||||
import org.eclipse.jgit.revwalk.RevCommit; |
||||
import org.junit.After; |
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
|
||||
public class NoteMapMergerTest extends RepositoryTestCase { |
||||
private TestRepository<Repository> tr; |
||||
|
||||
private ObjectReader reader; |
||||
|
||||
private ObjectInserter inserter; |
||||
|
||||
private NoteMap noRoot; |
||||
|
||||
private NoteMap empty; |
||||
|
||||
private NoteMap map_a; |
||||
|
||||
private NoteMap map_a_b; |
||||
|
||||
private RevBlob noteAId; |
||||
|
||||
private String noteAContent; |
||||
|
||||
private RevBlob noteABlob; |
||||
|
||||
private RevBlob noteBId; |
||||
|
||||
private String noteBContent; |
||||
|
||||
private RevBlob noteBBlob; |
||||
|
||||
private RevCommit sampleTree_a; |
||||
|
||||
private RevCommit sampleTree_a_b; |
||||
|
||||
@Override |
||||
@Before |
||||
public void setUp() throws Exception { |
||||
super.setUp(); |
||||
tr = new TestRepository<Repository>(db); |
||||
reader = db.newObjectReader(); |
||||
inserter = db.newObjectInserter(); |
||||
|
||||
noRoot = NoteMap.newMap(null, reader); |
||||
empty = NoteMap.newEmptyMap(); |
||||
|
||||
noteAId = tr.blob("a"); |
||||
noteAContent = "noteAContent"; |
||||
noteABlob = tr.blob(noteAContent); |
||||
sampleTree_a = tr.commit() |
||||
.add(noteAId.name(), noteABlob) |
||||
.create(); |
||||
tr.parseBody(sampleTree_a); |
||||
map_a = NoteMap.read(reader, sampleTree_a); |
||||
|
||||
noteBId = tr.blob("b"); |
||||
noteBContent = "noteBContent"; |
||||
noteBBlob = tr.blob(noteBContent); |
||||
sampleTree_a_b = tr.commit() |
||||
.add(noteAId.name(), noteABlob) |
||||
.add(noteBId.name(), noteBBlob) |
||||
.create(); |
||||
tr.parseBody(sampleTree_a_b); |
||||
map_a_b = NoteMap.read(reader, sampleTree_a_b); |
||||
} |
||||
|
||||
@Override |
||||
@After |
||||
public void tearDown() throws Exception { |
||||
reader.release(); |
||||
inserter.release(); |
||||
super.tearDown(); |
||||
} |
||||
|
||||
@Test |
||||
public void testNoChange() throws IOException { |
||||
NoteMapMerger merger = new NoteMapMerger(db, null, null); |
||||
NoteMap result; |
||||
|
||||
assertEquals(0, countNotes(merger.merge(noRoot, noRoot, noRoot))); |
||||
assertEquals(0, countNotes(merger.merge(empty, empty, empty))); |
||||
|
||||
result = merger.merge(map_a, map_a, map_a); |
||||
assertEquals(1, countNotes(result)); |
||||
assertEquals(noteABlob, result.get(noteAId)); |
||||
} |
||||
|
||||
@Test |
||||
public void testOursEqualsTheirs() throws Exception { |
||||
NoteMapMerger merger = new NoteMapMerger(db, null, null); |
||||
NoteMap result; |
||||
|
||||
assertEquals(0, countNotes(merger.merge(empty, noRoot, noRoot))); |
||||
assertEquals(0, countNotes(merger.merge(map_a, noRoot, noRoot))); |
||||
|
||||
assertEquals(0, countNotes(merger.merge(noRoot, empty, empty))); |
||||
assertEquals(0, countNotes(merger.merge(map_a, empty, empty))); |
||||
|
||||
result = merger.merge(noRoot, map_a, map_a); |
||||
assertEquals(1, countNotes(result)); |
||||
assertEquals(noteABlob, result.get(noteAId)); |
||||
|
||||
result = merger.merge(empty, map_a, map_a); |
||||
assertEquals(1, countNotes(result)); |
||||
assertEquals(noteABlob, result.get(noteAId)); |
||||
|
||||
result = merger.merge(map_a_b, map_a, map_a); |
||||
assertEquals(1, countNotes(result)); |
||||
assertEquals(noteABlob, result.get(noteAId)); |
||||
|
||||
result = merger.merge(map_a, map_a_b, map_a_b); |
||||
assertEquals(2, countNotes(result)); |
||||
assertEquals(noteABlob, result.get(noteAId)); |
||||
assertEquals(noteBBlob, result.get(noteBId)); |
||||
} |
||||
|
||||
@Test |
||||
public void testBaseEqualsOurs() throws Exception { |
||||
NoteMapMerger merger = new NoteMapMerger(db, null, null); |
||||
NoteMap result; |
||||
|
||||
assertEquals(0, countNotes(merger.merge(noRoot, noRoot, empty))); |
||||
result = merger.merge(noRoot, noRoot, map_a); |
||||
assertEquals(1, countNotes(result)); |
||||
assertEquals(noteABlob, result.get(noteAId)); |
||||
|
||||
assertEquals(0, countNotes(merger.merge(empty, empty, noRoot))); |
||||
result = merger.merge(empty, empty, map_a); |
||||
assertEquals(1, countNotes(result)); |
||||
assertEquals(noteABlob, result.get(noteAId)); |
||||
|
||||
assertEquals(0, countNotes(merger.merge(map_a, map_a, noRoot))); |
||||
assertEquals(0, countNotes(merger.merge(map_a, map_a, empty))); |
||||
result = merger.merge(map_a, map_a, map_a_b); |
||||
assertEquals(2, countNotes(result)); |
||||
assertEquals(noteABlob, result.get(noteAId)); |
||||
assertEquals(noteBBlob, result.get(noteBId)); |
||||
} |
||||
|
||||
@Test |
||||
public void testBaseEqualsTheirs() throws Exception { |
||||
NoteMapMerger merger = new NoteMapMerger(db, null, null); |
||||
NoteMap result; |
||||
|
||||
assertEquals(0, countNotes(merger.merge(noRoot, empty, noRoot))); |
||||
result = merger.merge(noRoot, map_a, noRoot); |
||||
assertEquals(1, countNotes(result)); |
||||
assertEquals(noteABlob, result.get(noteAId)); |
||||
|
||||
assertEquals(0, countNotes(merger.merge(empty, noRoot, empty))); |
||||
result = merger.merge(empty, map_a, empty); |
||||
assertEquals(1, countNotes(result)); |
||||
assertEquals(noteABlob, result.get(noteAId)); |
||||
|
||||
assertEquals(0, countNotes(merger.merge(map_a, noRoot, map_a))); |
||||
assertEquals(0, countNotes(merger.merge(map_a, empty, map_a))); |
||||
result = merger.merge(map_a, map_a_b, map_a); |
||||
assertEquals(2, countNotes(result)); |
||||
assertEquals(noteABlob, result.get(noteAId)); |
||||
assertEquals(noteBBlob, result.get(noteBId)); |
||||
} |
||||
|
||||
@Test |
||||
public void testAddDifferentNotes() throws Exception { |
||||
NoteMapMerger merger = new NoteMapMerger(db, null, null); |
||||
NoteMap result; |
||||
|
||||
NoteMap map_a_c = NoteMap.read(reader, sampleTree_a); |
||||
RevBlob noteCId = tr.blob("c"); |
||||
RevBlob noteCBlob = tr.blob("noteCContent"); |
||||
map_a_c.set(noteCId, noteCBlob); |
||||
map_a_c.writeTree(inserter); |
||||
|
||||
result = merger.merge(map_a, map_a_b, map_a_c); |
||||
assertEquals(3, countNotes(result)); |
||||
assertEquals(noteABlob, result.get(noteAId)); |
||||
assertEquals(noteBBlob, result.get(noteBId)); |
||||
assertEquals(noteCBlob, result.get(noteCId)); |
||||
} |
||||
|
||||
@Test |
||||
public void testAddSameNoteDifferentContent() throws Exception { |
||||
NoteMapMerger merger = new NoteMapMerger(db, new DefaultNoteMerger(), |
||||
null); |
||||
NoteMap result; |
||||
|
||||
NoteMap map_a_b1 = NoteMap.read(reader, sampleTree_a); |
||||
String noteBContent1 = noteBContent + "change"; |
||||
RevBlob noteBBlob1 = tr.blob(noteBContent1); |
||||
map_a_b1.set(noteBId, noteBBlob1); |
||||
map_a_b1.writeTree(inserter); |
||||
|
||||
result = merger.merge(map_a, map_a_b, map_a_b1); |
||||
assertEquals(2, countNotes(result)); |
||||
assertEquals(noteABlob, result.get(noteAId)); |
||||
assertEquals(tr.blob(noteBContent + noteBContent1), result.get(noteBId)); |
||||
} |
||||
|
||||
@Test |
||||
public void testEditSameNoteDifferentContent() throws Exception { |
||||
NoteMapMerger merger = new NoteMapMerger(db, new DefaultNoteMerger(), |
||||
null); |
||||
NoteMap result; |
||||
|
||||
NoteMap map_a1 = NoteMap.read(reader, sampleTree_a); |
||||
String noteAContent1 = noteAContent + "change1"; |
||||
RevBlob noteABlob1 = tr.blob(noteAContent1); |
||||
map_a1.set(noteAId, noteABlob1); |
||||
map_a1.writeTree(inserter); |
||||
|
||||
NoteMap map_a2 = NoteMap.read(reader, sampleTree_a); |
||||
String noteAContent2 = noteAContent + "change2"; |
||||
RevBlob noteABlob2 = tr.blob(noteAContent2); |
||||
map_a2.set(noteAId, noteABlob2); |
||||
map_a2.writeTree(inserter); |
||||
|
||||
result = merger.merge(map_a, map_a1, map_a2); |
||||
assertEquals(1, countNotes(result)); |
||||
assertEquals(tr.blob(noteAContent1 + noteAContent2), |
||||
result.get(noteAId)); |
||||
} |
||||
|
||||
@Test |
||||
public void testEditDifferentNotes() throws Exception { |
||||
NoteMapMerger merger = new NoteMapMerger(db, null, null); |
||||
NoteMap result; |
||||
|
||||
NoteMap map_a1_b = NoteMap.read(reader, sampleTree_a_b); |
||||
String noteAContent1 = noteAContent + "change"; |
||||
RevBlob noteABlob1 = tr.blob(noteAContent1); |
||||
map_a1_b.set(noteAId, noteABlob1); |
||||
map_a1_b.writeTree(inserter); |
||||
|
||||
NoteMap map_a_b1 = NoteMap.read(reader, sampleTree_a_b); |
||||
String noteBContent1 = noteBContent + "change"; |
||||
RevBlob noteBBlob1 = tr.blob(noteBContent1); |
||||
map_a_b1.set(noteBId, noteBBlob1); |
||||
map_a_b1.writeTree(inserter); |
||||
|
||||
result = merger.merge(map_a_b, map_a1_b, map_a_b1); |
||||
assertEquals(2, countNotes(result)); |
||||
assertEquals(noteABlob1, result.get(noteAId)); |
||||
assertEquals(noteBBlob1, result.get(noteBId)); |
||||
} |
||||
|
||||
@Test |
||||
public void testDeleteDifferentNotes() throws Exception { |
||||
NoteMapMerger merger = new NoteMapMerger(db, null, null); |
||||
|
||||
NoteMap map_b = NoteMap.read(reader, sampleTree_a_b); |
||||
map_b.set(noteAId, null); // delete note a
|
||||
map_b.writeTree(inserter); |
||||
|
||||
assertEquals(0, countNotes(merger.merge(map_a_b, map_a, map_b))); |
||||
} |
||||
|
||||
@Test |
||||
public void testEditDeleteConflict() throws Exception { |
||||
NoteMapMerger merger = new NoteMapMerger(db, new DefaultNoteMerger(), |
||||
null); |
||||
NoteMap result; |
||||
|
||||
NoteMap map_a_b1 = NoteMap.read(reader, sampleTree_a_b); |
||||
String noteBContent1 = noteBContent + "change"; |
||||
RevBlob noteBBlob1 = tr.blob(noteBContent1); |
||||
map_a_b1.set(noteBId, noteBBlob1); |
||||
map_a_b1.writeTree(inserter); |
||||
|
||||
result = merger.merge(map_a_b, map_a_b1, map_a); |
||||
assertEquals(2, countNotes(result)); |
||||
assertEquals(noteABlob, result.get(noteAId)); |
||||
assertEquals(noteBBlob1, result.get(noteBId)); |
||||
} |
||||
|
||||
@Test |
||||
public void testLargeTreesWithoutConflict() throws Exception { |
||||
NoteMapMerger merger = new NoteMapMerger(db, null, null); |
||||
NoteMap map1 = createLargeNoteMap("note_1_", "content_1_", 300, 0); |
||||
NoteMap map2 = createLargeNoteMap("note_2_", "content_2_", 300, 0); |
||||
|
||||
NoteMap result = merger.merge(empty, map1, map2); |
||||
assertEquals(600, countNotes(result)); |
||||
// check a few random notes
|
||||
assertEquals(tr.blob("content_1_59"), result.get(tr.blob("note_1_59"))); |
||||
assertEquals(tr.blob("content_2_10"), result.get(tr.blob("note_2_10"))); |
||||
assertEquals(tr.blob("content_2_99"), result.get(tr.blob("note_2_99"))); |
||||
} |
||||
|
||||
@Test |
||||
public void testLargeTreesWithConflict() throws Exception { |
||||
NoteMapMerger merger = new NoteMapMerger(db, new DefaultNoteMerger(), |
||||
null); |
||||
NoteMap largeTree1 = createLargeNoteMap("note_1_", "content_1_", 300, 0); |
||||
NoteMap largeTree2 = createLargeNoteMap("note_1_", "content_2_", 300, 0); |
||||
|
||||
NoteMap result = merger.merge(empty, largeTree1, largeTree2); |
||||
assertEquals(300, countNotes(result)); |
||||
// check a few random notes
|
||||
assertEquals(tr.blob("content_1_59content_2_59"), |
||||
result.get(tr.blob("note_1_59"))); |
||||
assertEquals(tr.blob("content_1_10content_2_10"), |
||||
result.get(tr.blob("note_1_10"))); |
||||
assertEquals(tr.blob("content_1_99content_2_99"), |
||||
result.get(tr.blob("note_1_99"))); |
||||
} |
||||
|
||||
private NoteMap createLargeNoteMap(String noteNamePrefix, |
||||
String noteContentPrefix, int notesCount, int firstIndex) |
||||
throws Exception { |
||||
NoteMap result = NoteMap.newEmptyMap(); |
||||
for (int i = 0; i < notesCount; i++) { |
||||
result.set(tr.blob(noteNamePrefix + (firstIndex + i)), |
||||
tr.blob(noteContentPrefix + (firstIndex + i))); |
||||
} |
||||
result.writeTree(inserter); |
||||
return result; |
||||
} |
||||
|
||||
@Test |
||||
public void testFanoutAndLeafWithoutConflict() throws Exception { |
||||
NoteMapMerger merger = new NoteMapMerger(db, null, null); |
||||
|
||||
NoteMap largeTree = createLargeNoteMap("note_1_", "content_1_", 300, 0); |
||||
NoteMap result = merger.merge(map_a, map_a_b, largeTree); |
||||
assertEquals(301, countNotes(result)); |
||||
} |
||||
|
||||
@Test |
||||
public void testFanoutAndLeafWitConflict() throws Exception { |
||||
NoteMapMerger merger = new NoteMapMerger(db, new DefaultNoteMerger(), |
||||
null); |
||||
|
||||
NoteMap largeTree_b1 = createLargeNoteMap("note_1_", "content_1_", 300, |
||||
0); |
||||
String noteBContent1 = noteBContent + "change"; |
||||
largeTree_b1.set(noteBId, tr.blob(noteBContent1)); |
||||
largeTree_b1.writeTree(inserter); |
||||
|
||||
NoteMap result = merger.merge(map_a, map_a_b, largeTree_b1); |
||||
assertEquals(301, countNotes(result)); |
||||
assertEquals(tr.blob(noteBContent + noteBContent1), result.get(noteBId)); |
||||
} |
||||
|
||||
@Test |
||||
public void testCollapseFanoutAfterMerge() throws Exception { |
||||
NoteMapMerger merger = new NoteMapMerger(db, null, null); |
||||
|
||||
NoteMap largeTree = createLargeNoteMap("note_", "content_", 257, 0); |
||||
assertTrue(largeTree.getRoot() instanceof FanoutBucket); |
||||
NoteMap deleteFirstHundredNotes = createLargeNoteMap("note_", "content_", 157, |
||||
100); |
||||
NoteMap deleteLastHundredNotes = createLargeNoteMap("note_", |
||||
"content_", 157, 0); |
||||
NoteMap result = merger.merge(largeTree, deleteFirstHundredNotes, |
||||
deleteLastHundredNotes); |
||||
assertEquals(57, countNotes(result)); |
||||
assertTrue(result.getRoot() instanceof LeafBucket); |
||||
} |
||||
|
||||
@Test |
||||
public void testNonNotesWithoutNonNoteConflict() throws Exception { |
||||
NoteMapMerger merger = new NoteMapMerger(db, null, |
||||
MergeStrategy.RESOLVE); |
||||
RevCommit treeWithNonNotes = |
||||
tr.commit() |
||||
.add(noteAId.name(), noteABlob) // this is a note
|
||||
.add("a.txt", tr.blob("content of a.txt")) // this is a non-note
|
||||
.create(); |
||||
tr.parseBody(treeWithNonNotes); |
||||
NoteMap base = NoteMap.read(reader, treeWithNonNotes); |
||||
|
||||
treeWithNonNotes = |
||||
tr.commit() |
||||
.add(noteAId.name(), noteABlob) |
||||
.add("a.txt", tr.blob("content of a.txt")) |
||||
.add("b.txt", tr.blob("content of b.txt")) |
||||
.create(); |
||||
tr.parseBody(treeWithNonNotes); |
||||
NoteMap ours = NoteMap.read(reader, treeWithNonNotes); |
||||
|
||||
treeWithNonNotes = |
||||
tr.commit() |
||||
.add(noteAId.name(), noteABlob) |
||||
.add("a.txt", tr.blob("content of a.txt")) |
||||
.add("c.txt", tr.blob("content of c.txt")) |
||||
.create(); |
||||
tr.parseBody(treeWithNonNotes); |
||||
NoteMap theirs = NoteMap.read(reader, treeWithNonNotes); |
||||
|
||||
NoteMap result = merger.merge(base, ours, theirs); |
||||
assertEquals(3, countNonNotes(result)); |
||||
} |
||||
|
||||
@Test |
||||
public void testNonNotesWithNonNoteConflict() throws Exception { |
||||
NoteMapMerger merger = new NoteMapMerger(db, null, |
||||
MergeStrategy.RESOLVE); |
||||
RevCommit treeWithNonNotes = |
||||
tr.commit() |
||||
.add(noteAId.name(), noteABlob) // this is a note
|
||||
.add("a.txt", tr.blob("content of a.txt")) // this is a non-note
|
||||
.create(); |
||||
tr.parseBody(treeWithNonNotes); |
||||
NoteMap base = NoteMap.read(reader, treeWithNonNotes); |
||||
|
||||
treeWithNonNotes = |
||||
tr.commit() |
||||
.add(noteAId.name(), noteABlob) |
||||
.add("a.txt", tr.blob("change 1")) |
||||
.create(); |
||||
tr.parseBody(treeWithNonNotes); |
||||
NoteMap ours = NoteMap.read(reader, treeWithNonNotes); |
||||
|
||||
treeWithNonNotes = |
||||
tr.commit() |
||||
.add(noteAId.name(), noteABlob) |
||||
.add("a.txt", tr.blob("change 2")) |
||||
.create(); |
||||
tr.parseBody(treeWithNonNotes); |
||||
NoteMap theirs = NoteMap.read(reader, treeWithNonNotes); |
||||
|
||||
try { |
||||
merger.merge(base, ours, theirs); |
||||
fail("NotesMergeConflictException was expected"); |
||||
} catch (NotesMergeConflictException e) { |
||||
// expected
|
||||
} |
||||
} |
||||
|
||||
private static int countNotes(NoteMap map) { |
||||
int c = 0; |
||||
Iterator<Note> it = map.iterator(); |
||||
while (it.hasNext()) { |
||||
it.next(); |
||||
c++; |
||||
} |
||||
return c; |
||||
} |
||||
|
||||
private static int countNonNotes(NoteMap map) { |
||||
int c = 0; |
||||
NonNoteEntry nonNotes = map.getRoot().nonNotes; |
||||
while (nonNotes != null) { |
||||
c++; |
||||
nonNotes = nonNotes.next; |
||||
} |
||||
return c; |
||||
} |
||||
} |
@ -0,0 +1,89 @@
|
||||
/* |
||||
* Copyright (C) 2010, Sasa Zivkov <sasa.zivkov@sap.com> |
||||
* and other copyright owners as documented in the project's IP log. |
||||
* |
||||
* This program and the accompanying materials are made available |
||||
* under the terms of the Eclipse Distribution License v1.0 which |
||||
* accompanies this distribution, is reproduced below, and is |
||||
* available at http://www.eclipse.org/org/documents/edl-v10.php
|
||||
* |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or |
||||
* without modification, are permitted provided that the following |
||||
* conditions are met: |
||||
* |
||||
* - Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* |
||||
* - Redistributions in binary form must reproduce the above |
||||
* copyright notice, this list of conditions and the following |
||||
* disclaimer in the documentation and/or other materials provided |
||||
* with the distribution. |
||||
* |
||||
* - Neither the name of the Eclipse Foundation, Inc. nor the |
||||
* names of its contributors may be used to endorse or promote |
||||
* products derived from this software without specific prior |
||||
* written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
||||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
package org.eclipse.jgit.notes; |
||||
|
||||
import java.io.IOException; |
||||
|
||||
import org.eclipse.jgit.lib.Constants; |
||||
import org.eclipse.jgit.lib.ObjectId; |
||||
import org.eclipse.jgit.lib.ObjectInserter; |
||||
import org.eclipse.jgit.lib.ObjectLoader; |
||||
import org.eclipse.jgit.lib.ObjectReader; |
||||
import org.eclipse.jgit.util.io.UnionInputStream; |
||||
|
||||
/** |
||||
* Default implementation of the {@link NoteMerger}. |
||||
* <p> |
||||
* If ours and theirs are both non-null, which means they are either both edits |
||||
* or both adds, then this merger will simply join the content of ours and |
||||
* theirs (in that order) and return that as the merge result. |
||||
* <p> |
||||
* If one or ours/theirs is non-null and the other one is null then the non-null |
||||
* value is returned as the merge result. This means that an edit/delete |
||||
* conflict is resolved by keeping the edit version. |
||||
* <p> |
||||
* If both ours and theirs are null then the result of the merge is also null. |
||||
*/ |
||||
public class DefaultNoteMerger implements NoteMerger { |
||||
|
||||
public Note merge(Note base, Note ours, Note theirs, ObjectReader reader, |
||||
ObjectInserter inserter) throws IOException { |
||||
if (ours == null) |
||||
return theirs; |
||||
|
||||
if (theirs == null) |
||||
return ours; |
||||
|
||||
if (ours.getData().equals(theirs.getData())) |
||||
return ours; |
||||
|
||||
ObjectLoader lo = reader.open(ours.getData()); |
||||
ObjectLoader lt = reader.open(theirs.getData()); |
||||
UnionInputStream union = new UnionInputStream(lo.openStream(), |
||||
lt.openStream()); |
||||
ObjectId noteData = inserter.insert(Constants.OBJ_BLOB, |
||||
lo.getSize() + lt.getSize(), union); |
||||
return new Note(ours, noteData); |
||||
} |
||||
} |
@ -0,0 +1,353 @@
|
||||
/* |
||||
* Copyright (C) 2010, Sasa Zivkov <sasa.zivkov@sap.com> |
||||
* and other copyright owners as documented in the project's IP log. |
||||
* |
||||
* This program and the accompanying materials are made available |
||||
* under the terms of the Eclipse Distribution License v1.0 which |
||||
* accompanies this distribution, is reproduced below, and is |
||||
* available at http://www.eclipse.org/org/documents/edl-v10.php
|
||||
* |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or |
||||
* without modification, are permitted provided that the following |
||||
* conditions are met: |
||||
* |
||||
* - Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* |
||||
* - Redistributions in binary form must reproduce the above |
||||
* copyright notice, this list of conditions and the following |
||||
* disclaimer in the documentation and/or other materials provided |
||||
* with the distribution. |
||||
* |
||||
* - Neither the name of the Eclipse Foundation, Inc. nor the |
||||
* names of its contributors may be used to endorse or promote |
||||
* products derived from this software without specific prior |
||||
* written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
||||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
package org.eclipse.jgit.notes; |
||||
|
||||
import java.io.IOException; |
||||
|
||||
import org.eclipse.jgit.errors.MissingObjectException; |
||||
import org.eclipse.jgit.lib.AbbreviatedObjectId; |
||||
import org.eclipse.jgit.lib.AnyObjectId; |
||||
import org.eclipse.jgit.lib.MutableObjectId; |
||||
import org.eclipse.jgit.lib.ObjectId; |
||||
import org.eclipse.jgit.lib.ObjectInserter; |
||||
import org.eclipse.jgit.lib.ObjectReader; |
||||
import org.eclipse.jgit.lib.Repository; |
||||
import org.eclipse.jgit.merge.MergeStrategy; |
||||
import org.eclipse.jgit.merge.Merger; |
||||
import org.eclipse.jgit.merge.ThreeWayMergeStrategy; |
||||
import org.eclipse.jgit.merge.ThreeWayMerger; |
||||
import org.eclipse.jgit.treewalk.AbstractTreeIterator; |
||||
import org.eclipse.jgit.treewalk.TreeWalk; |
||||
|
||||
/** |
||||
* Three-way note tree merge. |
||||
* <p> |
||||
* Direct implementation of NoteMap merger without using {@link TreeWalk} and |
||||
* {@link AbstractTreeIterator} |
||||
*/ |
||||
public class NoteMapMerger { |
||||
private static final FanoutBucket EMPTY_FANOUT = new FanoutBucket(0); |
||||
|
||||
private static final LeafBucket EMPTY_LEAF = new LeafBucket(0); |
||||
|
||||
private final Repository db; |
||||
|
||||
private final NoteMerger noteMerger; |
||||
|
||||
private final MergeStrategy nonNotesMergeStrategy; |
||||
|
||||
private final ObjectReader reader; |
||||
|
||||
private final ObjectInserter inserter; |
||||
|
||||
private final MutableObjectId objectIdPrefix; |
||||
|
||||
/** |
||||
* Constructs a NoteMapMerger with custom {@link NoteMerger} and custom |
||||
* {@link MergeStrategy}. |
||||
* |
||||
* @param db |
||||
* Git repository |
||||
* @param noteMerger |
||||
* note merger for merging conflicting changes on a note |
||||
* @param nonNotesMergeStrategy |
||||
* merge strategy for merging non-note entries |
||||
*/ |
||||
public NoteMapMerger(Repository db, NoteMerger noteMerger, |
||||
MergeStrategy nonNotesMergeStrategy) { |
||||
this.db = db; |
||||
this.reader = db.newObjectReader(); |
||||
this.inserter = db.newObjectInserter(); |
||||
this.noteMerger = noteMerger; |
||||
this.nonNotesMergeStrategy = nonNotesMergeStrategy; |
||||
this.objectIdPrefix = new MutableObjectId(); |
||||
} |
||||
|
||||
/** |
||||
* Constructs a NoteMapMerger with {@link DefaultNoteMerger} as the merger |
||||
* for notes and the {@link MergeStrategy#RESOLVE} as the strategy for |
||||
* resolving conflicts on non-notes |
||||
* |
||||
* @param db |
||||
* Git repository |
||||
*/ |
||||
public NoteMapMerger(Repository db) { |
||||
this(db, new DefaultNoteMerger(), MergeStrategy.RESOLVE); |
||||
} |
||||
|
||||
/** |
||||
* Performs the merge. |
||||
* |
||||
* @param base |
||||
* base version of the note tree |
||||
* @param ours |
||||
* ours version of the note tree |
||||
* @param theirs |
||||
* theirs version of the note tree |
||||
* @return merge result as a new NoteMap |
||||
* @throws IOException |
||||
*/ |
||||
public NoteMap merge(NoteMap base, NoteMap ours, NoteMap theirs) |
||||
throws IOException { |
||||
try { |
||||
InMemoryNoteBucket mergedBucket = merge(0, base.getRoot(), |
||||
ours.getRoot(), theirs.getRoot()); |
||||
inserter.flush(); |
||||
return NoteMap.newMap(mergedBucket, reader); |
||||
} finally { |
||||
reader.release(); |
||||
inserter.release(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* This method is called only when it is known that there is some difference |
||||
* between base, ours and theirs. |
||||
* |
||||
* @param treeDepth |
||||
* @param base |
||||
* @param ours |
||||
* @param theirs |
||||
* @return merge result as an InMemoryBucket |
||||
* @throws IOException |
||||
*/ |
||||
private InMemoryNoteBucket merge(int treeDepth, InMemoryNoteBucket base, |
||||
InMemoryNoteBucket ours, InMemoryNoteBucket theirs) |
||||
throws IOException { |
||||
InMemoryNoteBucket result; |
||||
|
||||
if (base instanceof FanoutBucket || ours instanceof FanoutBucket |
||||
|| theirs instanceof FanoutBucket) { |
||||
result = mergeFanoutBucket(treeDepth, asFanout(base), |
||||
asFanout(ours), asFanout(theirs)); |
||||
|
||||
} else { |
||||
result = mergeLeafBucket(treeDepth, (LeafBucket) base, |
||||
(LeafBucket) ours, (LeafBucket) theirs); |
||||
} |
||||
|
||||
result.nonNotes = mergeNonNotes(nonNotes(base), nonNotes(ours), |
||||
nonNotes(theirs)); |
||||
return result; |
||||
} |
||||
|
||||
private FanoutBucket asFanout(InMemoryNoteBucket bucket) { |
||||
if (bucket == null) |
||||
return EMPTY_FANOUT; |
||||
if (bucket instanceof FanoutBucket) |
||||
return (FanoutBucket) bucket; |
||||
return ((LeafBucket) bucket).split(); |
||||
} |
||||
|
||||
private static NonNoteEntry nonNotes(InMemoryNoteBucket b) { |
||||
return b == null ? null : b.nonNotes; |
||||
} |
||||
|
||||
private InMemoryNoteBucket mergeFanoutBucket(int treeDepth, |
||||
FanoutBucket base, |
||||
FanoutBucket ours, FanoutBucket theirs) throws IOException { |
||||
FanoutBucket result = new FanoutBucket(treeDepth * 2); |
||||
// walking through entries of base, ours, theirs
|
||||
for (int i = 0; i < 256; i++) { |
||||
NoteBucket b = base.getBucket(i); |
||||
NoteBucket o = ours.getBucket(i); |
||||
NoteBucket t = theirs.getBucket(i); |
||||
|
||||
if (equals(o, t)) |
||||
addIfNotNull(result, i, o); |
||||
|
||||
else if (equals(b, o)) |
||||
addIfNotNull(result, i, t); |
||||
|
||||
else if (equals(b, t)) |
||||
addIfNotNull(result, i, o); |
||||
|
||||
else { |
||||
objectIdPrefix.setByte(treeDepth, i); |
||||
InMemoryNoteBucket mergedBucket = merge(treeDepth + 1, |
||||
FanoutBucket.loadIfLazy(b, objectIdPrefix, reader), |
||||
FanoutBucket.loadIfLazy(o, objectIdPrefix, reader), |
||||
FanoutBucket.loadIfLazy(t, objectIdPrefix, reader)); |
||||
result.setBucket(i, mergedBucket); |
||||
} |
||||
} |
||||
return result.contractIfTooSmall(objectIdPrefix, reader); |
||||
} |
||||
|
||||
private static boolean equals(NoteBucket a, NoteBucket b) { |
||||
if (a == null && b == null) |
||||
return true; |
||||
return a != null && b != null && a.getTreeId().equals(b.getTreeId()); |
||||
} |
||||
|
||||
private void addIfNotNull(FanoutBucket b, int cell, NoteBucket child) |
||||
throws IOException { |
||||
if (child == null) |
||||
return; |
||||
if (child instanceof InMemoryNoteBucket) |
||||
b.setBucket(cell, ((InMemoryNoteBucket) child).writeTree(inserter)); |
||||
else |
||||
b.setBucket(cell, child.getTreeId()); |
||||
} |
||||
|
||||
private InMemoryNoteBucket mergeLeafBucket(int treeDepth, LeafBucket bb, |
||||
LeafBucket ob, LeafBucket tb) throws MissingObjectException, |
||||
IOException { |
||||
bb = notNullOrEmpty(bb); |
||||
ob = notNullOrEmpty(ob); |
||||
tb = notNullOrEmpty(tb); |
||||
|
||||
InMemoryNoteBucket result = new LeafBucket(treeDepth * 2); |
||||
int bi = 0, oi = 0, ti = 0; |
||||
while (bi < bb.size() || oi < ob.size() || ti < tb.size()) { |
||||
Note b = get(bb, bi), o = get(ob, oi), t = get(tb, ti); |
||||
Note min = min(b, o, t); |
||||
|
||||
b = sameNoteOrNull(min, b); |
||||
o = sameNoteOrNull(min, o); |
||||
t = sameNoteOrNull(min, t); |
||||
|
||||
if (sameContent(o, t)) |
||||
result = addIfNotNull(result, o); |
||||
|
||||
else if (sameContent(b, o)) |
||||
result = addIfNotNull(result, t); |
||||
|
||||
else if (sameContent(b, t)) |
||||
result = addIfNotNull(result, o); |
||||
|
||||
else |
||||
result = addIfNotNull(result, |
||||
noteMerger.merge(b, o, t, reader, inserter)); |
||||
|
||||
if (b != null) |
||||
bi++; |
||||
if (o != null) |
||||
oi++; |
||||
if (t != null) |
||||
ti++; |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
private static LeafBucket notNullOrEmpty(LeafBucket b) { |
||||
return b != null ? b : EMPTY_LEAF; |
||||
} |
||||
|
||||
private static Note get(LeafBucket b, int i) { |
||||
return i < b.size() ? b.get(i) : null; |
||||
} |
||||
|
||||
private static Note min(Note b, Note o, Note t) { |
||||
Note min = b; |
||||
if (min == null || (o != null && o.compareTo(min) < 0)) |
||||
min = o; |
||||
if (min == null || (t != null && t.compareTo(min) < 0)) |
||||
min = t; |
||||
return min; |
||||
} |
||||
|
||||
private static Note sameNoteOrNull(Note min, Note other) { |
||||
return sameNote(min, other) ? other : null; |
||||
} |
||||
|
||||
private static boolean sameNote(Note a, Note b) { |
||||
if (a == null && b == null) |
||||
return true; |
||||
return a != null && b != null && AnyObjectId.equals(a, b); |
||||
} |
||||
|
||||
private static boolean sameContent(Note a, Note b) { |
||||
if (a == null && b == null) |
||||
return true; |
||||
return a != null && b != null |
||||
&& AnyObjectId.equals(a.getData(), b.getData()); |
||||
} |
||||
|
||||
private static InMemoryNoteBucket addIfNotNull(InMemoryNoteBucket result, |
||||
Note note) { |
||||
if (note != null) |
||||
return result.append(note); |
||||
else |
||||
return result; |
||||
} |
||||
|
||||
private NonNoteEntry mergeNonNotes(NonNoteEntry baseList, |
||||
NonNoteEntry oursList, NonNoteEntry theirsList) throws IOException { |
||||
if (baseList == null && oursList == null && theirsList == null) |
||||
return null; |
||||
|
||||
ObjectId baseId = write(baseList); |
||||
ObjectId oursId = write(oursList); |
||||
ObjectId theirsId = write(theirsList); |
||||
inserter.flush(); |
||||
|
||||
ObjectId resultTreeId; |
||||
if (nonNotesMergeStrategy instanceof ThreeWayMergeStrategy) { |
||||
ThreeWayMerger m = ((ThreeWayMergeStrategy) nonNotesMergeStrategy) |
||||
.newMerger(db, true); |
||||
m.setBase(baseId); |
||||
if (!m.merge(oursId, theirsId)) |
||||
throw new NotesMergeConflictException(baseList, oursList, |
||||
theirsList); |
||||
|
||||
resultTreeId = m.getResultTreeId(); |
||||
} else { |
||||
Merger m = nonNotesMergeStrategy.newMerger(db, true); |
||||
if (!m.merge(new AnyObjectId[] { oursId, theirsId })) |
||||
throw new NotesMergeConflictException(baseList, oursList, |
||||
theirsList); |
||||
resultTreeId = m.getResultTreeId(); |
||||
} |
||||
AbbreviatedObjectId none = AbbreviatedObjectId.fromString(""); |
||||
return NoteParser.parse(none, resultTreeId, reader).nonNotes; |
||||
} |
||||
|
||||
private ObjectId write(NonNoteEntry list) |
||||
throws IOException { |
||||
LeafBucket b = new LeafBucket(0); |
||||
b.nonNotes = list; |
||||
return b.writeTree(inserter); |
||||
} |
||||
} |
@ -0,0 +1,87 @@
|
||||
/* |
||||
* Copyright (C) 2010, Sasa Zivkov <sasa.zivkov@sap.com> |
||||
* and other copyright owners as documented in the project's IP log. |
||||
* |
||||
* This program and the accompanying materials are made available |
||||
* under the terms of the Eclipse Distribution License v1.0 which |
||||
* accompanies this distribution, is reproduced below, and is |
||||
* available at http://www.eclipse.org/org/documents/edl-v10.php
|
||||
* |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or |
||||
* without modification, are permitted provided that the following |
||||
* conditions are met: |
||||
* |
||||
* - Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* |
||||
* - Redistributions in binary form must reproduce the above |
||||
* copyright notice, this list of conditions and the following |
||||
* disclaimer in the documentation and/or other materials provided |
||||
* with the distribution. |
||||
* |
||||
* - Neither the name of the Eclipse Foundation, Inc. nor the |
||||
* names of its contributors may be used to endorse or promote |
||||
* products derived from this software without specific prior |
||||
* written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
||||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
package org.eclipse.jgit.notes; |
||||
|
||||
import java.io.IOException; |
||||
|
||||
import org.eclipse.jgit.lib.ObjectInserter; |
||||
import org.eclipse.jgit.lib.ObjectReader; |
||||
|
||||
/** |
||||
* Three-way note merge operation. |
||||
* <p> |
||||
* This operation takes three versions of a note: base, ours and theirs, |
||||
* performs the three-way merge and returns the merge result. |
||||
*/ |
||||
public interface NoteMerger { |
||||
|
||||
/** |
||||
* Merges the conflicting note changes. |
||||
* <p> |
||||
* base, ours and their are all notes on the same object. |
||||
* |
||||
* @param base |
||||
* version of the Note |
||||
* @param ours |
||||
* version of the Note |
||||
* @param their |
||||
* version of the Note |
||||
* @param reader |
||||
* the object reader that must be used to read Git objects |
||||
* @param inserter |
||||
* the object inserter that must be used to insert Git objects |
||||
* @return the merge result |
||||
* @throws NotesMergeConflictException |
||||
* in case there was a merge conflict which this note merger |
||||
* couldn't resolve |
||||
* @throws IOException |
||||
* in case the reader or the inserter would throw an IOException |
||||
* the implementor will most likely want to propagate it as it |
||||
* can't do much to recover from it |
||||
*/ |
||||
Note merge(Note base, Note ours, Note their, ObjectReader reader, |
||||
ObjectInserter inserter) throws NotesMergeConflictException, |
||||
IOException; |
||||
} |
||||
|
@ -0,0 +1,110 @@
|
||||
/* |
||||
* Copyright (C) 2010, Sasa Zivkov <sasa.zivkov@sap.com> |
||||
* and other copyright owners as documented in the project's IP log. |
||||
* |
||||
* This program and the accompanying materials are made available |
||||
* under the terms of the Eclipse Distribution License v1.0 which |
||||
* accompanies this distribution, is reproduced below, and is |
||||
* available at http://www.eclipse.org/org/documents/edl-v10.php
|
||||
* |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or |
||||
* without modification, are permitted provided that the following |
||||
* conditions are met: |
||||
* |
||||
* - Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* |
||||
* - Redistributions in binary form must reproduce the above |
||||
* copyright notice, this list of conditions and the following |
||||
* disclaimer in the documentation and/or other materials provided |
||||
* with the distribution. |
||||
* |
||||
* - Neither the name of the Eclipse Foundation, Inc. nor the |
||||
* names of its contributors may be used to endorse or promote |
||||
* products derived from this software without specific prior |
||||
* written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
||||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
package org.eclipse.jgit.notes; |
||||
|
||||
import java.io.IOException; |
||||
import java.text.MessageFormat; |
||||
|
||||
import org.eclipse.jgit.JGitText; |
||||
|
||||
/** |
||||
* This exception will be thrown from the {@link NoteMerger} when a conflict on |
||||
* Notes content is found during merge. |
||||
*/ |
||||
public class NotesMergeConflictException extends IOException { |
||||
private static final long serialVersionUID = 1L; |
||||
|
||||
/** |
||||
* Construct a NotesMergeConflictException for the specified base, ours and |
||||
* theirs note versions. |
||||
* |
||||
* @param base |
||||
* note version |
||||
* @param ours |
||||
* note version |
||||
* @param theirs |
||||
* note version |
||||
*/ |
||||
public NotesMergeConflictException(Note base, Note ours, Note theirs) { |
||||
super(MessageFormat.format(JGitText.get().mergeConflictOnNotes, |
||||
noteOn(base, ours, theirs), noteData(base), noteData(ours), |
||||
noteData(theirs))); |
||||
} |
||||
|
||||
/** |
||||
* Constructs a NotesMergeConflictException for the specified base, ours and |
||||
* theirs versions of the root note tree. |
||||
* |
||||
* @param base |
||||
* version of the root note tree |
||||
* @param ours |
||||
* version of the root note tree |
||||
* @param theirs |
||||
* version of the root note tree |
||||
*/ |
||||
public NotesMergeConflictException(NonNoteEntry base, NonNoteEntry ours, |
||||
NonNoteEntry theirs) { |
||||
super(MessageFormat.format( |
||||
JGitText.get().mergeConflictOnNonNoteEntries, name(base), |
||||
name(ours), name(theirs))); |
||||
} |
||||
|
||||
private static String noteOn(Note base, Note ours, Note theirs) { |
||||
if (base != null) |
||||
return base.name(); |
||||
if (ours != null) |
||||
return ours.name(); |
||||
return theirs.name(); |
||||
} |
||||
|
||||
private static String noteData(Note n) { |
||||
if (n != null) |
||||
return n.getData().name(); |
||||
return ""; |
||||
} |
||||
|
||||
private static String name(NonNoteEntry e) { |
||||
return e != null ? e.name() : ""; |
||||
} |
||||
} |
Loading…
Reference in new issue