Improve handling of checkout conflicts
This converts a checkout conflict exception into a RebaseResult / MergeResult containing the conflicting paths, which enables EGit (or others) to handle the situation in a user-friendly way Change-Id: I48d9bdcc1e98095576513a54a225a42409f301f3
This commit is contained in:
parent
912ef3da19
commit
baf7ca9cc0
|
@ -1059,7 +1059,6 @@ public void testRebaseWithUntrackedFile() throws Exception {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SuppressWarnings("null")
|
|
||||||
public void testRebaseWithUnstagedTopicChange() throws Exception {
|
public void testRebaseWithUnstagedTopicChange() throws Exception {
|
||||||
// create file1, add and commit
|
// create file1, add and commit
|
||||||
writeTrashFile(FILE1, "file1");
|
writeTrashFile(FILE1, "file1");
|
||||||
|
@ -1084,19 +1083,14 @@ public void testRebaseWithUnstagedTopicChange() throws Exception {
|
||||||
writeTrashFile("file2", "unstaged file2");
|
writeTrashFile("file2", "unstaged file2");
|
||||||
|
|
||||||
// rebase
|
// rebase
|
||||||
JGitInternalException exception = null;
|
RebaseResult result = git.rebase().setUpstream("refs/heads/master")
|
||||||
try {
|
.call();
|
||||||
git.rebase().setUpstream("refs/heads/master").call();
|
assertEquals(Status.CONFLICTS, result.getStatus());
|
||||||
} catch (JGitInternalException e) {
|
assertEquals(1, result.getConflicts().size());
|
||||||
exception = e;
|
assertEquals("file2", result.getConflicts().get(0));
|
||||||
}
|
|
||||||
assertNotNull(exception);
|
|
||||||
assertEquals("Checkout conflict with files: \nfile2",
|
|
||||||
exception.getMessage());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SuppressWarnings("null")
|
|
||||||
public void testRebaseWithUncommittedTopicChange() throws Exception {
|
public void testRebaseWithUncommittedTopicChange() throws Exception {
|
||||||
// create file1, add and commit
|
// create file1, add and commit
|
||||||
writeTrashFile(FILE1, "file1");
|
writeTrashFile(FILE1, "file1");
|
||||||
|
@ -1122,23 +1116,17 @@ public void testRebaseWithUncommittedTopicChange() throws Exception {
|
||||||
git.add().addFilepattern("file2").call();
|
git.add().addFilepattern("file2").call();
|
||||||
// do not commit
|
// do not commit
|
||||||
|
|
||||||
// rebase
|
RebaseResult result = git.rebase().setUpstream("refs/heads/master")
|
||||||
JGitInternalException exception = null;
|
.call();
|
||||||
try {
|
assertEquals(Status.CONFLICTS, result.getStatus());
|
||||||
git.rebase().setUpstream("refs/heads/master").call();
|
assertEquals(1, result.getConflicts().size());
|
||||||
} catch (JGitInternalException e) {
|
assertEquals("file2", result.getConflicts().get(0));
|
||||||
exception = e;
|
|
||||||
}
|
|
||||||
assertNotNull(exception);
|
|
||||||
assertEquals("Checkout conflict with files: \nfile2",
|
|
||||||
exception.getMessage());
|
|
||||||
|
|
||||||
checkFile(uncommittedFile, "uncommitted file2");
|
checkFile(uncommittedFile, "uncommitted file2");
|
||||||
assertEquals(RepositoryState.SAFE, git.getRepository().getRepositoryState());
|
assertEquals(RepositoryState.SAFE, git.getRepository().getRepositoryState());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SuppressWarnings("null")
|
|
||||||
public void testRebaseWithUnstagedMasterChange() throws Exception {
|
public void testRebaseWithUnstagedMasterChange() throws Exception {
|
||||||
// create file1, add and commit
|
// create file1, add and commit
|
||||||
writeTrashFile(FILE1, "file1");
|
writeTrashFile(FILE1, "file1");
|
||||||
|
@ -1163,19 +1151,14 @@ public void testRebaseWithUnstagedMasterChange() throws Exception {
|
||||||
writeTrashFile(FILE1, "unstaged modified file1");
|
writeTrashFile(FILE1, "unstaged modified file1");
|
||||||
|
|
||||||
// rebase
|
// rebase
|
||||||
JGitInternalException exception = null;
|
RebaseResult result = git.rebase().setUpstream("refs/heads/master")
|
||||||
try {
|
.call();
|
||||||
git.rebase().setUpstream("refs/heads/master").call();
|
assertEquals(Status.CONFLICTS, result.getStatus());
|
||||||
} catch (JGitInternalException e) {
|
assertEquals(1, result.getConflicts().size());
|
||||||
exception = e;
|
assertEquals(FILE1, result.getConflicts().get(0));
|
||||||
}
|
|
||||||
assertNotNull(exception);
|
|
||||||
assertEquals("Checkout conflict with files: \nfile1",
|
|
||||||
exception.getMessage());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SuppressWarnings("null")
|
|
||||||
public void testRebaseWithUncommittedMasterChange() throws Exception {
|
public void testRebaseWithUncommittedMasterChange() throws Exception {
|
||||||
// create file1, add and commit
|
// create file1, add and commit
|
||||||
writeTrashFile(FILE1, "file1");
|
writeTrashFile(FILE1, "file1");
|
||||||
|
@ -1202,15 +1185,11 @@ public void testRebaseWithUncommittedMasterChange() throws Exception {
|
||||||
// do not commit
|
// do not commit
|
||||||
|
|
||||||
// rebase
|
// rebase
|
||||||
JGitInternalException exception = null;
|
RebaseResult result = git.rebase().setUpstream("refs/heads/master")
|
||||||
try {
|
.call();
|
||||||
git.rebase().setUpstream("refs/heads/master").call();
|
assertEquals(Status.CONFLICTS, result.getStatus());
|
||||||
} catch (JGitInternalException e) {
|
assertEquals(1, result.getConflicts().size());
|
||||||
exception = e;
|
assertEquals(FILE1, result.getConflicts().get(0));
|
||||||
}
|
|
||||||
assertNotNull(exception);
|
|
||||||
assertEquals("Checkout conflict with files: \nfile1",
|
|
||||||
exception.getMessage());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -1496,14 +1475,13 @@ public void testRebaseShouldLeaveWorkspaceUntouchedWithUnstagedChangesConflict()
|
||||||
File theFile = writeTrashFile(FILE1, "dirty the file");
|
File theFile = writeTrashFile(FILE1, "dirty the file");
|
||||||
|
|
||||||
// and attempt to rebase
|
// and attempt to rebase
|
||||||
try {
|
RebaseResult rebaseResult = git.rebase()
|
||||||
RebaseResult rebaseResult = git.rebase()
|
|
||||||
.setUpstream("refs/heads/master").call();
|
.setUpstream("refs/heads/master").call();
|
||||||
fail("Checkout with conflict should have occured, not "
|
assertEquals(Status.CONFLICTS, rebaseResult.getStatus());
|
||||||
+ rebaseResult.getStatus());
|
assertEquals(1, rebaseResult.getConflicts().size());
|
||||||
} catch (JGitInternalException e) {
|
assertEquals(FILE1, rebaseResult.getConflicts().get(0));
|
||||||
checkFile(theFile, "dirty the file");
|
|
||||||
}
|
checkFile(theFile, "dirty the file");
|
||||||
|
|
||||||
assertEquals(RepositoryState.SAFE, git.getRepository()
|
assertEquals(RepositoryState.SAFE, git.getRepository()
|
||||||
.getRepositoryState());
|
.getRepositoryState());
|
||||||
|
|
|
@ -45,6 +45,7 @@
|
||||||
|
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.eclipse.jgit.internal.JGitText;
|
import org.eclipse.jgit.internal.JGitText;
|
||||||
|
@ -173,6 +174,21 @@ public String toString() {
|
||||||
return "Not-yet-supported";
|
return "Not-yet-supported";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSuccessful() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Status representing a checkout conflict, meaning that nothing could
|
||||||
|
* be merged, as the pre-scan for the trees already failed for certain
|
||||||
|
* files (i.e. local modifications prevent checkout of files).
|
||||||
|
*/
|
||||||
|
CHECKOUT_CONFLICT {
|
||||||
|
public String toString() {
|
||||||
|
return "Checkout Conflict";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSuccessful() {
|
public boolean isSuccessful() {
|
||||||
return false;
|
return false;
|
||||||
|
@ -201,6 +217,8 @@ public boolean isSuccessful() {
|
||||||
|
|
||||||
private Map<String, MergeFailureReason> failingPaths;
|
private Map<String, MergeFailureReason> failingPaths;
|
||||||
|
|
||||||
|
private List<String> checkoutConflicts;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param newHead
|
* @param newHead
|
||||||
* the object the head points at after the merge
|
* the object the head points at after the merge
|
||||||
|
@ -294,6 +312,18 @@ public MergeResult(ObjectId newHead, ObjectId base,
|
||||||
addConflict(result.getKey(), result.getValue());
|
addConflict(result.getKey(), result.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new result that represents a checkout conflict before the
|
||||||
|
* operation even started for real.
|
||||||
|
*
|
||||||
|
* @param checkoutConflicts
|
||||||
|
* the conflicting files
|
||||||
|
*/
|
||||||
|
public MergeResult(List<String> checkoutConflicts) {
|
||||||
|
this.checkoutConflicts = checkoutConflicts;
|
||||||
|
this.mergeStatus = MergeStatus.CHECKOUT_CONFLICT;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the object the head points at after the merge
|
* @return the object the head points at after the merge
|
||||||
*/
|
*/
|
||||||
|
@ -450,4 +480,14 @@ public Map<String, int[][]> getConflicts() {
|
||||||
public Map<String, MergeFailureReason> getFailingPaths() {
|
public Map<String, MergeFailureReason> getFailingPaths() {
|
||||||
return failingPaths;
|
return failingPaths;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of paths that cause a checkout conflict. These paths
|
||||||
|
* prevent the operation from even starting.
|
||||||
|
*
|
||||||
|
* @return the list of files that caused the checkout conflict.
|
||||||
|
*/
|
||||||
|
public List<String> getCheckoutConflicts() {
|
||||||
|
return checkoutConflicts;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -355,6 +355,8 @@ public RebaseResult call() throws GitAPIException, NoHeadException,
|
||||||
return RebaseResult.OK_RESULT;
|
return RebaseResult.OK_RESULT;
|
||||||
}
|
}
|
||||||
return RebaseResult.FAST_FORWARD_RESULT;
|
return RebaseResult.FAST_FORWARD_RESULT;
|
||||||
|
} catch (CheckoutConflictException cce) {
|
||||||
|
return new RebaseResult(cce.getConflictingPaths());
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
throw new JGitInternalException(ioe.getMessage(), ioe);
|
throw new JGitInternalException(ioe.getMessage(), ioe);
|
||||||
}
|
}
|
||||||
|
@ -880,13 +882,18 @@ private String readFile(File directory, String fileName) throws IOException {
|
||||||
return RawParseUtils.decode(content, 0, end);
|
return RawParseUtils.decode(content, 0, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkoutCommit(RevCommit commit) throws IOException {
|
private boolean checkoutCommit(RevCommit commit) throws IOException,
|
||||||
|
CheckoutConflictException {
|
||||||
try {
|
try {
|
||||||
RevCommit head = walk.parseCommit(repo.resolve(Constants.HEAD));
|
RevCommit head = walk.parseCommit(repo.resolve(Constants.HEAD));
|
||||||
DirCacheCheckout dco = new DirCacheCheckout(repo, head.getTree(),
|
DirCacheCheckout dco = new DirCacheCheckout(repo, head.getTree(),
|
||||||
repo.lockDirCache(), commit.getTree());
|
repo.lockDirCache(), commit.getTree());
|
||||||
dco.setFailOnConflict(true);
|
dco.setFailOnConflict(true);
|
||||||
dco.checkout();
|
try {
|
||||||
|
dco.checkout();
|
||||||
|
} catch (org.eclipse.jgit.errors.CheckoutConflictException cce) {
|
||||||
|
throw new CheckoutConflictException(dco.getConflicts(), cce);
|
||||||
|
}
|
||||||
// update the HEAD
|
// update the HEAD
|
||||||
RefUpdate refUpdate = repo.updateRef(Constants.HEAD, true);
|
RefUpdate refUpdate = repo.updateRef(Constants.HEAD, true);
|
||||||
refUpdate.setExpectedOldObjectId(head);
|
refUpdate.setExpectedOldObjectId(head);
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
*/
|
*/
|
||||||
package org.eclipse.jgit.api;
|
package org.eclipse.jgit.api;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.eclipse.jgit.merge.ResolveMerger;
|
import org.eclipse.jgit.merge.ResolveMerger;
|
||||||
|
@ -92,6 +93,15 @@ public boolean isSuccessful() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Conflicts: checkout of target HEAD failed
|
||||||
|
*/
|
||||||
|
CONFLICTS {
|
||||||
|
@Override
|
||||||
|
public boolean isSuccessful() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Already up-to-date
|
* Already up-to-date
|
||||||
*/
|
*/
|
||||||
|
@ -148,6 +158,8 @@ public boolean isSuccessful() {
|
||||||
|
|
||||||
private Map<String, MergeFailureReason> failingPaths;
|
private Map<String, MergeFailureReason> failingPaths;
|
||||||
|
|
||||||
|
private List<String> conflicts;
|
||||||
|
|
||||||
private RebaseResult(Status status) {
|
private RebaseResult(Status status) {
|
||||||
this.status = status;
|
this.status = status;
|
||||||
currentCommit = null;
|
currentCommit = null;
|
||||||
|
@ -176,6 +188,18 @@ private RebaseResult(Status status) {
|
||||||
this.failingPaths = failingPaths;
|
this.failingPaths = failingPaths;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create <code>RebaseResult</code> with status {@link Status#CONFLICTS}
|
||||||
|
*
|
||||||
|
* @param conflicts
|
||||||
|
* the list of conflicting paths
|
||||||
|
*/
|
||||||
|
RebaseResult(List<String> conflicts) {
|
||||||
|
status = Status.CONFLICTS;
|
||||||
|
currentCommit = null;
|
||||||
|
this.conflicts = conflicts;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the overall status
|
* @return the overall status
|
||||||
*/
|
*/
|
||||||
|
@ -199,4 +223,11 @@ public RevCommit getCurrentCommit() {
|
||||||
public Map<String, MergeFailureReason> getFailingPaths() {
|
public Map<String, MergeFailureReason> getFailingPaths() {
|
||||||
return failingPaths;
|
return failingPaths;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the list of conflicts if status is {@link Status#CONFLICTS}
|
||||||
|
*/
|
||||||
|
public List<String> getConflicts() {
|
||||||
|
return conflicts;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue