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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. /*
  2. * Copyright (C) 2010, Sasa Zivkov <sasa.zivkov@sap.com> and others
  3. *
  4. * This program and the accompanying materials are made available under the
  5. * terms of the Eclipse Distribution License v. 1.0 which is available at
  6. * https://www.eclipse.org/org/documents/edl-v10.php.
  7. *
  8. * SPDX-License-Identifier: BSD-3-Clause
  9. */
  10. package org.eclipse.jgit.notes;
  11. import java.io.IOException;
  12. import org.eclipse.jgit.errors.MissingObjectException;
  13. import org.eclipse.jgit.lib.AbbreviatedObjectId;
  14. import org.eclipse.jgit.lib.AnyObjectId;
  15. import org.eclipse.jgit.lib.MutableObjectId;
  16. import org.eclipse.jgit.lib.ObjectId;
  17. import org.eclipse.jgit.lib.ObjectInserter;
  18. import org.eclipse.jgit.lib.ObjectReader;
  19. import org.eclipse.jgit.lib.Repository;
  20. import org.eclipse.jgit.merge.MergeStrategy;
  21. import org.eclipse.jgit.merge.Merger;
  22. import org.eclipse.jgit.merge.ThreeWayMerger;
  23. /**
  24. * Three-way note tree merge.
  25. * <p>
  26. * Direct implementation of NoteMap merger without using
  27. * {@link org.eclipse.jgit.treewalk.TreeWalk} and
  28. * {@link org.eclipse.jgit.treewalk.AbstractTreeIterator}
  29. */
  30. public class NoteMapMerger {
  31. private static final FanoutBucket EMPTY_FANOUT = new FanoutBucket(0);
  32. private static final LeafBucket EMPTY_LEAF = new LeafBucket(0);
  33. private final Repository db;
  34. private final NoteMerger noteMerger;
  35. private final MergeStrategy nonNotesMergeStrategy;
  36. private final ObjectReader reader;
  37. private final ObjectInserter inserter;
  38. private final MutableObjectId objectIdPrefix;
  39. /**
  40. * Constructs a NoteMapMerger with custom
  41. * {@link org.eclipse.jgit.notes.NoteMerger} and custom
  42. * {@link org.eclipse.jgit.merge.MergeStrategy}.
  43. *
  44. * @param db
  45. * Git repository
  46. * @param noteMerger
  47. * note merger for merging conflicting changes on a note
  48. * @param nonNotesMergeStrategy
  49. * merge strategy for merging non-note entries
  50. */
  51. public NoteMapMerger(Repository db, NoteMerger noteMerger,
  52. MergeStrategy nonNotesMergeStrategy) {
  53. this.db = db;
  54. this.reader = db.newObjectReader();
  55. this.inserter = db.newObjectInserter();
  56. this.noteMerger = noteMerger;
  57. this.nonNotesMergeStrategy = nonNotesMergeStrategy;
  58. this.objectIdPrefix = new MutableObjectId();
  59. }
  60. /**
  61. * Constructs a NoteMapMerger with
  62. * {@link org.eclipse.jgit.notes.DefaultNoteMerger} as the merger for notes
  63. * and the {@link org.eclipse.jgit.merge.MergeStrategy#RESOLVE} as the
  64. * strategy for resolving conflicts on non-notes
  65. *
  66. * @param db
  67. * Git repository
  68. */
  69. public NoteMapMerger(Repository db) {
  70. this(db, new DefaultNoteMerger(), MergeStrategy.RESOLVE);
  71. }
  72. /**
  73. * Performs the merge.
  74. *
  75. * @param base
  76. * base version of the note tree
  77. * @param ours
  78. * ours version of the note tree
  79. * @param theirs
  80. * theirs version of the note tree
  81. * @return merge result as a new NoteMap
  82. * @throws java.io.IOException
  83. */
  84. public NoteMap merge(NoteMap base, NoteMap ours, NoteMap theirs)
  85. throws IOException {
  86. try {
  87. InMemoryNoteBucket mergedBucket = merge(0, base.getRoot(),
  88. ours.getRoot(), theirs.getRoot());
  89. inserter.flush();
  90. return NoteMap.newMap(mergedBucket, reader);
  91. } finally {
  92. reader.close();
  93. inserter.close();
  94. }
  95. }
  96. /**
  97. * This method is called only when it is known that there is some difference
  98. * between base, ours and theirs.
  99. *
  100. * @param treeDepth
  101. * @param base
  102. * @param ours
  103. * @param theirs
  104. * @return merge result as an InMemoryBucket
  105. * @throws IOException
  106. */
  107. private InMemoryNoteBucket merge(int treeDepth, InMemoryNoteBucket base,
  108. InMemoryNoteBucket ours, InMemoryNoteBucket theirs)
  109. throws IOException {
  110. InMemoryNoteBucket result;
  111. if (base instanceof FanoutBucket || ours instanceof FanoutBucket
  112. || theirs instanceof FanoutBucket) {
  113. result = mergeFanoutBucket(treeDepth, asFanout(base),
  114. asFanout(ours), asFanout(theirs));
  115. } else {
  116. result = mergeLeafBucket(treeDepth, (LeafBucket) base,
  117. (LeafBucket) ours, (LeafBucket) theirs);
  118. }
  119. result.nonNotes = mergeNonNotes(nonNotes(base), nonNotes(ours),
  120. nonNotes(theirs));
  121. return result;
  122. }
  123. private FanoutBucket asFanout(InMemoryNoteBucket bucket) {
  124. if (bucket == null)
  125. return EMPTY_FANOUT;
  126. if (bucket instanceof FanoutBucket)
  127. return (FanoutBucket) bucket;
  128. return ((LeafBucket) bucket).split();
  129. }
  130. private static NonNoteEntry nonNotes(InMemoryNoteBucket b) {
  131. return b == null ? null : b.nonNotes;
  132. }
  133. private InMemoryNoteBucket mergeFanoutBucket(int treeDepth,
  134. FanoutBucket base,
  135. FanoutBucket ours, FanoutBucket theirs) throws IOException {
  136. FanoutBucket result = new FanoutBucket(treeDepth * 2);
  137. // walking through entries of base, ours, theirs
  138. for (int i = 0; i < 256; i++) {
  139. NoteBucket b = base.getBucket(i);
  140. NoteBucket o = ours.getBucket(i);
  141. NoteBucket t = theirs.getBucket(i);
  142. if (equals(o, t))
  143. addIfNotNull(result, i, o);
  144. else if (equals(b, o))
  145. addIfNotNull(result, i, t);
  146. else if (equals(b, t))
  147. addIfNotNull(result, i, o);
  148. else {
  149. objectIdPrefix.setByte(treeDepth, i);
  150. InMemoryNoteBucket mergedBucket = merge(treeDepth + 1,
  151. FanoutBucket.loadIfLazy(b, objectIdPrefix, reader),
  152. FanoutBucket.loadIfLazy(o, objectIdPrefix, reader),
  153. FanoutBucket.loadIfLazy(t, objectIdPrefix, reader));
  154. result.setBucket(i, mergedBucket);
  155. }
  156. }
  157. return result.contractIfTooSmall(objectIdPrefix, reader);
  158. }
  159. private static boolean equals(NoteBucket a, NoteBucket b) {
  160. if (a == null && b == null)
  161. return true;
  162. return a != null && b != null && a.getTreeId().equals(b.getTreeId());
  163. }
  164. private void addIfNotNull(FanoutBucket b, int cell, NoteBucket child)
  165. throws IOException {
  166. if (child == null)
  167. return;
  168. if (child instanceof InMemoryNoteBucket)
  169. b.setBucket(cell, ((InMemoryNoteBucket) child).writeTree(inserter));
  170. else
  171. b.setBucket(cell, child.getTreeId());
  172. }
  173. private InMemoryNoteBucket mergeLeafBucket(int treeDepth, LeafBucket bb,
  174. LeafBucket ob, LeafBucket tb) throws MissingObjectException,
  175. IOException {
  176. bb = notNullOrEmpty(bb);
  177. ob = notNullOrEmpty(ob);
  178. tb = notNullOrEmpty(tb);
  179. InMemoryNoteBucket result = new LeafBucket(treeDepth * 2);
  180. int bi = 0, oi = 0, ti = 0;
  181. while (bi < bb.size() || oi < ob.size() || ti < tb.size()) {
  182. Note b = get(bb, bi), o = get(ob, oi), t = get(tb, ti);
  183. Note min = min(b, o, t);
  184. b = sameNoteOrNull(min, b);
  185. o = sameNoteOrNull(min, o);
  186. t = sameNoteOrNull(min, t);
  187. if (sameContent(o, t))
  188. result = addIfNotNull(result, o);
  189. else if (sameContent(b, o))
  190. result = addIfNotNull(result, t);
  191. else if (sameContent(b, t))
  192. result = addIfNotNull(result, o);
  193. else
  194. result = addIfNotNull(result,
  195. noteMerger.merge(b, o, t, reader, inserter));
  196. if (b != null)
  197. bi++;
  198. if (o != null)
  199. oi++;
  200. if (t != null)
  201. ti++;
  202. }
  203. return result;
  204. }
  205. private static LeafBucket notNullOrEmpty(LeafBucket b) {
  206. return b != null ? b : EMPTY_LEAF;
  207. }
  208. private static Note get(LeafBucket b, int i) {
  209. return i < b.size() ? b.get(i) : null;
  210. }
  211. private static Note min(Note b, Note o, Note t) {
  212. Note min = b;
  213. if (min == null || (o != null && o.compareTo(min) < 0))
  214. min = o;
  215. if (min == null || (t != null && t.compareTo(min) < 0))
  216. min = t;
  217. return min;
  218. }
  219. private static Note sameNoteOrNull(Note min, Note other) {
  220. return sameNote(min, other) ? other : null;
  221. }
  222. private static boolean sameNote(Note a, Note b) {
  223. if (a == null && b == null)
  224. return true;
  225. return a != null && b != null && AnyObjectId.isEqual(a, b);
  226. }
  227. private static boolean sameContent(Note a, Note b) {
  228. if (a == null && b == null)
  229. return true;
  230. return a != null && b != null
  231. && AnyObjectId.isEqual(a.getData(), b.getData());
  232. }
  233. private static InMemoryNoteBucket addIfNotNull(InMemoryNoteBucket result,
  234. Note note) {
  235. if (note != null) {
  236. return result.append(note);
  237. }
  238. return result;
  239. }
  240. private NonNoteEntry mergeNonNotes(NonNoteEntry baseList,
  241. NonNoteEntry oursList, NonNoteEntry theirsList) throws IOException {
  242. if (baseList == null && oursList == null && theirsList == null)
  243. return null;
  244. ObjectId baseId = write(baseList);
  245. ObjectId oursId = write(oursList);
  246. ObjectId theirsId = write(theirsList);
  247. inserter.flush();
  248. Merger m = nonNotesMergeStrategy.newMerger(db, true);
  249. if (m instanceof ThreeWayMerger)
  250. ((ThreeWayMerger) m).setBase(baseId);
  251. if (!m.merge(oursId, theirsId))
  252. throw new NotesMergeConflictException(baseList, oursList,
  253. theirsList);
  254. ObjectId resultTreeId = m.getResultTreeId();
  255. AbbreviatedObjectId none = AbbreviatedObjectId.fromString(""); //$NON-NLS-1$
  256. return NoteParser.parse(none, resultTreeId, reader).nonNotes;
  257. }
  258. private ObjectId write(NonNoteEntry list)
  259. throws IOException {
  260. LeafBucket b = new LeafBucket(0);
  261. b.nonNotes = list;
  262. return b.writeTree(inserter);
  263. }
  264. }