/* Copyright (c) 2012-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: * Johnathan Garrett (LMN Solutions) - initial implementation */ package org.locationtech.geogig.api.porcelain; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import java.util.ArrayList; import java.util.List; import org.locationtech.geogig.api.AbstractGeoGigOp; import org.locationtech.geogig.api.Ref; import org.locationtech.geogig.api.Remote; import org.locationtech.geogig.api.SymRef; import org.locationtech.geogig.api.hooks.Hookable; import org.locationtech.geogig.api.plumbing.ForEachRef; import org.locationtech.geogig.api.plumbing.RefParse; import org.locationtech.geogig.api.plumbing.SendPack; import org.locationtech.geogig.api.plumbing.SendPack.TransferableRef; import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableSet; /** * Update remote refs along with associated objects. * * <b>NOTE:</b> so far we don't have the ability to merge non conflicting changes. Instead, the diff * list we get acts on whole objects, , so its possible that this operation overrides non * conflicting changes when pushing a branch that has non conflicting changes at both sides. This * needs to be revisited once we get more merge tools. */ @Hookable(name = "push") public class PushOp extends AbstractGeoGigOp<TransferSummary> { private boolean all; private List<String> refSpecs = new ArrayList<String>(); private String remoteName; /** * @param all if {@code true}, push all refs under refs/heads/ * @return {@code this} */ public PushOp setAll(final boolean all) { this.all = all; return this; } /** * @param refSpec the refspec of a remote branch * @return {@code this} */ public PushOp addRefSpec(final String refSpec) { refSpecs.add(refSpec); return this; } public List<String> getRefSpecs() { return refSpecs; } /** * @param remoteName the name or URL of a remote repository to push to * @return {@code this} */ public PushOp setRemote(final String remoteName) { checkNotNull(remoteName); this.remoteName = remoteName; return this; } public String getRemoteName() { return remoteName; } public Optional<Remote> getRemote() { try { return Optional.of(resolveRemote(remoteName)); } catch (IllegalArgumentException e) { return Optional.absent(); } } /** * Executes the push operation. * * @return {@code null} * @see org.locationtech.geogig.api.AbstractGeoGigOp#call() */ @Override protected TransferSummary _call() { final String remoteName = this.remoteName == null ? "origin" : this.remoteName; final Remote remote = resolveRemote(remoteName); final List<TransferableRef> refsToPush = resolveRefs(); SendPack sendPack = command(SendPack.class); sendPack.setRemote(remote); sendPack.setRefs(refsToPush); sendPack.setProgressListener(getProgressListener()); TransferSummary result = sendPack.call(); return result; } private List<TransferableRef> resolveRefs() { List<TransferableRef> refsToPush = new ArrayList<>(); List<String> refSpecsArg = this.refSpecs; if (refSpecsArg.isEmpty()) { if (all) { ImmutableSet<Ref> localRefs = getLocalRefs(); for (Ref ref : localRefs) { String localRef = ref.getName(); String remoteRef = null; boolean forceUpdate = false; boolean delete = false; refsToPush.add(new TransferableRef(localRef, remoteRef, forceUpdate, delete)); } } else { // push current branch Ref currentBranch = resolveHeadTarget(); String localRef = currentBranch.getName(); String remoteRef = null; boolean forceUpdate = false; boolean delete = false; refsToPush.add(new TransferableRef(localRef, remoteRef, forceUpdate, delete)); } } else { for (String refspec : refSpecsArg) { String[] refs = refspec.split(":"); if (refs.length == 0) { refs = new String[2]; refs[0] = resolveHeadTarget().getName(); refs[1] = null; } else { if (refs[0].startsWith("+")) { refs[0] = refs[0].substring(1); } for (int i = 0; i < refs.length; i++) { if (refs[i].trim().isEmpty()) { refs[i] = null; } } } checkArgument(refs.length < 3, "Invalid refspec, please use [+][<localref>][:][<remoteref>]."); boolean force = refspec.startsWith("+"); String localrefspec = refs[0]; boolean delete = localrefspec == null; String remoterefspec = refs[refs.length == 2 ? 1 : 0]; refsToPush.add(new TransferableRef(localrefspec, remoterefspec, force, delete)); } } return refsToPush; } private Remote resolveRemote(String remoteName) { Optional<Remote> pushRemote = command(RemoteResolve.class).setName(remoteName).call(); checkArgument(pushRemote.isPresent(), "Remote could not be resolved."); return pushRemote.get(); } private ImmutableSet<Ref> getLocalRefs() { Predicate<Ref> filter = new Predicate<Ref>() { final String prefix = Ref.HEADS_PREFIX; @Override public boolean apply(Ref input) { return !(input instanceof SymRef) && input.getName().startsWith(prefix); } }; ImmutableSet<Ref> localRefs = command(ForEachRef.class).setFilter(filter).call(); return localRefs; } private Ref resolveHeadTarget() { final Optional<Ref> currHead = refParse(Ref.HEAD); checkState(currHead.isPresent(), "Repository has no HEAD, can't push."); checkState(currHead.get() instanceof SymRef, "Can't push from detached HEAD"); final Optional<Ref> headTarget = refParse(((SymRef) currHead.get()).getTarget()); checkState(headTarget.isPresent()); return headTarget.get(); } private Optional<Ref> refParse(String refSpec) { return command(RefParse.class).setName(refSpec).call(); } }