org.eclipse.jgit.api.FetchCommand.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.jgit.api.FetchCommand.java

Source

/*
 * Copyright (C) 2010, Chris Aniszczyk <caniszczyk@gmail.com>
 * and other copyright owners as documented in the project's IP log.
 *
 * This program and the accompanying materials are made available
 * under the terms of the Eclipse Distribution License v1.0 which
 * accompanies this distribution, is reproduced below, and is
 * available at http://www.eclipse.org/org/documents/edl-v10.php
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above
 *   copyright notice, this list of conditions and the following
 *   disclaimer in the documentation and/or other materials provided
 *   with the distribution.
 *
 * - Neither the name of the Eclipse Foundation, Inc. nor the
 *   names of its contributors may be used to endorse or promote
 *   products derived from this software without specific prior
 *   written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.eclipse.jgit.api;

import static java.util.stream.Collectors.toList;

import java.io.IOException;
import java.net.URISyntaxException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.InvalidConfigurationException;
import org.eclipse.jgit.api.errors.InvalidRemoteException;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.NoRemoteRepositoryException;
import org.eclipse.jgit.errors.NotSupportedException;
import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.lib.SubmoduleConfig.FetchRecurseSubmodulesMode;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.submodule.SubmoduleWalk;
import org.eclipse.jgit.transport.FetchResult;
import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.transport.TagOpt;
import org.eclipse.jgit.transport.Transport;

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

    private List<RefSpec> refSpecs;

    private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;

    private boolean checkFetchedObjects;

    private Boolean removeDeletedRefs;

    private boolean dryRun;

    private boolean thin = Transport.DEFAULT_FETCH_THIN;

    private TagOpt tagOption;

    private FetchRecurseSubmodulesMode submoduleRecurseMode = null;

    private Callback callback;

    private boolean isForceUpdate;

    /**
     * Callback for status of fetch operation.
     *
     * @since 4.8
     *
     */
    public interface Callback {
        /**
         * Notify fetching a submodule.
         *
         * @param name
         *            the submodule name.
         */
        void fetchingSubmodule(String name);
    }

    /**
     * Constructor for FetchCommand.
     *
     * @param repo
     *            a {@link org.eclipse.jgit.lib.Repository} object.
     */
    protected FetchCommand(Repository repo) {
        super(repo);
        refSpecs = new ArrayList<>(3);
    }

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

        // Fall back to submodule.name.fetchRecurseSubmodules, if set
        FetchRecurseSubmodulesMode mode = repo.getConfig().getEnum(FetchRecurseSubmodulesMode.values(),
                ConfigConstants.CONFIG_SUBMODULE_SECTION, path, ConfigConstants.CONFIG_KEY_FETCH_RECURSE_SUBMODULES,
                null);
        if (mode != null) {
            return mode;
        }

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

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

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

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

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

                    // When the fetch mode is "yes" we always fetch. When the
                    // mode
                    // is "on demand", we only fetch if the submodule's revision
                    // was
                    // updated to an object that is not currently present in the
                    // submodule.
                    if ((recurseMode == FetchRecurseSubmodulesMode.ON_DEMAND
                            && !submoduleRepo.getObjectDatabase().has(walk.getObjectId()))
                            || recurseMode == FetchRecurseSubmodulesMode.YES) {
                        FetchCommand f = new FetchCommand(submoduleRepo).setProgressMonitor(monitor)
                                .setTagOpt(tagOption).setCheckFetchedObjects(checkFetchedObjects)
                                .setRemoveDeletedRefs(isRemoveDeletedRefs()).setThin(thin)
                                .setRefSpecs(applyOptions(refSpecs)).setDryRun(dryRun)
                                .setRecurseSubmodules(recurseMode);
                        configure(f);
                        if (callback != null) {
                            callback.fetchingSubmodule(walk.getPath());
                        }
                        results.addSubmodule(walk.getPath(), f.call());
                    }
                }
            }
        } catch (IOException e) {
            throw new JGitInternalException(e.getMessage(), e);
        } catch (ConfigInvalidException e) {
            throw new InvalidConfigurationException(e.getMessage(), e);
        }
    }

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

        try (Transport transport = Transport.open(repo, remote)) {
            transport.setCheckFetchedObjects(checkFetchedObjects);
            transport.setRemoveDeletedRefs(isRemoveDeletedRefs());
            transport.setDryRun(dryRun);
            if (tagOption != null)
                transport.setTagOpt(tagOption);
            transport.setFetchThin(thin);
            configure(transport);
            FetchResult result = transport.fetch(monitor, applyOptions(refSpecs));
            if (!repo.isBare()) {
                fetchSubmodules(result);
            }

            return result;
        } catch (NoRemoteRepositoryException e) {
            throw new InvalidRemoteException(MessageFormat.format(JGitText.get().invalidRemote, remote), e);
        } catch (TransportException e) {
            throw new org.eclipse.jgit.api.errors.TransportException(e.getMessage(), e);
        } catch (URISyntaxException e) {
            throw new InvalidRemoteException(MessageFormat.format(JGitText.get().invalidRemote, remote));
        } catch (NotSupportedException e) {
            throw new JGitInternalException(JGitText.get().exceptionCaughtDuringExecutionOfFetchCommand, e);
        }

    }

    private List<RefSpec> applyOptions(List<RefSpec> refSpecs2) {
        if (!isForceUpdate()) {
            return refSpecs2;
        }
        List<RefSpec> updated = new ArrayList<>(3);
        for (RefSpec refSpec : refSpecs2) {
            updated.add(refSpec.setForceUpdate(true));
        }
        return updated;
    }

    /**
     * Set the mode to be used for recursing into submodules.
     *
     * @param recurse
     *            corresponds to the
     *            --recurse-submodules/--no-recurse-submodules options. If
     *            {@code null} use the value of the
     *            {@code submodule.name.fetchRecurseSubmodules} option
     *            configured per submodule. If not specified there, use the
     *            value of the {@code fetch.recurseSubmodules} option configured
     *            in git config. If not configured in either, "on-demand" is the
     *            built-in default.
     * @return {@code this}
     * @since 4.7
     */
    public FetchCommand setRecurseSubmodules(@Nullable FetchRecurseSubmodulesMode recurse) {
        checkCallable();
        submoduleRecurseMode = recurse;
        return this;
    }

    /**
     * The remote (uri or name) used for the fetch operation. If no remote is
     * set, the default value of <code>Constants.DEFAULT_REMOTE_NAME</code> will
     * be used.
     *
     * @see Constants#DEFAULT_REMOTE_NAME
     * @param remote
     *            name of a remote
     * @return {@code this}
     */
    public FetchCommand setRemote(String remote) {
        checkCallable();
        this.remote = remote;
        return this;
    }

    /**
     * Get the remote
     *
     * @return the remote used for the remote operation
     */
    public String getRemote() {
        return remote;
    }

    /**
     * Get timeout
     *
     * @return the timeout used for the fetch operation
     */
    public int getTimeout() {
        return timeout;
    }

    /**
     * Whether to check received objects for validity
     *
     * @return whether to check received objects for validity
     */
    public boolean isCheckFetchedObjects() {
        return checkFetchedObjects;
    }

    /**
     * If set to {@code true}, objects received will be checked for validity
     *
     * @param checkFetchedObjects
     *            whether to check objects for validity
     * @return {@code this}
     */
    public FetchCommand setCheckFetchedObjects(boolean checkFetchedObjects) {
        checkCallable();
        this.checkFetchedObjects = checkFetchedObjects;
        return this;
    }

    /**
     * Whether to remove refs which no longer exist in the source
     *
     * @return whether to remove refs which no longer exist in the source
     */
    public boolean isRemoveDeletedRefs() {
        if (removeDeletedRefs != null) {
            return removeDeletedRefs.booleanValue();
        }
        // fall back to configuration
        boolean result = false;
        StoredConfig config = repo.getConfig();
        result = config.getBoolean(ConfigConstants.CONFIG_FETCH_SECTION, null, ConfigConstants.CONFIG_KEY_PRUNE,
                result);
        result = config.getBoolean(ConfigConstants.CONFIG_REMOTE_SECTION, remote, ConfigConstants.CONFIG_KEY_PRUNE,
                result);
        return result;
    }

    /**
     * If set to {@code true}, refs are removed which no longer exist in the
     * source
     *
     * @param removeDeletedRefs
     *            whether to remove deleted {@code Ref}s
     * @return {@code this}
     */
    public FetchCommand setRemoveDeletedRefs(boolean removeDeletedRefs) {
        checkCallable();
        this.removeDeletedRefs = Boolean.valueOf(removeDeletedRefs);
        return this;
    }

    /**
     * Get progress monitor
     *
     * @return the progress monitor for the fetch operation
     */
    public ProgressMonitor getProgressMonitor() {
        return monitor;
    }

    /**
     * The progress monitor associated with the fetch operation. By default,
     * this is set to <code>NullProgressMonitor</code>
     *
     * @see NullProgressMonitor
     * @param monitor
     *            a {@link org.eclipse.jgit.lib.ProgressMonitor}
     * @return {@code this}
     */
    public FetchCommand setProgressMonitor(ProgressMonitor monitor) {
        checkCallable();
        if (monitor == null) {
            monitor = NullProgressMonitor.INSTANCE;
        }
        this.monitor = monitor;
        return this;
    }

    /**
     * Get list of {@code RefSpec}s
     *
     * @return the ref specs
     */
    public List<RefSpec> getRefSpecs() {
        return refSpecs;
    }

    /**
     * The ref specs to be used in the fetch operation
     *
     * @param specs
     *            String representation of {@code RefSpec}s
     * @return {@code this}
     * @since 4.9
     */
    public FetchCommand setRefSpecs(String... specs) {
        return setRefSpecs(Arrays.stream(specs).map(RefSpec::new).collect(toList()));
    }

    /**
     * The ref specs to be used in the fetch operation
     *
     * @param specs
     *            one or multiple {@link org.eclipse.jgit.transport.RefSpec}s
     * @return {@code this}
     */
    public FetchCommand setRefSpecs(RefSpec... specs) {
        return setRefSpecs(Arrays.asList(specs));
    }

    /**
     * The ref specs to be used in the fetch operation
     *
     * @param specs
     *            list of {@link org.eclipse.jgit.transport.RefSpec}s
     * @return {@code this}
     */
    public FetchCommand setRefSpecs(List<RefSpec> specs) {
        checkCallable();
        this.refSpecs.clear();
        this.refSpecs.addAll(specs);
        return this;
    }

    /**
     * Whether to do a dry run
     *
     * @return the dry run preference for the fetch operation
     */
    public boolean isDryRun() {
        return dryRun;
    }

    /**
     * Sets whether the fetch operation should be a dry run
     *
     * @param dryRun
     *            whether to do a dry run
     * @return {@code this}
     */
    public FetchCommand setDryRun(boolean dryRun) {
        checkCallable();
        this.dryRun = dryRun;
        return this;
    }

    /**
     * Get thin-pack preference
     *
     * @return the thin-pack preference for fetch operation
     */
    public boolean isThin() {
        return thin;
    }

    /**
     * Sets the thin-pack preference for fetch operation.
     *
     * Default setting is Transport.DEFAULT_FETCH_THIN
     *
     * @param thin
     *            the thin-pack preference
     * @return {@code this}
     */
    public FetchCommand setThin(boolean thin) {
        checkCallable();
        this.thin = thin;
        return this;
    }

    /**
     * Sets the specification of annotated tag behavior during fetch
     *
     * @param tagOpt
     *            the {@link org.eclipse.jgit.transport.TagOpt}
     * @return {@code this}
     */
    public FetchCommand setTagOpt(TagOpt tagOpt) {
        checkCallable();
        this.tagOption = tagOpt;
        return this;
    }

    /**
     * Register a progress callback.
     *
     * @param callback
     *            the callback
     * @return {@code this}
     * @since 4.8
     */
    public FetchCommand setCallback(Callback callback) {
        this.callback = callback;
        return this;
    }

    /**
     * Whether fetch --force option is enabled
     *
     * @return whether refs affected by the fetch are updated forcefully
     * @since 5.0
     */
    public boolean isForceUpdate() {
        return this.isForceUpdate;
    }

    /**
     * Set fetch --force option
     *
     * @param force
     *            whether to update refs affected by the fetch forcefully
     * @return this command
     * @since 5.0
     */
    public FetchCommand setForceUpdate(boolean force) {
        this.isForceUpdate = force;
        return this;
    }
}