/*******************************************************************************
* 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, Thomas Wolf <thomas.wolf@paranor.ch>
*
* 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
*******************************************************************************/
package org.eclipse.egit.core.op;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.URISyntaxException;
import java.util.Collection;
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.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.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(final Repository localDb,
final PushOperationSpecification specification,
final 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(final Repository localDb, final String remoteName,
final boolean dryRun, int timeout) {
this(localDb, remoteName, null, dryRun, timeout);
}
private PushOperation(final Repository localDb, final String remoteName,
PushOperationSpecification specification, final boolean dryRun,
int timeout) {
this.localDb = localDb;
this.specification = specification;
this.dryRun = dryRun;
this.remoteName = remoteName;
this.timeout = timeout;
}
/**
* @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)) {
if (specification != null)
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);
}
PushResult result = transport.push(gitSubMonitor,
refUpdates, out);
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());
}
}
else {
final EclipseGitProgressTransformer gitMonitor = new EclipseGitProgressTransformer(
progress.newChild(totalWork));
try {
Iterable<PushResult> results = git.push()
.setRemote(remoteName).setDryRun(dryRun)
.setTimeout(timeout).setProgressMonitor(gitMonitor)
.setCredentialsProvider(credentialsProvider)
.setOutputStream(out).call();
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 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;
}
}