| /******************************************************************************* |
| * Copyright (C) 2008, 2022 Marek Zawirski <marek.zawirski@gmail.com> and others. |
| * |
| * 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.IOException; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.LinkedHashMap; |
| import java.util.Set; |
| |
| import org.eclipse.jgit.lib.ObjectId; |
| import org.eclipse.jgit.lib.Ref; |
| import org.eclipse.jgit.transport.PushResult; |
| import org.eclipse.jgit.transport.RemoteRefUpdate; |
| import org.eclipse.jgit.transport.URIish; |
| |
| /** |
| * Data class for storing push operation results for each remote repository/URI |
| * being part of push operation. |
| * <p> |
| * One instance of this class is dedicated for result of one push operation: |
| * either to one URI or to many URIs. |
| * |
| * @see PushOperation |
| */ |
| public class PushOperationResult { |
| |
| private LinkedHashMap<URIish, Entry> urisEntries; |
| |
| private String hookOut; |
| |
| private String hookErr; |
| |
| /** |
| * Construct empty push operation result. |
| */ |
| public PushOperationResult() { |
| this.urisEntries = new LinkedHashMap<>(); |
| } |
| |
| /** |
| * Add push result for the repository (URI) with successful connection. |
| * |
| * @param uri |
| * remote repository URI. |
| * @param result |
| * push result. |
| */ |
| public void addOperationResult(final URIish uri, final PushResult result) { |
| urisEntries.put(uri, new Entry(result)); |
| } |
| |
| /** |
| * Add error message for the repository (URI) with unsuccessful connection. |
| * |
| * @param uri |
| * remote repository URI. |
| * @param errorMessage |
| * failure error message. |
| */ |
| public void addOperationResult(final URIish uri, final String errorMessage) { |
| urisEntries.put(uri, new Entry(errorMessage)); |
| } |
| |
| /** |
| * @return set of remote repositories URIish. Set is ordered in addition |
| * sequence, which is usually the same as that from |
| * {@link PushOperationSpecification}. |
| */ |
| public Set<URIish> getURIs() { |
| return Collections.unmodifiableSet(urisEntries.keySet()); |
| } |
| |
| /** |
| * @param uri |
| * remote repository URI. |
| * @return true if connection was successful for this repository (URI), |
| * false if this operation ended with unsuccessful connection. |
| */ |
| public boolean isSuccessfulConnection(final URIish uri) { |
| return urisEntries.get(uri).isSuccessfulConnection(); |
| } |
| |
| /** |
| * @return true if connection was successful for any repository (URI), false |
| * otherwise. |
| */ |
| public boolean isSuccessfulConnectionForAnyURI() { |
| for (final URIish uri : getURIs()) { |
| if (isSuccessfulConnection(uri)) |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * @param uri |
| * remote repository URI. |
| * @return push result for this repository (URI) or null if operation ended |
| * with unsuccessful connection for this URI. |
| */ |
| public PushResult getPushResult(final URIish uri) { |
| return urisEntries.get(uri).getResult(); |
| } |
| |
| /** |
| * @param uri |
| * remote repository URI. |
| * @return error message for this repository (URI) or null if operation |
| * ended with successful connection for this URI. |
| */ |
| public String getErrorMessage(final URIish uri) { |
| return urisEntries.get(uri).getErrorMessage(); |
| } |
| |
| /** |
| * Sets the output of a pre-push hook. |
| * |
| * @param stdout |
| * of the pre-push hook |
| * @param stderr |
| * of the pre-push hook |
| */ |
| public void setHookOutput(String stdout, String stderr) { |
| hookOut = stdout; |
| hookErr = stderr; |
| } |
| |
| /** |
| * Retrieves the stdout output of a pre-push hook, if any. |
| * |
| * @return the hook's output to stdout, or an empty string |
| */ |
| public String getHookStdOut() { |
| return hookOut == null ? "" : hookOut; //$NON-NLS-1$ |
| } |
| |
| /** |
| * Retrieves the stderr output of a pre-push hook, if any. |
| * |
| * @return the hook's output to stderr, or an empty string |
| */ |
| public String getHookStdErr() { |
| return hookErr == null ? "" : hookErr; //$NON-NLS-1$ |
| } |
| |
| /** |
| * @return string being list of failed URIs with their error messages. |
| */ |
| public String getErrorStringForAllURis() { |
| final StringBuilder sb = new StringBuilder(); |
| boolean first = true; |
| for (final URIish uri : getURIs()) { |
| if (first) |
| first = false; |
| else |
| sb.append(", "); //$NON-NLS-1$ |
| sb.append(uri); |
| sb.append(" ("); //$NON-NLS-1$ |
| sb.append(getErrorMessage(uri)); |
| sb.append(")"); //$NON-NLS-1$ |
| } |
| return sb.toString(); |
| } |
| |
| /** |
| * Derive push operation specification from this push operation result. |
| * <p> |
| * Specification is created basing on URIs of remote repositories in this |
| * result that completed without connection errors, and remote ref updates |
| * from push results. |
| * <p> |
| * This method is targeted to provide support for 2-stage push, where first |
| * operation is dry run for user confirmation and second one is a real |
| * operation. |
| * |
| * @param requireUnchanged |
| * if true, newly created copies of remote ref updates have |
| * expected old object id set to previously advertised ref value |
| * (remote ref won't be updated if it change in the mean time), |
| * if false, newly create copies of remote ref updates have |
| * expected object id set up as in this result source |
| * specification. |
| * @return derived specification for another push operation. |
| * @throws IOException |
| * when some previously locally available source ref is not |
| * available anymore, or some error occurred during creation |
| * locally tracking ref update. |
| * |
| */ |
| public PushOperationSpecification deriveSpecification( |
| final boolean requireUnchanged) throws IOException { |
| final PushOperationSpecification spec = new PushOperationSpecification(); |
| for (final URIish uri : getURIs()) { |
| final PushResult pr = getPushResult(uri); |
| if (pr == null) |
| continue; |
| |
| final Collection<RemoteRefUpdate> oldUpdates = pr |
| .getRemoteUpdates(); |
| final ArrayList<RemoteRefUpdate> newUpdates = new ArrayList<>( |
| oldUpdates.size()); |
| for (final RemoteRefUpdate rru : oldUpdates) { |
| final ObjectId expectedOldObjectId; |
| if (requireUnchanged) { |
| final Ref advertisedRef = getPushResult(uri) |
| .getAdvertisedRef(rru.getRemoteName()); |
| if (advertisedRef == null) |
| expectedOldObjectId = ObjectId.zeroId(); |
| else |
| expectedOldObjectId = advertisedRef.getObjectId(); |
| } else |
| expectedOldObjectId = rru.getExpectedOldObjectId(); |
| final RemoteRefUpdate newRru = new RemoteRefUpdate(rru, |
| expectedOldObjectId); |
| newUpdates.add(newRru); |
| } |
| spec.addURIRefUpdates(uri, newUpdates); |
| } |
| return spec; |
| } |
| |
| /** |
| * This implementation returns true if all following conditions are met: |
| * <ul> |
| * <li>both objects result have the same set successfully connected |
| * repositories (URIs) - unsuccessful connections are discarded, AND <li> |
| * remote ref updates must match for each successful connection in sense of |
| * equal remoteName, equal status and equal newObjectId value.</li> |
| * </ul> |
| * |
| * @see Object#equals(Object) |
| * @param obj |
| * other push operation result to compare to. |
| * @return true if object is equal to this one in terms of conditions |
| * described above, false otherwise. |
| */ |
| @Override |
| public boolean equals(final Object obj) { |
| if (obj == this) |
| return true; |
| |
| if (!(obj instanceof PushOperationResult)) |
| return false; |
| |
| final PushOperationResult other = (PushOperationResult) obj; |
| |
| // Check successful connections/URIs two-ways: |
| final Set<URIish> otherURIs = other.getURIs(); |
| for (final URIish uri : getURIs()) { |
| if (isSuccessfulConnection(uri) |
| && (!otherURIs.contains(uri) || !other |
| .isSuccessfulConnection(uri))) |
| return false; |
| } |
| for (final URIish uri : other.getURIs()) { |
| if (other.isSuccessfulConnection(uri) |
| && (!urisEntries.containsKey(uri) || !isSuccessfulConnection(uri))) |
| return false; |
| } |
| |
| for (final URIish uri : getURIs()) { |
| if (!isSuccessfulConnection(uri)) |
| continue; |
| |
| final PushResult otherPushResult = other.getPushResult(uri); |
| for (final RemoteRefUpdate rru : getPushResult(uri) |
| .getRemoteUpdates()) { |
| final RemoteRefUpdate otherRru = otherPushResult |
| .getRemoteUpdate(rru.getRemoteName()); |
| if (otherRru == null) |
| return false; |
| if (otherRru.getStatus() != rru.getStatus() |
| || otherRru.getNewObjectId() != rru.getNewObjectId()) |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| @Override |
| public int hashCode() { |
| return urisEntries.hashCode(); |
| } |
| |
| private static class Entry { |
| private String errorMessage; |
| |
| private PushResult result; |
| |
| Entry(final PushResult result) { |
| this.result = result; |
| } |
| |
| Entry(final String errorMessage) { |
| this.errorMessage = errorMessage; |
| } |
| |
| boolean isSuccessfulConnection() { |
| return result != null; |
| } |
| |
| String getErrorMessage() { |
| return errorMessage; |
| } |
| |
| PushResult getResult() { |
| return result; |
| } |
| } |
| } |