Browse Source

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 <christian.halstrick@sap.com>
Signed-off-by: Chris Aniszczyk <caniszczyk@gmail.com>
stable-0.9
Christian Halstrick 14 years ago committed by Chris Aniszczyk
parent
commit
75c9b24385
  1. 2
      org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties
  2. 26
      org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java
  3. 133
      org.eclipse.jgit/src/org/eclipse/jgit/api/MergeResult.java

2
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} malformedpersonIdentString=Malformed PersonIdent string (no < was found): {0}
mergeStrategyAlreadyExistsAsDefault=Merge strategy "{0}" already exists as a default strategy mergeStrategyAlreadyExistsAsDefault=Merge strategy "{0}" already exists as a default strategy
mergeStrategyDoesNotSupportHeads=merge strategy {0} does not support {1} heads to be merged into HEAD 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. missingAccesskey=Missing accesskey.
missingDeltaBase=delta base missingDeltaBase=delta base
missingForwardImageInGITBinaryPatch=Missing forward-image in GIT binary patch missingForwardImageInGITBinaryPatch=Missing forward-image in GIT binary patch

26
org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java

@ -114,7 +114,8 @@ public class MergeCommand extends GitCommand<MergeResult> {
try { try {
Ref head = repo.getRef(Constants.HEAD); Ref head = repo.getRef(Constants.HEAD);
if (head == null) if (head == null)
throw new NoHeadException(JGitText.get().commitOnRepoWithoutHEADCurrentlyNotSupported); throw new NoHeadException(
JGitText.get().commitOnRepoWithoutHEADCurrentlyNotSupported);
StringBuilder refLogMessage = new StringBuilder("merge "); StringBuilder refLogMessage = new StringBuilder("merge ");
// Check for FAST_FORWARD, ALREADY_UP_TO_DATE // Check for FAST_FORWARD, ALREADY_UP_TO_DATE
@ -134,7 +135,8 @@ public class MergeCommand extends GitCommand<MergeResult> {
RevCommit srcCommit = revWalk.lookupCommit(objectId); RevCommit srcCommit = revWalk.lookupCommit(objectId);
if (revWalk.isMergedInto(srcCommit, headCommit)) { if (revWalk.isMergedInto(srcCommit, headCommit)) {
setCallable(false); setCallable(false);
return new MergeResult(headCommit, return new MergeResult(headCommit, srcCommit,
new ObjectId[] { srcCommit, headCommit },
MergeStatus.ALREADY_UP_TO_DATE, mergeStrategy); MergeStatus.ALREADY_UP_TO_DATE, mergeStrategy);
} else if (revWalk.isMergedInto(headCommit, srcCommit)) { } else if (revWalk.isMergedInto(headCommit, srcCommit)) {
// FAST_FORWARD detected: skip doing a real merge but only // FAST_FORWARD detected: skip doing a real merge but only
@ -143,11 +145,14 @@ public class MergeCommand extends GitCommand<MergeResult> {
checkoutNewHead(revWalk, headCommit, srcCommit); checkoutNewHead(revWalk, headCommit, srcCommit);
updateHead(refLogMessage, srcCommit, head.getObjectId()); updateHead(refLogMessage, srcCommit, head.getObjectId());
setCallable(false); setCallable(false);
return new MergeResult(srcCommit, MergeStatus.FAST_FORWARD, return new MergeResult(srcCommit, headCommit,
mergeStrategy); new ObjectId[] { srcCommit, headCommit },
MergeStatus.FAST_FORWARD, mergeStrategy);
} else { } else {
return new MergeResult( return new MergeResult(
headCommit, headCommit,
null,
new ObjectId[] { srcCommit, headCommit },
MergeResult.MergeStatus.NOT_SUPPORTED, MergeResult.MergeStatus.NOT_SUPPORTED,
mergeStrategy, mergeStrategy,
JGitText.get().onlyAlreadyUpToDateAndFastForwardMergesAreAvailable); JGitText.get().onlyAlreadyUpToDateAndFastForwardMergesAreAvailable);
@ -164,7 +169,8 @@ public class MergeCommand extends GitCommand<MergeResult> {
} }
private void checkoutNewHead(RevWalk revWalk, RevCommit headCommit, private void checkoutNewHead(RevWalk revWalk, RevCommit headCommit,
RevCommit newHeadCommit) throws IOException, CheckoutConflictException { RevCommit newHeadCommit) throws IOException,
CheckoutConflictException {
GitIndex index = repo.getIndex(); GitIndex index = repo.getIndex();
File workDir = repo.getWorkTree(); File workDir = repo.getWorkTree();
@ -184,8 +190,8 @@ public class MergeCommand extends GitCommand<MergeResult> {
} }
} }
private void updateHead(StringBuilder refLogMessage, private void updateHead(StringBuilder refLogMessage, ObjectId newHeadId,
ObjectId newHeadId, ObjectId oldHeadID) throws IOException, ObjectId oldHeadID) throws IOException,
ConcurrentRefUpdateException { ConcurrentRefUpdateException {
RefUpdate refUpdate = repo.updateRef(Constants.HEAD); RefUpdate refUpdate = repo.updateRef(Constants.HEAD);
refUpdate.setNewObjectId(newHeadId); refUpdate.setNewObjectId(newHeadId);
@ -221,8 +227,7 @@ public class MergeCommand extends GitCommand<MergeResult> {
/** /**
* @param commit * @param commit
* a reference to a commit which is merged with the current * a reference to a commit which is merged with the current head
* head
* @return {@code this} * @return {@code this}
*/ */
public MergeCommand include(Ref commit) { public MergeCommand include(Ref commit) {
@ -241,7 +246,8 @@ public class MergeCommand extends GitCommand<MergeResult> {
} }
/** /**
* @param name a name given to the commit * @param name
* a name given to the commit
* @param commit * @param commit
* the Id of a commit which is merged with the current head * the Id of a commit which is merged with the current head
* @return {@code this} * @return {@code this}

133
org.eclipse.jgit/src/org/eclipse/jgit/api/MergeResult.java

@ -44,6 +44,7 @@
package org.eclipse.jgit.api; package org.eclipse.jgit.api;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.Map;
import org.eclipse.jgit.JGitText; import org.eclipse.jgit.JGitText;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
@ -84,6 +85,12 @@ public class MergeResult {
} }
}, },
/** */ /** */
CONFLICTING {
public String toString() {
return "Conflicting";
}
},
/** */
NOT_SUPPORTED { NOT_SUPPORTED {
public String toString() { public String toString() {
return "Not-yet-supported"; return "Not-yet-supported";
@ -91,8 +98,14 @@ public class MergeResult {
} }
} }
private ObjectId[] mergedCommits;
private ObjectId base;
private ObjectId newHead; private ObjectId newHead;
private Map<String, int[][]> conflicts;
private MergeStatus mergeStatus; private MergeStatus mergeStatus;
private String description; private String description;
@ -100,26 +113,47 @@ public class MergeResult {
private MergeStrategy mergeStrategy; private MergeStrategy mergeStrategy;
/** /**
* @param newHead the object the head points at after the merge * @param newHead
* @param mergeStatus the status the merge resulted in * the object the head points at after the merge
* @param mergeStrategy the used {@link MergeStrategy} * @param base
* the common base which was used to produce a content-merge. May
* be <code>null</code> 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) { MergeStrategy mergeStrategy) {
this.newHead = newHead; this(newHead, base, mergedCommits, mergeStatus, mergeStrategy, null);
this.mergeStatus = mergeStatus;
this.mergeStrategy = mergeStrategy;
} }
/** /**
* @param newHead the object the head points at after the merge * @param newHead
* @param mergeStatus the status the merge resulted in * the object the head points at after the merge
* @param mergeStrategy the used {@link MergeStrategy} * @param base
* @param description a user friendly description of the merge result * the common base which was used to produce a content-merge. May
* be <code>null</code> 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) { MergeStrategy mergeStrategy, String description) {
this.newHead = newHead; this.newHead = newHead;
this.mergedCommits = mergedCommits;
this.base = base;
this.mergeStatus = mergeStatus; this.mergeStatus = mergeStatus;
this.mergeStrategy = mergeStrategy; this.mergeStrategy = mergeStrategy;
this.description = description; this.description = description;
@ -139,12 +173,83 @@ public class MergeResult {
return mergeStatus; 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 <code>null</code> if the merge-result was produced without
* computing a common base
*/
public ObjectId getBase() {
return base;
}
@Override @Override
public String toString() { 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( return MessageFormat.format(
JGitText.get().mergeUsingStrategyResultedInDescription, JGitText.get().mergeUsingStrategyResultedInDescription,
mergeStrategy.getName(), mergeStatus, (description == null ? "" commits, ObjectId.toString(base), mergeStrategy.getName(),
: ", " + description)); mergeStatus, (description == null ? "" : ", " + description));
} }
/**
* @param conflicts
* the conflicts to set
*/
public void setConflicts(Map<String, int[][]> 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.
* <p>
* If the returned value contains a mapping "path"->[x][y]=z then this means
* <ul>
* <li>the file with path "path" contains conflicts</li>
* <li>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.</li>
* <li>if y == "number of merged commits": the first non-conflicting line
* after conflict number x starts at line number z</li>
* </ul>
* <p>
* Example code how to parse this data:
* <pre> MergeResult m=...;
* Map<String, int[][]> 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]);
* }
* }
* }</pre>
*
* @return the conflicts or <code>null</code> if no conflict occured
*/
public Map<String, int[][]> getConflicts() {
return conflicts;
}
} }

Loading…
Cancel
Save