From 75c9b2438594dc6ac125ff1bdf97022c7f429b78 Mon Sep 17 00:00:00 2001 From: Christian Halstrick Date: Thu, 19 Aug 2010 15:52:12 +0200 Subject: [PATCH] Enhance MergeResult to report conflicts, etc The MergeResult class is enhanced to report more data about a three-way merge. Information about conflicts and the base, ours, theirs commits can be retrived. Change-Id: Iaaf41a1f4002b8fe3ddfa62dc73c787f363460c2 Signed-off-by: Christian Halstrick Signed-off-by: Chris Aniszczyk --- .../org/eclipse/jgit/JGitText.properties | 2 +- .../org/eclipse/jgit/api/MergeCommand.java | 26 ++-- .../src/org/eclipse/jgit/api/MergeResult.java | 133 ++++++++++++++++-- 3 files changed, 136 insertions(+), 25 deletions(-) diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties index a9878f8d2..faeb1d53f 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties @@ -222,7 +222,7 @@ lockOnNotHeld=Lock on {0} not held. malformedpersonIdentString=Malformed PersonIdent string (no < was found): {0} mergeStrategyAlreadyExistsAsDefault=Merge strategy "{0}" already exists as a default strategy mergeStrategyDoesNotSupportHeads=merge strategy {0} does not support {1} heads to be merged into HEAD -mergeUsingStrategyResultedInDescription=Merge using strategy {0} resulted in: {1}. {2} +mergeUsingStrategyResultedInDescription=Merge of revisions {0} with base {1} using strategy {2} resulted in: {3}. {4} missingAccesskey=Missing accesskey. missingDeltaBase=delta base missingForwardImageInGITBinaryPatch=Missing forward-image in GIT binary patch diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java index 972aa618a..4d37c28e0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java @@ -114,7 +114,8 @@ public class MergeCommand extends GitCommand { try { Ref head = repo.getRef(Constants.HEAD); if (head == null) - throw new NoHeadException(JGitText.get().commitOnRepoWithoutHEADCurrentlyNotSupported); + throw new NoHeadException( + JGitText.get().commitOnRepoWithoutHEADCurrentlyNotSupported); StringBuilder refLogMessage = new StringBuilder("merge "); // Check for FAST_FORWARD, ALREADY_UP_TO_DATE @@ -134,7 +135,8 @@ public class MergeCommand extends GitCommand { RevCommit srcCommit = revWalk.lookupCommit(objectId); if (revWalk.isMergedInto(srcCommit, headCommit)) { setCallable(false); - return new MergeResult(headCommit, + return new MergeResult(headCommit, srcCommit, + new ObjectId[] { srcCommit, headCommit }, MergeStatus.ALREADY_UP_TO_DATE, mergeStrategy); } else if (revWalk.isMergedInto(headCommit, srcCommit)) { // FAST_FORWARD detected: skip doing a real merge but only @@ -143,11 +145,14 @@ public class MergeCommand extends GitCommand { checkoutNewHead(revWalk, headCommit, srcCommit); updateHead(refLogMessage, srcCommit, head.getObjectId()); setCallable(false); - return new MergeResult(srcCommit, MergeStatus.FAST_FORWARD, - mergeStrategy); + return new MergeResult(srcCommit, headCommit, + new ObjectId[] { srcCommit, headCommit }, + MergeStatus.FAST_FORWARD, mergeStrategy); } else { return new MergeResult( headCommit, + null, + new ObjectId[] { srcCommit, headCommit }, MergeResult.MergeStatus.NOT_SUPPORTED, mergeStrategy, JGitText.get().onlyAlreadyUpToDateAndFastForwardMergesAreAvailable); @@ -164,7 +169,8 @@ public class MergeCommand extends GitCommand { } private void checkoutNewHead(RevWalk revWalk, RevCommit headCommit, - RevCommit newHeadCommit) throws IOException, CheckoutConflictException { + RevCommit newHeadCommit) throws IOException, + CheckoutConflictException { GitIndex index = repo.getIndex(); File workDir = repo.getWorkTree(); @@ -184,8 +190,8 @@ public class MergeCommand extends GitCommand { } } - private void updateHead(StringBuilder refLogMessage, - ObjectId newHeadId, ObjectId oldHeadID) throws IOException, + private void updateHead(StringBuilder refLogMessage, ObjectId newHeadId, + ObjectId oldHeadID) throws IOException, ConcurrentRefUpdateException { RefUpdate refUpdate = repo.updateRef(Constants.HEAD); refUpdate.setNewObjectId(newHeadId); @@ -221,8 +227,7 @@ public class MergeCommand extends GitCommand { /** * @param commit - * a reference to a commit which is merged with the current - * head + * a reference to a commit which is merged with the current head * @return {@code this} */ public MergeCommand include(Ref commit) { @@ -241,7 +246,8 @@ public class MergeCommand extends GitCommand { } /** - * @param name a name given to the commit + * @param name + * a name given to the commit * @param commit * the Id of a commit which is merged with the current head * @return {@code this} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeResult.java index a293ad0c9..6fcf2ee6d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeResult.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeResult.java @@ -44,6 +44,7 @@ package org.eclipse.jgit.api; import java.text.MessageFormat; +import java.util.Map; import org.eclipse.jgit.JGitText; import org.eclipse.jgit.lib.ObjectId; @@ -84,6 +85,12 @@ public class MergeResult { } }, /** */ + CONFLICTING { + public String toString() { + return "Conflicting"; + } + }, + /** */ NOT_SUPPORTED { public String toString() { return "Not-yet-supported"; @@ -91,8 +98,14 @@ public class MergeResult { } } + private ObjectId[] mergedCommits; + + private ObjectId base; + private ObjectId newHead; + private Map conflicts; + private MergeStatus mergeStatus; private String description; @@ -100,26 +113,47 @@ public class MergeResult { private MergeStrategy mergeStrategy; /** - * @param newHead the object the head points at after the merge - * @param mergeStatus the status the merge resulted in - * @param mergeStrategy the used {@link MergeStrategy} + * @param newHead + * the object the head points at after the merge + * @param base + * the common base which was used to produce a content-merge. May + * be null if the merge-result was produced without + * computing a common base + * @param mergedCommits + * all the commits which have been merged together + * @param mergeStatus + * the status the merge resulted in + * @param mergeStrategy + * the used {@link MergeStrategy} */ - public MergeResult(ObjectId newHead, MergeStatus mergeStatus, + public MergeResult(ObjectId newHead, ObjectId base, + ObjectId[] mergedCommits, MergeStatus mergeStatus, MergeStrategy mergeStrategy) { - this.newHead = newHead; - this.mergeStatus = mergeStatus; - this.mergeStrategy = mergeStrategy; + this(newHead, base, mergedCommits, mergeStatus, mergeStrategy, null); } /** - * @param newHead the object the head points at after the merge - * @param mergeStatus the status the merge resulted in - * @param mergeStrategy the used {@link MergeStrategy} - * @param description a user friendly description of the merge result + * @param newHead + * the object the head points at after the merge + * @param base + * the common base which was used to produce a content-merge. May + * be null if the merge-result was produced without + * computing a common base + * @param mergedCommits + * all the commits which have been merged together + * @param mergeStatus + * the status the merge resulted in + * @param mergeStrategy + * the used {@link MergeStrategy} + * @param description + * a user friendly description of the merge result */ - public MergeResult(ObjectId newHead, MergeStatus mergeStatus, + public MergeResult(ObjectId newHead, ObjectId base, + ObjectId[] mergedCommits, MergeStatus mergeStatus, MergeStrategy mergeStrategy, String description) { this.newHead = newHead; + this.mergedCommits = mergedCommits; + this.base = base; this.mergeStatus = mergeStatus; this.mergeStrategy = mergeStrategy; this.description = description; @@ -139,12 +173,83 @@ public class MergeResult { return mergeStatus; } + /** + * @return all the commits which have been merged together + */ + public ObjectId[] getMergedCommits() { + return mergedCommits; + } + + /** + * @return base the common base which was used to produce a content-merge. + * May be null if the merge-result was produced without + * computing a common base + */ + public ObjectId getBase() { + return base; + } + @Override public String toString() { + boolean first = true; + StringBuilder commits = new StringBuilder(); + for (ObjectId commit : mergedCommits) { + if (!first) + commits.append(", "); + else + first = false; + commits.append(ObjectId.toString(commit)); + } return MessageFormat.format( JGitText.get().mergeUsingStrategyResultedInDescription, - mergeStrategy.getName(), mergeStatus, (description == null ? "" - : ", " + description)); + commits, ObjectId.toString(base), mergeStrategy.getName(), + mergeStatus, (description == null ? "" : ", " + description)); } + /** + * @param conflicts + * the conflicts to set + */ + public void setConflicts(Map conflicts) { + this.conflicts = conflicts; + } + + /** + * Returns information about the conflicts which occurred during a + * {@link MergeCommand}. The returned value maps the path of a conflicting + * file to a two-dimensional int-array of line-numbers telling where in the + * file conflict markers for which merged commit can be found. + *

+ * If the returned value contains a mapping "path"->[x][y]=z then this means + *

    + *
  • the file with path "path" contains conflicts
  • + *
  • if y < "number of merged commits": for conflict number x in this file + * the chunk which was copied from commit number y starts on line number z. + * All numberings and line numbers start with 0.
  • + *
  • if y == "number of merged commits": the first non-conflicting line + * after conflict number x starts at line number z
  • + *
+ *

+ * Example code how to parse this data: + *

 MergeResult m=...;
+	 * Map allConflicts = m.getConflicts();
+	 * for (String path : allConflicts.keySet()) {
+	 * 	int[][] c = allConflicts.get(path);
+	 * 	System.out.println("Conflicts in file " + path);
+	 * 	for (int i = 0; i < c.length; ++i) {
+	 * 		System.out.println("  Conflict #" + i);
+	 * 		for (int j = 0; j < (c[i].length) - 1; ++j) {
+	 * 			if (c[i][j] >= 0)
+	 * 				System.out.println("    Chunk for "
+	 * 						+ m.getMergedCommits()[j] + " starts on line #"
+	 * 						+ c[i][j]);
+	 * 		}
+	 * 	}
+	 * }
+ * + * @return the conflicts or null if no conflict occured + */ + public Map getConflicts() { + return conflicts; + } }