/******************************************************************************* * Copyright (c) 2010, 2016 SAP AG and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Mathias Kinzler <mathias.kinzler@sap.com> - initial implementation * Laurent Delaigue (Obeo) - use of preferred merge strategy * Stephan Hackstedt - bug 477695 * Mickael Istria (Red Hat Inc.) - [485124] Introduce PullReferenceConfig *******************************************************************************/ package org.eclipse.egit.core.op; import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.IWorkspaceRunnable; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.SubMonitor; import org.eclipse.core.runtime.jobs.ISchedulingRule; import org.eclipse.egit.core.Activator; import org.eclipse.egit.core.EclipseGitProgressTransformer; import org.eclipse.egit.core.internal.CoreText; import org.eclipse.egit.core.internal.job.RuleUtil; import org.eclipse.egit.core.internal.util.ProjectUtil; import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.MergeResult; import org.eclipse.jgit.api.MergeResult.MergeStatus; import org.eclipse.jgit.api.PullCommand; import org.eclipse.jgit.api.PullResult; import org.eclipse.jgit.api.errors.DetachedHeadException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.InvalidConfigurationException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.merge.MergeStrategy; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.osgi.util.NLS; /** * Wraps the JGit API {@link PullCommand} into an operation */ public class PullOperation implements IEGitOperation { /** * This describe a specification of a Pull command * @since 4.2 */ public static class PullReferenceConfig { private String remote; private String reference; private BranchRebaseMode upstreamConfig; /** * @param remote * @param reference * @param upstreamConfig */ public PullReferenceConfig(@Nullable String remote, @Nullable String reference, @Nullable BranchRebaseMode upstreamConfig) { this.remote = remote; this.reference = reference; this.upstreamConfig = upstreamConfig; } /** * @return the remote to pull from. Can be null (in which case client is * free to either ignore the pull, send an error, use default * configuration for the current branch...) */ @Nullable public String getRemote() { return this.remote; } /** * @return the reference (commit, tag, id...) to pull from. Can be null * (in which case client is free to either ignore the pull, send * an error, use default configuration for the current * branch...) */ @Nullable public String getReference() { return this.reference; } /** * @return the upstream config strategy to use for the specified pull */ @Nullable public BranchRebaseMode getUpstreamConfig() { return this.upstreamConfig; } } private final Repository[] repositories; private Map<Repository, PullReferenceConfig> configs; private final Map<Repository, Object> results = new LinkedHashMap<Repository, Object>(); private final int timeout; private CredentialsProvider credentialsProvider; /** * @param repositories * the repositories * @param timeout * in seconds */ public PullOperation(Set<Repository> repositories, int timeout) { this.timeout = timeout; this.repositories = repositories.toArray(new Repository[repositories .size()]); this.configs = Collections.emptyMap(); } /** * @param repositories * Repositories to pull, with specific configuration * @param timeout * in seconds * @since 4.2 */ public PullOperation( @NonNull Map<Repository, PullReferenceConfig> repositories, int timeout) { this(repositories.keySet(), timeout); this.configs = repositories; } @Override public void execute(IProgressMonitor m) throws CoreException { if (!results.isEmpty()) throw new CoreException(new Status(IStatus.ERROR, Activator .getPluginId(), CoreText.OperationAlreadyExecuted)); SubMonitor totalProgress = SubMonitor.convert(m, NLS.bind(CoreText.PullOperation_TaskName, Integer.valueOf(repositories.length)), 1); IWorkspaceRunnable action = new IWorkspaceRunnable() { @Override public void run(IProgressMonitor mymonitor) throws CoreException { if (mymonitor.isCanceled()) throw new CoreException(Status.CANCEL_STATUS); SubMonitor progress = SubMonitor.convert(mymonitor, repositories.length * 2); for (int i = 0; i < repositories.length; i++) { Repository repository = repositories[i]; IProject[] validProjects = ProjectUtil.getValidOpenProjects(repository); PullResult pullResult = null; try (Git git = new Git(repository)) { PullCommand pull = git.pull(); SubMonitor newChild = progress.newChild(1, SubMonitor.SUPPRESS_NONE); pull.setProgressMonitor(new EclipseGitProgressTransformer( newChild)); pull.setTimeout(timeout); pull.setCredentialsProvider(credentialsProvider); PullReferenceConfig config = configs.get(repository); newChild.setTaskName( getPullTaskName(repository, config)); if (config != null) { if (config.getRemote() != null) { pull.setRemote(config.getRemote()); } if (config.getReference() != null) { pull.setRemoteBranchName(config.getReference()); } pull.setRebase(config.getUpstreamConfig()); } MergeStrategy strategy = Activator.getDefault() .getPreferredMergeStrategy(); if (strategy != null) { pull.setStrategy(strategy); } pullResult = pull.call(); results.put(repository, pullResult); } catch (DetachedHeadException e) { results.put(repository, Activator.error( CoreText.PullOperation_DetachedHeadMessage, e)); } catch (InvalidConfigurationException e) { IStatus error = Activator .error(CoreText.PullOperation_PullNotConfiguredMessage, e); results.put(repository, error); } catch (GitAPIException e) { results.put(repository, Activator.error(e.getMessage(), e)); } catch (JGitInternalException e) { Throwable cause = e.getCause(); if (cause == null || !(cause instanceof TransportException)) cause = e; results.put(repository, Activator.error(cause.getMessage(), cause)); } finally { if (refreshNeeded(pullResult)) { ProjectUtil.refreshValidProjects(validProjects, progress.newChild(1, SubMonitor.SUPPRESS_NONE)); } else { progress.worked(1); } } } } }; // lock workspace to protect working tree changes ResourcesPlugin.getWorkspace().run(action, getSchedulingRule(), IWorkspace.AVOID_UPDATE, totalProgress); } static String getPullTaskName(Repository repo, PullReferenceConfig rc) { StoredConfig config = repo.getConfig(); if (rc != null) { String remoteUri = config.getString( ConfigConstants.CONFIG_REMOTE_SECTION, rc.remote, ConfigConstants.CONFIG_KEY_URL); return "Pulling " + rc.remote + " from " + remoteUri; //$NON-NLS-1$ //$NON-NLS-2$ } String branchName; try { String fullBranch = repo.getFullBranch(); branchName = fullBranch != null ? fullBranch.substring(Constants.R_HEADS.length()) : ""; //$NON-NLS-1$ } catch (IOException e) { return "Pulling from " + repo.toString(); //$NON-NLS-1$ } // get the configured remote for the currently checked out branch // stored in configuration key branch.<branch name>.remote String remote = config.getString(ConfigConstants.CONFIG_BRANCH_SECTION, branchName, ConfigConstants.CONFIG_KEY_REMOTE); if (remote == null) { // fall back to default remote remote = Constants.DEFAULT_REMOTE_NAME; } String remoteUri = config.getString( ConfigConstants.CONFIG_REMOTE_SECTION, remote, ConfigConstants.CONFIG_KEY_URL); if (remoteUri != null) { return "Pulling " + remote + " from " + remoteUri; //$NON-NLS-1$ //$NON-NLS-2$ } return "Pulling from " + repo.getDirectory(); //$NON-NLS-1$ } boolean refreshNeeded(PullResult pullResult) { if (pullResult == null) return true; MergeResult mergeResult = pullResult.getMergeResult(); if (mergeResult == null) return true; if (mergeResult.getMergeStatus() == MergeStatus.ALREADY_UP_TO_DATE) return false; return true; } /** * @return the results, or an empty Map if this has not been executed */ public Map<Repository, Object> getResults() { return this.results; } @Override public ISchedulingRule getSchedulingRule() { return RuleUtil.getRuleForRepositories(Arrays.asList(repositories)); } /** * @param credentialsProvider */ public void setCredentialsProvider(CredentialsProvider credentialsProvider) { this.credentialsProvider = credentialsProvider; } /** * @return the operation's credentials provider */ public CredentialsProvider getCredentialsProvider() { return credentialsProvider; } }