NoteMapMerger.java

  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. }