SubmoduleUpdateCommand.java

  1. /*
  2.  * Copyright (C) 2011, GitHub Inc.
  3.  * Copyright (C) 2016, Laurent Delaigue <laurent.delaigue@obeo.fr> and others
  4.  *
  5.  * This program and the accompanying materials are made available under the
  6.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  7.  * https://www.eclipse.org/org/documents/edl-v10.php.
  8.  *
  9.  * SPDX-License-Identifier: BSD-3-Clause
  10.  */
  11. package org.eclipse.jgit.api;

  12. import java.io.File;
  13. import java.io.IOException;
  14. import java.util.ArrayList;
  15. import java.util.Collection;
  16. import java.util.List;

  17. import org.eclipse.jgit.api.errors.CheckoutConflictException;
  18. import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
  19. import org.eclipse.jgit.api.errors.GitAPIException;
  20. import org.eclipse.jgit.api.errors.InvalidConfigurationException;
  21. import org.eclipse.jgit.api.errors.InvalidMergeHeadsException;
  22. import org.eclipse.jgit.api.errors.JGitInternalException;
  23. import org.eclipse.jgit.api.errors.NoHeadException;
  24. import org.eclipse.jgit.api.errors.NoMessageException;
  25. import org.eclipse.jgit.api.errors.RefNotFoundException;
  26. import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
  27. import org.eclipse.jgit.dircache.DirCacheCheckout;
  28. import org.eclipse.jgit.errors.ConfigInvalidException;
  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.ProgressMonitor;
  33. import org.eclipse.jgit.lib.RefUpdate;
  34. import org.eclipse.jgit.lib.Repository;
  35. import org.eclipse.jgit.merge.MergeStrategy;
  36. import org.eclipse.jgit.revwalk.RevCommit;
  37. import org.eclipse.jgit.revwalk.RevWalk;
  38. import org.eclipse.jgit.submodule.SubmoduleWalk;
  39. import org.eclipse.jgit.treewalk.filter.PathFilterGroup;

  40. /**
  41.  * A class used to execute a submodule update command.
  42.  *
  43.  * @see <a
  44.  *      href="http://www.kernel.org/pub/software/scm/git/docs/git-submodule.html"
  45.  *      >Git documentation about submodules</a>
  46.  */
  47. public class SubmoduleUpdateCommand extends
  48.         TransportCommand<SubmoduleUpdateCommand, Collection<String>> {

  49.     private ProgressMonitor monitor;

  50.     private final Collection<String> paths;

  51.     private MergeStrategy strategy = MergeStrategy.RECURSIVE;

  52.     private CloneCommand.Callback callback;

  53.     private FetchCommand.Callback fetchCallback;

  54.     private boolean fetch = false;

  55.     /**
  56.      * <p>
  57.      * Constructor for SubmoduleUpdateCommand.
  58.      * </p>
  59.      *
  60.      * @param repo
  61.      *            a {@link org.eclipse.jgit.lib.Repository} object.
  62.      */
  63.     public SubmoduleUpdateCommand(Repository repo) {
  64.         super(repo);
  65.         paths = new ArrayList<>();
  66.     }

  67.     /**
  68.      * The progress monitor associated with the clone operation. By default,
  69.      * this is set to <code>NullProgressMonitor</code>
  70.      *
  71.      * @see NullProgressMonitor
  72.      * @param monitor
  73.      *            a {@link org.eclipse.jgit.lib.ProgressMonitor} object.
  74.      * @return this command
  75.      */
  76.     public SubmoduleUpdateCommand setProgressMonitor(
  77.             final ProgressMonitor monitor) {
  78.         this.monitor = monitor;
  79.         return this;
  80.     }

  81.     /**
  82.      * Whether to fetch the submodules before we update them. By default, this
  83.      * is set to <code>false</code>
  84.      *
  85.      * @param fetch
  86.      *            whether to fetch the submodules before we update them
  87.      * @return this command
  88.      * @since 4.9
  89.      */
  90.     public SubmoduleUpdateCommand setFetch(boolean fetch) {
  91.         this.fetch = fetch;
  92.         return this;
  93.     }

  94.     /**
  95.      * Add repository-relative submodule path to initialize
  96.      *
  97.      * @param path
  98.      *            (with <code>/</code> as separator)
  99.      * @return this command
  100.      */
  101.     public SubmoduleUpdateCommand addPath(String path) {
  102.         paths.add(path);
  103.         return this;
  104.     }

  105.     private Repository getOrCloneSubmodule(SubmoduleWalk generator, String url)
  106.             throws IOException, GitAPIException {
  107.         Repository repository = generator.getRepository();
  108.         if (repository == null) {
  109.             if (callback != null) {
  110.                 callback.cloningSubmodule(generator.getPath());
  111.             }
  112.             CloneCommand clone = Git.cloneRepository();
  113.             configure(clone);
  114.             clone.setURI(url);
  115.             clone.setDirectory(generator.getDirectory());
  116.             clone.setGitDir(
  117.                     new File(new File(repo.getDirectory(), Constants.MODULES),
  118.                             generator.getPath()));
  119.             if (monitor != null) {
  120.                 clone.setProgressMonitor(monitor);
  121.             }
  122.             repository = clone.call().getRepository();
  123.         } else if (this.fetch) {
  124.             if (fetchCallback != null) {
  125.                 fetchCallback.fetchingSubmodule(generator.getPath());
  126.             }
  127.             FetchCommand fetchCommand = Git.wrap(repository).fetch();
  128.             if (monitor != null) {
  129.                 fetchCommand.setProgressMonitor(monitor);
  130.             }
  131.             configure(fetchCommand);
  132.             fetchCommand.call();
  133.         }
  134.         return repository;
  135.     }

  136.     /**
  137.      * {@inheritDoc}
  138.      *
  139.      * Execute the SubmoduleUpdateCommand command.
  140.      */
  141.     @Override
  142.     public Collection<String> call() throws InvalidConfigurationException,
  143.             NoHeadException, ConcurrentRefUpdateException,
  144.             CheckoutConflictException, InvalidMergeHeadsException,
  145.             WrongRepositoryStateException, NoMessageException, NoHeadException,
  146.             RefNotFoundException, GitAPIException {
  147.         checkCallable();

  148.         try (SubmoduleWalk generator = SubmoduleWalk.forIndex(repo)) {
  149.             if (!paths.isEmpty())
  150.                 generator.setFilter(PathFilterGroup.createFromStrings(paths));
  151.             List<String> updated = new ArrayList<>();
  152.             while (generator.next()) {
  153.                 // Skip submodules not registered in .gitmodules file
  154.                 if (generator.getModulesPath() == null)
  155.                     continue;
  156.                 // Skip submodules not registered in parent repository's config
  157.                 String url = generator.getConfigUrl();
  158.                 if (url == null)
  159.                     continue;

  160.                 try (Repository submoduleRepo = getOrCloneSubmodule(generator,
  161.                         url); RevWalk walk = new RevWalk(submoduleRepo)) {
  162.                     RevCommit commit = walk
  163.                             .parseCommit(generator.getObjectId());

  164.                     String update = generator.getConfigUpdate();
  165.                     if (ConfigConstants.CONFIG_KEY_MERGE.equals(update)) {
  166.                         MergeCommand merge = new MergeCommand(submoduleRepo);
  167.                         merge.include(commit);
  168.                         merge.setProgressMonitor(monitor);
  169.                         merge.setStrategy(strategy);
  170.                         merge.call();
  171.                     } else if (ConfigConstants.CONFIG_KEY_REBASE.equals(update)) {
  172.                         RebaseCommand rebase = new RebaseCommand(submoduleRepo);
  173.                         rebase.setUpstream(commit);
  174.                         rebase.setProgressMonitor(monitor);
  175.                         rebase.setStrategy(strategy);
  176.                         rebase.call();
  177.                     } else {
  178.                         // Checkout commit referenced in parent repository's
  179.                         // index as a detached HEAD
  180.                         DirCacheCheckout co = new DirCacheCheckout(
  181.                                 submoduleRepo, submoduleRepo.lockDirCache(),
  182.                                 commit.getTree());
  183.                         co.setFailOnConflict(true);
  184.                         co.setProgressMonitor(monitor);
  185.                         co.checkout();
  186.                         RefUpdate refUpdate = submoduleRepo.updateRef(
  187.                                 Constants.HEAD, true);
  188.                         refUpdate.setNewObjectId(commit);
  189.                         refUpdate.forceUpdate();
  190.                         if (callback != null) {
  191.                             callback.checkingOut(commit,
  192.                                     generator.getPath());
  193.                         }
  194.                     }
  195.                 }
  196.                 updated.add(generator.getPath());
  197.             }
  198.             return updated;
  199.         } catch (IOException e) {
  200.             throw new JGitInternalException(e.getMessage(), e);
  201.         } catch (ConfigInvalidException e) {
  202.             throw new InvalidConfigurationException(e.getMessage(), e);
  203.         }
  204.     }

  205.     /**
  206.      * Setter for the field <code>strategy</code>.
  207.      *
  208.      * @param strategy
  209.      *            The merge strategy to use during this update operation.
  210.      * @return {@code this}
  211.      * @since 3.4
  212.      */
  213.     public SubmoduleUpdateCommand setStrategy(MergeStrategy strategy) {
  214.         this.strategy = strategy;
  215.         return this;
  216.     }

  217.     /**
  218.      * Set status callback for submodule clone operation.
  219.      *
  220.      * @param callback
  221.      *            the callback
  222.      * @return {@code this}
  223.      * @since 4.8
  224.      */
  225.     public SubmoduleUpdateCommand setCallback(CloneCommand.Callback callback) {
  226.         this.callback = callback;
  227.         return this;
  228.     }

  229.     /**
  230.      * Set status callback for submodule fetch operation.
  231.      *
  232.      * @param callback
  233.      *            the callback
  234.      * @return {@code this}
  235.      * @since 4.9
  236.      */
  237.     public SubmoduleUpdateCommand setFetchCallback(
  238.             FetchCommand.Callback callback) {
  239.         this.fetchCallback = callback;
  240.         return this;
  241.     }
  242. }