| /******************************************************************************* |
| * 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; |
| } |
| } |