You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

NoteMapMerger.java 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. /*
  2. * Copyright (C) 2010, Sasa Zivkov <sasa.zivkov@sap.com>
  3. * and other copyright owners as documented in the project's IP log.
  4. *
  5. * This program and the accompanying materials are made available
  6. * under the terms of the Eclipse Distribution License v1.0 which
  7. * accompanies this distribution, is reproduced below, and is
  8. * available at http://www.eclipse.org/org/documents/edl-v10.php
  9. *
  10. * All rights reserved.
  11. *
  12. * Redistribution and use in source and binary forms, with or
  13. * without modification, are permitted provided that the following
  14. * conditions are met:
  15. *
  16. * - Redistributions of source code must retain the above copyright
  17. * notice, this list of conditions and the following disclaimer.
  18. *
  19. * - Redistributions in binary form must reproduce the above
  20. * copyright notice, this list of conditions and the following
  21. * disclaimer in the documentation and/or other materials provided
  22. * with the distribution.
  23. *
  24. * - Neither the name of the Eclipse Foundation, Inc. nor the
  25. * names of its contributors may be used to endorse or promote
  26. * products derived from this software without specific prior
  27. * written permission.
  28. *
  29. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  30. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  31. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  32. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  33. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  34. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  35. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  36. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  37. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  38. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  39. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  40. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  41. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  42. */
  43. package org.eclipse.jgit.notes;
  44. import java.io.IOException;
  45. import org.eclipse.jgit.errors.MissingObjectException;
  46. import org.eclipse.jgit.lib.AbbreviatedObjectId;
  47. import org.eclipse.jgit.lib.AnyObjectId;
  48. import org.eclipse.jgit.lib.MutableObjectId;
  49. import org.eclipse.jgit.lib.ObjectId;
  50. import org.eclipse.jgit.lib.ObjectInserter;
  51. import org.eclipse.jgit.lib.ObjectReader;
  52. import org.eclipse.jgit.lib.Repository;
  53. import org.eclipse.jgit.merge.MergeStrategy;
  54. import org.eclipse.jgit.merge.Merger;
  55. import org.eclipse.jgit.merge.ThreeWayMerger;
  56. /**
  57. * Three-way note tree merge.
  58. * <p>
  59. * Direct implementation of NoteMap merger without using
  60. * {@link org.eclipse.jgit.treewalk.TreeWalk} and
  61. * {@link org.eclipse.jgit.treewalk.AbstractTreeIterator}
  62. */
  63. public class NoteMapMerger {
  64. private static final FanoutBucket EMPTY_FANOUT = new FanoutBucket(0);
  65. private static final LeafBucket EMPTY_LEAF = new LeafBucket(0);
  66. private final Repository db;
  67. private final NoteMerger noteMerger;
  68. private final MergeStrategy nonNotesMergeStrategy;
  69. private final ObjectReader reader;
  70. private final ObjectInserter inserter;
  71. private final MutableObjectId objectIdPrefix;
  72. /**
  73. * Constructs a NoteMapMerger with custom
  74. * {@link org.eclipse.jgit.notes.NoteMerger} and custom
  75. * {@link org.eclipse.jgit.merge.MergeStrategy}.
  76. *
  77. * @param db
  78. * Git repository
  79. * @param noteMerger
  80. * note merger for merging conflicting changes on a note
  81. * @param nonNotesMergeStrategy
  82. * merge strategy for merging non-note entries
  83. */
  84. public NoteMapMerger(Repository db, NoteMerger noteMerger,
  85. MergeStrategy nonNotesMergeStrategy) {
  86. this.db = db;
  87. this.reader = db.newObjectReader();
  88. this.inserter = db.newObjectInserter();
  89. this.noteMerger = noteMerger;
  90. this.nonNotesMergeStrategy = nonNotesMergeStrategy;
  91. this.objectIdPrefix = new MutableObjectId();
  92. }
  93. /**
  94. * Constructs a NoteMapMerger with
  95. * {@link org.eclipse.jgit.notes.DefaultNoteMerger} as the merger for notes
  96. * and the {@link org.eclipse.jgit.merge.MergeStrategy#RESOLVE} as the
  97. * strategy for resolving conflicts on non-notes
  98. *
  99. * @param db
  100. * Git repository
  101. */
  102. public NoteMapMerger(Repository db) {
  103. this(db, new DefaultNoteMerger(), MergeStrategy.RESOLVE);
  104. }
  105. /**
  106. * Performs the merge.
  107. *
  108. * @param base
  109. * base version of the note tree
  110. * @param ours
  111. * ours version of the note tree
  112. * @param theirs
  113. * theirs version of the note tree
  114. * @return merge result as a new NoteMap
  115. * @throws java.io.IOException
  116. */
  117. public NoteMap merge(NoteMap base, NoteMap ours, NoteMap theirs)
  118. throws IOException {
  119. try {
  120. InMemoryNoteBucket mergedBucket = merge(0, base.getRoot(),
  121. ours.getRoot(), theirs.getRoot());
  122. inserter.flush();
  123. return NoteMap.newMap(mergedBucket, reader);
  124. } finally {
  125. reader.close();
  126. inserter.close();
  127. }
  128. }
  129. /**
  130. * This method is called only when it is known that there is some difference
  131. * between base, ours and theirs.
  132. *
  133. * @param treeDepth
  134. * @param base
  135. * @param ours
  136. * @param theirs
  137. * @return merge result as an InMemoryBucket
  138. * @throws IOException
  139. */
  140. private InMemoryNoteBucket merge(int treeDepth, InMemoryNoteBucket base,
  141. InMemoryNoteBucket ours, InMemoryNoteBucket theirs)
  142. throws IOException {
  143. InMemoryNoteBucket result;
  144. if (base instanceof FanoutBucket || ours instanceof FanoutBucket
  145. || theirs instanceof FanoutBucket) {
  146. result = mergeFanoutBucket(treeDepth, asFanout(base),
  147. asFanout(ours), asFanout(theirs));
  148. } else {
  149. result = mergeLeafBucket(treeDepth, (LeafBucket) base,
  150. (LeafBucket) ours, (LeafBucket) theirs);
  151. }
  152. result.nonNotes = mergeNonNotes(nonNotes(base), nonNotes(ours),
  153. nonNotes(theirs));
  154. return result;
  155. }
  156. private FanoutBucket asFanout(InMemoryNoteBucket bucket) {
  157. if (bucket == null)
  158. return EMPTY_FANOUT;
  159. if (bucket instanceof FanoutBucket)
  160. return (FanoutBucket) bucket;
  161. return ((LeafBucket) bucket).split();
  162. }
  163. private static NonNoteEntry nonNotes(InMemoryNoteBucket b) {
  164. return b == null ? null : b.nonNotes;
  165. }
  166. private InMemoryNoteBucket mergeFanoutBucket(int treeDepth,
  167. FanoutBucket base,
  168. FanoutBucket ours, FanoutBucket theirs) throws IOException {
  169. FanoutBucket result = new FanoutBucket(treeDepth * 2);
  170. // walking through entries of base, ours, theirs
  171. for (int i = 0; i < 256; i++) {
  172. NoteBucket b = base.getBucket(i);
  173. NoteBucket o = ours.getBucket(i);
  174. NoteBucket t = theirs.getBucket(i);
  175. if (equals(o, t))
  176. addIfNotNull(result, i, o);
  177. else if (equals(b, o))
  178. addIfNotNull(result, i, t);
  179. else if (equals(b, t))
  180. addIfNotNull(result, i, o);
  181. else {
  182. objectIdPrefix.setByte(treeDepth, i);
  183. InMemoryNoteBucket mergedBucket = merge(treeDepth + 1,
  184. FanoutBucket.loadIfLazy(b, objectIdPrefix, reader),
  185. FanoutBucket.loadIfLazy(o, objectIdPrefix, reader),
  186. FanoutBucket.loadIfLazy(t, objectIdPrefix, reader));
  187. result.setBucket(i, mergedBucket);
  188. }
  189. }
  190. return result.contractIfTooSmall(objectIdPrefix, reader);
  191. }
  192. private static boolean equals(NoteBucket a, NoteBucket b) {
  193. if (a == null && b == null)
  194. return true;
  195. return a != null && b != null && a.getTreeId().equals(b.getTreeId());
  196. }
  197. private void addIfNotNull(FanoutBucket b, int cell, NoteBucket child)
  198. throws IOException {
  199. if (child == null)
  200. return;
  201. if (child instanceof InMemoryNoteBucket)
  202. b.setBucket(cell, ((InMemoryNoteBucket) child).writeTree(inserter));
  203. else
  204. b.setBucket(cell, child.getTreeId());
  205. }
  206. private InMemoryNoteBucket mergeLeafBucket(int treeDepth, LeafBucket bb,
  207. LeafBucket ob, LeafBucket tb) throws MissingObjectException,
  208. IOException {
  209. bb = notNullOrEmpty(bb);
  210. ob = notNullOrEmpty(ob);
  211. tb = notNullOrEmpty(tb);
  212. InMemoryNoteBucket result = new LeafBucket(treeDepth * 2);
  213. int bi = 0, oi = 0, ti = 0;
  214. while (bi < bb.size() || oi < ob.size() || ti < tb.size()) {
  215. Note b = get(bb, bi), o = get(ob, oi), t = get(tb, ti);
  216. Note min = min(b, o, t);
  217. b = sameNoteOrNull(min, b);
  218. o = sameNoteOrNull(min, o);
  219. t = sameNoteOrNull(min, t);
  220. if (sameContent(o, t))
  221. result = addIfNotNull(result, o);
  222. else if (sameContent(b, o))
  223. result = addIfNotNull(result, t);
  224. else if (sameContent(b, t))
  225. result = addIfNotNull(result, o);
  226. else
  227. result = addIfNotNull(result,
  228. noteMerger.merge(b, o, t, reader, inserter));
  229. if (b != null)
  230. bi++;
  231. if (o != null)
  232. oi++;
  233. if (t != null)
  234. ti++;
  235. }
  236. return result;
  237. }
  238. private static LeafBucket notNullOrEmpty(LeafBucket b) {
  239. return b != null ? b : EMPTY_LEAF;
  240. }
  241. private static Note get(LeafBucket b, int i) {
  242. return i < b.size() ? b.get(i) : null;
  243. }
  244. private static Note min(Note b, Note o, Note t) {
  245. Note min = b;
  246. if (min == null || (o != null && o.compareTo(min) < 0))
  247. min = o;
  248. if (min == null || (t != null && t.compareTo(min) < 0))
  249. min = t;
  250. return min;
  251. }
  252. private static Note sameNoteOrNull(Note min, Note other) {
  253. return sameNote(min, other) ? other : null;
  254. }
  255. private static boolean sameNote(Note a, Note b) {
  256. if (a == null && b == null)
  257. return true;
  258. return a != null && b != null && AnyObjectId.isEqual(a, b);
  259. }
  260. private static boolean sameContent(Note a, Note b) {
  261. if (a == null && b == null)
  262. return true;
  263. return a != null && b != null
  264. && AnyObjectId.isEqual(a.getData(), b.getData());
  265. }
  266. private static InMemoryNoteBucket addIfNotNull(InMemoryNoteBucket result,
  267. Note note) {
  268. if (note != null) {
  269. return result.append(note);
  270. }
  271. return result;
  272. }
  273. private NonNoteEntry mergeNonNotes(NonNoteEntry baseList,
  274. NonNoteEntry oursList, NonNoteEntry theirsList) throws IOException {
  275. if (baseList == null && oursList == null && theirsList == null)
  276. return null;
  277. ObjectId baseId = write(baseList);
  278. ObjectId oursId = write(oursList);
  279. ObjectId theirsId = write(theirsList);
  280. inserter.flush();
  281. Merger m = nonNotesMergeStrategy.newMerger(db, true);
  282. if (m instanceof ThreeWayMerger)
  283. ((ThreeWayMerger) m).setBase(baseId);
  284. if (!m.merge(oursId, theirsId))
  285. throw new NotesMergeConflictException(baseList, oursList,
  286. theirsList);
  287. ObjectId resultTreeId = m.getResultTreeId();
  288. AbbreviatedObjectId none = AbbreviatedObjectId.fromString(""); //$NON-NLS-1$
  289. return NoteParser.parse(none, resultTreeId, reader).nonNotes;
  290. }
  291. private ObjectId write(NonNoteEntry list)
  292. throws IOException {
  293. LeafBucket b = new LeafBucket(0);
  294. b.nonNotes = list;
  295. return b.writeTree(inserter);
  296. }
  297. }