Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

NoteMapMerger.java 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  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. import org.eclipse.jgit.treewalk.AbstractTreeIterator;
  57. import org.eclipse.jgit.treewalk.TreeWalk;
  58. /**
  59. * Three-way note tree merge.
  60. * <p>
  61. * Direct implementation of NoteMap merger without using {@link TreeWalk} and
  62. * {@link AbstractTreeIterator}
  63. */
  64. public class NoteMapMerger {
  65. private static final FanoutBucket EMPTY_FANOUT = new FanoutBucket(0);
  66. private static final LeafBucket EMPTY_LEAF = new LeafBucket(0);
  67. private final Repository db;
  68. private final NoteMerger noteMerger;
  69. private final MergeStrategy nonNotesMergeStrategy;
  70. private final ObjectReader reader;
  71. private final ObjectInserter inserter;
  72. private final MutableObjectId objectIdPrefix;
  73. /**
  74. * Constructs a NoteMapMerger with custom {@link NoteMerger} and custom
  75. * {@link 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 {@link DefaultNoteMerger} as the merger
  95. * for notes and the {@link MergeStrategy#RESOLVE} as the strategy for
  96. * resolving conflicts on non-notes
  97. *
  98. * @param db
  99. * Git repository
  100. */
  101. public NoteMapMerger(Repository db) {
  102. this(db, new DefaultNoteMerger(), MergeStrategy.RESOLVE);
  103. }
  104. /**
  105. * Performs the merge.
  106. *
  107. * @param base
  108. * base version of the note tree
  109. * @param ours
  110. * ours version of the note tree
  111. * @param theirs
  112. * theirs version of the note tree
  113. * @return merge result as a new NoteMap
  114. * @throws IOException
  115. */
  116. public NoteMap merge(NoteMap base, NoteMap ours, NoteMap theirs)
  117. throws IOException {
  118. try {
  119. InMemoryNoteBucket mergedBucket = merge(0, base.getRoot(),
  120. ours.getRoot(), theirs.getRoot());
  121. inserter.flush();
  122. return NoteMap.newMap(mergedBucket, reader);
  123. } finally {
  124. reader.release();
  125. inserter.release();
  126. }
  127. }
  128. /**
  129. * This method is called only when it is known that there is some difference
  130. * between base, ours and theirs.
  131. *
  132. * @param treeDepth
  133. * @param base
  134. * @param ours
  135. * @param theirs
  136. * @return merge result as an InMemoryBucket
  137. * @throws IOException
  138. */
  139. private InMemoryNoteBucket merge(int treeDepth, InMemoryNoteBucket base,
  140. InMemoryNoteBucket ours, InMemoryNoteBucket theirs)
  141. throws IOException {
  142. InMemoryNoteBucket result;
  143. if (base instanceof FanoutBucket || ours instanceof FanoutBucket
  144. || theirs instanceof FanoutBucket) {
  145. result = mergeFanoutBucket(treeDepth, asFanout(base),
  146. asFanout(ours), asFanout(theirs));
  147. } else {
  148. result = mergeLeafBucket(treeDepth, (LeafBucket) base,
  149. (LeafBucket) ours, (LeafBucket) theirs);
  150. }
  151. result.nonNotes = mergeNonNotes(nonNotes(base), nonNotes(ours),
  152. nonNotes(theirs));
  153. return result;
  154. }
  155. private FanoutBucket asFanout(InMemoryNoteBucket bucket) {
  156. if (bucket == null)
  157. return EMPTY_FANOUT;
  158. if (bucket instanceof FanoutBucket)
  159. return (FanoutBucket) bucket;
  160. return ((LeafBucket) bucket).split();
  161. }
  162. private static NonNoteEntry nonNotes(InMemoryNoteBucket b) {
  163. return b == null ? null : b.nonNotes;
  164. }
  165. private InMemoryNoteBucket mergeFanoutBucket(int treeDepth,
  166. FanoutBucket base,
  167. FanoutBucket ours, FanoutBucket theirs) throws IOException {
  168. FanoutBucket result = new FanoutBucket(treeDepth * 2);
  169. // walking through entries of base, ours, theirs
  170. for (int i = 0; i < 256; i++) {
  171. NoteBucket b = base.getBucket(i);
  172. NoteBucket o = ours.getBucket(i);
  173. NoteBucket t = theirs.getBucket(i);
  174. if (equals(o, t))
  175. addIfNotNull(result, i, o);
  176. else if (equals(b, o))
  177. addIfNotNull(result, i, t);
  178. else if (equals(b, t))
  179. addIfNotNull(result, i, o);
  180. else {
  181. objectIdPrefix.setByte(treeDepth, i);
  182. InMemoryNoteBucket mergedBucket = merge(treeDepth + 1,
  183. FanoutBucket.loadIfLazy(b, objectIdPrefix, reader),
  184. FanoutBucket.loadIfLazy(o, objectIdPrefix, reader),
  185. FanoutBucket.loadIfLazy(t, objectIdPrefix, reader));
  186. result.setBucket(i, mergedBucket);
  187. }
  188. }
  189. return result.contractIfTooSmall(objectIdPrefix, reader);
  190. }
  191. private static boolean equals(NoteBucket a, NoteBucket b) {
  192. if (a == null && b == null)
  193. return true;
  194. return a != null && b != null && a.getTreeId().equals(b.getTreeId());
  195. }
  196. private void addIfNotNull(FanoutBucket b, int cell, NoteBucket child)
  197. throws IOException {
  198. if (child == null)
  199. return;
  200. if (child instanceof InMemoryNoteBucket)
  201. b.setBucket(cell, ((InMemoryNoteBucket) child).writeTree(inserter));
  202. else
  203. b.setBucket(cell, child.getTreeId());
  204. }
  205. private InMemoryNoteBucket mergeLeafBucket(int treeDepth, LeafBucket bb,
  206. LeafBucket ob, LeafBucket tb) throws MissingObjectException,
  207. IOException {
  208. bb = notNullOrEmpty(bb);
  209. ob = notNullOrEmpty(ob);
  210. tb = notNullOrEmpty(tb);
  211. InMemoryNoteBucket result = new LeafBucket(treeDepth * 2);
  212. int bi = 0, oi = 0, ti = 0;
  213. while (bi < bb.size() || oi < ob.size() || ti < tb.size()) {
  214. Note b = get(bb, bi), o = get(ob, oi), t = get(tb, ti);
  215. Note min = min(b, o, t);
  216. b = sameNoteOrNull(min, b);
  217. o = sameNoteOrNull(min, o);
  218. t = sameNoteOrNull(min, t);
  219. if (sameContent(o, t))
  220. result = addIfNotNull(result, o);
  221. else if (sameContent(b, o))
  222. result = addIfNotNull(result, t);
  223. else if (sameContent(b, t))
  224. result = addIfNotNull(result, o);
  225. else
  226. result = addIfNotNull(result,
  227. noteMerger.merge(b, o, t, reader, inserter));
  228. if (b != null)
  229. bi++;
  230. if (o != null)
  231. oi++;
  232. if (t != null)
  233. ti++;
  234. }
  235. return result;
  236. }
  237. private static LeafBucket notNullOrEmpty(LeafBucket b) {
  238. return b != null ? b : EMPTY_LEAF;
  239. }
  240. private static Note get(LeafBucket b, int i) {
  241. return i < b.size() ? b.get(i) : null;
  242. }
  243. private static Note min(Note b, Note o, Note t) {
  244. Note min = b;
  245. if (min == null || (o != null && o.compareTo(min) < 0))
  246. min = o;
  247. if (min == null || (t != null && t.compareTo(min) < 0))
  248. min = t;
  249. return min;
  250. }
  251. private static Note sameNoteOrNull(Note min, Note other) {
  252. return sameNote(min, other) ? other : null;
  253. }
  254. private static boolean sameNote(Note a, Note b) {
  255. if (a == null && b == null)
  256. return true;
  257. return a != null && b != null && AnyObjectId.equals(a, b);
  258. }
  259. private static boolean sameContent(Note a, Note b) {
  260. if (a == null && b == null)
  261. return true;
  262. return a != null && b != null
  263. && AnyObjectId.equals(a.getData(), b.getData());
  264. }
  265. private static InMemoryNoteBucket addIfNotNull(InMemoryNoteBucket result,
  266. Note note) {
  267. if (note != null)
  268. return result.append(note);
  269. else
  270. return result;
  271. }
  272. private NonNoteEntry mergeNonNotes(NonNoteEntry baseList,
  273. NonNoteEntry oursList, NonNoteEntry theirsList) throws IOException {
  274. if (baseList == null && oursList == null && theirsList == null)
  275. return null;
  276. ObjectId baseId = write(baseList);
  277. ObjectId oursId = write(oursList);
  278. ObjectId theirsId = write(theirsList);
  279. inserter.flush();
  280. Merger m = nonNotesMergeStrategy.newMerger(db, true);
  281. if (m instanceof ThreeWayMerger)
  282. ((ThreeWayMerger) m).setBase(baseId);
  283. if (!m.merge(oursId, theirsId))
  284. throw new NotesMergeConflictException(baseList, oursList,
  285. theirsList);
  286. ObjectId resultTreeId = m.getResultTreeId();
  287. AbbreviatedObjectId none = AbbreviatedObjectId.fromString(""); //$NON-NLS-1$
  288. return NoteParser.parse(none, resultTreeId, reader).nonNotes;
  289. }
  290. private ObjectId write(NonNoteEntry list)
  291. throws IOException {
  292. LeafBucket b = new LeafBucket(0);
  293. b.nonNotes = list;
  294. return b.writeTree(inserter);
  295. }
  296. }