Merger.java

  1. /*
  2.  * Copyright (C) 2008-2013, Google Inc.
  3.  * Copyright (C) 2016, Laurent Delaigue <laurent.delaigue@obeo.fr> and others
  4.  *
  5.  * This program and the accompanying materials are made available under the
  6.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  7.  * https://www.eclipse.org/org/documents/edl-v10.php.
  8.  *
  9.  * SPDX-License-Identifier: BSD-3-Clause
  10.  */

  11. package org.eclipse.jgit.merge;

  12. import java.io.IOException;
  13. import java.text.MessageFormat;

  14. import org.eclipse.jgit.annotations.Nullable;
  15. import org.eclipse.jgit.errors.IncorrectObjectTypeException;
  16. import org.eclipse.jgit.errors.NoMergeBaseException;
  17. import org.eclipse.jgit.errors.NoMergeBaseException.MergeBaseFailureReason;
  18. import org.eclipse.jgit.internal.JGitText;
  19. import org.eclipse.jgit.lib.AnyObjectId;
  20. import org.eclipse.jgit.lib.NullProgressMonitor;
  21. import org.eclipse.jgit.lib.ObjectId;
  22. import org.eclipse.jgit.lib.ObjectInserter;
  23. import org.eclipse.jgit.lib.ObjectReader;
  24. import org.eclipse.jgit.lib.ProgressMonitor;
  25. import org.eclipse.jgit.lib.Repository;
  26. import org.eclipse.jgit.revwalk.RevCommit;
  27. import org.eclipse.jgit.revwalk.RevObject;
  28. import org.eclipse.jgit.revwalk.RevTree;
  29. import org.eclipse.jgit.revwalk.RevWalk;
  30. import org.eclipse.jgit.revwalk.filter.RevFilter;
  31. import org.eclipse.jgit.treewalk.AbstractTreeIterator;
  32. import org.eclipse.jgit.treewalk.CanonicalTreeParser;

  33. /**
  34.  * Instance of a specific {@link org.eclipse.jgit.merge.MergeStrategy} for a
  35.  * single {@link org.eclipse.jgit.lib.Repository}.
  36.  */
  37. public abstract class Merger {
  38.     /**
  39.      * The repository this merger operates on.
  40.      * <p>
  41.      * Null if and only if the merger was constructed with {@link
  42.      * #Merger(ObjectInserter)}. Callers that want to assume the repo is not null
  43.      * (e.g. because of a previous check that the merger is not in-core) may use
  44.      * {@link #nonNullRepo()}.
  45.      */
  46.     @Nullable
  47.     protected final Repository db;

  48.     /** Reader to support {@link #walk} and other object loading. */
  49.     protected ObjectReader reader;

  50.     /** A RevWalk for computing merge bases, or listing incoming commits. */
  51.     protected RevWalk walk;

  52.     private ObjectInserter inserter;

  53.     /** The original objects supplied in the merge; this can be any tree-ish. */
  54.     protected RevObject[] sourceObjects;

  55.     /** If {@link #sourceObjects}[i] is a commit, this is the commit. */
  56.     protected RevCommit[] sourceCommits;

  57.     /** The trees matching every entry in {@link #sourceObjects}. */
  58.     protected RevTree[] sourceTrees;

  59.     /**
  60.      * A progress monitor.
  61.      *
  62.      * @since 4.2
  63.      */
  64.     protected ProgressMonitor monitor = NullProgressMonitor.INSTANCE;

  65.     /**
  66.      * Create a new merge instance for a repository.
  67.      *
  68.      * @param local
  69.      *            the repository this merger will read and write data on.
  70.      */
  71.     protected Merger(Repository local) {
  72.         if (local == null) {
  73.             throw new NullPointerException(JGitText.get().repositoryIsRequired);
  74.         }
  75.         db = local;
  76.         inserter = local.newObjectInserter();
  77.         reader = inserter.newReader();
  78.         walk = new RevWalk(reader);
  79.     }

  80.     /**
  81.      * Create a new in-core merge instance from an inserter.
  82.      *
  83.      * @param oi
  84.      *            the inserter to write objects to. Will be closed at the
  85.      *            conclusion of {@code merge}, unless {@code flush} is false.
  86.      * @since 4.8
  87.      */
  88.     protected Merger(ObjectInserter oi) {
  89.         db = null;
  90.         inserter = oi;
  91.         reader = oi.newReader();
  92.         walk = new RevWalk(reader);
  93.     }

  94.     /**
  95.      * Get the repository this merger operates on.
  96.      *
  97.      * @return the repository this merger operates on.
  98.      */
  99.     @Nullable
  100.     public Repository getRepository() {
  101.         return db;
  102.     }

  103.     /**
  104.      * Get non-null repository instance
  105.      *
  106.      * @return non-null repository instance
  107.      * @throws java.lang.NullPointerException
  108.      *             if the merger was constructed without a repository.
  109.      * @since 4.8
  110.      */
  111.     protected Repository nonNullRepo() {
  112.         if (db == null) {
  113.             throw new NullPointerException(JGitText.get().repositoryIsRequired);
  114.         }
  115.         return db;
  116.     }

  117.     /**
  118.      * Get an object writer to create objects, writing objects to
  119.      * {@link #getRepository()}
  120.      *
  121.      * @return an object writer to create objects, writing objects to
  122.      *         {@link #getRepository()} (if a repository was provided).
  123.      */
  124.     public ObjectInserter getObjectInserter() {
  125.         return inserter;
  126.     }

  127.     /**
  128.      * Set the inserter this merger will use to create objects.
  129.      * <p>
  130.      * If an inserter was already set on this instance (such as by a prior set,
  131.      * or a prior call to {@link #getObjectInserter()}), the prior inserter as
  132.      * well as the in-progress walk will be released.
  133.      *
  134.      * @param oi
  135.      *            the inserter instance to use. Must be associated with the
  136.      *            repository instance returned by {@link #getRepository()} (if a
  137.      *            repository was provided). Will be closed at the conclusion of
  138.      *            {@code merge}, unless {@code flush} is false.
  139.      */
  140.     public void setObjectInserter(ObjectInserter oi) {
  141.         walk.close();
  142.         reader.close();
  143.         inserter.close();
  144.         inserter = oi;
  145.         reader = oi.newReader();
  146.         walk = new RevWalk(reader);
  147.     }

  148.     /**
  149.      * Merge together two or more tree-ish objects.
  150.      * <p>
  151.      * Any tree-ish may be supplied as inputs. Commits and/or tags pointing at
  152.      * trees or commits may be passed as input objects.
  153.      *
  154.      * @param tips
  155.      *            source trees to be combined together. The merge base is not
  156.      *            included in this set.
  157.      * @return true if the merge was completed without conflicts; false if the
  158.      *         merge strategy cannot handle this merge or there were conflicts
  159.      *         preventing it from automatically resolving all paths.
  160.      * @throws IncorrectObjectTypeException
  161.      *             one of the input objects is not a commit, but the strategy
  162.      *             requires it to be a commit.
  163.      * @throws java.io.IOException
  164.      *             one or more sources could not be read, or outputs could not
  165.      *             be written to the Repository.
  166.      */
  167.     public boolean merge(AnyObjectId... tips) throws IOException {
  168.         return merge(true, tips);
  169.     }

  170.     /**
  171.      * Merge together two or more tree-ish objects.
  172.      * <p>
  173.      * Any tree-ish may be supplied as inputs. Commits and/or tags pointing at
  174.      * trees or commits may be passed as input objects.
  175.      *
  176.      * @since 3.5
  177.      * @param flush
  178.      *            whether to flush and close the underlying object inserter when
  179.      *            finished to store any content-merged blobs and virtual merged
  180.      *            bases; if false, callers are responsible for flushing.
  181.      * @param tips
  182.      *            source trees to be combined together. The merge base is not
  183.      *            included in this set.
  184.      * @return true if the merge was completed without conflicts; false if the
  185.      *         merge strategy cannot handle this merge or there were conflicts
  186.      *         preventing it from automatically resolving all paths.
  187.      * @throws IncorrectObjectTypeException
  188.      *             one of the input objects is not a commit, but the strategy
  189.      *             requires it to be a commit.
  190.      * @throws java.io.IOException
  191.      *             one or more sources could not be read, or outputs could not
  192.      *             be written to the Repository.
  193.      */
  194.     public boolean merge(boolean flush, AnyObjectId... tips)
  195.             throws IOException {
  196.         sourceObjects = new RevObject[tips.length];
  197.         for (int i = 0; i < tips.length; i++)
  198.             sourceObjects[i] = walk.parseAny(tips[i]);

  199.         sourceCommits = new RevCommit[sourceObjects.length];
  200.         for (int i = 0; i < sourceObjects.length; i++) {
  201.             try {
  202.                 sourceCommits[i] = walk.parseCommit(sourceObjects[i]);
  203.             } catch (IncorrectObjectTypeException err) {
  204.                 sourceCommits[i] = null;
  205.             }
  206.         }

  207.         sourceTrees = new RevTree[sourceObjects.length];
  208.         for (int i = 0; i < sourceObjects.length; i++)
  209.             sourceTrees[i] = walk.parseTree(sourceObjects[i]);

  210.         try {
  211.             boolean ok = mergeImpl();
  212.             if (ok && flush)
  213.                 inserter.flush();
  214.             return ok;
  215.         } finally {
  216.             if (flush)
  217.                 inserter.close();
  218.             reader.close();
  219.         }
  220.     }

  221.     /**
  222.      * Get the ID of the commit that was used as merge base for merging
  223.      *
  224.      * @return the ID of the commit that was used as merge base for merging, or
  225.      *         null if no merge base was used or it was set manually
  226.      * @since 3.2
  227.      */
  228.     public abstract ObjectId getBaseCommitId();

  229.     /**
  230.      * Return the merge base of two commits.
  231.      *
  232.      * @param a
  233.      *            the first commit in {@link #sourceObjects}.
  234.      * @param b
  235.      *            the second commit in {@link #sourceObjects}.
  236.      * @return the merge base of two commits
  237.      * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
  238.      *             one of the input objects is not a commit.
  239.      * @throws java.io.IOException
  240.      *             objects are missing or multiple merge bases were found.
  241.      * @since 3.0
  242.      */
  243.     protected RevCommit getBaseCommit(RevCommit a, RevCommit b)
  244.             throws IncorrectObjectTypeException, IOException {
  245.         walk.reset();
  246.         walk.setRevFilter(RevFilter.MERGE_BASE);
  247.         walk.markStart(a);
  248.         walk.markStart(b);
  249.         final RevCommit base = walk.next();
  250.         if (base == null)
  251.             return null;
  252.         final RevCommit base2 = walk.next();
  253.         if (base2 != null) {
  254.             throw new NoMergeBaseException(
  255.                     MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED,
  256.                     MessageFormat.format(
  257.                     JGitText.get().multipleMergeBasesFor, a.name(), b.name(),
  258.                     base.name(), base2.name()));
  259.         }
  260.         return base;
  261.     }

  262.     /**
  263.      * Open an iterator over a tree.
  264.      *
  265.      * @param treeId
  266.      *            the tree to scan; must be a tree (not a treeish).
  267.      * @return an iterator for the tree.
  268.      * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
  269.      *             the input object is not a tree.
  270.      * @throws java.io.IOException
  271.      *             the tree object is not found or cannot be read.
  272.      */
  273.     protected AbstractTreeIterator openTree(AnyObjectId treeId)
  274.             throws IncorrectObjectTypeException, IOException {
  275.         return new CanonicalTreeParser(null, reader, treeId);
  276.     }

  277.     /**
  278.      * Execute the merge.
  279.      * <p>
  280.      * This method is called from {@link #merge(AnyObjectId[])} after the
  281.      * {@link #sourceObjects}, {@link #sourceCommits} and {@link #sourceTrees}
  282.      * have been populated.
  283.      *
  284.      * @return true if the merge was completed without conflicts; false if the
  285.      *         merge strategy cannot handle this merge or there were conflicts
  286.      *         preventing it from automatically resolving all paths.
  287.      * @throws IncorrectObjectTypeException
  288.      *             one of the input objects is not a commit, but the strategy
  289.      *             requires it to be a commit.
  290.      * @throws java.io.IOException
  291.      *             one or more sources could not be read, or outputs could not
  292.      *             be written to the Repository.
  293.      */
  294.     protected abstract boolean mergeImpl() throws IOException;

  295.     /**
  296.      * Get resulting tree.
  297.      *
  298.      * @return resulting tree, if {@link #merge(AnyObjectId[])} returned true.
  299.      */
  300.     public abstract ObjectId getResultTreeId();

  301.     /**
  302.      * Set a progress monitor.
  303.      *
  304.      * @param monitor
  305.      *            Monitor to use, can be null to indicate no progress reporting
  306.      *            is desired.
  307.      * @since 4.2
  308.      */
  309.     public void setProgressMonitor(ProgressMonitor monitor) {
  310.         if (monitor == null) {
  311.             this.monitor = NullProgressMonitor.INSTANCE;
  312.         } else {
  313.             this.monitor = monitor;
  314.         }
  315.     }
  316. }