CloneCommand.java

  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. import java.io.File;
  12. import java.io.IOException;
  13. import java.net.URISyntaxException;
  14. import java.text.MessageFormat;
  15. import java.util.ArrayList;
  16. import java.util.Collection;
  17. import java.util.List;

  18. import org.eclipse.jgit.annotations.Nullable;
  19. import org.eclipse.jgit.api.errors.GitAPIException;
  20. import org.eclipse.jgit.api.errors.InvalidRemoteException;
  21. import org.eclipse.jgit.api.errors.JGitInternalException;
  22. import org.eclipse.jgit.dircache.DirCache;
  23. import org.eclipse.jgit.dircache.DirCacheCheckout;
  24. import org.eclipse.jgit.errors.IncorrectObjectTypeException;
  25. import org.eclipse.jgit.errors.MissingObjectException;
  26. import org.eclipse.jgit.internal.JGitText;
  27. import org.eclipse.jgit.lib.AnyObjectId;
  28. import org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode;
  29. import org.eclipse.jgit.lib.ConfigConstants;
  30. import org.eclipse.jgit.lib.Constants;
  31. import org.eclipse.jgit.lib.NullProgressMonitor;
  32. import org.eclipse.jgit.lib.ObjectId;
  33. import org.eclipse.jgit.lib.ProgressMonitor;
  34. import org.eclipse.jgit.lib.Ref;
  35. import org.eclipse.jgit.lib.RefUpdate;
  36. import org.eclipse.jgit.lib.Repository;
  37. import org.eclipse.jgit.revwalk.RevCommit;
  38. import org.eclipse.jgit.revwalk.RevWalk;
  39. import org.eclipse.jgit.submodule.SubmoduleWalk;
  40. import org.eclipse.jgit.transport.FetchResult;
  41. import org.eclipse.jgit.transport.RefSpec;
  42. import org.eclipse.jgit.transport.RemoteConfig;
  43. import org.eclipse.jgit.transport.TagOpt;
  44. import org.eclipse.jgit.transport.URIish;
  45. import org.eclipse.jgit.util.FS;
  46. import org.eclipse.jgit.util.FileUtils;

  47. /**
  48.  * Clone a repository into a new working directory
  49.  *
  50.  * @see <a href="http://www.kernel.org/pub/software/scm/git/docs/git-clone.html"
  51.  *      >Git documentation about Clone</a>
  52.  */
  53. public class CloneCommand extends TransportCommand<CloneCommand, Git> {

  54.     private String uri;

  55.     private File directory;

  56.     private File gitDir;

  57.     private boolean bare;

  58.     private FS fs;

  59.     private String remote = Constants.DEFAULT_REMOTE_NAME;

  60.     private String branch = Constants.HEAD;

  61.     private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;

  62.     private boolean cloneAllBranches;

  63.     private boolean mirror;

  64.     private boolean cloneSubmodules;

  65.     private boolean noCheckout;

  66.     private Collection<String> branchesToClone;

  67.     private Callback callback;

  68.     private boolean directoryExistsInitially;

  69.     private boolean gitDirExistsInitially;

  70.     private FETCH_TYPE fetchType;

  71.     private TagOpt tagOption;

  72.     private enum FETCH_TYPE {
  73.         MULTIPLE_BRANCHES, ALL_BRANCHES, MIRROR
  74.     }

  75.     /**
  76.      * Callback for status of clone operation.
  77.      *
  78.      * @since 4.8
  79.      */
  80.     public interface Callback {
  81.         /**
  82.          * Notify initialized submodules.
  83.          *
  84.          * @param submodules
  85.          *            the submodules
  86.          *
  87.          */
  88.         void initializedSubmodules(Collection<String> submodules);

  89.         /**
  90.          * Notify starting to clone a submodule.
  91.          *
  92.          * @param path
  93.          *            the submodule path
  94.          */
  95.         void cloningSubmodule(String path);

  96.         /**
  97.          * Notify checkout of commit
  98.          *
  99.          * @param commit
  100.          *            the id of the commit being checked out
  101.          * @param path
  102.          *            the submodule path
  103.          */
  104.         void checkingOut(AnyObjectId commit, String path);
  105.     }

  106.     /**
  107.      * Create clone command with no repository set
  108.      */
  109.     public CloneCommand() {
  110.         super(null);
  111.     }

  112.     /**
  113.      * Get the git directory. This is primarily used for tests.
  114.      *
  115.      * @return the git directory
  116.      */
  117.     @Nullable
  118.     File getDirectory() {
  119.         return directory;
  120.     }

  121.     /**
  122.      * {@inheritDoc}
  123.      * <p>
  124.      * Executes the {@code Clone} command.
  125.      *
  126.      * The Git instance returned by this command needs to be closed by the
  127.      * caller to free resources held by the underlying {@link Repository}
  128.      * instance. It is recommended to call this method as soon as you don't need
  129.      * a reference to this {@link Git} instance and the underlying
  130.      * {@link Repository} instance anymore.
  131.      */
  132.     @Override
  133.     public Git call() throws GitAPIException, InvalidRemoteException,
  134.             org.eclipse.jgit.api.errors.TransportException {
  135.         URIish u = null;
  136.         try {
  137.             u = new URIish(uri);
  138.             verifyDirectories(u);
  139.         } catch (URISyntaxException e) {
  140.             throw new InvalidRemoteException(
  141.                     MessageFormat.format(JGitText.get().invalidURL, uri), e);
  142.         }
  143.         setFetchType();
  144.         @SuppressWarnings("resource") // Closed by caller
  145.         Repository repository = init();
  146.         FetchResult fetchResult = null;
  147.         Thread cleanupHook = new Thread(() -> cleanup());
  148.         Runtime.getRuntime().addShutdownHook(cleanupHook);
  149.         try {
  150.             fetchResult = fetch(repository, u);
  151.         } catch (IOException ioe) {
  152.             if (repository != null) {
  153.                 repository.close();
  154.             }
  155.             cleanup();
  156.             throw new JGitInternalException(ioe.getMessage(), ioe);
  157.         } catch (URISyntaxException e) {
  158.             if (repository != null) {
  159.                 repository.close();
  160.             }
  161.             cleanup();
  162.             throw new InvalidRemoteException(
  163.                     MessageFormat.format(JGitText.get().invalidRemote, remote),
  164.                     e);
  165.         } catch (GitAPIException | RuntimeException e) {
  166.             if (repository != null) {
  167.                 repository.close();
  168.             }
  169.             cleanup();
  170.             throw e;
  171.         } finally {
  172.             Runtime.getRuntime().removeShutdownHook(cleanupHook);
  173.         }
  174.         if (!noCheckout) {
  175.             try {
  176.                 checkout(repository, fetchResult);
  177.             } catch (IOException ioe) {
  178.                 repository.close();
  179.                 throw new JGitInternalException(ioe.getMessage(), ioe);
  180.             } catch (GitAPIException | RuntimeException e) {
  181.                 repository.close();
  182.                 throw e;
  183.             }
  184.         }
  185.         return new Git(repository, true);
  186.     }

  187.     private void setFetchType() {
  188.         if (mirror) {
  189.             fetchType = FETCH_TYPE.MIRROR;
  190.             setBare(true);
  191.         } else if (cloneAllBranches) {
  192.             fetchType = FETCH_TYPE.ALL_BRANCHES;
  193.         } else if (branchesToClone != null && !branchesToClone.isEmpty()) {
  194.             fetchType = FETCH_TYPE.MULTIPLE_BRANCHES;
  195.         } else {
  196.             // Default: neither mirror nor all nor specific refs given
  197.             fetchType = FETCH_TYPE.ALL_BRANCHES;
  198.         }
  199.     }

  200.     private static boolean isNonEmptyDirectory(File dir) {
  201.         if (dir != null && dir.exists()) {
  202.             File[] files = dir.listFiles();
  203.             return files != null && files.length != 0;
  204.         }
  205.         return false;
  206.     }

  207.     void verifyDirectories(URIish u) {
  208.         if (directory == null && gitDir == null) {
  209.             directory = new File(u.getHumanishName() + (bare ? Constants.DOT_GIT_EXT : "")); //$NON-NLS-1$
  210.         }
  211.         directoryExistsInitially = directory != null && directory.exists();
  212.         gitDirExistsInitially = gitDir != null && gitDir.exists();
  213.         validateDirs(directory, gitDir, bare);
  214.         if (isNonEmptyDirectory(directory)) {
  215.             throw new JGitInternalException(MessageFormat.format(
  216.                     JGitText.get().cloneNonEmptyDirectory, directory.getName()));
  217.         }
  218.         if (isNonEmptyDirectory(gitDir)) {
  219.             throw new JGitInternalException(MessageFormat.format(
  220.                     JGitText.get().cloneNonEmptyDirectory, gitDir.getName()));
  221.         }
  222.     }

  223.     private Repository init() throws GitAPIException {
  224.         InitCommand command = Git.init();
  225.         command.setBare(bare);
  226.         if (fs != null) {
  227.             command.setFs(fs);
  228.         }
  229.         if (directory != null) {
  230.             command.setDirectory(directory);
  231.         }
  232.         if (gitDir != null) {
  233.             command.setGitDir(gitDir);
  234.         }
  235.         return command.call().getRepository();
  236.     }

  237.     private FetchResult fetch(Repository clonedRepo, URIish u)
  238.             throws URISyntaxException,
  239.             org.eclipse.jgit.api.errors.TransportException, IOException,
  240.             GitAPIException {
  241.         // create the remote config and save it
  242.         RemoteConfig config = new RemoteConfig(clonedRepo.getConfig(), remote);
  243.         config.addURI(u);

  244.         boolean fetchAll = fetchType == FETCH_TYPE.ALL_BRANCHES
  245.                 || fetchType == FETCH_TYPE.MIRROR;

  246.         config.setFetchRefSpecs(calculateRefSpecs(fetchType, config.getName()));
  247.         config.setMirror(fetchType == FETCH_TYPE.MIRROR);
  248.         if (tagOption != null) {
  249.             config.setTagOpt(tagOption);
  250.         }
  251.         config.update(clonedRepo.getConfig());

  252.         clonedRepo.getConfig().save();

  253.         // run the fetch command
  254.         FetchCommand command = new FetchCommand(clonedRepo);
  255.         command.setRemote(remote);
  256.         command.setProgressMonitor(monitor);
  257.         if (tagOption != null) {
  258.             command.setTagOpt(tagOption);
  259.         } else {
  260.             command.setTagOpt(
  261.                     fetchAll ? TagOpt.FETCH_TAGS : TagOpt.AUTO_FOLLOW);
  262.         }
  263.         command.setInitialBranch(branch);
  264.         configure(command);

  265.         return command.call();
  266.     }

  267.     private List<RefSpec> calculateRefSpecs(FETCH_TYPE type,
  268.             String remoteName) {
  269.         List<RefSpec> specs = new ArrayList<>();
  270.         if (type == FETCH_TYPE.MIRROR) {
  271.             specs.add(new RefSpec().setForceUpdate(true).setSourceDestination(
  272.                     Constants.R_REFS + '*', Constants.R_REFS + '*'));
  273.         } else {
  274.             RefSpec heads = new RefSpec();
  275.             heads = heads.setForceUpdate(true);
  276.             final String dst = (bare ? Constants.R_HEADS
  277.                     : Constants.R_REMOTES + remoteName + '/') + '*';
  278.             heads = heads.setSourceDestination(Constants.R_HEADS + '*', dst);
  279.             if (type == FETCH_TYPE.MULTIPLE_BRANCHES) {
  280.                 RefSpec tags = new RefSpec().setForceUpdate(true)
  281.                         .setSourceDestination(Constants.R_TAGS + '*',
  282.                                 Constants.R_TAGS + '*');
  283.                 for (String selectedRef : branchesToClone) {
  284.                     if (heads.matchSource(selectedRef)) {
  285.                         specs.add(heads.expandFromSource(selectedRef));
  286.                     } else if (tags.matchSource(selectedRef)) {
  287.                         specs.add(tags.expandFromSource(selectedRef));
  288.                     }
  289.                 }
  290.             } else {
  291.                 // We'll fetch the tags anyway.
  292.                 specs.add(heads);
  293.             }
  294.         }
  295.         return specs;
  296.     }

  297.     private void checkout(Repository clonedRepo, FetchResult result)
  298.             throws MissingObjectException, IncorrectObjectTypeException,
  299.             IOException, GitAPIException {

  300.         Ref head = null;
  301.         if (branch.equals(Constants.HEAD)) {
  302.             Ref foundBranch = findBranchToCheckout(result);
  303.             if (foundBranch != null)
  304.                 head = foundBranch;
  305.         }
  306.         if (head == null) {
  307.             head = result.getAdvertisedRef(branch);
  308.             if (head == null)
  309.                 head = result.getAdvertisedRef(Constants.R_HEADS + branch);
  310.             if (head == null)
  311.                 head = result.getAdvertisedRef(Constants.R_TAGS + branch);
  312.         }

  313.         if (head == null || head.getObjectId() == null)
  314.             return; // TODO throw exception?

  315.         if (head.getName().startsWith(Constants.R_HEADS)) {
  316.             final RefUpdate newHead = clonedRepo.updateRef(Constants.HEAD);
  317.             newHead.disableRefLog();
  318.             newHead.link(head.getName());
  319.             addMergeConfig(clonedRepo, head);
  320.         }

  321.         final RevCommit commit = parseCommit(clonedRepo, head);

  322.         boolean detached = !head.getName().startsWith(Constants.R_HEADS);
  323.         RefUpdate u = clonedRepo.updateRef(Constants.HEAD, detached);
  324.         u.setNewObjectId(commit.getId());
  325.         u.forceUpdate();

  326.         if (!bare) {
  327.             DirCache dc = clonedRepo.lockDirCache();
  328.             DirCacheCheckout co = new DirCacheCheckout(clonedRepo, dc,
  329.                     commit.getTree());
  330.             co.setProgressMonitor(monitor);
  331.             co.checkout();
  332.             if (cloneSubmodules)
  333.                 cloneSubmodules(clonedRepo);
  334.         }
  335.     }

  336.     private void cloneSubmodules(Repository clonedRepo) throws IOException,
  337.             GitAPIException {
  338.         SubmoduleInitCommand init = new SubmoduleInitCommand(clonedRepo);
  339.         Collection<String> submodules = init.call();
  340.         if (submodules.isEmpty()) {
  341.             return;
  342.         }
  343.         if (callback != null) {
  344.             callback.initializedSubmodules(submodules);
  345.         }

  346.         SubmoduleUpdateCommand update = new SubmoduleUpdateCommand(clonedRepo);
  347.         configure(update);
  348.         update.setProgressMonitor(monitor);
  349.         update.setCallback(callback);
  350.         if (!update.call().isEmpty()) {
  351.             SubmoduleWalk walk = SubmoduleWalk.forIndex(clonedRepo);
  352.             while (walk.next()) {
  353.                 try (Repository subRepo = walk.getRepository()) {
  354.                     if (subRepo != null) {
  355.                         cloneSubmodules(subRepo);
  356.                     }
  357.                 }
  358.             }
  359.         }
  360.     }

  361.     private Ref findBranchToCheckout(FetchResult result) {
  362.         final Ref idHEAD = result.getAdvertisedRef(Constants.HEAD);
  363.         ObjectId headId = idHEAD != null ? idHEAD.getObjectId() : null;
  364.         if (headId == null) {
  365.             return null;
  366.         }

  367.         if (idHEAD != null && idHEAD.isSymbolic()) {
  368.             return idHEAD.getTarget();
  369.         }

  370.         Ref master = result.getAdvertisedRef(Constants.R_HEADS
  371.                 + Constants.MASTER);
  372.         ObjectId objectId = master != null ? master.getObjectId() : null;
  373.         if (headId.equals(objectId)) {
  374.             return master;
  375.         }

  376.         Ref foundBranch = null;
  377.         for (Ref r : result.getAdvertisedRefs()) {
  378.             final String n = r.getName();
  379.             if (!n.startsWith(Constants.R_HEADS))
  380.                 continue;
  381.             if (headId.equals(r.getObjectId())) {
  382.                 foundBranch = r;
  383.                 break;
  384.             }
  385.         }
  386.         return foundBranch;
  387.     }

  388.     private void addMergeConfig(Repository clonedRepo, Ref head)
  389.             throws IOException {
  390.         String branchName = Repository.shortenRefName(head.getName());
  391.         clonedRepo.getConfig().setString(ConfigConstants.CONFIG_BRANCH_SECTION,
  392.                 branchName, ConfigConstants.CONFIG_KEY_REMOTE, remote);
  393.         clonedRepo.getConfig().setString(ConfigConstants.CONFIG_BRANCH_SECTION,
  394.                 branchName, ConfigConstants.CONFIG_KEY_MERGE, head.getName());
  395.         String autosetupRebase = clonedRepo.getConfig().getString(
  396.                 ConfigConstants.CONFIG_BRANCH_SECTION, null,
  397.                 ConfigConstants.CONFIG_KEY_AUTOSETUPREBASE);
  398.         if (ConfigConstants.CONFIG_KEY_ALWAYS.equals(autosetupRebase)
  399.                 || ConfigConstants.CONFIG_KEY_REMOTE.equals(autosetupRebase))
  400.             clonedRepo.getConfig().setEnum(
  401.                     ConfigConstants.CONFIG_BRANCH_SECTION, branchName,
  402.                     ConfigConstants.CONFIG_KEY_REBASE, BranchRebaseMode.REBASE);
  403.         clonedRepo.getConfig().save();
  404.     }

  405.     private RevCommit parseCommit(Repository clonedRepo, Ref ref)
  406.             throws MissingObjectException, IncorrectObjectTypeException,
  407.             IOException {
  408.         final RevCommit commit;
  409.         try (RevWalk rw = new RevWalk(clonedRepo)) {
  410.             commit = rw.parseCommit(ref.getObjectId());
  411.         }
  412.         return commit;
  413.     }

  414.     /**
  415.      * Set the URI to clone from
  416.      *
  417.      * @param uri
  418.      *            the URI to clone from, or {@code null} to unset the URI. The
  419.      *            URI must be set before {@link #call} is called.
  420.      * @return this instance
  421.      */
  422.     public CloneCommand setURI(String uri) {
  423.         this.uri = uri;
  424.         return this;
  425.     }

  426.     /**
  427.      * The optional directory associated with the clone operation. If the
  428.      * directory isn't set, a name associated with the source uri will be used.
  429.      *
  430.      * @see URIish#getHumanishName()
  431.      * @param directory
  432.      *            the directory to clone to, or {@code null} if the directory
  433.      *            name should be taken from the source uri
  434.      * @return this instance
  435.      * @throws java.lang.IllegalStateException
  436.      *             if the combination of directory, gitDir and bare is illegal.
  437.      *             E.g. if for a non-bare repository directory and gitDir point
  438.      *             to the same directory of if for a bare repository both
  439.      *             directory and gitDir are specified
  440.      */
  441.     public CloneCommand setDirectory(File directory) {
  442.         validateDirs(directory, gitDir, bare);
  443.         this.directory = directory;
  444.         return this;
  445.     }

  446.     /**
  447.      * Set the repository meta directory (.git)
  448.      *
  449.      * @param gitDir
  450.      *            the repository meta directory, or {@code null} to choose one
  451.      *            automatically at clone time
  452.      * @return this instance
  453.      * @throws java.lang.IllegalStateException
  454.      *             if the combination of directory, gitDir and bare is illegal.
  455.      *             E.g. if for a non-bare repository directory and gitDir point
  456.      *             to the same directory of if for a bare repository both
  457.      *             directory and gitDir are specified
  458.      * @since 3.6
  459.      */
  460.     public CloneCommand setGitDir(File gitDir) {
  461.         validateDirs(directory, gitDir, bare);
  462.         this.gitDir = gitDir;
  463.         return this;
  464.     }

  465.     /**
  466.      * Set whether the cloned repository shall be bare
  467.      *
  468.      * @param bare
  469.      *            whether the cloned repository is bare or not
  470.      * @return this instance
  471.      * @throws java.lang.IllegalStateException
  472.      *             if the combination of directory, gitDir and bare is illegal.
  473.      *             E.g. if for a non-bare repository directory and gitDir point
  474.      *             to the same directory of if for a bare repository both
  475.      *             directory and gitDir are specified
  476.      */
  477.     public CloneCommand setBare(boolean bare) throws IllegalStateException {
  478.         validateDirs(directory, gitDir, bare);
  479.         this.bare = bare;
  480.         return this;
  481.     }

  482.     /**
  483.      * Set the file system abstraction to be used for repositories created by
  484.      * this command.
  485.      *
  486.      * @param fs
  487.      *            the abstraction.
  488.      * @return {@code this} (for chaining calls).
  489.      * @since 4.10
  490.      */
  491.     public CloneCommand setFs(FS fs) {
  492.         this.fs = fs;
  493.         return this;
  494.     }

  495.     /**
  496.      * The remote name used to keep track of the upstream repository for the
  497.      * clone operation. If no remote name is set, the default value of
  498.      * <code>Constants.DEFAULT_REMOTE_NAME</code> will be used.
  499.      *
  500.      * @see Constants#DEFAULT_REMOTE_NAME
  501.      * @param remote
  502.      *            name that keeps track of the upstream repository.
  503.      *            {@code null} means to use DEFAULT_REMOTE_NAME.
  504.      * @return this instance
  505.      */
  506.     public CloneCommand setRemote(String remote) {
  507.         if (remote == null) {
  508.             remote = Constants.DEFAULT_REMOTE_NAME;
  509.         }
  510.         this.remote = remote;
  511.         return this;
  512.     }

  513.     /**
  514.      * Set the initial branch
  515.      *
  516.      * @param branch
  517.      *            the initial branch to check out when cloning the repository.
  518.      *            Can be specified as ref name (<code>refs/heads/master</code>),
  519.      *            branch name (<code>master</code>) or tag name
  520.      *            (<code>v1.2.3</code>). The default is to use the branch
  521.      *            pointed to by the cloned repository's HEAD and can be
  522.      *            requested by passing {@code null} or <code>HEAD</code>.
  523.      * @return this instance
  524.      */
  525.     public CloneCommand setBranch(String branch) {
  526.         if (branch == null) {
  527.             branch = Constants.HEAD;
  528.         }
  529.         this.branch = branch;
  530.         return this;
  531.     }

  532.     /**
  533.      * The progress monitor associated with the clone operation. By default,
  534.      * this is set to <code>NullProgressMonitor</code>
  535.      *
  536.      * @see NullProgressMonitor
  537.      * @param monitor
  538.      *            a {@link org.eclipse.jgit.lib.ProgressMonitor}
  539.      * @return {@code this}
  540.      */
  541.     public CloneCommand setProgressMonitor(ProgressMonitor monitor) {
  542.         if (monitor == null) {
  543.             monitor = NullProgressMonitor.INSTANCE;
  544.         }
  545.         this.monitor = monitor;
  546.         return this;
  547.     }

  548.     /**
  549.      * Set whether all branches have to be fetched.
  550.      * <p>
  551.      * If {@code false}, use {@link #setBranchesToClone(Collection)} to define
  552.      * what will be cloned. If neither are set, all branches will be cloned.
  553.      * </p>
  554.      *
  555.      * @param cloneAllBranches
  556.      *            {@code true} when all branches have to be fetched (indicates
  557.      *            wildcard in created fetch refspec), {@code false} otherwise.
  558.      * @return {@code this}
  559.      */
  560.     public CloneCommand setCloneAllBranches(boolean cloneAllBranches) {
  561.         this.cloneAllBranches = cloneAllBranches;
  562.         return this;
  563.     }

  564.     /**
  565.      * Set up a mirror of the source repository. This implies that a bare
  566.      * repository will be created. Compared to {@link #setBare},
  567.      * {@code #setMirror} not only maps local branches of the source to local
  568.      * branches of the target, it maps all refs (including remote-tracking
  569.      * branches, notes etc.) and sets up a refspec configuration such that all
  570.      * these refs are overwritten by a git remote update in the target
  571.      * repository.
  572.      *
  573.      * @param mirror
  574.      *            whether to mirror all refs from the source repository
  575.      *
  576.      * @return {@code this}
  577.      * @since 5.6
  578.      */
  579.     public CloneCommand setMirror(boolean mirror) {
  580.         this.mirror = mirror;
  581.         return this;
  582.     }

  583.     /**
  584.      * Set whether to clone submodules
  585.      *
  586.      * @param cloneSubmodules
  587.      *            true to initialize and update submodules. Ignored when
  588.      *            {@link #setBare(boolean)} is set to true.
  589.      * @return {@code this}
  590.      */
  591.     public CloneCommand setCloneSubmodules(boolean cloneSubmodules) {
  592.         this.cloneSubmodules = cloneSubmodules;
  593.         return this;
  594.     }

  595.     /**
  596.      * Set the branches or tags to clone.
  597.      * <p>
  598.      * This is ignored if {@link #setCloneAllBranches(boolean)
  599.      * setCloneAllBranches(true)} or {@link #setMirror(boolean) setMirror(true)}
  600.      * is used. If {@code branchesToClone} is {@code null} or empty, it's also
  601.      * ignored.
  602.      * </p>
  603.      *
  604.      * @param branchesToClone
  605.      *            collection of branches to clone. Must be specified as full ref
  606.      *            names (e.g. {@code refs/heads/master} or
  607.      *            {@code refs/tags/v1.0.0}).
  608.      * @return {@code this}
  609.      */
  610.     public CloneCommand setBranchesToClone(Collection<String> branchesToClone) {
  611.         this.branchesToClone = branchesToClone;
  612.         return this;
  613.     }

  614.     /**
  615.      * Set the tag option used for the remote configuration explicitly.
  616.      *
  617.      * @param tagOption
  618.      *            tag option to be used for the remote config
  619.      * @return {@code this}
  620.      * @since 5.8
  621.      */
  622.     public CloneCommand setTagOption(TagOpt tagOption) {
  623.         this.tagOption = tagOption;
  624.         return this;
  625.     }

  626.     /**
  627.      * Set the --no-tags option. Tags are not cloned now and the remote
  628.      * configuration is initialized with the --no-tags option as well.
  629.      *
  630.      * @return {@code this}
  631.      * @since 5.8
  632.      */
  633.     public CloneCommand setNoTags() {
  634.         return setTagOption(TagOpt.NO_TAGS);
  635.     }

  636.     /**
  637.      * Set whether to skip checking out a branch
  638.      *
  639.      * @param noCheckout
  640.      *            if set to <code>true</code> no branch will be checked out
  641.      *            after the clone. This enhances performance of the clone
  642.      *            command when there is no need for a checked out branch.
  643.      * @return {@code this}
  644.      */
  645.     public CloneCommand setNoCheckout(boolean noCheckout) {
  646.         this.noCheckout = noCheckout;
  647.         return this;
  648.     }

  649.     /**
  650.      * Register a progress callback.
  651.      *
  652.      * @param callback
  653.      *            the callback
  654.      * @return {@code this}
  655.      * @since 4.8
  656.      */
  657.     public CloneCommand setCallback(Callback callback) {
  658.         this.callback = callback;
  659.         return this;
  660.     }

  661.     private static void validateDirs(File directory, File gitDir, boolean bare)
  662.             throws IllegalStateException {
  663.         if (directory != null) {
  664.             if (directory.exists() && !directory.isDirectory()) {
  665.                 throw new IllegalStateException(MessageFormat.format(
  666.                         JGitText.get().initFailedDirIsNoDirectory, directory));
  667.             }
  668.             if (gitDir != null && gitDir.exists() && !gitDir.isDirectory()) {
  669.                 throw new IllegalStateException(MessageFormat.format(
  670.                         JGitText.get().initFailedGitDirIsNoDirectory,
  671.                         gitDir));
  672.             }
  673.             if (bare) {
  674.                 if (gitDir != null && !gitDir.equals(directory))
  675.                     throw new IllegalStateException(MessageFormat.format(
  676.                             JGitText.get().initFailedBareRepoDifferentDirs,
  677.                             gitDir, directory));
  678.             } else {
  679.                 if (gitDir != null && gitDir.equals(directory))
  680.                     throw new IllegalStateException(MessageFormat.format(
  681.                             JGitText.get().initFailedNonBareRepoSameDirs,
  682.                             gitDir, directory));
  683.             }
  684.         }
  685.     }

  686.     private void cleanup() {
  687.         try {
  688.             if (directory != null) {
  689.                 if (!directoryExistsInitially) {
  690.                     FileUtils.delete(directory, FileUtils.RECURSIVE
  691.                             | FileUtils.SKIP_MISSING | FileUtils.IGNORE_ERRORS);
  692.                 } else {
  693.                     deleteChildren(directory);
  694.                 }
  695.             }
  696.             if (gitDir != null) {
  697.                 if (!gitDirExistsInitially) {
  698.                     FileUtils.delete(gitDir, FileUtils.RECURSIVE
  699.                             | FileUtils.SKIP_MISSING | FileUtils.IGNORE_ERRORS);
  700.                 } else {
  701.                     deleteChildren(gitDir);
  702.                 }
  703.             }
  704.         } catch (IOException e) {
  705.             // Ignore; this is a best-effort cleanup in error cases, and
  706.             // IOException should not be raised anyway
  707.         }
  708.     }

  709.     private void deleteChildren(File file) throws IOException {
  710.         File[] files = file.listFiles();
  711.         if (files == null) {
  712.             return;
  713.         }
  714.         for (File child : files) {
  715.             FileUtils.delete(child, FileUtils.RECURSIVE | FileUtils.SKIP_MISSING
  716.                     | FileUtils.IGNORE_ERRORS);
  717.         }
  718.     }
  719. }