View Javadoc
1   /*
2    * Copyright (C) 2011, 2017 Chris Aniszczyk <caniszczyk@gmail.com> and others
3    *
4    * This program and the accompanying materials are made available under the
5    * terms of the Eclipse Distribution License v. 1.0 which is available at
6    * https://www.eclipse.org/org/documents/edl-v10.php.
7    *
8    * SPDX-License-Identifier: BSD-3-Clause
9    */
10  package org.eclipse.jgit.api;
11  
12  import java.io.File;
13  import java.io.IOException;
14  import java.net.URISyntaxException;
15  import java.text.MessageFormat;
16  import java.util.ArrayList;
17  import java.util.Collection;
18  import java.util.List;
19  
20  import org.eclipse.jgit.annotations.Nullable;
21  import org.eclipse.jgit.api.errors.GitAPIException;
22  import org.eclipse.jgit.api.errors.InvalidRemoteException;
23  import org.eclipse.jgit.api.errors.JGitInternalException;
24  import org.eclipse.jgit.dircache.DirCache;
25  import org.eclipse.jgit.dircache.DirCacheCheckout;
26  import org.eclipse.jgit.errors.IncorrectObjectTypeException;
27  import org.eclipse.jgit.errors.MissingObjectException;
28  import org.eclipse.jgit.internal.JGitText;
29  import org.eclipse.jgit.lib.AnyObjectId;
30  import org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode;
31  import org.eclipse.jgit.lib.ConfigConstants;
32  import org.eclipse.jgit.lib.Constants;
33  import org.eclipse.jgit.lib.NullProgressMonitor;
34  import org.eclipse.jgit.lib.ObjectId;
35  import org.eclipse.jgit.lib.ProgressMonitor;
36  import org.eclipse.jgit.lib.Ref;
37  import org.eclipse.jgit.lib.RefUpdate;
38  import org.eclipse.jgit.lib.Repository;
39  import org.eclipse.jgit.revwalk.RevCommit;
40  import org.eclipse.jgit.revwalk.RevWalk;
41  import org.eclipse.jgit.submodule.SubmoduleWalk;
42  import org.eclipse.jgit.transport.FetchResult;
43  import org.eclipse.jgit.transport.RefSpec;
44  import org.eclipse.jgit.transport.RemoteConfig;
45  import org.eclipse.jgit.transport.TagOpt;
46  import org.eclipse.jgit.transport.URIish;
47  import org.eclipse.jgit.util.FS;
48  import org.eclipse.jgit.util.FileUtils;
49  
50  /**
51   * Clone a repository into a new working directory
52   *
53   * @see <a href="http://www.kernel.org/pub/software/scm/git/docs/git-clone.html"
54   *      >Git documentation about Clone</a>
55   */
56  public class CloneCommand extends TransportCommand<CloneCommand, Git> {
57  
58  	private String uri;
59  
60  	private File directory;
61  
62  	private File gitDir;
63  
64  	private boolean bare;
65  
66  	private FS fs;
67  
68  	private String remote = Constants.DEFAULT_REMOTE_NAME;
69  
70  	private String branch = Constants.HEAD;
71  
72  	private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;
73  
74  	private boolean cloneAllBranches;
75  
76  	private boolean mirror;
77  
78  	private boolean cloneSubmodules;
79  
80  	private boolean noCheckout;
81  
82  	private Collection<String> branchesToClone;
83  
84  	private Callback callback;
85  
86  	private boolean directoryExistsInitially;
87  
88  	private boolean gitDirExistsInitially;
89  
90  	private FETCH_TYPE fetchType;
91  
92  	private enum FETCH_TYPE {
93  		MULTIPLE_BRANCHES, ALL_BRANCHES, MIRROR
94  	}
95  
96  	/**
97  	 * Callback for status of clone operation.
98  	 *
99  	 * @since 4.8
100 	 */
101 	public interface Callback {
102 		/**
103 		 * Notify initialized submodules.
104 		 *
105 		 * @param submodules
106 		 *            the submodules
107 		 *
108 		 */
109 		void initializedSubmodules(Collection<String> submodules);
110 
111 		/**
112 		 * Notify starting to clone a submodule.
113 		 *
114 		 * @param path
115 		 *            the submodule path
116 		 */
117 		void cloningSubmodule(String path);
118 
119 		/**
120 		 * Notify checkout of commit
121 		 *
122 		 * @param commit
123 		 *            the id of the commit being checked out
124 		 * @param path
125 		 *            the submodule path
126 		 */
127 		void checkingOut(AnyObjectId commit, String path);
128 	}
129 
130 	/**
131 	 * Create clone command with no repository set
132 	 */
133 	public CloneCommand() {
134 		super(null);
135 	}
136 
137 	/**
138 	 * Get the git directory. This is primarily used for tests.
139 	 *
140 	 * @return the git directory
141 	 */
142 	@Nullable
143 	File getDirectory() {
144 		return directory;
145 	}
146 
147 	/**
148 	 * {@inheritDoc}
149 	 * <p>
150 	 * Executes the {@code Clone} command.
151 	 *
152 	 * The Git instance returned by this command needs to be closed by the
153 	 * caller to free resources held by the underlying {@link Repository}
154 	 * instance. It is recommended to call this method as soon as you don't need
155 	 * a reference to this {@link Git} instance and the underlying
156 	 * {@link Repository} instance anymore.
157 	 */
158 	@Override
159 	public Git call() throws GitAPIException, InvalidRemoteException,
160 			org.eclipse.jgit.api.errors.TransportException {
161 		URIish u = null;
162 		try {
163 			u = new URIish(uri);
164 			verifyDirectories(u);
165 		} catch (URISyntaxException e) {
166 			throw new InvalidRemoteException(
167 					MessageFormat.format(JGitText.get().invalidURL, uri), e);
168 		}
169 		setFetchType();
170 		@SuppressWarnings("resource") // Closed by caller
171 		Repository repository = init();
172 		FetchResult fetchResult = null;
173 		Thread cleanupHook = new Thread(() -> cleanup());
174 		Runtime.getRuntime().addShutdownHook(cleanupHook);
175 		try {
176 			fetchResult = fetch(repository, u);
177 		} catch (IOException ioe) {
178 			if (repository != null) {
179 				repository.close();
180 			}
181 			cleanup();
182 			throw new JGitInternalException(ioe.getMessage(), ioe);
183 		} catch (URISyntaxException e) {
184 			if (repository != null) {
185 				repository.close();
186 			}
187 			cleanup();
188 			throw new InvalidRemoteException(
189 					MessageFormat.format(JGitText.get().invalidRemote, remote),
190 					e);
191 		} catch (GitAPIException | RuntimeException e) {
192 			if (repository != null) {
193 				repository.close();
194 			}
195 			cleanup();
196 			throw e;
197 		} finally {
198 			Runtime.getRuntime().removeShutdownHook(cleanupHook);
199 		}
200 		if (!noCheckout) {
201 			try {
202 				checkout(repository, fetchResult);
203 			} catch (IOException ioe) {
204 				repository.close();
205 				throw new JGitInternalException(ioe.getMessage(), ioe);
206 			} catch (GitAPIException | RuntimeException e) {
207 				repository.close();
208 				throw e;
209 			}
210 		}
211 		return new Git(repository, true);
212 	}
213 
214 	private void setFetchType() {
215 		if (mirror) {
216 			fetchType = FETCH_TYPE.MIRROR;
217 			setBare(true);
218 		} else if (cloneAllBranches) {
219 			fetchType = FETCH_TYPE.ALL_BRANCHES;
220 		} else if (branchesToClone != null && !branchesToClone.isEmpty()) {
221 			fetchType = FETCH_TYPE.MULTIPLE_BRANCHES;
222 		} else {
223 			// Default: neither mirror nor all nor specific refs given
224 			fetchType = FETCH_TYPE.ALL_BRANCHES;
225 		}
226 	}
227 
228 	private static boolean isNonEmptyDirectory(File dir) {
229 		if (dir != null && dir.exists()) {
230 			File[] files = dir.listFiles();
231 			return files != null && files.length != 0;
232 		}
233 		return false;
234 	}
235 
236 	void verifyDirectories(URIish u) {
237 		if (directory == null && gitDir == null) {
238 			directory = new File(u.getHumanishName() + (bare ? Constants.DOT_GIT_EXT : "")); //$NON-NLS-1$
239 		}
240 		directoryExistsInitially = directory != null && directory.exists();
241 		gitDirExistsInitially = gitDir != null && gitDir.exists();
242 		validateDirs(directory, gitDir, bare);
243 		if (isNonEmptyDirectory(directory)) {
244 			throw new JGitInternalException(MessageFormat.format(
245 					JGitText.get().cloneNonEmptyDirectory, directory.getName()));
246 		}
247 		if (isNonEmptyDirectory(gitDir)) {
248 			throw new JGitInternalException(MessageFormat.format(
249 					JGitText.get().cloneNonEmptyDirectory, gitDir.getName()));
250 		}
251 	}
252 
253 	private Repository init() throws GitAPIException {
254 		InitCommand command = Git.init();
255 		command.setBare(bare);
256 		if (fs != null) {
257 			command.setFs(fs);
258 		}
259 		if (directory != null) {
260 			command.setDirectory(directory);
261 		}
262 		if (gitDir != null) {
263 			command.setGitDir(gitDir);
264 		}
265 		return command.call().getRepository();
266 	}
267 
268 	private FetchResult fetch(Repository clonedRepo, URIish u)
269 			throws URISyntaxException,
270 			org.eclipse.jgit.api.errors.TransportException, IOException,
271 			GitAPIException {
272 		// create the remote config and save it
273 		RemoteConfig config = new RemoteConfig(clonedRepo.getConfig(), remote);
274 		config.addURI(u);
275 
276 		boolean fetchAll = fetchType == FETCH_TYPE.ALL_BRANCHES
277 				|| fetchType == FETCH_TYPE.MIRROR;
278 
279 		config.setFetchRefSpecs(calculateRefSpecs(fetchType, config.getName()));
280 		config.setMirror(fetchType == FETCH_TYPE.MIRROR);
281 		config.update(clonedRepo.getConfig());
282 
283 		clonedRepo.getConfig().save();
284 
285 		// run the fetch command
286 		FetchCommand command = new FetchCommand(clonedRepo);
287 		command.setRemote(remote);
288 		command.setProgressMonitor(monitor);
289 		command.setTagOpt(fetchAll ? TagOpt.FETCH_TAGS : TagOpt.AUTO_FOLLOW);
290 		configure(command);
291 
292 		return command.call();
293 	}
294 
295 	private List<RefSpec> calculateRefSpecs(FETCH_TYPE type,
296 			String remoteName) {
297 		List<RefSpec> specs = new ArrayList<>();
298 		if (type == FETCH_TYPE.MIRROR) {
299 			specs.add(new RefSpec().setForceUpdate(true).setSourceDestination(
300 					Constants.R_REFS + '*', Constants.R_REFS + '*'));
301 		} else {
302 			RefSpec heads = new RefSpec();
303 			heads = heads.setForceUpdate(true);
304 			final String dst = (bare ? Constants.R_HEADS
305 					: Constants.R_REMOTES + remoteName + '/') + '*';
306 			heads = heads.setSourceDestination(Constants.R_HEADS + '*', dst);
307 			if (type == FETCH_TYPE.MULTIPLE_BRANCHES) {
308 				RefSpec tags = new RefSpec().setForceUpdate(true)
309 						.setSourceDestination(Constants.R_TAGS + '*',
310 								Constants.R_TAGS + '*');
311 				for (String selectedRef : branchesToClone) {
312 					if (heads.matchSource(selectedRef)) {
313 						specs.add(heads.expandFromSource(selectedRef));
314 					} else if (tags.matchSource(selectedRef)) {
315 						specs.add(tags.expandFromSource(selectedRef));
316 					}
317 				}
318 			} else {
319 				// We'll fetch the tags anyway.
320 				specs.add(heads);
321 			}
322 		}
323 		return specs;
324 	}
325 
326 	private void checkout(Repository clonedRepo, FetchResult result)
327 			throws MissingObjectException, IncorrectObjectTypeException,
328 			IOException, GitAPIException {
329 
330 		Ref head = null;
331 		if (branch.equals(Constants.HEAD)) {
332 			Ref foundBranch = findBranchToCheckout(result);
333 			if (foundBranch != null)
334 				head = foundBranch;
335 		}
336 		if (head == null) {
337 			head = result.getAdvertisedRef(branch);
338 			if (head == null)
339 				head = result.getAdvertisedRef(Constants.R_HEADS + branch);
340 			if (head == null)
341 				head = result.getAdvertisedRef(Constants.R_TAGS + branch);
342 		}
343 
344 		if (head == null || head.getObjectId() == null)
345 			return; // TODO throw exception?
346 
347 		if (head.getName().startsWith(Constants.R_HEADS)) {
348 			final RefUpdate newHead = clonedRepo.updateRef(Constants.HEAD);
349 			newHead.disableRefLog();
350 			newHead.link(head.getName());
351 			addMergeConfig(clonedRepo, head);
352 		}
353 
354 		final RevCommit commit = parseCommit(clonedRepo, head);
355 
356 		boolean detached = !head.getName().startsWith(Constants.R_HEADS);
357 		RefUpdate u = clonedRepo.updateRef(Constants.HEAD, detached);
358 		u.setNewObjectId(commit.getId());
359 		u.forceUpdate();
360 
361 		if (!bare) {
362 			DirCache dc = clonedRepo.lockDirCache();
363 			DirCacheCheckout co = new DirCacheCheckout(clonedRepo, dc,
364 					commit.getTree());
365 			co.setProgressMonitor(monitor);
366 			co.checkout();
367 			if (cloneSubmodules)
368 				cloneSubmodules(clonedRepo);
369 		}
370 	}
371 
372 	private void cloneSubmodules(Repository clonedRepo) throws IOException,
373 			GitAPIException {
374 		SubmoduleInitCommand init = new SubmoduleInitCommand(clonedRepo);
375 		Collection<String> submodules = init.call();
376 		if (submodules.isEmpty()) {
377 			return;
378 		}
379 		if (callback != null) {
380 			callback.initializedSubmodules(submodules);
381 		}
382 
383 		SubmoduleUpdateCommand update = new SubmoduleUpdateCommand(clonedRepo);
384 		configure(update);
385 		update.setProgressMonitor(monitor);
386 		update.setCallback(callback);
387 		if (!update.call().isEmpty()) {
388 			SubmoduleWalk walk = SubmoduleWalk.forIndex(clonedRepo);
389 			while (walk.next()) {
390 				try (Repository subRepo = walk.getRepository()) {
391 					if (subRepo != null) {
392 						cloneSubmodules(subRepo);
393 					}
394 				}
395 			}
396 		}
397 	}
398 
399 	private Ref findBranchToCheckout(FetchResult result) {
400 		final Ref idHEAD = result.getAdvertisedRef(Constants.HEAD);
401 		ObjectId headId = idHEAD != null ? idHEAD.getObjectId() : null;
402 		if (headId == null) {
403 			return null;
404 		}
405 
406 		Ref master = result.getAdvertisedRef(Constants.R_HEADS
407 				+ Constants.MASTER);
408 		ObjectId objectId = master != null ? master.getObjectId() : null;
409 		if (headId.equals(objectId)) {
410 			return master;
411 		}
412 
413 		Ref foundBranch = null;
414 		for (Ref r : result.getAdvertisedRefs()) {
415 			final String n = r.getName();
416 			if (!n.startsWith(Constants.R_HEADS))
417 				continue;
418 			if (headId.equals(r.getObjectId())) {
419 				foundBranch = r;
420 				break;
421 			}
422 		}
423 		return foundBranch;
424 	}
425 
426 	private void addMergeConfig(Repository clonedRepo, Ref head)
427 			throws IOException {
428 		String branchName = Repository.shortenRefName(head.getName());
429 		clonedRepo.getConfig().setString(ConfigConstants.CONFIG_BRANCH_SECTION,
430 				branchName, ConfigConstants.CONFIG_KEY_REMOTE, remote);
431 		clonedRepo.getConfig().setString(ConfigConstants.CONFIG_BRANCH_SECTION,
432 				branchName, ConfigConstants.CONFIG_KEY_MERGE, head.getName());
433 		String autosetupRebase = clonedRepo.getConfig().getString(
434 				ConfigConstants.CONFIG_BRANCH_SECTION, null,
435 				ConfigConstants.CONFIG_KEY_AUTOSETUPREBASE);
436 		if (ConfigConstants.CONFIG_KEY_ALWAYS.equals(autosetupRebase)
437 				|| ConfigConstants.CONFIG_KEY_REMOTE.equals(autosetupRebase))
438 			clonedRepo.getConfig().setEnum(
439 					ConfigConstants.CONFIG_BRANCH_SECTION, branchName,
440 					ConfigConstants.CONFIG_KEY_REBASE, BranchRebaseMode.REBASE);
441 		clonedRepo.getConfig().save();
442 	}
443 
444 	private RevCommit parseCommit(Repository clonedRepo, Ref ref)
445 			throws MissingObjectException, IncorrectObjectTypeException,
446 			IOException {
447 		final RevCommit commit;
448 		try (RevWalkRevWalk.html#RevWalk">RevWalk rw = new RevWalk(clonedRepo)) {
449 			commit = rw.parseCommit(ref.getObjectId());
450 		}
451 		return commit;
452 	}
453 
454 	/**
455 	 * Set the URI to clone from
456 	 *
457 	 * @param uri
458 	 *            the URI to clone from, or {@code null} to unset the URI. The
459 	 *            URI must be set before {@link #call} is called.
460 	 * @return this instance
461 	 */
462 	public CloneCommand setURI(String uri) {
463 		this.uri = uri;
464 		return this;
465 	}
466 
467 	/**
468 	 * The optional directory associated with the clone operation. If the
469 	 * directory isn't set, a name associated with the source uri will be used.
470 	 *
471 	 * @see URIish#getHumanishName()
472 	 * @param directory
473 	 *            the directory to clone to, or {@code null} if the directory
474 	 *            name should be taken from the source uri
475 	 * @return this instance
476 	 * @throws java.lang.IllegalStateException
477 	 *             if the combination of directory, gitDir and bare is illegal.
478 	 *             E.g. if for a non-bare repository directory and gitDir point
479 	 *             to the same directory of if for a bare repository both
480 	 *             directory and gitDir are specified
481 	 */
482 	public CloneCommand setDirectory(File directory) {
483 		validateDirs(directory, gitDir, bare);
484 		this.directory = directory;
485 		return this;
486 	}
487 
488 	/**
489 	 * Set the repository meta directory (.git)
490 	 *
491 	 * @param gitDir
492 	 *            the repository meta directory, or {@code null} to choose one
493 	 *            automatically at clone time
494 	 * @return this instance
495 	 * @throws java.lang.IllegalStateException
496 	 *             if the combination of directory, gitDir and bare is illegal.
497 	 *             E.g. if for a non-bare repository directory and gitDir point
498 	 *             to the same directory of if for a bare repository both
499 	 *             directory and gitDir are specified
500 	 * @since 3.6
501 	 */
502 	public CloneCommand setGitDir(File gitDir) {
503 		validateDirs(directory, gitDir, bare);
504 		this.gitDir = gitDir;
505 		return this;
506 	}
507 
508 	/**
509 	 * Set whether the cloned repository shall be bare
510 	 *
511 	 * @param bare
512 	 *            whether the cloned repository is bare or not
513 	 * @return this instance
514 	 * @throws java.lang.IllegalStateException
515 	 *             if the combination of directory, gitDir and bare is illegal.
516 	 *             E.g. if for a non-bare repository directory and gitDir point
517 	 *             to the same directory of if for a bare repository both
518 	 *             directory and gitDir are specified
519 	 */
520 	public CloneCommand setBare(boolean bare) throws IllegalStateException {
521 		validateDirs(directory, gitDir, bare);
522 		this.bare = bare;
523 		return this;
524 	}
525 
526 	/**
527 	 * Set the file system abstraction to be used for repositories created by
528 	 * this command.
529 	 *
530 	 * @param fs
531 	 *            the abstraction.
532 	 * @return {@code this} (for chaining calls).
533 	 * @since 4.10
534 	 */
535 	public CloneCommand setFs(FS fs) {
536 		this.fs = fs;
537 		return this;
538 	}
539 
540 	/**
541 	 * The remote name used to keep track of the upstream repository for the
542 	 * clone operation. If no remote name is set, the default value of
543 	 * <code>Constants.DEFAULT_REMOTE_NAME</code> will be used.
544 	 *
545 	 * @see Constants#DEFAULT_REMOTE_NAME
546 	 * @param remote
547 	 *            name that keeps track of the upstream repository.
548 	 *            {@code null} means to use DEFAULT_REMOTE_NAME.
549 	 * @return this instance
550 	 */
551 	public CloneCommand setRemote(String remote) {
552 		if (remote == null) {
553 			remote = Constants.DEFAULT_REMOTE_NAME;
554 		}
555 		this.remote = remote;
556 		return this;
557 	}
558 
559 	/**
560 	 * Set the initial branch
561 	 *
562 	 * @param branch
563 	 *            the initial branch to check out when cloning the repository.
564 	 *            Can be specified as ref name (<code>refs/heads/master</code>),
565 	 *            branch name (<code>master</code>) or tag name
566 	 *            (<code>v1.2.3</code>). The default is to use the branch
567 	 *            pointed to by the cloned repository's HEAD and can be
568 	 *            requested by passing {@code null} or <code>HEAD</code>.
569 	 * @return this instance
570 	 */
571 	public CloneCommand setBranch(String branch) {
572 		if (branch == null) {
573 			branch = Constants.HEAD;
574 		}
575 		this.branch = branch;
576 		return this;
577 	}
578 
579 	/**
580 	 * The progress monitor associated with the clone operation. By default,
581 	 * this is set to <code>NullProgressMonitor</code>
582 	 *
583 	 * @see NullProgressMonitor
584 	 * @param monitor
585 	 *            a {@link org.eclipse.jgit.lib.ProgressMonitor}
586 	 * @return {@code this}
587 	 */
588 	public CloneCommand setProgressMonitor(ProgressMonitor monitor) {
589 		if (monitor == null) {
590 			monitor = NullProgressMonitor.INSTANCE;
591 		}
592 		this.monitor = monitor;
593 		return this;
594 	}
595 
596 	/**
597 	 * Set whether all branches have to be fetched.
598 	 * <p>
599 	 * If {@code false}, use {@link #setBranchesToClone(Collection)} to define
600 	 * what will be cloned. If neither are set, all branches will be cloned.
601 	 * </p>
602 	 *
603 	 * @param cloneAllBranches
604 	 *            {@code true} when all branches have to be fetched (indicates
605 	 *            wildcard in created fetch refspec), {@code false} otherwise.
606 	 * @return {@code this}
607 	 */
608 	public CloneCommand setCloneAllBranches(boolean cloneAllBranches) {
609 		this.cloneAllBranches = cloneAllBranches;
610 		return this;
611 	}
612 
613 	/**
614 	 * Set up a mirror of the source repository. This implies that a bare
615 	 * repository will be created. Compared to {@link #setBare},
616 	 * {@code #setMirror} not only maps local branches of the source to local
617 	 * branches of the target, it maps all refs (including remote-tracking
618 	 * branches, notes etc.) and sets up a refspec configuration such that all
619 	 * these refs are overwritten by a git remote update in the target
620 	 * repository.
621 	 *
622 	 * @param mirror
623 	 *            whether to mirror all refs from the source repository
624 	 *
625 	 * @return {@code this}
626 	 * @since 5.6
627 	 */
628 	public CloneCommand setMirror(boolean mirror) {
629 		this.mirror = mirror;
630 		return this;
631 	}
632 
633 	/**
634 	 * Set whether to clone submodules
635 	 *
636 	 * @param cloneSubmodules
637 	 *            true to initialize and update submodules. Ignored when
638 	 *            {@link #setBare(boolean)} is set to true.
639 	 * @return {@code this}
640 	 */
641 	public CloneCommand setCloneSubmodules(boolean cloneSubmodules) {
642 		this.cloneSubmodules = cloneSubmodules;
643 		return this;
644 	}
645 
646 	/**
647 	 * Set the branches or tags to clone.
648 	 * <p>
649 	 * This is ignored if {@link #setCloneAllBranches(boolean)
650 	 * setCloneAllBranches(true)} or {@link #setMirror(boolean) setMirror(true)}
651 	 * is used. If {@code branchesToClone} is {@code null} or empty, it's also
652 	 * ignored.
653 	 * </p>
654 	 *
655 	 * @param branchesToClone
656 	 *            collection of branches to clone. Must be specified as full ref
657 	 *            names (e.g. {@code refs/heads/master} or
658 	 *            {@code refs/tags/v1.0.0}).
659 	 * @return {@code this}
660 	 */
661 	public CloneCommand setBranchesToClone(Collection<String> branchesToClone) {
662 		this.branchesToClone = branchesToClone;
663 		return this;
664 	}
665 
666 	/**
667 	 * Set whether to skip checking out a branch
668 	 *
669 	 * @param noCheckout
670 	 *            if set to <code>true</code> no branch will be checked out
671 	 *            after the clone. This enhances performance of the clone
672 	 *            command when there is no need for a checked out branch.
673 	 * @return {@code this}
674 	 */
675 	public CloneCommand setNoCheckout(boolean noCheckout) {
676 		this.noCheckout = noCheckout;
677 		return this;
678 	}
679 
680 	/**
681 	 * Register a progress callback.
682 	 *
683 	 * @param callback
684 	 *            the callback
685 	 * @return {@code this}
686 	 * @since 4.8
687 	 */
688 	public CloneCommand setCallback(Callback callback) {
689 		this.callback = callback;
690 		return this;
691 	}
692 
693 	private static void validateDirs(File directory, File gitDir, boolean bare)
694 			throws IllegalStateException {
695 		if (directory != null) {
696 			if (directory.exists() && !directory.isDirectory()) {
697 				throw new IllegalStateException(MessageFormat.format(
698 						JGitText.get().initFailedDirIsNoDirectory, directory));
699 			}
700 			if (gitDir != null && gitDir.exists() && !gitDir.isDirectory()) {
701 				throw new IllegalStateException(MessageFormat.format(
702 						JGitText.get().initFailedGitDirIsNoDirectory,
703 						gitDir));
704 			}
705 			if (bare) {
706 				if (gitDir != null && !gitDir.equals(directory))
707 					throw new IllegalStateException(MessageFormat.format(
708 							JGitText.get().initFailedBareRepoDifferentDirs,
709 							gitDir, directory));
710 			} else {
711 				if (gitDir != null && gitDir.equals(directory))
712 					throw new IllegalStateException(MessageFormat.format(
713 							JGitText.get().initFailedNonBareRepoSameDirs,
714 							gitDir, directory));
715 			}
716 		}
717 	}
718 
719 	private void cleanup() {
720 		try {
721 			if (directory != null) {
722 				if (!directoryExistsInitially) {
723 					FileUtils.delete(directory, FileUtils.RECURSIVE
724 							| FileUtils.SKIP_MISSING | FileUtils.IGNORE_ERRORS);
725 				} else {
726 					deleteChildren(directory);
727 				}
728 			}
729 			if (gitDir != null) {
730 				if (!gitDirExistsInitially) {
731 					FileUtils.delete(gitDir, FileUtils.RECURSIVE
732 							| FileUtils.SKIP_MISSING | FileUtils.IGNORE_ERRORS);
733 				} else {
734 					deleteChildren(gitDir);
735 				}
736 			}
737 		} catch (IOException e) {
738 			// Ignore; this is a best-effort cleanup in error cases, and
739 			// IOException should not be raised anyway
740 		}
741 	}
742 
743 	private void deleteChildren(File file) throws IOException {
744 		File[] files = file.listFiles();
745 		if (files == null) {
746 			return;
747 		}
748 		for (File child : files) {
749 			FileUtils.delete(child, FileUtils.RECURSIVE | FileUtils.SKIP_MISSING
750 					| FileUtils.IGNORE_ERRORS);
751 		}
752 	}
753 }