aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRobin Stocker2011-04-03 13:37:55 (EDT)
committerChris Aniszczyk2011-04-06 14:28:10 (EDT)
commit6e10aa42e90a25b82f00f0c27574f57ffa9e4a25 (patch)
treee2bf294858e9a16220df5ab54508d94267c14109
parentfbf35fea4ec254339f9b0eee7865eb6ccfe22700 (diff)
downloadjgit-6e10aa42e90a25b82f00f0c27574f57ffa9e4a25.zip
jgit-6e10aa42e90a25b82f00f0c27574f57ffa9e4a25.tar.gz
jgit-6e10aa42e90a25b82f00f0c27574f57ffa9e4a25.tar.bz2
Add CHERRY_PICK_HEAD for cherry-pick conflictsrefs/changes/96/2996/3
Add handling of CHERRY_PICK_HEAD file in .git (similar to MERGE_HEAD), which is written in case of a conflicting cherry-pick merge. It is used so that Repository.getRepositoryState can return the new states CHERRY_PICKING and CHERRY_PICKING_RESOLVED. These states, as well as CHERRY_PICK_HEAD can be used in EGit to properly show the merge tool. Also, in case of a conflict, MERGE_MSG is written with the original commit message and a "Conflicts" section appended. This way, the cherry-picked message is not lost and can later be re-used in the commit dialog. Bug: 339092 Change-Id: I947967fdc2f1d55016c95106b104c2afcc9797a1 Signed-off-by: Robin Stocker <robin@nibor.org> Signed-off-by: Chris Aniszczyk <caniszczyk@gmail.com>
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java52
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java11
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java24
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java72
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryState.java20
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java21
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectory.java3
10 files changed, 203 insertions, 10 deletions
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java
index 1646c7b..94af81e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java
@@ -44,14 +44,17 @@ package org.eclipse.jgit.api;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import org.eclipse.jgit.api.CherryPickResult.CherryPickStatus;
+import org.eclipse.jgit.api.ResetCommand.ResetType;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.RepositoryState;
import org.eclipse.jgit.lib.RepositoryTestCase;
import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
@@ -130,6 +133,55 @@ public class CherryPickCommandTest extends RepositoryTestCase {
MergeFailureReason.DIRTY_WORKTREE);
}
+ @Test
+ public void testCherryPickConflictResolution() throws Exception {
+ Git git = new Git(db);
+ RevCommit sideCommit = prepareCherryPick(git);
+
+ CherryPickResult result = git.cherryPick().include(sideCommit.getId())
+ .call();
+
+ assertEquals(CherryPickStatus.CONFLICTING, result.getStatus());
+ assertTrue(new File(db.getDirectory(), Constants.MERGE_MSG).exists());
+ assertEquals("side\n\nConflicts:\n\ta\n", db.readMergeCommitMsg());
+ assertTrue(new File(db.getDirectory(), Constants.CHERRY_PICK_HEAD)
+ .exists());
+ assertEquals(sideCommit.getId(), db.readCherryPickHead());
+ assertEquals(RepositoryState.CHERRY_PICKING, db.getRepositoryState());
+
+ // Resolve
+ writeTrashFile("a", "a");
+ git.add().addFilepattern("a").call();
+
+ assertEquals(RepositoryState.CHERRY_PICKING_RESOLVED,
+ db.getRepositoryState());
+
+ git.commit().setOnly("a").setMessage("resolve").call();
+
+ assertEquals(RepositoryState.SAFE, db.getRepositoryState());
+ }
+
+ @Test
+ public void testCherryPickConflictReset() throws Exception {
+ Git git = new Git(db);
+
+ RevCommit sideCommit = prepareCherryPick(git);
+
+ CherryPickResult result = git.cherryPick().include(sideCommit.getId())
+ .call();
+
+ assertEquals(CherryPickStatus.CONFLICTING, result.getStatus());
+ assertEquals(RepositoryState.CHERRY_PICKING, db.getRepositoryState());
+ assertTrue(new File(db.getDirectory(), Constants.CHERRY_PICK_HEAD)
+ .exists());
+
+ git.reset().setMode(ResetType.MIXED).setRef("HEAD").call();
+
+ assertEquals(RepositoryState.SAFE, db.getRepositoryState());
+ assertFalse(new File(db.getDirectory(), Constants.CHERRY_PICK_HEAD)
+ .exists());
+ }
+
private RevCommit prepareCherryPick(final Git git) throws Exception {
// create, add and commit file a
writeTrashFile("a", "a");
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java
index 1ececd2..c8b7e89 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java
@@ -60,6 +60,7 @@ import org.eclipse.jgit.lib.ObjectIdRef;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Ref.Storage;
import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.merge.MergeMessageFormatter;
import org.eclipse.jgit.merge.MergeStrategy;
import org.eclipse.jgit.merge.ResolveMerger;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -150,7 +151,15 @@ public class CherryPickCommand extends GitCommand<CherryPickResult> {
if (merger.failed())
return new CherryPickResult(merger.getFailingPaths());
- // merge conflicts
+ // there are merge conflicts
+
+ String message = new MergeMessageFormatter()
+ .formatWithConflicts(srcCommit.getFullMessage(),
+ merger.getUnmergedPaths());
+
+ repo.writeCherryPickHead(srcCommit.getId());
+ repo.writeMergeCommitMsg(message);
+
return CherryPickResult.CONFLICT;
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
index 963e3ed..2412e2c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
@@ -233,6 +233,9 @@ public class CommitCommand extends GitCommand<RevCommit> {
// used for merge commits
repo.writeMergeCommitMsg(null);
repo.writeMergeHeads(null);
+ } else if (state == RepositoryState.CHERRY_PICKING_RESOLVED) {
+ repo.writeMergeCommitMsg(null);
+ repo.writeCherryPickHead(null);
}
return revCommit;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
index cf2111e..7e2e677 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
@@ -399,6 +399,9 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
Constants.CHARACTER_ENCODING));
createFile(rebaseDir, STOPPED_SHA, repo.newObjectReader().abbreviate(
commitToPick).name());
+ // Remove cherry pick state file created by CherryPickCommand, it's not
+ // needed for rebase
+ repo.writeCherryPickHead(null);
return new RebaseResult(commitToPick);
}
@@ -744,6 +747,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
}
// cleanup the files
FileUtils.delete(rebaseDir, FileUtils.RECURSIVE);
+ repo.writeCherryPickHead(null);
return result;
} finally {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java
index d55767d..24aae22 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java
@@ -129,11 +129,12 @@ public class ResetCommand extends GitCommand<Ref> {
RevCommit commit;
try {
- boolean merging = false;
- if (repo.getRepositoryState().equals(RepositoryState.MERGING)
- || repo.getRepositoryState().equals(
- RepositoryState.MERGING_RESOLVED))
- merging = true;
+ RepositoryState state = repo.getRepositoryState();
+ final boolean merging = state.equals(RepositoryState.MERGING)
+ || state.equals(RepositoryState.MERGING_RESOLVED);
+ final boolean cherryPicking = state
+ .equals(RepositoryState.CHERRY_PICKING)
+ || state.equals(RepositoryState.CHERRY_PICKING_RESOLVED);
// resolve the ref to a commit
final ObjectId commitId;
@@ -183,8 +184,12 @@ public class ResetCommand extends GitCommand<Ref> {
}
- if (mode != ResetType.SOFT && merging)
- resetMerge();
+ if (mode != ResetType.SOFT) {
+ if (merging)
+ resetMerge();
+ else if (cherryPicking)
+ resetCherryPick();
+ }
setCallable(false);
r = ru.getRef();
@@ -255,4 +260,9 @@ public class ResetCommand extends GitCommand<Ref> {
repo.writeMergeCommitMsg(null);
}
+ private void resetCherryPick() throws IOException {
+ repo.writeCherryPickHead(null);
+ repo.writeMergeCommitMsg(null);
+ }
+
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
index 0290689..1d77cc1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
@@ -536,6 +536,9 @@ public final class Constants {
/** name of the file containing the IDs of the parents of a merge commit */
public static final String MERGE_HEAD = "MERGE_HEAD";
+ /** name of the file containing the ID of a cherry pick commit in case of conflicts */
+ public static final String CHERRY_PICK_HEAD = "CHERRY_PICK_HEAD";
+
/**
* name of the ref ORIG_HEAD used by certain commands to store the original
* value of HEAD
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
index 759ab3e..4847a5d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
@@ -922,7 +922,7 @@ public abstract class Repository {
return RepositoryState.REBASING_MERGE;
// Both versions
- if (new File(getDirectory(), "MERGE_HEAD").exists()) {
+ if (new File(getDirectory(), Constants.MERGE_HEAD).exists()) {
// we are merging - now check whether we have unmerged paths
try {
if (!readDirCache().hasUnmergedPaths()) {
@@ -941,6 +941,20 @@ public abstract class Repository {
if (new File(getDirectory(), "BISECT_LOG").exists())
return RepositoryState.BISECTING;
+ if (new File(getDirectory(), Constants.CHERRY_PICK_HEAD).exists()) {
+ try {
+ if (!readDirCache().hasUnmergedPaths()) {
+ // no unmerged paths
+ return RepositoryState.CHERRY_PICKING_RESOLVED;
+ }
+ } catch (IOException e) {
+ // fall through to CHERRY_PICKING
+ e.printStackTrace();
+ }
+
+ return RepositoryState.CHERRY_PICKING;
+ }
+
return RepositoryState.SAFE;
}
@@ -1192,4 +1206,60 @@ public abstract class Repository {
FileUtils.delete(mergeHeadFile);
}
}
+
+ /**
+ * Return the information stored in the file $GIT_DIR/CHERRY_PICK_HEAD.
+ *
+ * @return object id from CHERRY_PICK_HEAD file or {@code null} if this file
+ * doesn't exist. Also if the file exists but is empty {@code null}
+ * will be returned
+ * @throws IOException
+ * @throws NoWorkTreeException
+ * if this is bare, which implies it has no working directory.
+ * See {@link #isBare()}.
+ */
+ public ObjectId readCherryPickHead() throws IOException,
+ NoWorkTreeException {
+ if (isBare() || getDirectory() == null)
+ throw new NoWorkTreeException();
+
+ File mergeHeadFile = new File(getDirectory(),
+ Constants.CHERRY_PICK_HEAD);
+ byte[] raw;
+ try {
+ raw = IO.readFully(mergeHeadFile);
+ } catch (FileNotFoundException notFound) {
+ return null;
+ }
+
+ if (raw.length == 0)
+ return null;
+
+ return ObjectId.fromString(raw, 0);
+ }
+
+ /**
+ * Write cherry pick commit into $GIT_DIR/CHERRY_PICK_HEAD. This is used in
+ * case of conflicts to store the cherry which was tried to be picked.
+ *
+ * @param head
+ * an object id of the cherry commit or <code>null</code> to
+ * delete the file
+ * @throws IOException
+ */
+ public void writeCherryPickHead(ObjectId head) throws IOException {
+ File cherryPickHeadFile = new File(gitDir, Constants.CHERRY_PICK_HEAD);
+ if (head != null) {
+ BufferedOutputStream bos = new BufferedOutputStream(
+ new FileOutputStream(cherryPickHeadFile));
+ try {
+ head.copyTo(bos);
+ bos.write('\n');
+ } finally {
+ bos.close();
+ }
+ } else {
+ FileUtils.delete(cherryPickHeadFile, FileUtils.SKIP_MISSING);
+ }
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryState.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryState.java
index e0b5389..1017062 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryState.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryState.java
@@ -93,6 +93,26 @@ public enum RepositoryState {
public String getDescription() { return JGitText.get().repositoryState_merged; }
},
+ /** An unfinished cherry-pick. Must resolve or reset before continuing normally
+ */
+ CHERRY_PICKING {
+ public boolean canCheckout() { return false; }
+ public boolean canResetHead() { return true; }
+ public boolean canCommit() { return false; }
+ public String getDescription() { return JGitText.get().repositoryState_conflicts; }
+ },
+
+ /**
+ * A cherry-pick where all conflicts have been resolved. The index does not
+ * contain any unmerged paths.
+ */
+ CHERRY_PICKING_RESOLVED {
+ public boolean canCheckout() { return true; }
+ public boolean canResetHead() { return true; }
+ public boolean canCommit() { return true; }
+ public String getDescription() { return JGitText.get().repositoryState_merged; }
+ },
+
/**
* An unfinished rebase or am. Must resolve, skip or abort before normal work can take place
*/
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java
index cdd7a2f..96395d0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java
@@ -123,6 +123,27 @@ public class MergeMessageFormatter {
return sb.toString();
}
+ /**
+ * Add section with conflicting paths to merge message.
+ *
+ * @param message
+ * the original merge message
+ * @param conflictingPaths
+ * the paths with conflicts
+ * @return merge message with conflicting paths added
+ */
+ public String formatWithConflicts(String message,
+ List<String> conflictingPaths) {
+ StringBuilder sb = new StringBuilder(message);
+ if (!message.endsWith("\n"))
+ sb.append("\n");
+ sb.append("\n");
+ sb.append("Conflicts:\n");
+ for (String conflictingPath : conflictingPaths)
+ sb.append('\t').append(conflictingPath).append('\n');
+ return sb.toString();
+ }
+
private static String joinNames(List<String> names, String singular,
String plural) {
if (names.size() == 1)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectory.java
index 5a36a71..bba634a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectory.java
@@ -130,7 +130,8 @@ public class RefDirectory extends RefDatabase {
/** The names of the additional refs supported by this class */
private static final String[] additionalRefsNames = new String[] {
- Constants.MERGE_HEAD, Constants.FETCH_HEAD, Constants.ORIG_HEAD };
+ Constants.MERGE_HEAD, Constants.FETCH_HEAD, Constants.ORIG_HEAD,
+ Constants.CHERRY_PICK_HEAD };
private final FileRepository parent;