blob: f4c247c58b8ea60bdd109480a1ab406cff318767 [file] [log] [blame]
/*******************************************************************************
* Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
* Copyright (C) 2011, Mathias Kinzler <mathias.kinzler@sap.com>
* Copyright (C) 2012, Robin Stocker <robin@nibor.org>
* Copyright (C) 2015, Stephan Hackstedt <stephan.hackstedt@googlemail.com>
* Copyright (C) 2016, 2022 Thomas Wolf <twolf@apache.org>
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.egit.core.op;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.text.MessageFormat;
import java.util.Collection;
import java.util.List;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.egit.core.Activator;
import org.eclipse.egit.core.EclipseGitProgressTransformer;
import org.eclipse.egit.core.internal.CoreText;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.PushResult;
import org.eclipse.jgit.transport.RemoteConfig;
import org.eclipse.jgit.transport.RemoteRefUpdate;
import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
import org.eclipse.jgit.transport.Transport;
import org.eclipse.jgit.transport.URIish;
import org.eclipse.jgit.util.SystemReader;
import org.eclipse.osgi.util.NLS;
/**
* Push operation: pushing from local repository to one or many remote ones.
*/
public class PushOperation {
private final Repository localDb;
private final PushOperationSpecification specification;
private final boolean dryRun;
private final String remoteName;
private final int timeout;
private OutputStream out;
private PushOperationResult operationResult;
private CredentialsProvider credentialsProvider;
/**
* Create push operation for provided specification.
*
* @param localDb
* local repository.
* @param specification
* specification of ref updates for remote repositories.
* @param dryRun
* true if push operation should just check for possible result
* and not really update remote refs, false otherwise - when push
* should act normally.
* @param timeout
* the timeout in seconds (0 for no timeout)
*/
public PushOperation(Repository localDb,
PushOperationSpecification specification, boolean dryRun,
int timeout) {
this(localDb, null, specification, dryRun, timeout);
}
/**
* Creates a push operation for a remote configuration.
*
* @param localDb
* @param remoteName
* @param dryRun
* @param timeout
*/
public PushOperation(Repository localDb, String remoteName, boolean dryRun,
int timeout) {
this(localDb, remoteName, null, dryRun, timeout);
}
private PushOperation(Repository localDb, String remoteName,
PushOperationSpecification specification, boolean dryRun,
int timeout) {
this.localDb = localDb;
this.specification = specification;
this.dryRun = dryRun;
this.remoteName = remoteName;
this.timeout = timeout;
}
/**
* Determines the RemoteConfig to use for a given branch.
*
* @param branch
* short name of the branch to get the {@link RemoteConfig} for
* @param config
* repository {@link Config} to use
* @return the {@link RemoteConfig}, or {@code null} if nothing is
* configured
*/
public static RemoteConfig getRemote(String branch,
Config config) {
if (branch == null) {
return null;
}
String defaultPushRemote = config.getString(
ConfigConstants.CONFIG_REMOTE_SECTION, branch,
ConfigConstants.CONFIG_KEY_PUSH_DEFAULT);
String remoteName = null;
if (!ObjectId.isId(branch)) {
remoteName = config.getString(ConfigConstants.CONFIG_BRANCH_SECTION,
branch, ConfigConstants.CONFIG_KEY_PUSH_REMOTE);
if (remoteName == null) {
if (defaultPushRemote != null) {
remoteName = defaultPushRemote;
} else {
remoteName = config.getString(
ConfigConstants.CONFIG_BRANCH_SECTION, branch,
ConfigConstants.CONFIG_KEY_REMOTE);
}
}
}
if (defaultPushRemote == null) {
defaultPushRemote = Constants.DEFAULT_REMOTE_NAME;
}
// check if we find the configured and default Remotes
List<RemoteConfig> allRemotes;
try {
allRemotes = RemoteConfig.getAllRemoteConfigs(config);
} catch (URISyntaxException e) {
return null;
}
RemoteConfig defaultConfig = null;
for (RemoteConfig cfg : allRemotes) {
if (remoteName != null && cfg.getName().equals(remoteName)) {
return cfg;
} else if (cfg.getName().equals(defaultPushRemote)) {
defaultConfig = cfg;
} else if (defaultConfig == null
&& cfg.getName().equals(Constants.DEFAULT_REMOTE_NAME)) {
defaultConfig = cfg;
}
}
return defaultConfig;
}
/**
* @param credentialsProvider
*/
public void setCredentialsProvider(CredentialsProvider credentialsProvider) {
this.credentialsProvider = credentialsProvider;
}
/**
* @return the operation's credentials provider
*/
public CredentialsProvider getCredentialsProvider() {
return credentialsProvider;
}
/**
* @return push operation result
*/
public PushOperationResult getOperationResult() {
if (operationResult == null)
throw new IllegalStateException(CoreText.OperationNotYetExecuted);
return operationResult;
}
/**
* @return operation specification, as provided in constructor (may be
* <code>null</code>)
*/
public PushOperationSpecification getSpecification() {
return specification;
}
/**
* @param actMonitor
* may be <code>null</code> if progress monitoring is not desired
* @throws InvocationTargetException
* not really used: failure is communicated via the result (see
* {@link #getOperationResult()})
*/
public void run(IProgressMonitor actMonitor)
throws InvocationTargetException {
if (operationResult != null)
throw new IllegalStateException(CoreText.OperationAlreadyExecuted);
if (this.specification != null)
for (URIish uri : this.specification.getURIs()) {
for (RemoteRefUpdate update : this.specification
.getRefUpdates(uri))
if (update.getStatus() != Status.NOT_ATTEMPTED)
throw new IllegalStateException(
CoreText.RemoteRefUpdateCantBeReused);
}
final int totalWork;
if (specification != null) {
totalWork = specification.getURIsNumber();
} else {
totalWork = 1;
}
String taskName = dryRun ? CoreText.PushOperation_taskNameDryRun
: CoreText.PushOperation_taskNameNormalRun;
SubMonitor progress = SubMonitor.convert(actMonitor, totalWork);
progress.setTaskName(taskName);
operationResult = new PushOperationResult();
try (Git git = new Git(localDb)) {
Charset hookCharset = SystemReader.getInstance()
.getDefaultCharset();
if (specification != null) {
StringBuilder allHookOutputs = new StringBuilder();
StringBuilder allHookErrors = new StringBuilder();
for (final URIish uri : specification.getURIs()) {
if (progress.isCanceled()) {
operationResult.addOperationResult(uri,
CoreText.PushOperation_resultCancelled);
progress.worked(1);
continue;
}
Collection<RemoteRefUpdate> refUpdates = specification
.getRefUpdates(uri);
final EclipseGitProgressTransformer gitSubMonitor = new EclipseGitProgressTransformer(
progress.newChild(1));
try (Transport transport = Transport.open(localDb, uri)) {
transport.setDryRun(dryRun);
transport.setTimeout(timeout);
if (credentialsProvider != null) {
transport.setCredentialsProvider(
credentialsProvider);
}
try (ByteArrayOutputStream hookOutBytes = new ByteArrayOutputStream();
ByteArrayOutputStream hookErrBytes = new ByteArrayOutputStream();
PrintStream stdout = new PrintStream(hookOutBytes, true, hookCharset);
PrintStream stderr = new PrintStream(hookErrBytes, true, hookCharset)) {
transport.setHookOutputStream(stdout);
transport.setHookErrorStream(stderr);
PushResult result = transport.push(gitSubMonitor,
refUpdates, out);
stdout.flush();
stderr.flush();
addHookMessage(result.getURI(),
hookOutBytes.toString(hookCharset),
allHookOutputs);
addHookMessage(result.getURI(),
hookErrBytes.toString(hookCharset),
allHookErrors);
operationResult.addOperationResult(result.getURI(),
result);
specification.addURIRefUpdates(result.getURI(),
result.getRemoteUpdates());
}
} catch (JGitInternalException e) {
String errorMessage = e.getCause() != null
? e.getCause().getMessage() : e.getMessage();
String userMessage = NLS.bind(
CoreText.PushOperation_InternalExceptionOccurredMessage,
errorMessage);
handleException(uri, e, userMessage);
} catch (Exception e) {
handleException(uri, e, e.getMessage());
}
}
operationResult.setHookOutput(allHookOutputs.toString(),
allHookErrors.toString());
} else {
final EclipseGitProgressTransformer gitMonitor = new EclipseGitProgressTransformer(
progress.newChild(totalWork));
try (ByteArrayOutputStream hookOutBytes = new ByteArrayOutputStream();
ByteArrayOutputStream hookErrBytes = new ByteArrayOutputStream();
PrintStream stdout = new PrintStream(hookOutBytes, true,
hookCharset);
PrintStream stderr = new PrintStream(hookErrBytes, true,
hookCharset)) {
Iterable<PushResult> results = git.push()
.setRemote(remoteName)
.setDryRun(dryRun)
.setTimeout(timeout)
.setProgressMonitor(gitMonitor)
.setCredentialsProvider(credentialsProvider)
.setOutputStream(out)
.setHookOutputStream(stdout)
.setHookErrorStream(stderr)
.call();
stdout.flush();
stderr.flush();
operationResult.setHookOutput(
hookOutBytes.toString(hookCharset),
hookErrBytes.toString(hookCharset));
for (PushResult result : results) {
operationResult.addOperationResult(result.getURI(),
result);
}
} catch (JGitInternalException e) {
String errorMessage = e.getCause() != null
? e.getCause().getMessage() : e.getMessage();
String userMessage = NLS.bind(
CoreText.PushOperation_InternalExceptionOccurredMessage,
errorMessage);
URIish uri = getPushURIForErrorHandling();
handleException(uri, e, userMessage);
} catch (Exception e) {
URIish uri = getPushURIForErrorHandling();
handleException(uri, e, e.getMessage());
}
}
}
}
private void addHookMessage(URIish uri, String msg, StringBuilder all) {
if (!msg.isEmpty()) {
if (all.length() > 0 && all.charAt(all.length() - 1) != '\n') {
all.append('\n');
}
all.append(
MessageFormat.format(CoreText.PushOperation_ForUri, uri));
all.append('\n');
all.append(msg);
}
}
private void handleException(final URIish uri, Exception e,
String userMessage) {
String uriString;
if (uri != null) {
operationResult.addOperationResult(uri, userMessage);
uriString = uri.toString();
} else
uriString = "retrieving URI failed"; //$NON-NLS-1$
String userMessageForUri = NLS.bind(
CoreText.PushOperation_ExceptionOccurredDuringPushOnUriMessage,
uriString, userMessage);
Activator.logError(userMessageForUri, e);
}
private URIish getPushURIForErrorHandling() {
RemoteConfig rc = null;
try {
rc = new RemoteConfig(localDb.getConfig(), remoteName);
return rc.getPushURIs().isEmpty() ? rc.getURIs().get(0) : rc
.getPushURIs().get(0);
} catch (URISyntaxException e) {
// should not happen
Activator.logError("Reading RemoteConfig failed", e); //$NON-NLS-1$
return null;
}
}
/**
* Sets the output stream this operation will write sideband messages to.
*
* @param out
* the outputstream to write to
* @since 3.0
*/
public void setOutputStream(OutputStream out) {
this.out = out;
}
}