MergeResult.java

  1. /*
  2.  * Copyright (C) 2010, Stefan Lay <stefan.lay@sap.com>
  3.  * Copyright (C) 2010-2012, Christian Halstrick <christian.halstrick@sap.com> 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.api;

  12. import java.text.MessageFormat;
  13. import java.util.HashMap;
  14. import java.util.List;
  15. import java.util.Map;

  16. import org.eclipse.jgit.internal.JGitText;
  17. import org.eclipse.jgit.lib.ObjectId;
  18. import org.eclipse.jgit.merge.MergeChunk;
  19. import org.eclipse.jgit.merge.MergeChunk.ConflictState;
  20. import org.eclipse.jgit.merge.MergeStrategy;
  21. import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;

  22. /**
  23.  * Encapsulates the result of a {@link org.eclipse.jgit.api.MergeCommand}.
  24.  */
  25. public class MergeResult {

  26.     /**
  27.      * The status the merge resulted in.
  28.      */
  29.     public enum MergeStatus {
  30.         /** */
  31.         FAST_FORWARD {
  32.             @Override
  33.             public String toString() {
  34.                 return "Fast-forward"; //$NON-NLS-1$
  35.             }

  36.             @Override
  37.             public boolean isSuccessful() {
  38.                 return true;
  39.             }
  40.         },
  41.         /**
  42.          * @since 2.0
  43.          */
  44.         FAST_FORWARD_SQUASHED {
  45.             @Override
  46.             public String toString() {
  47.                 return "Fast-forward-squashed"; //$NON-NLS-1$
  48.             }

  49.             @Override
  50.             public boolean isSuccessful() {
  51.                 return true;
  52.             }
  53.         },
  54.         /** */
  55.         ALREADY_UP_TO_DATE {
  56.             @Override
  57.             public String toString() {
  58.                 return "Already-up-to-date"; //$NON-NLS-1$
  59.             }

  60.             @Override
  61.             public boolean isSuccessful() {
  62.                 return true;
  63.             }
  64.         },
  65.         /** */
  66.         FAILED {
  67.             @Override
  68.             public String toString() {
  69.                 return "Failed"; //$NON-NLS-1$
  70.             }

  71.             @Override
  72.             public boolean isSuccessful() {
  73.                 return false;
  74.             }
  75.         },
  76.         /** */
  77.         MERGED {
  78.             @Override
  79.             public String toString() {
  80.                 return "Merged"; //$NON-NLS-1$
  81.             }

  82.             @Override
  83.             public boolean isSuccessful() {
  84.                 return true;
  85.             }
  86.         },
  87.         /**
  88.          * @since 2.0
  89.          */
  90.         MERGED_SQUASHED {
  91.             @Override
  92.             public String toString() {
  93.                 return "Merged-squashed"; //$NON-NLS-1$
  94.             }

  95.             @Override
  96.             public boolean isSuccessful() {
  97.                 return true;
  98.             }
  99.         },
  100.         /**
  101.          * @since 3.0
  102.          */
  103.         MERGED_SQUASHED_NOT_COMMITTED {
  104.             @Override
  105.             public String toString() {
  106.                 return "Merged-squashed-not-committed"; //$NON-NLS-1$
  107.             }

  108.             @Override
  109.             public boolean isSuccessful() {
  110.                 return true;
  111.             }
  112.         },
  113.         /** */
  114.         CONFLICTING {
  115.             @Override
  116.             public String toString() {
  117.                 return "Conflicting"; //$NON-NLS-1$
  118.             }

  119.             @Override
  120.             public boolean isSuccessful() {
  121.                 return false;
  122.             }
  123.         },
  124.         /**
  125.          * @since 2.2
  126.          */
  127.         ABORTED {
  128.             @Override
  129.             public String toString() {
  130.                 return "Aborted"; //$NON-NLS-1$
  131.             }

  132.             @Override
  133.             public boolean isSuccessful() {
  134.                 return false;
  135.             }
  136.         },
  137.         /**
  138.          * @since 3.0
  139.          **/
  140.         MERGED_NOT_COMMITTED {
  141.             @Override
  142.             public String toString() {
  143.                 return "Merged-not-committed"; //$NON-NLS-1$
  144.             }

  145.             @Override
  146.             public boolean isSuccessful() {
  147.                 return true;
  148.             }
  149.         },
  150.         /** */
  151.         NOT_SUPPORTED {
  152.             @Override
  153.             public String toString() {
  154.                 return "Not-yet-supported"; //$NON-NLS-1$
  155.             }

  156.             @Override
  157.             public boolean isSuccessful() {
  158.                 return false;
  159.             }
  160.         },
  161.         /**
  162.          * Status representing a checkout conflict, meaning that nothing could
  163.          * be merged, as the pre-scan for the trees already failed for certain
  164.          * files (i.e. local modifications prevent checkout of files).
  165.          */
  166.         CHECKOUT_CONFLICT {
  167.             @Override
  168.             public String toString() {
  169.                 return "Checkout Conflict"; //$NON-NLS-1$
  170.             }

  171.             @Override
  172.             public boolean isSuccessful() {
  173.                 return false;
  174.             }
  175.         };

  176.         /**
  177.          * @return whether the status indicates a successful result
  178.          */
  179.         public abstract boolean isSuccessful();
  180.     }

  181.     private ObjectId[] mergedCommits;

  182.     private ObjectId base;

  183.     private ObjectId newHead;

  184.     private Map<String, int[][]> conflicts;

  185.     private MergeStatus mergeStatus;

  186.     private String description;

  187.     private MergeStrategy mergeStrategy;

  188.     private Map<String, MergeFailureReason> failingPaths;

  189.     private List<String> checkoutConflicts;

  190.     /**
  191.      * Constructor for MergeResult.
  192.      *
  193.      * @param newHead
  194.      *            the object the head points at after the merge
  195.      * @param base
  196.      *            the common base which was used to produce a content-merge. May
  197.      *            be <code>null</code> if the merge-result was produced without
  198.      *            computing a common base
  199.      * @param mergedCommits
  200.      *            all the commits which have been merged together
  201.      * @param mergeStatus
  202.      *            the status the merge resulted in
  203.      * @param mergeStrategy
  204.      *            the used {@link org.eclipse.jgit.merge.MergeStrategy}
  205.      * @param lowLevelResults
  206.      *            merge results as returned by
  207.      *            {@link org.eclipse.jgit.merge.ResolveMerger#getMergeResults()}
  208.      * @since 2.0
  209.      */
  210.     public MergeResult(ObjectId newHead, ObjectId base,
  211.             ObjectId[] mergedCommits, MergeStatus mergeStatus,
  212.             MergeStrategy mergeStrategy,
  213.             Map<String, org.eclipse.jgit.merge.MergeResult<?>> lowLevelResults) {
  214.         this(newHead, base, mergedCommits, mergeStatus, mergeStrategy,
  215.                 lowLevelResults, null);
  216.     }

  217.     /**
  218.      * Constructor for MergeResult.
  219.      *
  220.      * @param newHead
  221.      *            the object the head points at after the merge
  222.      * @param base
  223.      *            the common base which was used to produce a content-merge. May
  224.      *            be <code>null</code> if the merge-result was produced without
  225.      *            computing a common base
  226.      * @param mergedCommits
  227.      *            all the commits which have been merged together
  228.      * @param mergeStatus
  229.      *            the status the merge resulted in
  230.      * @param mergeStrategy
  231.      *            the used {@link org.eclipse.jgit.merge.MergeStrategy}
  232.      * @param lowLevelResults
  233.      *            merge results as returned by
  234.      *            {@link org.eclipse.jgit.merge.ResolveMerger#getMergeResults()}
  235.      * @param description
  236.      *            a user friendly description of the merge result
  237.      */
  238.     public MergeResult(ObjectId newHead, ObjectId base,
  239.             ObjectId[] mergedCommits, MergeStatus mergeStatus,
  240.             MergeStrategy mergeStrategy,
  241.             Map<String, org.eclipse.jgit.merge.MergeResult<?>> lowLevelResults,
  242.             String description) {
  243.         this(newHead, base, mergedCommits, mergeStatus, mergeStrategy,
  244.                 lowLevelResults, null, description);
  245.     }

  246.     /**
  247.      * Constructor for MergeResult.
  248.      *
  249.      * @param newHead
  250.      *            the object the head points at after the merge
  251.      * @param base
  252.      *            the common base which was used to produce a content-merge. May
  253.      *            be <code>null</code> if the merge-result was produced without
  254.      *            computing a common base
  255.      * @param mergedCommits
  256.      *            all the commits which have been merged together
  257.      * @param mergeStatus
  258.      *            the status the merge resulted in
  259.      * @param mergeStrategy
  260.      *            the used {@link org.eclipse.jgit.merge.MergeStrategy}
  261.      * @param lowLevelResults
  262.      *            merge results as returned by
  263.      *            {@link org.eclipse.jgit.merge.ResolveMerger#getMergeResults()}
  264.      * @param failingPaths
  265.      *            list of paths causing this merge to fail as returned by
  266.      *            {@link org.eclipse.jgit.merge.ResolveMerger#getFailingPaths()}
  267.      * @param description
  268.      *            a user friendly description of the merge result
  269.      */
  270.     public MergeResult(ObjectId newHead, ObjectId base,
  271.             ObjectId[] mergedCommits, MergeStatus mergeStatus,
  272.             MergeStrategy mergeStrategy,
  273.             Map<String, org.eclipse.jgit.merge.MergeResult<?>> lowLevelResults,
  274.             Map<String, MergeFailureReason> failingPaths, String description) {
  275.         this.newHead = newHead;
  276.         this.mergedCommits = mergedCommits;
  277.         this.base = base;
  278.         this.mergeStatus = mergeStatus;
  279.         this.mergeStrategy = mergeStrategy;
  280.         this.description = description;
  281.         this.failingPaths = failingPaths;
  282.         if (lowLevelResults != null)
  283.             for (Map.Entry<String, org.eclipse.jgit.merge.MergeResult<?>> result : lowLevelResults
  284.                     .entrySet())
  285.                 addConflict(result.getKey(), result.getValue());
  286.     }

  287.     /**
  288.      * Creates a new result that represents a checkout conflict before the
  289.      * operation even started for real.
  290.      *
  291.      * @param checkoutConflicts
  292.      *            the conflicting files
  293.      */
  294.     public MergeResult(List<String> checkoutConflicts) {
  295.         this.checkoutConflicts = checkoutConflicts;
  296.         this.mergeStatus = MergeStatus.CHECKOUT_CONFLICT;
  297.     }

  298.     /**
  299.      * Get the object the head points at after the merge
  300.      *
  301.      * @return the object the head points at after the merge
  302.      */
  303.     public ObjectId getNewHead() {
  304.         return newHead;
  305.     }

  306.     /**
  307.      * Get the merge status
  308.      *
  309.      * @return the status the merge resulted in
  310.      */
  311.     public MergeStatus getMergeStatus() {
  312.         return mergeStatus;
  313.     }

  314.     /**
  315.      * Get the commits which have been merged
  316.      *
  317.      * @return all the commits which have been merged together
  318.      */
  319.     public ObjectId[] getMergedCommits() {
  320.         return mergedCommits;
  321.     }

  322.     /**
  323.      * Get the common base
  324.      *
  325.      * @return base the common base which was used to produce a content-merge.
  326.      *         May be <code>null</code> if the merge-result was produced without
  327.      *         computing a common base
  328.      */
  329.     public ObjectId getBase() {
  330.         return base;
  331.     }

  332.     /** {@inheritDoc} */
  333.     @SuppressWarnings("nls")
  334.     @Override
  335.     public String toString() {
  336.         boolean first = true;
  337.         StringBuilder commits = new StringBuilder();
  338.         for (ObjectId commit : mergedCommits) {
  339.             if (!first)
  340.                 commits.append(", ");
  341.             else
  342.                 first = false;
  343.             commits.append(ObjectId.toString(commit));
  344.         }
  345.         return MessageFormat.format(
  346.                 JGitText.get().mergeUsingStrategyResultedInDescription,
  347.                 commits, ObjectId.toString(base), mergeStrategy.getName(),
  348.                 mergeStatus, (description == null ? "" : ", " + description));
  349.     }

  350.     /**
  351.      * Set conflicts
  352.      *
  353.      * @param conflicts
  354.      *            the conflicts to set
  355.      */
  356.     public void setConflicts(Map<String, int[][]> conflicts) {
  357.         this.conflicts = conflicts;
  358.     }

  359.     /**
  360.      * Add a conflict
  361.      *
  362.      * @param path
  363.      *            path of the file to add a conflict for
  364.      * @param conflictingRanges
  365.      *            the conflicts to set
  366.      */
  367.     public void addConflict(String path, int[][] conflictingRanges) {
  368.         if (conflicts == null)
  369.             conflicts = new HashMap<>();
  370.         conflicts.put(path, conflictingRanges);
  371.     }

  372.     /**
  373.      * Add a conflict
  374.      *
  375.      * @param path
  376.      *            path of the file to add a conflict for
  377.      * @param lowLevelResult
  378.      *            a {@link org.eclipse.jgit.merge.MergeResult}
  379.      */
  380.     public void addConflict(String path, org.eclipse.jgit.merge.MergeResult<?> lowLevelResult) {
  381.         if (!lowLevelResult.containsConflicts())
  382.             return;
  383.         if (conflicts == null)
  384.             conflicts = new HashMap<>();
  385.         int nrOfConflicts = 0;
  386.         // just counting
  387.         for (MergeChunk mergeChunk : lowLevelResult) {
  388.             if (mergeChunk.getConflictState().equals(ConflictState.FIRST_CONFLICTING_RANGE)) {
  389.                 nrOfConflicts++;
  390.             }
  391.         }
  392.         int currentConflict = -1;
  393.         int[][] ret=new int[nrOfConflicts][mergedCommits.length+1];
  394.         for (MergeChunk mergeChunk : lowLevelResult) {
  395.             // to store the end of this chunk (end of the last conflicting range)
  396.             int endOfChunk = 0;
  397.             if (mergeChunk.getConflictState().equals(ConflictState.FIRST_CONFLICTING_RANGE)) {
  398.                 if (currentConflict > -1) {
  399.                     // there was a previous conflicting range for which the end
  400.                     // is not set yet - set it!
  401.                     ret[currentConflict][mergedCommits.length] = endOfChunk;
  402.                 }
  403.                 currentConflict++;
  404.                 endOfChunk = mergeChunk.getEnd();
  405.                 ret[currentConflict][mergeChunk.getSequenceIndex()] = mergeChunk.getBegin();
  406.             }
  407.             if (mergeChunk.getConflictState().equals(ConflictState.NEXT_CONFLICTING_RANGE)) {
  408.                 if (mergeChunk.getEnd() > endOfChunk)
  409.                     endOfChunk = mergeChunk.getEnd();
  410.                 ret[currentConflict][mergeChunk.getSequenceIndex()] = mergeChunk.getBegin();
  411.             }
  412.         }
  413.         conflicts.put(path, ret);
  414.     }

  415.     /**
  416.      * Returns information about the conflicts which occurred during a
  417.      * {@link org.eclipse.jgit.api.MergeCommand}. The returned value maps the
  418.      * path of a conflicting file to a two-dimensional int-array of line-numbers
  419.      * telling where in the file conflict markers for which merged commit can be
  420.      * found.
  421.      * <p>
  422.      * If the returned value contains a mapping "path"-&gt;[x][y]=z then this
  423.      * means
  424.      * <ul>
  425.      * <li>the file with path "path" contains conflicts</li>
  426.      * <li>if y &lt; "number of merged commits": for conflict number x in this
  427.      * file the chunk which was copied from commit number y starts on line
  428.      * number z. All numberings and line numbers start with 0.</li>
  429.      * <li>if y == "number of merged commits": the first non-conflicting line
  430.      * after conflict number x starts at line number z</li>
  431.      * </ul>
  432.      * <p>
  433.      * Example code how to parse this data:
  434.      *
  435.      * <pre>
  436.      * MergeResult m=...;
  437.      * Map&lt;String, int[][]&gt; allConflicts = m.getConflicts();
  438.      * for (String path : allConflicts.keySet()) {
  439.      *  int[][] c = allConflicts.get(path);
  440.      *  System.out.println("Conflicts in file " + path);
  441.      *  for (int i = 0; i &lt; c.length; ++i) {
  442.      *      System.out.println("  Conflict #" + i);
  443.      *      for (int j = 0; j &lt; (c[i].length) - 1; ++j) {
  444.      *          if (c[i][j] &gt;= 0)
  445.      *              System.out.println("    Chunk for "
  446.      *                      + m.getMergedCommits()[j] + " starts on line #"
  447.      *                      + c[i][j]);
  448.      *      }
  449.      *  }
  450.      * }
  451.      * </pre>
  452.      *
  453.      * @return the conflicts or <code>null</code> if no conflict occurred
  454.      */
  455.     public Map<String, int[][]> getConflicts() {
  456.         return conflicts;
  457.     }

  458.     /**
  459.      * Returns a list of paths causing this merge to fail as returned by
  460.      * {@link org.eclipse.jgit.merge.ResolveMerger#getFailingPaths()}
  461.      *
  462.      * @return the list of paths causing this merge to fail or <code>null</code>
  463.      *         if no failure occurred
  464.      */
  465.     public Map<String, MergeFailureReason> getFailingPaths() {
  466.         return failingPaths;
  467.     }

  468.     /**
  469.      * Returns a list of paths that cause a checkout conflict. These paths
  470.      * prevent the operation from even starting.
  471.      *
  472.      * @return the list of files that caused the checkout conflict.
  473.      */
  474.     public List<String> getCheckoutConflicts() {
  475.         return checkoutConflicts;
  476.     }
  477. }