FetchCommand.java

  1. /*
  2.  * Copyright (C) 2010, 2022 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 static java.util.stream.Collectors.toList;

  12. import java.io.IOException;
  13. import java.net.URISyntaxException;
  14. import java.text.MessageFormat;
  15. import java.time.Instant;
  16. import java.time.OffsetDateTime;
  17. import java.util.ArrayList;
  18. import java.util.Arrays;
  19. import java.util.List;

  20. import org.eclipse.jgit.annotations.NonNull;
  21. import org.eclipse.jgit.annotations.Nullable;
  22. import org.eclipse.jgit.api.errors.GitAPIException;
  23. import org.eclipse.jgit.api.errors.InvalidConfigurationException;
  24. import org.eclipse.jgit.api.errors.InvalidRemoteException;
  25. import org.eclipse.jgit.api.errors.JGitInternalException;
  26. import org.eclipse.jgit.errors.ConfigInvalidException;
  27. import org.eclipse.jgit.errors.NoRemoteRepositoryException;
  28. import org.eclipse.jgit.errors.NotSupportedException;
  29. import org.eclipse.jgit.errors.TransportException;
  30. import org.eclipse.jgit.internal.JGitText;
  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.Repository;
  37. import org.eclipse.jgit.lib.StoredConfig;
  38. import org.eclipse.jgit.lib.SubmoduleConfig.FetchRecurseSubmodulesMode;
  39. import org.eclipse.jgit.revwalk.RevWalk;
  40. import org.eclipse.jgit.submodule.SubmoduleWalk;
  41. import org.eclipse.jgit.transport.FetchResult;
  42. import org.eclipse.jgit.transport.RefSpec;
  43. import org.eclipse.jgit.transport.TagOpt;
  44. import org.eclipse.jgit.transport.Transport;

  45. /**
  46.  * A class used to execute a {@code Fetch} command. It has setters for all
  47.  * supported options and arguments of this command and a {@link #call()} method
  48.  * to finally execute the command.
  49.  *
  50.  * @see <a href="http://www.kernel.org/pub/software/scm/git/docs/git-fetch.html"
  51.  *      >Git documentation about Fetch</a>
  52.  */
  53. public class FetchCommand extends TransportCommand<FetchCommand, FetchResult> {
  54.     private String remote = Constants.DEFAULT_REMOTE_NAME;

  55.     private List<RefSpec> refSpecs;

  56.     private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;

  57.     private boolean checkFetchedObjects;

  58.     private Boolean removeDeletedRefs;

  59.     private boolean dryRun;

  60.     private boolean thin = Transport.DEFAULT_FETCH_THIN;

  61.     private TagOpt tagOption;

  62.     private FetchRecurseSubmodulesMode submoduleRecurseMode = null;

  63.     private Callback callback;

  64.     private boolean isForceUpdate;

  65.     private String initialBranch;

  66.     private Integer depth;

  67.     private Instant deepenSince;

  68.     private List<String> shallowExcludes = new ArrayList<>();

  69.     private boolean unshallow;

  70.     /**
  71.      * Callback for status of fetch operation.
  72.      *
  73.      * @since 4.8
  74.      *
  75.      */
  76.     public interface Callback {
  77.         /**
  78.          * Notify fetching a submodule.
  79.          *
  80.          * @param name
  81.          *            the submodule name.
  82.          */
  83.         void fetchingSubmodule(String name);
  84.     }

  85.     /**
  86.      * Constructor for FetchCommand.
  87.      *
  88.      * @param repo
  89.      *            a {@link org.eclipse.jgit.lib.Repository} object.
  90.      */
  91.     protected FetchCommand(Repository repo) {
  92.         super(repo);
  93.         refSpecs = new ArrayList<>(3);
  94.     }

  95.     private FetchRecurseSubmodulesMode getRecurseMode(String path) {
  96.         // Use the caller-specified mode, if set
  97.         if (submoduleRecurseMode != null) {
  98.             return submoduleRecurseMode;
  99.         }

  100.         // Fall back to submodule.name.fetchRecurseSubmodules, if set
  101.         FetchRecurseSubmodulesMode mode = repo.getConfig().getEnum(
  102.                 FetchRecurseSubmodulesMode.values(),
  103.                 ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
  104.                 ConfigConstants.CONFIG_KEY_FETCH_RECURSE_SUBMODULES, null);
  105.         if (mode != null) {
  106.             return mode;
  107.         }

  108.         // Fall back to fetch.recurseSubmodules, if set
  109.         mode = repo.getConfig().getEnum(FetchRecurseSubmodulesMode.values(),
  110.                 ConfigConstants.CONFIG_FETCH_SECTION, null,
  111.                 ConfigConstants.CONFIG_KEY_RECURSE_SUBMODULES, null);
  112.         if (mode != null) {
  113.             return mode;
  114.         }

  115.         // Default to on-demand mode
  116.         return FetchRecurseSubmodulesMode.ON_DEMAND;
  117.     }

  118.     private void fetchSubmodules(FetchResult results)
  119.             throws org.eclipse.jgit.api.errors.TransportException,
  120.             GitAPIException, InvalidConfigurationException {
  121.         try (SubmoduleWalk walk = new SubmoduleWalk(repo);
  122.                 RevWalk revWalk = new RevWalk(repo)) {
  123.             // Walk over submodules in the parent repository's FETCH_HEAD.
  124.             ObjectId fetchHead = repo.resolve(Constants.FETCH_HEAD);
  125.             if (fetchHead == null) {
  126.                 return;
  127.             }
  128.             walk.setTree(revWalk.parseTree(fetchHead));
  129.             while (walk.next()) {
  130.                 try (Repository submoduleRepo = walk.getRepository()) {

  131.                     // Skip submodules that don't exist locally (have not been
  132.                     // cloned), are not registered in the .gitmodules file, or
  133.                     // not registered in the parent repository's config.
  134.                     if (submoduleRepo == null || walk.getModulesPath() == null
  135.                             || walk.getConfigUrl() == null) {
  136.                         continue;
  137.                     }

  138.                     FetchRecurseSubmodulesMode recurseMode = getRecurseMode(
  139.                             walk.getPath());

  140.                     // When the fetch mode is "yes" we always fetch. When the
  141.                     // mode is "on demand", we only fetch if the submodule's
  142.                     // revision was updated to an object that is not currently
  143.                     // present in the submodule.
  144.                     if ((recurseMode == FetchRecurseSubmodulesMode.ON_DEMAND
  145.                             && !submoduleRepo.getObjectDatabase()
  146.                                     .has(walk.getObjectId()))
  147.                             || recurseMode == FetchRecurseSubmodulesMode.YES) {
  148.                         FetchCommand f = new FetchCommand(submoduleRepo)
  149.                                 .setProgressMonitor(monitor)
  150.                                 .setTagOpt(tagOption)
  151.                                 .setCheckFetchedObjects(checkFetchedObjects)
  152.                                 .setRemoveDeletedRefs(isRemoveDeletedRefs())
  153.                                 .setThin(thin)
  154.                                 .setRefSpecs(applyOptions(refSpecs))
  155.                                 .setDryRun(dryRun)
  156.                                 .setRecurseSubmodules(recurseMode);
  157.                         configure(f);
  158.                         if (callback != null) {
  159.                             callback.fetchingSubmodule(walk.getPath());
  160.                         }
  161.                         results.addSubmodule(walk.getPath(), f.call());
  162.                     }
  163.                 }
  164.             }
  165.         } catch (IOException e) {
  166.             throw new JGitInternalException(e.getMessage(), e);
  167.         } catch (ConfigInvalidException e) {
  168.             throw new InvalidConfigurationException(e.getMessage(), e);
  169.         }
  170.     }

  171.     /**
  172.      * {@inheritDoc}
  173.      * <p>
  174.      * Execute the {@code fetch} command with all the options and parameters
  175.      * collected by the setter methods of this class. Each instance of this
  176.      * class should only be used for one invocation of the command (means: one
  177.      * call to {@link #call()})
  178.      */
  179.     @Override
  180.     public FetchResult call() throws GitAPIException, InvalidRemoteException,
  181.             org.eclipse.jgit.api.errors.TransportException {
  182.         checkCallable();

  183.         try (Transport transport = Transport.open(repo, remote)) {
  184.             transport.setCheckFetchedObjects(checkFetchedObjects);
  185.             transport.setRemoveDeletedRefs(isRemoveDeletedRefs());
  186.             transport.setDryRun(dryRun);
  187.             if (tagOption != null)
  188.                 transport.setTagOpt(tagOption);
  189.             transport.setFetchThin(thin);
  190.             if (depth != null) {
  191.                 transport.setDepth(depth);
  192.             }
  193.             if (unshallow) {
  194.                 if (depth != null) {
  195.                     throw new IllegalStateException(JGitText.get().depthWithUnshallow);
  196.                 }
  197.                 transport.setDepth(Constants.INFINITE_DEPTH);
  198.             }
  199.             if (deepenSince != null) {
  200.                 transport.setDeepenSince(deepenSince);
  201.             }
  202.             transport.setDeepenNots(shallowExcludes);
  203.             configure(transport);
  204.             FetchResult result = transport.fetch(monitor,
  205.                     applyOptions(refSpecs), initialBranch);
  206.             if (!repo.isBare()) {
  207.                 fetchSubmodules(result);
  208.             }

  209.             return result;
  210.         } catch (NoRemoteRepositoryException e) {
  211.             throw new InvalidRemoteException(MessageFormat.format(
  212.                     JGitText.get().invalidRemote, remote), e);
  213.         } catch (TransportException e) {
  214.             throw new org.eclipse.jgit.api.errors.TransportException(
  215.                     e.getMessage(), e);
  216.         } catch (URISyntaxException e) {
  217.             throw new InvalidRemoteException(MessageFormat.format(
  218.                     JGitText.get().invalidRemote, remote), e);
  219.         } catch (NotSupportedException e) {
  220.             throw new JGitInternalException(
  221.                     JGitText.get().exceptionCaughtDuringExecutionOfFetchCommand,
  222.                     e);
  223.         }

  224.     }

  225.     private List<RefSpec> applyOptions(List<RefSpec> refSpecs2) {
  226.         if (!isForceUpdate()) {
  227.             return refSpecs2;
  228.         }
  229.         List<RefSpec> updated = new ArrayList<>(3);
  230.         for (RefSpec refSpec : refSpecs2) {
  231.             updated.add(refSpec.setForceUpdate(true));
  232.         }
  233.         return updated;
  234.     }

  235.     /**
  236.      * Set the mode to be used for recursing into submodules.
  237.      *
  238.      * @param recurse
  239.      *            corresponds to the
  240.      *            --recurse-submodules/--no-recurse-submodules options. If
  241.      *            {@code null} use the value of the
  242.      *            {@code submodule.name.fetchRecurseSubmodules} option
  243.      *            configured per submodule. If not specified there, use the
  244.      *            value of the {@code fetch.recurseSubmodules} option configured
  245.      *            in git config. If not configured in either, "on-demand" is the
  246.      *            built-in default.
  247.      * @return {@code this}
  248.      * @since 4.7
  249.      */
  250.     public FetchCommand setRecurseSubmodules(
  251.             @Nullable FetchRecurseSubmodulesMode recurse) {
  252.         checkCallable();
  253.         submoduleRecurseMode = recurse;
  254.         return this;
  255.     }

  256.     /**
  257.      * The remote (uri or name) used for the fetch operation. If no remote is
  258.      * set, the default value of <code>Constants.DEFAULT_REMOTE_NAME</code> will
  259.      * be used.
  260.      *
  261.      * @see Constants#DEFAULT_REMOTE_NAME
  262.      * @param remote
  263.      *            name of a remote
  264.      * @return {@code this}
  265.      */
  266.     public FetchCommand setRemote(String remote) {
  267.         checkCallable();
  268.         this.remote = remote;
  269.         return this;
  270.     }

  271.     /**
  272.      * Get the remote
  273.      *
  274.      * @return the remote used for the remote operation
  275.      */
  276.     public String getRemote() {
  277.         return remote;
  278.     }

  279.     /**
  280.      * Get timeout
  281.      *
  282.      * @return the timeout used for the fetch operation
  283.      */
  284.     public int getTimeout() {
  285.         return timeout;
  286.     }

  287.     /**
  288.      * Whether to check received objects for validity
  289.      *
  290.      * @return whether to check received objects for validity
  291.      */
  292.     public boolean isCheckFetchedObjects() {
  293.         return checkFetchedObjects;
  294.     }

  295.     /**
  296.      * If set to {@code true}, objects received will be checked for validity
  297.      *
  298.      * @param checkFetchedObjects
  299.      *            whether to check objects for validity
  300.      * @return {@code this}
  301.      */
  302.     public FetchCommand setCheckFetchedObjects(boolean checkFetchedObjects) {
  303.         checkCallable();
  304.         this.checkFetchedObjects = checkFetchedObjects;
  305.         return this;
  306.     }

  307.     /**
  308.      * Whether to remove refs which no longer exist in the source
  309.      *
  310.      * @return whether to remove refs which no longer exist in the source
  311.      */
  312.     public boolean isRemoveDeletedRefs() {
  313.         if (removeDeletedRefs != null) {
  314.             return removeDeletedRefs.booleanValue();
  315.         }
  316.         // fall back to configuration
  317.         boolean result = false;
  318.         StoredConfig config = repo.getConfig();
  319.         result = config.getBoolean(ConfigConstants.CONFIG_FETCH_SECTION, null,
  320.                 ConfigConstants.CONFIG_KEY_PRUNE, result);
  321.         result = config.getBoolean(ConfigConstants.CONFIG_REMOTE_SECTION,
  322.                 remote, ConfigConstants.CONFIG_KEY_PRUNE, result);
  323.         return result;
  324.     }

  325.     /**
  326.      * If set to {@code true}, refs are removed which no longer exist in the
  327.      * source
  328.      *
  329.      * @param removeDeletedRefs
  330.      *            whether to remove deleted {@code Ref}s
  331.      * @return {@code this}
  332.      */
  333.     public FetchCommand setRemoveDeletedRefs(boolean removeDeletedRefs) {
  334.         checkCallable();
  335.         this.removeDeletedRefs = Boolean.valueOf(removeDeletedRefs);
  336.         return this;
  337.     }

  338.     /**
  339.      * Get progress monitor
  340.      *
  341.      * @return the progress monitor for the fetch operation
  342.      */
  343.     public ProgressMonitor getProgressMonitor() {
  344.         return monitor;
  345.     }

  346.     /**
  347.      * The progress monitor associated with the fetch operation. By default,
  348.      * this is set to <code>NullProgressMonitor</code>
  349.      *
  350.      * @see NullProgressMonitor
  351.      * @param monitor
  352.      *            a {@link org.eclipse.jgit.lib.ProgressMonitor}
  353.      * @return {@code this}
  354.      */
  355.     public FetchCommand setProgressMonitor(ProgressMonitor monitor) {
  356.         checkCallable();
  357.         if (monitor == null) {
  358.             monitor = NullProgressMonitor.INSTANCE;
  359.         }
  360.         this.monitor = monitor;
  361.         return this;
  362.     }

  363.     /**
  364.      * Get list of {@code RefSpec}s
  365.      *
  366.      * @return the ref specs
  367.      */
  368.     public List<RefSpec> getRefSpecs() {
  369.         return refSpecs;
  370.     }

  371.     /**
  372.      * The ref specs to be used in the fetch operation
  373.      *
  374.      * @param specs
  375.      *            String representation of {@code RefSpec}s
  376.      * @return {@code this}
  377.      * @since 4.9
  378.      */
  379.     public FetchCommand setRefSpecs(String... specs) {
  380.         return setRefSpecs(
  381.                 Arrays.stream(specs).map(RefSpec::new).collect(toList()));
  382.     }

  383.     /**
  384.      * The ref specs to be used in the fetch operation
  385.      *
  386.      * @param specs
  387.      *            one or multiple {@link org.eclipse.jgit.transport.RefSpec}s
  388.      * @return {@code this}
  389.      */
  390.     public FetchCommand setRefSpecs(RefSpec... specs) {
  391.         return setRefSpecs(Arrays.asList(specs));
  392.     }

  393.     /**
  394.      * The ref specs to be used in the fetch operation
  395.      *
  396.      * @param specs
  397.      *            list of {@link org.eclipse.jgit.transport.RefSpec}s
  398.      * @return {@code this}
  399.      */
  400.     public FetchCommand setRefSpecs(List<RefSpec> specs) {
  401.         checkCallable();
  402.         this.refSpecs.clear();
  403.         this.refSpecs.addAll(specs);
  404.         return this;
  405.     }

  406.     /**
  407.      * Whether to do a dry run
  408.      *
  409.      * @return the dry run preference for the fetch operation
  410.      */
  411.     public boolean isDryRun() {
  412.         return dryRun;
  413.     }

  414.     /**
  415.      * Sets whether the fetch operation should be a dry run
  416.      *
  417.      * @param dryRun
  418.      *            whether to do a dry run
  419.      * @return {@code this}
  420.      */
  421.     public FetchCommand setDryRun(boolean dryRun) {
  422.         checkCallable();
  423.         this.dryRun = dryRun;
  424.         return this;
  425.     }

  426.     /**
  427.      * Get thin-pack preference
  428.      *
  429.      * @return the thin-pack preference for fetch operation
  430.      */
  431.     public boolean isThin() {
  432.         return thin;
  433.     }

  434.     /**
  435.      * Sets the thin-pack preference for fetch operation.
  436.      *
  437.      * Default setting is Transport.DEFAULT_FETCH_THIN
  438.      *
  439.      * @param thin
  440.      *            the thin-pack preference
  441.      * @return {@code this}
  442.      */
  443.     public FetchCommand setThin(boolean thin) {
  444.         checkCallable();
  445.         this.thin = thin;
  446.         return this;
  447.     }

  448.     /**
  449.      * Sets the specification of annotated tag behavior during fetch
  450.      *
  451.      * @param tagOpt
  452.      *            the {@link org.eclipse.jgit.transport.TagOpt}
  453.      * @return {@code this}
  454.      */
  455.     public FetchCommand setTagOpt(TagOpt tagOpt) {
  456.         checkCallable();
  457.         this.tagOption = tagOpt;
  458.         return this;
  459.     }

  460.     /**
  461.      * Set the initial branch
  462.      *
  463.      * @param branch
  464.      *            the initial branch to check out when cloning the repository.
  465.      *            Can be specified as ref name (<code>refs/heads/master</code>),
  466.      *            branch name (<code>master</code>) or tag name
  467.      *            (<code>v1.2.3</code>). The default is to use the branch
  468.      *            pointed to by the cloned repository's HEAD and can be
  469.      *            requested by passing {@code null} or <code>HEAD</code>.
  470.      * @return {@code this}
  471.      * @since 5.11
  472.      */
  473.     public FetchCommand setInitialBranch(String branch) {
  474.         this.initialBranch = branch;
  475.         return this;
  476.     }

  477.     /**
  478.      * Register a progress callback.
  479.      *
  480.      * @param callback
  481.      *            the callback
  482.      * @return {@code this}
  483.      * @since 4.8
  484.      */
  485.     public FetchCommand setCallback(Callback callback) {
  486.         this.callback = callback;
  487.         return this;
  488.     }

  489.     /**
  490.      * Whether fetch --force option is enabled
  491.      *
  492.      * @return whether refs affected by the fetch are updated forcefully
  493.      * @since 5.0
  494.      */
  495.     public boolean isForceUpdate() {
  496.         return this.isForceUpdate;
  497.     }

  498.     /**
  499.      * Set fetch --force option
  500.      *
  501.      * @param force
  502.      *            whether to update refs affected by the fetch forcefully
  503.      * @return this command
  504.      * @since 5.0
  505.      */
  506.     public FetchCommand setForceUpdate(boolean force) {
  507.         this.isForceUpdate = force;
  508.         return this;
  509.     }

  510.     /**
  511.      * Limits fetching to the specified number of commits from the tip of each
  512.      * remote branch history.
  513.      *
  514.      * @param depth
  515.      *            the depth
  516.      * @return {@code this}
  517.      *
  518.      * @since 6.3
  519.      */
  520.     public FetchCommand setDepth(int depth) {
  521.         if (depth < 1) {
  522.             throw new IllegalArgumentException(JGitText.get().depthMustBeAt1);
  523.         }
  524.         this.depth = Integer.valueOf(depth);
  525.         return this;
  526.     }

  527.     /**
  528.      * Deepens or shortens the history of a shallow repository to include all
  529.      * reachable commits after a specified time.
  530.      *
  531.      * @param shallowSince
  532.      *            the timestammp; must not be {@code null}
  533.      * @return {@code this}
  534.      *
  535.      * @since 6.3
  536.      */
  537.     public FetchCommand setShallowSince(@NonNull OffsetDateTime shallowSince) {
  538.         this.deepenSince = shallowSince.toInstant();
  539.         return this;
  540.     }

  541.     /**
  542.      * Deepens or shortens the history of a shallow repository to include all
  543.      * reachable commits after a specified time.
  544.      *
  545.      * @param shallowSince
  546.      *            the timestammp; must not be {@code null}
  547.      * @return {@code this}
  548.      *
  549.      * @since 6.3
  550.      */
  551.     public FetchCommand setShallowSince(@NonNull Instant shallowSince) {
  552.         this.deepenSince = shallowSince;
  553.         return this;
  554.     }

  555.     /**
  556.      * Deepens or shortens the history of a shallow repository to exclude
  557.      * commits reachable from a specified remote branch or tag.
  558.      *
  559.      * @param shallowExclude
  560.      *            the ref or commit; must not be {@code null}
  561.      * @return {@code this}
  562.      *
  563.      * @since 6.3
  564.      */
  565.     public FetchCommand addShallowExclude(@NonNull String shallowExclude) {
  566.         shallowExcludes.add(shallowExclude);
  567.         return this;
  568.     }

  569.     /**
  570.      * Creates a shallow clone with a history, excluding commits reachable from
  571.      * a specified remote branch or tag.
  572.      *
  573.      * @param shallowExclude
  574.      *            the commit; must not be {@code null}
  575.      * @return {@code this}
  576.      *
  577.      * @since 6.3
  578.      */
  579.     public FetchCommand addShallowExclude(@NonNull ObjectId shallowExclude) {
  580.         shallowExcludes.add(shallowExclude.name());
  581.         return this;
  582.     }

  583.     /**
  584.      * If the source repository is complete, converts a shallow repository to a
  585.      * complete one, removing all the limitations imposed by shallow
  586.      * repositories.
  587.      *
  588.      * If the source repository is shallow, fetches as much as possible so that
  589.      * the current repository has the same history as the source repository.
  590.      *
  591.      * @param unshallow
  592.      *            whether to unshallow or not
  593.      * @return {@code this}
  594.      *
  595.      * @since 6.3
  596.      */
  597.     public FetchCommand setUnshallow(boolean unshallow) {
  598.         this.unshallow = unshallow;
  599.         return this;
  600.     }

  601.     void setShallowExcludes(List<String> shallowExcludes) {
  602.         this.shallowExcludes = shallowExcludes;
  603.     }
  604. }