/* 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 java.util.Iterator; import org.locationtech.geogig.api.AbstractGeoGigOp; import org.locationtech.geogig.api.ObjectId; import org.locationtech.geogig.api.Ref; import org.locationtech.geogig.api.RevCommit; import org.locationtech.geogig.api.SymRef; import org.locationtech.geogig.api.plumbing.DiffTree; import org.locationtech.geogig.api.plumbing.RefParse; import org.locationtech.geogig.api.plumbing.UpdateRef; import org.locationtech.geogig.api.plumbing.WriteTree2; import org.locationtech.geogig.api.plumbing.diff.DiffEntry; import org.locationtech.geogig.api.plumbing.merge.Conflict; import org.locationtech.geogig.api.plumbing.merge.ConflictsWriteOp; import org.locationtech.geogig.api.plumbing.merge.MergeScenarioReport; import org.locationtech.geogig.api.plumbing.merge.ReportCommitConflictsOp; import org.locationtech.geogig.repository.Repository; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Supplier; /** * * Apply the changes introduced by an existing commit. * <p> * */ public class CherryPickOp extends AbstractGeoGigOp<RevCommit> { private ObjectId commit; /** * Sets the commit to replay commits onto. * * @param onto a supplier for the commit id * @return {@code this} */ public CherryPickOp setCommit(final Supplier<ObjectId> commit) { Preconditions.checkNotNull(commit); this.commit = commit.get(); return this; } /** * Executes the cherry pick operation. * * @return RevCommit the new commit with the changes from the cherry-picked commit */ @Override protected RevCommit _call() { final Repository repository = repository(); final Optional<Ref> currHead = command(RefParse.class).setName(Ref.HEAD).call(); Preconditions .checkState(currHead.isPresent(), "Repository has no HEAD, can't cherry pick."); Preconditions.checkState(currHead.get() instanceof SymRef, "Can't cherry pick from detached HEAD"); final SymRef headRef = (SymRef) currHead.get(); Preconditions.checkState(index().isClean() && workingTree().isClean(), "You must have a clean working tree and index to perform a cherry pick."); getProgressListener().started(); Preconditions.checkArgument(repository.commitExists(commit), "Commit could not be resolved: %s.", commit); RevCommit commitToApply = repository.getCommit(commit); ObjectId headId = headRef.getObjectId(); ObjectId parentCommitId = ObjectId.NULL; if (commitToApply.getParentIds().size() > 0) { parentCommitId = commitToApply.getParentIds().get(0); } ObjectId parentTreeId = ObjectId.NULL; if (repository.commitExists(parentCommitId)) { parentTreeId = repository.getCommit(parentCommitId).getTreeId(); } // get changes Iterator<DiffEntry> diff = command(DiffTree.class).setOldTree(parentTreeId) .setNewTree(commitToApply.getTreeId()).setReportTrees(true).call(); // see if there are conflicts MergeScenarioReport report = command(ReportCommitConflictsOp.class) .setCommit(commitToApply).call(); if (report.getConflicts().isEmpty()) { // stage changes index().stage(getProgressListener(), diff, 0); // write new tree ObjectId newTreeId = command(WriteTree2.class).call(); RevCommit newCommit = command(CommitOp.class).setCommit(commitToApply).call(); repository.workingTree().updateWorkHead(newTreeId); repository.index().updateStageHead(newTreeId); getProgressListener().complete(); return newCommit; } else { Iterator<DiffEntry> unconflicted = report.getUnconflicted().iterator(); // stage changes index().stage(getProgressListener(), unconflicted, 0); workingTree().updateWorkHead(index().getTree().getId()); command(UpdateRef.class).setName(Ref.CHERRY_PICK_HEAD).setNewValue(commit).call(); command(UpdateRef.class).setName(Ref.ORIG_HEAD).setNewValue(headId).call(); command(ConflictsWriteOp.class).setConflicts(report.getConflicts()).call(); StringBuilder msg = new StringBuilder(); msg.append("error: could not apply "); msg.append(commitToApply.getId().toString().substring(0, 7)); msg.append(" " + commitToApply.getMessage()); for (Conflict conflict : report.getConflicts()) { msg.append("\t" + conflict.getPath() + "\n"); } StringBuilder sb = new StringBuilder(); for (Conflict conflict : report.getConflicts()) { sb.append("CONFLICT: conflict in " + conflict.getPath() + "\n"); } sb.append("Fix conflicts and then commit the result using 'geogig commit -c " + commitToApply.getId().toString().substring(0, 7) + "\n"); throw new IllegalStateException(sb.toString()); } } }