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