From 645d262de6da32fdb05aba5a48dbf6188dfa7776 Mon Sep 17 00:00:00 2001 From: Mathias Kinzler Date: Mon, 20 Dec 2010 10:21:49 +0100 Subject: [PATCH] Checkout: expose a CheckoutResult This is needed by callers to determine checkout conflicts and possible files that were not deleted during the checkout so that they can present the end user with a better Exception description and retry to delete the undeleted files later, respectively. Change-Id: I037930da7b1a4dfb24cfa3205afb51dc29e4a5b8 Signed-off-by: Mathias Kinzler --- .../eclipse/jgit/api/CheckoutCommandTest.java | 51 +++++++ .../org/eclipse/jgit/api/CheckoutCommand.java | 62 ++++++-- .../org/eclipse/jgit/api/CheckoutResult.java | 141 ++++++++++++++++++ 3 files changed, 240 insertions(+), 14 deletions(-) create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutResult.java diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java index 295a284c0..cf78a0e56 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java @@ -42,8 +42,11 @@ */ package org.eclipse.jgit.api; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; +import org.eclipse.jgit.api.CheckoutResult.Status; import org.eclipse.jgit.api.errors.InvalidRefNameException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.RefAlreadyExistsException; @@ -52,6 +55,7 @@ import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RepositoryTestCase; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.util.FileUtils; public class CheckoutCommandTest extends RepositoryTestCase { private Git git; @@ -120,4 +124,51 @@ public class CheckoutCommandTest extends RepositoryTestCase { } } + public void testCheckoutWithConflict() { + CheckoutCommand co = git.checkout(); + try { + writeTrashFile("Test.txt", "Another change"); + assertEquals(Status.NOT_TRIED, co.getResult().getStatus()); + co.setName("master").call(); + fail("Should have failed"); + } catch (Exception e) { + assertEquals(Status.CONFLICTS, co.getResult().getStatus()); + assertTrue(co.getResult().getConflictList().contains("Test.txt")); + } + } + + public void testCheckoutWithNonDeletedFiles() throws Exception { + File testFile = writeTrashFile("temp", ""); + FileInputStream fis = new FileInputStream(testFile); + try { + FileUtils.delete(testFile); + return; + } catch (IOException e) { + // the test makes only sense if deletion of + // a file with open stream fails + } + fis.close(); + FileUtils.delete(testFile); + CheckoutCommand co = git.checkout(); + // delete Test.txt in branch test + testFile = new File(db.getWorkTree(), "Test.txt"); + assertTrue(testFile.exists()); + FileUtils.delete(testFile); + assertFalse(testFile.exists()); + git.add().addFilepattern("Test.txt"); + git.commit().setMessage("Delete Test.txt").setAll(true).call(); + git.checkout().setName("master").call(); + assertTrue(testFile.exists()); + // lock the file so it can't be deleted (in Windows, that is) + fis = new FileInputStream(testFile); + try { + assertEquals(Status.NOT_TRIED, co.getResult().getStatus()); + co.setName("test").call(); + assertTrue(testFile.exists()); + assertEquals(Status.NONDELETED, co.getResult().getStatus()); + assertTrue(co.getResult().getUndeletedList().contains("Test.txt")); + } finally { + fis.close(); + } + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java index 4e74a5460..2546231cf 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java @@ -42,22 +42,27 @@ */ package org.eclipse.jgit.api; +import java.io.File; import java.io.IOException; import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.api.CheckoutResult.Status; import org.eclipse.jgit.api.errors.InvalidRefNameException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.RefAlreadyExistsException; import org.eclipse.jgit.api.errors.RefNotFoundException; import org.eclipse.jgit.dircache.DirCacheCheckout; import org.eclipse.jgit.errors.AmbiguousObjectException; +import org.eclipse.jgit.errors.CheckoutConflictException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; -import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; @@ -81,6 +86,8 @@ public class CheckoutCommand extends GitCommand { private RevCommit startCommit; + private CheckoutResult status; + /** * @param repo */ @@ -105,7 +112,7 @@ public class CheckoutCommand extends GitCommand { processOptions(); try { - if(createBranch) { + if (createBranch) { Git git = new Git(repo); CreateBranchCommand command = git.branchCreate(); command.setName(name); @@ -123,20 +130,28 @@ public class CheckoutCommand extends GitCommand { ObjectId branch = repo.resolve(name); Ref ref = repo.getRef(name); if (branch == null) - throw new RefNotFoundException(MessageFormat.format( - JGitText.get().refNotResolved, name)); + throw new RefNotFoundException(MessageFormat.format(JGitText + .get().refNotResolved, name)); RevCommit newCommit = revWalk.parseCommit(branch); - DirCacheCheckout dco = new DirCacheCheckout(repo, - headCommit.getTree(), repo.lockDirCache(), - newCommit.getTree()); + DirCacheCheckout dco = new DirCacheCheckout(repo, headCommit + .getTree(), repo.lockDirCache(), newCommit.getTree()); dco.setFailOnConflict(true); - dco.checkout(); + try { + dco.checkout(); + } catch (CheckoutConflictException e) { + List fileList = new ArrayList(); + for (String filePath : dco.getConflicts()) { + fileList.add(new File(repo.getWorkTree(), filePath)); + } + status = new CheckoutResult(Status.CONFLICTS, fileList); + throw e; + } RefUpdate refUpdate = repo.updateRef(Constants.HEAD); refUpdate.setForceUpdate(force); - refUpdate.setRefLogMessage( - refLogMessage + "to " + newCommit.getName(), false); + refUpdate.setRefLogMessage(refLogMessage + "to " + + newCommit.getName(), false); Result updateResult = refUpdate.link(ref.getName()); setCallable(false); @@ -156,16 +171,26 @@ public class CheckoutCommand extends GitCommand { } if (!ok) - throw new JGitInternalException(MessageFormat.format( - JGitText.get().checkoutUnexpectedResult, - updateResult - .name())); + throw new JGitInternalException(MessageFormat.format(JGitText + .get().checkoutUnexpectedResult, updateResult.name())); Ref result = repo.getRef(name); + if (!repo.isBare() && !dco.getToBeDeleted().isEmpty()) { + List fileList = new ArrayList(); + for (String filePath : dco.getToBeDeleted()) { + fileList.add(new File(repo.getWorkTree(), filePath)); + } + status = new CheckoutResult(Status.NONDELETED, fileList); + } + else + status = CheckoutResult.OK_RESULT; return result; } catch (IOException ioe) { throw new JGitInternalException(ioe.getMessage(), ioe); + } finally { + if (status == null) + status = CheckoutResult.ERROR_RESULT; } } @@ -269,4 +294,13 @@ public class CheckoutCommand extends GitCommand { this.upstreamMode = mode; return this; } + + /** + * @return the result + */ + public CheckoutResult getResult() { + if (status == null) + return CheckoutResult.NOT_TRIED_RESULT; + return status; + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutResult.java new file mode 100644 index 000000000..bddfe2be5 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutResult.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2010, Mathias Kinzler + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.api; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * Encapsulates the result of a {@link CheckoutCommand} + * + */ +public class CheckoutResult { + + /** + * The {@link Status#OK} result; + */ + public static CheckoutResult OK_RESULT = new CheckoutResult(Status.OK, null); + + /** + * The {@link Status#ERROR} result; + */ + public static CheckoutResult ERROR_RESULT = new CheckoutResult( + Status.ERROR, null); + + /** + * The {@link Status#NOT_TRIED} result; + */ + public static CheckoutResult NOT_TRIED_RESULT = new CheckoutResult( + Status.NOT_TRIED, null); + + /** + * The status + */ + public enum Status { + /** + * The call() method has not yet been executed + */ + NOT_TRIED, + /** + * Checkout completed normally + */ + OK, + /** + * Checkout has not completed because of checkout conflicts + */ + CONFLICTS, + /** + * Checkout has completed, but some files could not be deleted + */ + NONDELETED, + /** + * An Exception occurred during checkout + */ + ERROR; + } + + private final Status myStatus; + + private final List conflictList; + + private final List undeletedList; + + CheckoutResult(Status status, List fileList) { + myStatus = status; + if (status == Status.CONFLICTS) + this.conflictList = fileList; + else + this.conflictList = new ArrayList(0); + if (status == Status.NONDELETED) + this.undeletedList = fileList; + else + this.undeletedList = new ArrayList(0); + + } + + /** + * @return the status + */ + public Status getStatus() { + return myStatus; + } + + /** + * @return the list of files that created a checkout conflict, or an empty + * list if {@link #getStatus()} is not {@link Status#CONFLICTS}; + */ + public List getConflictList() { + return conflictList; + } + + /** + * @return the list of files that could not be deleted during checkout, or + * an empty list if {@link #getStatus()} is not + * {@link Status#NONDELETED}; + */ + public List getUndeletedList() { + return undeletedList; + } + +}