aboutsummaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
diff options
context:
space:
mode:
Diffstat (limited to 'org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java')
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java278
1 files changed, 189 insertions, 89 deletions
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 8d8aada622..32c242f271 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
@@ -1,54 +1,24 @@
/*
* Copyright (C) 2010, Chris Aniszczyk <caniszczyk@gmail.com>
- * Copyright (C) 2011, Matthias Sohn <matthias.sohn@sap.com>
- * and other copyright owners as documented in the project's IP log.
+ * Copyright (C) 2011, 2023 Matthias Sohn <matthias.sohn@sap.com> and others
*
- * 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
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://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.
+ * SPDX-License-Identifier: BSD-3-Clause
*/
package org.eclipse.jgit.api;
+import static org.eclipse.jgit.treewalk.TreeWalk.OperationType.CHECKOUT_OP;
+
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.EnumSet;
-import java.util.LinkedList;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import org.eclipse.jgit.api.CheckoutResult.Status;
import org.eclipse.jgit.api.errors.CheckoutConflictException;
@@ -57,20 +27,26 @@ 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.Checkout;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheCheckout;
+import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata;
import org.eclipse.jgit.dircache.DirCacheEditor;
import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.errors.AmbiguousObjectException;
import org.eclipse.jgit.errors.UnmergedPathException;
+import org.eclipse.jgit.events.WorkingTreeModifiedEvent;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.RefUpdate.Result;
@@ -84,7 +60,7 @@ import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
/**
* Checkout a branch to the working tree.
* <p>
- * Examples (<code>git</code> is a {@link Git} instance):
+ * Examples (<code>git</code> is a {@link org.eclipse.jgit.api.Git} instance):
* <p>
* Check out an existing branch:
*
@@ -119,16 +95,16 @@ import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
* .setStartPoint(&quot;origin/stable&quot;).call();
* </pre>
*
- * @see <a
- * href="http://www.kernel.org/pub/software/scm/git/docs/git-checkout.html"
- * >Git documentation about Checkout</a>
+ * @see <a href=
+ * "http://www.kernel.org/pub/software/scm/git/docs/git-checkout.html" >Git
+ * documentation about Checkout</a>
*/
public class CheckoutCommand extends GitCommand<Ref> {
/**
* Stage to check out, see {@link CheckoutCommand#setStage(Stage)}.
*/
- public static enum Stage {
+ public enum Stage {
/**
* Base stage (#1)
*/
@@ -153,7 +129,9 @@ public class CheckoutCommand extends GitCommand<Ref> {
private String name;
- private boolean force = false;
+ private boolean forceRefUpdate = false;
+
+ private boolean forced = false;
private boolean createBranch = false;
@@ -173,27 +151,22 @@ public class CheckoutCommand extends GitCommand<Ref> {
private boolean checkoutAllPaths;
+ private Set<String> actuallyModifiedPaths;
+
+ private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;
+
/**
+ * Constructor for CheckoutCommand
+ *
* @param repo
+ * the {@link org.eclipse.jgit.lib.Repository}
*/
protected CheckoutCommand(Repository repo) {
super(repo);
- this.paths = new LinkedList<String>();
+ this.paths = new ArrayList<>();
}
- /**
- * @throws RefAlreadyExistsException
- * when trying to create (without force) a branch with a name
- * that already exists
- * @throws RefNotFoundException
- * if the start point or branch can not be found
- * @throws InvalidRefNameException
- * if the provided name is <code>null</code> or otherwise
- * invalid
- * @throws CheckoutConflictException
- * if the checkout results in a conflict
- * @return the newly created branch
- */
+ @Override
public Ref call() throws GitAPIException, RefAlreadyExistsException,
RefNotFoundException, InvalidRefNameException,
CheckoutConflictException {
@@ -221,7 +194,13 @@ public class CheckoutCommand extends GitCommand<Ref> {
}
}
- Ref headRef = repo.getRef(Constants.HEAD);
+ Ref headRef = repo.exactRef(Constants.HEAD);
+ if (headRef == null) {
+ // TODO Git CLI supports checkout from unborn branch, we should
+ // also allow this
+ throw new UnsupportedOperationException(
+ JGitText.get().cannotCheckoutFromUnbornBranch);
+ }
String shortHeadRef = getShortBranchName(headRef);
String refLogMessage = "checkout: moving from " + shortHeadRef; //$NON-NLS-1$
ObjectId branch;
@@ -234,7 +213,7 @@ public class CheckoutCommand extends GitCommand<Ref> {
JGitText.get().checkoutUnexpectedResult,
r.name()));
this.status = CheckoutResult.NOT_TRIED_RESULT;
- return repo.getRef(Constants.HEAD);
+ return repo.exactRef(Constants.HEAD);
}
branch = getStartPointObjectId();
} else {
@@ -259,6 +238,11 @@ public class CheckoutCommand extends GitCommand<Ref> {
dco = new DirCacheCheckout(repo, headTree, dc,
newCommit.getTree());
dco.setFailOnConflict(true);
+ dco.setForce(forced);
+ if (forced) {
+ dco.setFailOnConflict(false);
+ }
+ dco.setProgressMonitor(monitor);
try {
dco.checkout();
} catch (org.eclipse.jgit.errors.CheckoutConflictException e) {
@@ -269,19 +253,19 @@ public class CheckoutCommand extends GitCommand<Ref> {
} finally {
dc.unlock();
}
- Ref ref = repo.getRef(name);
+ Ref ref = repo.findRef(name);
if (ref != null && !ref.getName().startsWith(Constants.R_HEADS))
ref = null;
String toName = Repository.shortenRefName(name);
RefUpdate refUpdate = repo.updateRef(Constants.HEAD, ref == null);
- refUpdate.setForceUpdate(force);
+ refUpdate.setForceUpdate(forceRefUpdate);
refUpdate.setRefLogMessage(refLogMessage + " to " + toName, false); //$NON-NLS-1$
Result updateResult;
if (ref != null)
updateResult = refUpdate.link(ref.getName());
else if (orphan) {
updateResult = refUpdate.link(getBranchName());
- ref = repo.getRef(Constants.HEAD);
+ ref = repo.exactRef(Constants.HEAD);
} else {
refUpdate.setNewObjectId(newCommit);
updateResult = refUpdate.forceUpdate();
@@ -310,9 +294,11 @@ public class CheckoutCommand extends GitCommand<Ref> {
if (!dco.getToBeDeleted().isEmpty()) {
status = new CheckoutResult(Status.NONDELETED,
- dco.getToBeDeleted());
+ dco.getToBeDeleted(),
+ new ArrayList<>(dco.getUpdated().keySet()),
+ dco.getRemoved());
} else
- status = new CheckoutResult(new ArrayList<String>(dco
+ status = new CheckoutResult(new ArrayList<>(dco
.getUpdated().keySet()), dco.getRemoved());
return ref;
@@ -325,9 +311,32 @@ public class CheckoutCommand extends GitCommand<Ref> {
}
private String getShortBranchName(Ref headRef) {
- if (headRef.getTarget().getName().equals(headRef.getName()))
- return headRef.getTarget().getObjectId().getName();
- return Repository.shortenRefName(headRef.getTarget().getName());
+ if (headRef.isSymbolic()) {
+ return Repository.shortenRefName(headRef.getTarget().getName());
+ }
+ // Detached HEAD. Every non-symbolic ref in the ref database has an
+ // object id, so this cannot be null.
+ ObjectId id = headRef.getObjectId();
+ if (id == null) {
+ throw new NullPointerException();
+ }
+ return id.getName();
+ }
+
+ /**
+ * Set progress monitor
+ *
+ * @param monitor
+ * a progress monitor
+ * @return this instance
+ * @since 4.11
+ */
+ public CheckoutCommand setProgressMonitor(ProgressMonitor monitor) {
+ if (monitor == null) {
+ monitor = NullProgressMonitor.INSTANCE;
+ }
+ this.monitor = monitor;
+ return this;
}
/**
@@ -350,6 +359,26 @@ public class CheckoutCommand extends GitCommand<Ref> {
}
/**
+ * Add multiple slash-separated paths to the list of paths to check out. To
+ * check out all paths, use {@link #setAllPaths(boolean)}.
+ * <p>
+ * If this option is set, neither the {@link #setCreateBranch(boolean)} nor
+ * {@link #setName(String)} option is considered. In other words, these
+ * options are exclusive.
+ *
+ * @param p
+ * paths to update in the working tree and index (with
+ * <code>/</code> as separator)
+ * @return {@code this}
+ * @since 4.6
+ */
+ public CheckoutCommand addPaths(List<String> p) {
+ checkCallable();
+ this.paths.addAll(p);
+ return this;
+ }
+
+ /**
* Set whether to checkout all paths.
* <p>
* This options should be used when you want to do a path checkout on the
@@ -372,33 +401,50 @@ public class CheckoutCommand extends GitCommand<Ref> {
}
/**
- * Checkout paths into index and working directory
+ * Checkout paths into index and working directory, firing a
+ * {@link org.eclipse.jgit.events.WorkingTreeModifiedEvent} if the working
+ * tree was modified.
*
* @return this instance
- * @throws IOException
- * @throws RefNotFoundException
+ * @throws java.io.IOException
+ * if an IO error occurred
+ * @throws org.eclipse.jgit.api.errors.RefNotFoundException
+ * if {@code Ref} couldn't be resolved
*/
protected CheckoutCommand checkoutPaths() throws IOException,
RefNotFoundException {
+ actuallyModifiedPaths = new HashSet<>();
+ Checkout checkout = new Checkout(repo).setRecursiveDeletion(true);
DirCache dc = repo.lockDirCache();
try (RevWalk revWalk = new RevWalk(repo);
- TreeWalk treeWalk = new TreeWalk(revWalk.getObjectReader())) {
+ TreeWalk treeWalk = new TreeWalk(repo,
+ revWalk.getObjectReader())) {
treeWalk.setRecursive(true);
if (!checkoutAllPaths)
treeWalk.setFilter(PathFilterGroup.createFromStrings(paths));
if (isCheckoutIndex())
- checkoutPathsFromIndex(treeWalk, dc);
+ checkoutPathsFromIndex(treeWalk, dc, checkout);
else {
RevCommit commit = revWalk.parseCommit(getStartPointObjectId());
- checkoutPathsFromCommit(treeWalk, dc, commit);
+ checkoutPathsFromCommit(treeWalk, dc, commit, checkout);
}
} finally {
- dc.unlock();
+ try {
+ dc.unlock();
+ } finally {
+ WorkingTreeModifiedEvent event = new WorkingTreeModifiedEvent(
+ actuallyModifiedPaths, null);
+ actuallyModifiedPaths = null;
+ if (!event.isEmpty()) {
+ repo.fireEvent(event);
+ }
+ }
}
return this;
}
- private void checkoutPathsFromIndex(TreeWalk treeWalk, DirCache dc)
+ private void checkoutPathsFromIndex(TreeWalk treeWalk, DirCache dc,
+ Checkout checkout)
throws IOException {
DirCacheIterator dci = new DirCacheIterator(dc);
treeWalk.addTree(dci);
@@ -413,20 +459,32 @@ public class CheckoutCommand extends GitCommand<Ref> {
if (path.equals(previousPath))
continue;
+ final EolStreamType eolStreamType = treeWalk
+ .getEolStreamType(CHECKOUT_OP);
+ final String filterCommand = treeWalk
+ .getFilterCommand(Constants.ATTR_FILTER_TYPE_SMUDGE);
editor.add(new PathEdit(path) {
+ @Override
public void apply(DirCacheEntry ent) {
int stage = ent.getStage();
if (stage > DirCacheEntry.STAGE_0) {
if (checkoutStage != null) {
- if (stage == checkoutStage.number)
- checkoutPath(ent, r);
+ if (stage == checkoutStage.number) {
+ checkoutPath(ent, r, checkout, path,
+ new CheckoutMetadata(eolStreamType,
+ filterCommand));
+ actuallyModifiedPaths.add(path);
+ }
} else {
UnmergedPathException e = new UnmergedPathException(
ent);
throw new JGitInternalException(e.getMessage(), e);
}
} else {
- checkoutPath(ent, r);
+ checkoutPath(ent, r, checkout, path,
+ new CheckoutMetadata(eolStreamType,
+ filterCommand));
+ actuallyModifiedPaths.add(path);
}
}
});
@@ -437,27 +495,41 @@ public class CheckoutCommand extends GitCommand<Ref> {
}
private void checkoutPathsFromCommit(TreeWalk treeWalk, DirCache dc,
- RevCommit commit) throws IOException {
+ RevCommit commit, Checkout checkout) throws IOException {
treeWalk.addTree(commit.getTree());
final ObjectReader r = treeWalk.getObjectReader();
DirCacheEditor editor = dc.editor();
while (treeWalk.next()) {
final ObjectId blobId = treeWalk.getObjectId(0);
final FileMode mode = treeWalk.getFileMode(0);
- editor.add(new PathEdit(treeWalk.getPathString()) {
+ final EolStreamType eolStreamType = treeWalk
+ .getEolStreamType(CHECKOUT_OP);
+ final String filterCommand = treeWalk
+ .getFilterCommand(Constants.ATTR_FILTER_TYPE_SMUDGE);
+ final String path = treeWalk.getPathString();
+ editor.add(new PathEdit(path) {
+ @Override
public void apply(DirCacheEntry ent) {
+ if (ent.getStage() != DirCacheEntry.STAGE_0) {
+ // A checkout on a conflicting file stages the checked
+ // out file and resolves the conflict.
+ ent.setStage(DirCacheEntry.STAGE_0);
+ }
ent.setObjectId(blobId);
ent.setFileMode(mode);
- checkoutPath(ent, r);
+ checkoutPath(ent, r, checkout, path,
+ new CheckoutMetadata(eolStreamType, filterCommand));
+ actuallyModifiedPaths.add(path);
}
});
}
editor.commit();
}
- private void checkoutPath(DirCacheEntry entry, ObjectReader reader) {
+ private void checkoutPath(DirCacheEntry entry, ObjectReader reader,
+ Checkout checkout, String path, CheckoutMetadata checkoutMetadata) {
try {
- DirCacheCheckout.checkoutEntry(repo, entry, reader);
+ checkout.checkout(entry, checkoutMetadata, reader, path);
} catch (IOException e) {
throw new JGitInternalException(MessageFormat.format(
JGitText.get().checkoutConflictWithFile,
@@ -492,7 +564,7 @@ public class CheckoutCommand extends GitCommand<Ref> {
.get().branchNameInvalid, name == null ? "<null>" : name)); //$NON-NLS-1$
if (orphan) {
- Ref refToCheck = repo.getRef(getBranchName());
+ Ref refToCheck = repo.exactRef(getBranchName());
if (refToCheck != null)
throw new RefAlreadyExistsException(MessageFormat.format(
JGitText.get().refAlreadyExists, name));
@@ -572,16 +644,42 @@ public class CheckoutCommand extends GitCommand<Ref> {
/**
* Specify to force the ref update in case of a branch switch.
*
- * @param force
+ * In releases prior to 5.2 this method was called setForce() but this name
+ * was misunderstood to implement native git's --force option, which is not
+ * true.
+ *
+ * @param forceRefUpdate
* if <code>true</code> and the branch with the given name
* already exists, the start-point of an existing branch will be
* set to a new start-point; if false, the existing branch will
* not be changed
* @return this instance
+ * @since 5.3
+ */
+ public CheckoutCommand setForceRefUpdate(boolean forceRefUpdate) {
+ checkCallable();
+ this.forceRefUpdate = forceRefUpdate;
+ return this;
+ }
+
+ /**
+ * Allow a checkout even if the workingtree or index differs from HEAD. This
+ * matches native git's '--force' option.
+ *
+ * JGit releases before 5.2 had a method <code>setForce()</code> offering
+ * semantics different from this new <code>setForced()</code>. This old
+ * semantic can now be found in {@link #setForceRefUpdate(boolean)}
+ *
+ * @param forced
+ * if set to <code>true</code> then allow the checkout even if
+ * workingtree or index doesn't match HEAD. Overwrite workingtree
+ * files and index content with the new content in this case.
+ * @return this instance
+ * @since 5.3
*/
- public CheckoutCommand setForce(boolean force) {
+ public CheckoutCommand setForced(boolean forced) {
checkCallable();
- this.force = force;
+ this.forced = forced;
return this;
}
@@ -662,6 +760,8 @@ public class CheckoutCommand extends GitCommand<Ref> {
}
/**
+ * Get the result, never <code>null</code>
+ *
* @return the result, never <code>null</code>
*/
public CheckoutResult getResult() {