/******************************************************************************* * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com> * * 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.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; /** * Construct empty push operation result. */ public PushOperationResult() { this.urisEntries = new LinkedHashMap<URIish, Entry>(); } /** * 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(); } /** * @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<RemoteRefUpdate>( 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; } } }