/* Copyright (c) 2013-2014 Boundless and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Distribution License v1.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/org/documents/edl-v10.html * * Contributors: * Gabriel Roldan (Boundless) - initial implementation */ package org.locationtech.geogig.web.api.commands; import static com.google.common.base.Preconditions.checkState; import java.util.Collections; import java.util.HashSet; import java.util.Set; import org.locationtech.geogig.api.GeoGIG; import org.locationtech.geogig.api.GeogigTransaction; import org.locationtech.geogig.api.ObjectId; import org.locationtech.geogig.api.Ref; import org.locationtech.geogig.api.SymRef; import org.locationtech.geogig.api.plumbing.RefParse; import org.locationtech.geogig.api.plumbing.ResolveTreeish; import org.locationtech.geogig.api.plumbing.TransactionBegin; import org.locationtech.geogig.api.plumbing.UpdateRef; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Optional; import com.google.common.base.Throwables; /** * Provides a safety net for remote pushes. This class keeps track of the IP addresses of remotes * that have pushed contents to this repository. If every object is successfully transfered, a * message will be sent to the PushManager to update the local references as indicated by the * remote. */ public class PushManager { private static final Logger LOGGER = LoggerFactory.getLogger(PushManager.class); private Set<String> incomingIPs; private static PushManager instance = new PushManager(); private PushManager() { incomingIPs = Collections.synchronizedSet(new HashSet<String>()); } /** * @return the singleton instance of the {@code PushManager} */ public static PushManager get() { return instance; } /** * Begins tracking incoming objects from the specified ip address. * * @param ipAddress the remote machine that is pushing objects */ public void connectionBegin(String ipAddress) { if (incomingIPs.contains(ipAddress)) { incomingIPs.remove(ipAddress); } if (incomingIPs.size() > 0) { // Fail? } incomingIPs.add(ipAddress); } /** * This is called when the machine at the specified ip address is finished pushing objects to * the server. This causes the ref given by {@code refSpec} to be updated to point to the given * {@code newCommit} object id, as well as the {@link Ref#WORK_HEAD WORK_HEAD} and * {@link Ref#STAGE_HEAD STAGE_HEAD} refs if {@code refSpec} is the current branch. * * @param geogig the geogig of the local repository * @param ipAddress the remote machine that is pushing objects */ public void connectionSucceeded(final GeoGIG geogig, final String ipAddress, final String refspec, final ObjectId newCommit) { if (!incomingIPs.remove(ipAddress)) {// remove and check for existence in one shot throw new RuntimeException("Tried to end a connection that didn't exist."); } // Do not use the geogig instance after this, but the tx one! GeogigTransaction tx = geogig.command(TransactionBegin.class).call(); try { Optional<Ref> oldRef = tx.command(RefParse.class).setName(refspec).call(); Optional<Ref> headRef = tx.command(RefParse.class).setName(Ref.HEAD).call(); String refName = refspec; if (oldRef.isPresent()) { if (oldRef.get().getObjectId().equals(newCommit)) { LOGGER.info("ref '{}' -> {} not updated, got same id", refName, newCommit); return; } LOGGER.info("Updating ref '{}'[{}] -> {}", refName, oldRef.get().getObjectId(), newCommit); refName = oldRef.get().getName(); } else { LOGGER.info("Creating new ref '{}' -> {}", refName, newCommit); } if (headRef.isPresent() && headRef.get() instanceof SymRef) { if (((SymRef) headRef.get()).getTarget().equals(refName)) { Optional<ObjectId> commitTreeId = tx.command(ResolveTreeish.class) .setTreeish(newCommit).call(); checkState(commitTreeId.isPresent(), "Commit %s not found", newCommit); tx.command(UpdateRef.class).setName(Ref.WORK_HEAD) .setNewValue(commitTreeId.get()).call(); tx.command(UpdateRef.class).setName(Ref.STAGE_HEAD) .setNewValue(commitTreeId.get()).call(); } } tx.command(UpdateRef.class).setName(refName).setNewValue(newCommit).call(); tx.commit(); } catch (Exception e) { tx.abort(); throw Throwables.propagate(e); } } }