View Javadoc
1   /*
2    * Copyright (C) 2010, Chris Aniszczyk <caniszczyk@gmail.com>
3    * Copyright (C) 2011, Matthias Sohn <matthias.sohn@sap.com>
4    * and other copyright owners as documented in the project's IP log.
5    *
6    * This program and the accompanying materials are made available
7    * under the terms of the Eclipse Distribution License v1.0 which
8    * accompanies this distribution, is reproduced below, and is
9    * available at http://www.eclipse.org/org/documents/edl-v10.php
10   *
11   * All rights reserved.
12   *
13   * Redistribution and use in source and binary forms, with or
14   * without modification, are permitted provided that the following
15   * conditions are met:
16   *
17   * - Redistributions of source code must retain the above copyright
18   *   notice, this list of conditions and the following disclaimer.
19   *
20   * - Redistributions in binary form must reproduce the above
21   *   copyright notice, this list of conditions and the following
22   *   disclaimer in the documentation and/or other materials provided
23   *   with the distribution.
24   *
25   * - Neither the name of the Eclipse Foundation, Inc. nor the
26   *   names of its contributors may be used to endorse or promote
27   *   products derived from this software without specific prior
28   *   written permission.
29   *
30   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
31   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
32   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
33   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
34   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
35   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
36   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
37   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
38   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
39   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
40   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
41   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
42   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
43   */
44  package org.eclipse.jgit.api;
45  
46  import static org.eclipse.jgit.treewalk.TreeWalk.OperationType.CHECKOUT_OP;
47  
48  import java.io.IOException;
49  import java.text.MessageFormat;
50  import java.util.ArrayList;
51  import java.util.EnumSet;
52  import java.util.HashSet;
53  import java.util.LinkedList;
54  import java.util.List;
55  import java.util.Set;
56  
57  import org.eclipse.jgit.api.CheckoutResult.Status;
58  import org.eclipse.jgit.api.errors.CheckoutConflictException;
59  import org.eclipse.jgit.api.errors.GitAPIException;
60  import org.eclipse.jgit.api.errors.InvalidRefNameException;
61  import org.eclipse.jgit.api.errors.JGitInternalException;
62  import org.eclipse.jgit.api.errors.RefAlreadyExistsException;
63  import org.eclipse.jgit.api.errors.RefNotFoundException;
64  import org.eclipse.jgit.dircache.DirCache;
65  import org.eclipse.jgit.dircache.DirCacheCheckout;
66  import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata;
67  import org.eclipse.jgit.dircache.DirCacheEditor;
68  import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
69  import org.eclipse.jgit.dircache.DirCacheEntry;
70  import org.eclipse.jgit.dircache.DirCacheIterator;
71  import org.eclipse.jgit.errors.AmbiguousObjectException;
72  import org.eclipse.jgit.errors.UnmergedPathException;
73  import org.eclipse.jgit.events.WorkingTreeModifiedEvent;
74  import org.eclipse.jgit.internal.JGitText;
75  import org.eclipse.jgit.lib.AnyObjectId;
76  import org.eclipse.jgit.lib.Constants;
77  import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
78  import org.eclipse.jgit.lib.FileMode;
79  import org.eclipse.jgit.lib.ObjectId;
80  import org.eclipse.jgit.lib.ObjectReader;
81  import org.eclipse.jgit.lib.Ref;
82  import org.eclipse.jgit.lib.RefUpdate;
83  import org.eclipse.jgit.lib.RefUpdate.Result;
84  import org.eclipse.jgit.lib.Repository;
85  import org.eclipse.jgit.revwalk.RevCommit;
86  import org.eclipse.jgit.revwalk.RevTree;
87  import org.eclipse.jgit.revwalk.RevWalk;
88  import org.eclipse.jgit.treewalk.TreeWalk;
89  import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
90  
91  /**
92   * Checkout a branch to the working tree.
93   * <p>
94   * Examples (<code>git</code> is a {@link org.eclipse.jgit.api.Git} instance):
95   * <p>
96   * Check out an existing branch:
97   *
98   * <pre>
99   * git.checkout().setName(&quot;feature&quot;).call();
100  * </pre>
101  * <p>
102  * Check out paths from the index:
103  *
104  * <pre>
105  * git.checkout().addPath(&quot;file1.txt&quot;).addPath(&quot;file2.txt&quot;).call();
106  * </pre>
107  * <p>
108  * Check out a path from a commit:
109  *
110  * <pre>
111  * git.checkout().setStartPoint(&quot;HEAD&circ;&quot;).addPath(&quot;file1.txt&quot;).call();
112  * </pre>
113  *
114  * <p>
115  * Create a new branch and check it out:
116  *
117  * <pre>
118  * git.checkout().setCreateBranch(true).setName(&quot;newbranch&quot;).call();
119  * </pre>
120  * <p>
121  * Create a new tracking branch for a remote branch and check it out:
122  *
123  * <pre>
124  * git.checkout().setCreateBranch(true).setName(&quot;stable&quot;)
125  * 		.setUpstreamMode(SetupUpstreamMode.SET_UPSTREAM)
126  * 		.setStartPoint(&quot;origin/stable&quot;).call();
127  * </pre>
128  *
129  * @see <a href=
130  *      "http://www.kernel.org/pub/software/scm/git/docs/git-checkout.html" >Git
131  *      documentation about Checkout</a>
132  */
133 public class CheckoutCommand extends GitCommand<Ref> {
134 
135 	/**
136 	 * Stage to check out, see {@link CheckoutCommand#setStage(Stage)}.
137 	 */
138 	public static enum Stage {
139 		/**
140 		 * Base stage (#1)
141 		 */
142 		BASE(DirCacheEntry.STAGE_1),
143 
144 		/**
145 		 * Ours stage (#2)
146 		 */
147 		OURS(DirCacheEntry.STAGE_2),
148 
149 		/**
150 		 * Theirs stage (#3)
151 		 */
152 		THEIRS(DirCacheEntry.STAGE_3);
153 
154 		private final int number;
155 
156 		private Stage(int number) {
157 			this.number = number;
158 		}
159 	}
160 
161 	private String name;
162 
163 	private boolean force = false;
164 
165 	private boolean createBranch = false;
166 
167 	private boolean orphan = false;
168 
169 	private CreateBranchCommand.SetupUpstreamMode upstreamMode;
170 
171 	private String startPoint = null;
172 
173 	private RevCommit startCommit;
174 
175 	private Stage checkoutStage = null;
176 
177 	private CheckoutResult status;
178 
179 	private List<String> paths;
180 
181 	private boolean checkoutAllPaths;
182 
183 	private Set<String> actuallyModifiedPaths;
184 
185 	/**
186 	 * Constructor for CheckoutCommand
187 	 *
188 	 * @param repo
189 	 *            the {@link org.eclipse.jgit.lib.Repository}
190 	 */
191 	protected CheckoutCommand(Repository repo) {
192 		super(repo);
193 		this.paths = new LinkedList<>();
194 	}
195 
196 	/** {@inheritDoc} */
197 	@Override
198 	public Ref call() throws GitAPIException, RefAlreadyExistsException,
199 			RefNotFoundException, InvalidRefNameException,
200 			CheckoutConflictException {
201 		checkCallable();
202 		try {
203 			processOptions();
204 			if (checkoutAllPaths || !paths.isEmpty()) {
205 				checkoutPaths();
206 				status = new CheckoutResult(Status.OK, paths);
207 				setCallable(false);
208 				return null;
209 			}
210 
211 			if (createBranch) {
212 				try (Git git = new Git(repo)) {
213 					CreateBranchCommand command = git.branchCreate();
214 					command.setName(name);
215 					if (startCommit != null)
216 						command.setStartPoint(startCommit);
217 					else
218 						command.setStartPoint(startPoint);
219 					if (upstreamMode != null)
220 						command.setUpstreamMode(upstreamMode);
221 					command.call();
222 				}
223 			}
224 
225 			Ref headRef = repo.exactRef(Constants.HEAD);
226 			if (headRef == null) {
227 				// TODO Git CLI supports checkout from unborn branch, we should
228 				// also allow this
229 				throw new UnsupportedOperationException(
230 						JGitText.get().cannotCheckoutFromUnbornBranch);
231 			}
232 			String shortHeadRef = getShortBranchName(headRef);
233 			String refLogMessage = "checkout: moving from " + shortHeadRef; //$NON-NLS-1$
234 			ObjectId branch;
235 			if (orphan) {
236 				if (startPoint == null && startCommit == null) {
237 					Result r = repo.updateRef(Constants.HEAD).link(
238 							getBranchName());
239 					if (!EnumSet.of(Result.NEW, Result.FORCED).contains(r))
240 						throw new JGitInternalException(MessageFormat.format(
241 								JGitText.get().checkoutUnexpectedResult,
242 								r.name()));
243 					this.status = CheckoutResult.NOT_TRIED_RESULT;
244 					return repo.exactRef(Constants.HEAD);
245 				}
246 				branch = getStartPointObjectId();
247 			} else {
248 				branch = repo.resolve(name);
249 				if (branch == null)
250 					throw new RefNotFoundException(MessageFormat.format(
251 							JGitText.get().refNotResolved, name));
252 			}
253 
254 			RevCommit headCommit = null;
255 			RevCommit newCommit = null;
256 			try (RevWalk revWalk = new RevWalk(repo)) {
257 				AnyObjectId headId = headRef.getObjectId();
258 				headCommit = headId == null ? null
259 						: revWalk.parseCommit(headId);
260 				newCommit = revWalk.parseCommit(branch);
261 			}
262 			RevTree headTree = headCommit == null ? null : headCommit.getTree();
263 			DirCacheCheckout dco;
264 			DirCache dc = repo.lockDirCache();
265 			try {
266 				dco = new DirCacheCheckout(repo, headTree, dc,
267 						newCommit.getTree());
268 				dco.setFailOnConflict(true);
269 				try {
270 					dco.checkout();
271 				} catch (org.eclipse.jgit.errors.CheckoutConflictException e) {
272 					status = new CheckoutResult(Status.CONFLICTS,
273 							dco.getConflicts());
274 					throw new CheckoutConflictException(dco.getConflicts(), e);
275 				}
276 			} finally {
277 				dc.unlock();
278 			}
279 			Ref ref = repo.findRef(name);
280 			if (ref != null && !ref.getName().startsWith(Constants.R_HEADS))
281 				ref = null;
282 			String toName = Repository.shortenRefName(name);
283 			RefUpdate refUpdate = repo.updateRef(Constants.HEAD, ref == null);
284 			refUpdate.setForceUpdate(force);
285 			refUpdate.setRefLogMessage(refLogMessage + " to " + toName, false); //$NON-NLS-1$
286 			Result updateResult;
287 			if (ref != null)
288 				updateResult = refUpdate.link(ref.getName());
289 			else if (orphan) {
290 				updateResult = refUpdate.link(getBranchName());
291 				ref = repo.exactRef(Constants.HEAD);
292 			} else {
293 				refUpdate.setNewObjectId(newCommit);
294 				updateResult = refUpdate.forceUpdate();
295 			}
296 
297 			setCallable(false);
298 
299 			boolean ok = false;
300 			switch (updateResult) {
301 			case NEW:
302 				ok = true;
303 				break;
304 			case NO_CHANGE:
305 			case FAST_FORWARD:
306 			case FORCED:
307 				ok = true;
308 				break;
309 			default:
310 				break;
311 			}
312 
313 			if (!ok)
314 				throw new JGitInternalException(MessageFormat.format(JGitText
315 						.get().checkoutUnexpectedResult, updateResult.name()));
316 
317 
318 			if (!dco.getToBeDeleted().isEmpty()) {
319 				status = new CheckoutResult(Status.NONDELETED,
320 						dco.getToBeDeleted(),
321 						new ArrayList<>(dco.getUpdated().keySet()),
322 						dco.getRemoved());
323 			} else
324 				status = new CheckoutResult(new ArrayList<>(dco
325 						.getUpdated().keySet()), dco.getRemoved());
326 
327 			return ref;
328 		} catch (IOException ioe) {
329 			throw new JGitInternalException(ioe.getMessage(), ioe);
330 		} finally {
331 			if (status == null)
332 				status = CheckoutResult.ERROR_RESULT;
333 		}
334 	}
335 
336 	private String getShortBranchName(Ref headRef) {
337 		if (headRef.isSymbolic()) {
338 			return Repository.shortenRefName(headRef.getTarget().getName());
339 		}
340 		// Detached HEAD. Every non-symbolic ref in the ref database has an
341 		// object id, so this cannot be null.
342 		ObjectId id = headRef.getObjectId();
343 		if (id == null) {
344 			throw new NullPointerException();
345 		}
346 		return id.getName();
347 	}
348 
349 	/**
350 	 * Add a single slash-separated path to the list of paths to check out. To
351 	 * check out all paths, use {@link #setAllPaths(boolean)}.
352 	 * <p>
353 	 * If this option is set, neither the {@link #setCreateBranch(boolean)} nor
354 	 * {@link #setName(String)} option is considered. In other words, these
355 	 * options are exclusive.
356 	 *
357 	 * @param path
358 	 *            path to update in the working tree and index (with
359 	 *            <code>/</code> as separator)
360 	 * @return {@code this}
361 	 */
362 	public CheckoutCommand addPath(String path) {
363 		checkCallable();
364 		this.paths.add(path);
365 		return this;
366 	}
367 
368 	/**
369 	 * Add multiple slash-separated paths to the list of paths to check out. To
370 	 * check out all paths, use {@link #setAllPaths(boolean)}.
371 	 * <p>
372 	 * If this option is set, neither the {@link #setCreateBranch(boolean)} nor
373 	 * {@link #setName(String)} option is considered. In other words, these
374 	 * options are exclusive.
375 	 *
376 	 * @param p
377 	 *            paths to update in the working tree and index (with
378 	 *            <code>/</code> as separator)
379 	 * @return {@code this}
380 	 * @since 4.6
381 	 */
382 	public CheckoutCommand addPaths(List<String> p) {
383 		checkCallable();
384 		this.paths.addAll(p);
385 		return this;
386 	}
387 
388 	/**
389 	 * Set whether to checkout all paths.
390 	 * <p>
391 	 * This options should be used when you want to do a path checkout on the
392 	 * entire repository and so calling {@link #addPath(String)} is not possible
393 	 * since empty paths are not allowed.
394 	 * <p>
395 	 * If this option is set, neither the {@link #setCreateBranch(boolean)} nor
396 	 * {@link #setName(String)} option is considered. In other words, these
397 	 * options are exclusive.
398 	 *
399 	 * @param all
400 	 *            <code>true</code> to checkout all paths, <code>false</code>
401 	 *            otherwise
402 	 * @return {@code this}
403 	 * @since 2.0
404 	 */
405 	public CheckoutCommand setAllPaths(boolean all) {
406 		checkoutAllPaths = all;
407 		return this;
408 	}
409 
410 	/**
411 	 * Checkout paths into index and working directory, firing a
412 	 * {@link org.eclipse.jgit.events.WorkingTreeModifiedEvent} if the working
413 	 * tree was modified.
414 	 *
415 	 * @return this instance
416 	 * @throws java.io.IOException
417 	 * @throws org.eclipse.jgit.api.errors.RefNotFoundException
418 	 */
419 	protected CheckoutCommand checkoutPaths() throws IOException,
420 			RefNotFoundException {
421 		actuallyModifiedPaths = new HashSet<>();
422 		DirCache dc = repo.lockDirCache();
423 		try (RevWalk revWalk = new RevWalk(repo);
424 				TreeWalk treeWalk = new TreeWalk(repo,
425 						revWalk.getObjectReader())) {
426 			treeWalk.setRecursive(true);
427 			if (!checkoutAllPaths)
428 				treeWalk.setFilter(PathFilterGroup.createFromStrings(paths));
429 			if (isCheckoutIndex())
430 				checkoutPathsFromIndex(treeWalk, dc);
431 			else {
432 				RevCommit commit = revWalk.parseCommit(getStartPointObjectId());
433 				checkoutPathsFromCommit(treeWalk, dc, commit);
434 			}
435 		} finally {
436 			try {
437 				dc.unlock();
438 			} finally {
439 				WorkingTreeModifiedEvent event = new WorkingTreeModifiedEvent(
440 						actuallyModifiedPaths, null);
441 				actuallyModifiedPaths = null;
442 				if (!event.isEmpty()) {
443 					repo.fireEvent(event);
444 				}
445 			}
446 		}
447 		return this;
448 	}
449 
450 	private void checkoutPathsFromIndex(TreeWalk treeWalk, DirCache dc)
451 			throws IOException {
452 		DirCacheIterator dci = new DirCacheIterator(dc);
453 		treeWalk.addTree(dci);
454 
455 		String previousPath = null;
456 
457 		final ObjectReader r = treeWalk.getObjectReader();
458 		DirCacheEditor editor = dc.editor();
459 		while (treeWalk.next()) {
460 			String path = treeWalk.getPathString();
461 			// Only add one edit per path
462 			if (path.equals(previousPath))
463 				continue;
464 
465 			final EolStreamType eolStreamType = treeWalk
466 					.getEolStreamType(CHECKOUT_OP);
467 			final String filterCommand = treeWalk
468 					.getFilterCommand(Constants.ATTR_FILTER_TYPE_SMUDGE);
469 			editor.add(new PathEdit(path) {
470 				@Override
471 				public void apply(DirCacheEntry ent) {
472 					int stage = ent.getStage();
473 					if (stage > DirCacheEntry.STAGE_0) {
474 						if (checkoutStage != null) {
475 							if (stage == checkoutStage.number) {
476 								checkoutPath(ent, r, new CheckoutMetadata(
477 										eolStreamType, filterCommand));
478 								actuallyModifiedPaths.add(path);
479 							}
480 						} else {
481 							UnmergedPathException e = new UnmergedPathException(
482 									ent);
483 							throw new JGitInternalException(e.getMessage(), e);
484 						}
485 					} else {
486 						checkoutPath(ent, r, new CheckoutMetadata(eolStreamType,
487 								filterCommand));
488 						actuallyModifiedPaths.add(path);
489 					}
490 				}
491 			});
492 
493 			previousPath = path;
494 		}
495 		editor.commit();
496 	}
497 
498 	private void checkoutPathsFromCommit(TreeWalk treeWalk, DirCache dc,
499 			RevCommit commit) throws IOException {
500 		treeWalk.addTree(commit.getTree());
501 		final ObjectReader r = treeWalk.getObjectReader();
502 		DirCacheEditor editor = dc.editor();
503 		while (treeWalk.next()) {
504 			final ObjectId blobId = treeWalk.getObjectId(0);
505 			final FileMode mode = treeWalk.getFileMode(0);
506 			final EolStreamType eolStreamType = treeWalk
507 					.getEolStreamType(CHECKOUT_OP);
508 			final String filterCommand = treeWalk
509 					.getFilterCommand(Constants.ATTR_FILTER_TYPE_SMUDGE);
510 			final String path = treeWalk.getPathString();
511 			editor.add(new PathEdit(path) {
512 				@Override
513 				public void apply(DirCacheEntry ent) {
514 					ent.setObjectId(blobId);
515 					ent.setFileMode(mode);
516 					checkoutPath(ent, r,
517 							new CheckoutMetadata(eolStreamType, filterCommand));
518 					actuallyModifiedPaths.add(path);
519 				}
520 			});
521 		}
522 		editor.commit();
523 	}
524 
525 	private void checkoutPath(DirCacheEntry entry, ObjectReader reader,
526 			CheckoutMetadata checkoutMetadata) {
527 		try {
528 			DirCacheCheckout.checkoutEntry(repo, entry, reader, true,
529 					checkoutMetadata);
530 		} catch (IOException e) {
531 			throw new JGitInternalException(MessageFormat.format(
532 					JGitText.get().checkoutConflictWithFile,
533 					entry.getPathString()), e);
534 		}
535 	}
536 
537 	private boolean isCheckoutIndex() {
538 		return startCommit == null && startPoint == null;
539 	}
540 
541 	private ObjectId getStartPointObjectId() throws AmbiguousObjectException,
542 			RefNotFoundException, IOException {
543 		if (startCommit != null)
544 			return startCommit.getId();
545 
546 		String startPointOrHead = (startPoint != null) ? startPoint
547 				: Constants.HEAD;
548 		ObjectId result = repo.resolve(startPointOrHead);
549 		if (result == null)
550 			throw new RefNotFoundException(MessageFormat.format(
551 					JGitText.get().refNotResolved, startPointOrHead));
552 		return result;
553 	}
554 
555 	private void processOptions() throws InvalidRefNameException,
556 			RefAlreadyExistsException, IOException {
557 		if (((!checkoutAllPaths && paths.isEmpty()) || orphan)
558 				&& (name == null || !Repository
559 						.isValidRefName(Constants.R_HEADS + name)))
560 			throw new InvalidRefNameException(MessageFormat.format(JGitText
561 					.get().branchNameInvalid, name == null ? "<null>" : name)); //$NON-NLS-1$
562 
563 		if (orphan) {
564 			Ref refToCheck = repo.exactRef(getBranchName());
565 			if (refToCheck != null)
566 				throw new RefAlreadyExistsException(MessageFormat.format(
567 						JGitText.get().refAlreadyExists, name));
568 		}
569 	}
570 
571 	private String getBranchName() {
572 		if (name.startsWith(Constants.R_REFS))
573 			return name;
574 
575 		return Constants.R_HEADS + name;
576 	}
577 
578 	/**
579 	 * Specify the name of the branch or commit to check out, or the new branch
580 	 * name.
581 	 * <p>
582 	 * When only checking out paths and not switching branches, use
583 	 * {@link #setStartPoint(String)} or {@link #setStartPoint(RevCommit)} to
584 	 * specify from which branch or commit to check out files.
585 	 * <p>
586 	 * When {@link #setCreateBranch(boolean)} is set to <code>true</code>, use
587 	 * this method to set the name of the new branch to create and
588 	 * {@link #setStartPoint(String)} or {@link #setStartPoint(RevCommit)} to
589 	 * specify the start point of the branch.
590 	 *
591 	 * @param name
592 	 *            the name of the branch or commit
593 	 * @return this instance
594 	 */
595 	public CheckoutCommand setName(String name) {
596 		checkCallable();
597 		this.name = name;
598 		return this;
599 	}
600 
601 	/**
602 	 * Specify whether to create a new branch.
603 	 * <p>
604 	 * If <code>true</code> is used, the name of the new branch must be set
605 	 * using {@link #setName(String)}. The commit at which to start the new
606 	 * branch can be set using {@link #setStartPoint(String)} or
607 	 * {@link #setStartPoint(RevCommit)}; if not specified, HEAD is used. Also
608 	 * see {@link #setUpstreamMode} for setting up branch tracking.
609 	 *
610 	 * @param createBranch
611 	 *            if <code>true</code> a branch will be created as part of the
612 	 *            checkout and set to the specified start point
613 	 * @return this instance
614 	 */
615 	public CheckoutCommand setCreateBranch(boolean createBranch) {
616 		checkCallable();
617 		this.createBranch = createBranch;
618 		return this;
619 	}
620 
621 	/**
622 	 * Specify whether to create a new orphan branch.
623 	 * <p>
624 	 * If <code>true</code> is used, the name of the new orphan branch must be
625 	 * set using {@link #setName(String)}. The commit at which to start the new
626 	 * orphan branch can be set using {@link #setStartPoint(String)} or
627 	 * {@link #setStartPoint(RevCommit)}; if not specified, HEAD is used.
628 	 *
629 	 * @param orphan
630 	 *            if <code>true</code> a orphan branch will be created as part
631 	 *            of the checkout to the specified start point
632 	 * @return this instance
633 	 * @since 3.3
634 	 */
635 	public CheckoutCommand setOrphan(boolean orphan) {
636 		checkCallable();
637 		this.orphan = orphan;
638 		return this;
639 	}
640 
641 	/**
642 	 * Specify to force the ref update in case of a branch switch.
643 	 *
644 	 * @param force
645 	 *            if <code>true</code> and the branch with the given name
646 	 *            already exists, the start-point of an existing branch will be
647 	 *            set to a new start-point; if false, the existing branch will
648 	 *            not be changed
649 	 * @return this instance
650 	 */
651 	public CheckoutCommand setForce(boolean force) {
652 		checkCallable();
653 		this.force = force;
654 		return this;
655 	}
656 
657 	/**
658 	 * Set the name of the commit that should be checked out.
659 	 * <p>
660 	 * When checking out files and this is not specified or <code>null</code>,
661 	 * the index is used.
662 	 * <p>
663 	 * When creating a new branch, this will be used as the start point. If not
664 	 * specified or <code>null</code>, the current HEAD is used.
665 	 *
666 	 * @param startPoint
667 	 *            commit name to check out
668 	 * @return this instance
669 	 */
670 	public CheckoutCommand setStartPoint(String startPoint) {
671 		checkCallable();
672 		this.startPoint = startPoint;
673 		this.startCommit = null;
674 		checkOptions();
675 		return this;
676 	}
677 
678 	/**
679 	 * Set the commit that should be checked out.
680 	 * <p>
681 	 * When creating a new branch, this will be used as the start point. If not
682 	 * specified or <code>null</code>, the current HEAD is used.
683 	 * <p>
684 	 * When checking out files and this is not specified or <code>null</code>,
685 	 * the index is used.
686 	 *
687 	 * @param startCommit
688 	 *            commit to check out
689 	 * @return this instance
690 	 */
691 	public CheckoutCommand setStartPoint(RevCommit startCommit) {
692 		checkCallable();
693 		this.startCommit = startCommit;
694 		this.startPoint = null;
695 		checkOptions();
696 		return this;
697 	}
698 
699 	/**
700 	 * When creating a branch with {@link #setCreateBranch(boolean)}, this can
701 	 * be used to configure branch tracking.
702 	 *
703 	 * @param mode
704 	 *            corresponds to the --track/--no-track options; may be
705 	 *            <code>null</code>
706 	 * @return this instance
707 	 */
708 	public CheckoutCommand setUpstreamMode(
709 			CreateBranchCommand.SetupUpstreamMode mode) {
710 		checkCallable();
711 		this.upstreamMode = mode;
712 		return this;
713 	}
714 
715 	/**
716 	 * When checking out the index, check out the specified stage (ours or
717 	 * theirs) for unmerged paths.
718 	 * <p>
719 	 * This can not be used when checking out a branch, only when checking out
720 	 * the index.
721 	 *
722 	 * @param stage
723 	 *            the stage to check out
724 	 * @return this
725 	 */
726 	public CheckoutCommand setStage(Stage stage) {
727 		checkCallable();
728 		this.checkoutStage = stage;
729 		checkOptions();
730 		return this;
731 	}
732 
733 	/**
734 	 * Get the result, never <code>null</code>
735 	 *
736 	 * @return the result, never <code>null</code>
737 	 */
738 	public CheckoutResult getResult() {
739 		if (status == null)
740 			return CheckoutResult.NOT_TRIED_RESULT;
741 		return status;
742 	}
743 
744 	private void checkOptions() {
745 		if (checkoutStage != null && !isCheckoutIndex())
746 			throw new IllegalStateException(
747 					JGitText.get().cannotCheckoutOursSwitchBranch);
748 	}
749 }