/* 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.HashSet; import java.util.Iterator; import java.util.Set; 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.UpdateSymRef; import org.locationtech.geogig.api.plumbing.diff.DiffEntry; import org.locationtech.geogig.di.CanRunDuringConflict; import org.locationtech.geogig.repository.Repository; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.collect.Iterators; /** * * Reset current HEAD to the specified state. * */ @CanRunDuringConflict public class ResetOp extends AbstractGeoGigOp<Boolean> { /** * Enumeration of the possible reset modes. */ public enum ResetMode { SOFT, MIXED, HARD, MERGE, KEEP, NONE }; private Supplier<ObjectId> commit; private ResetMode mode = ResetMode.NONE; private Set<String> patterns = new HashSet<String>(); /** * Sets the reset mode. * * @param mode the reset mode * @return {@code this} */ public ResetOp setMode(ResetMode mode) { this.mode = mode; return this; } /** * Sets the base commit. * * @param commit a supplier for the {@link ObjectId id} of the commit * @return {@code this} */ public ResetOp setCommit(final Supplier<ObjectId> commit) { this.commit = commit; return this; } /** * Adds a pattern. * * @param pattern a regular expression to match what content to be reset * @return {@code this} */ public ResetOp addPattern(final String pattern) { patterns.add(pattern); return this; } /** * Executes the reset operation. * * @return always {@code true} */ @Override protected Boolean _call() { Preconditions.checkState(!(patterns.size() > 0 && mode != ResetMode.NONE), "Ambiguous call, cannot specify paths and reset mode."); final Optional<Ref> currHead = command(RefParse.class).setName(Ref.HEAD).call(); Preconditions.checkState(currHead.isPresent(), "Repository has no HEAD, can't reset."); Preconditions .checkState(currHead.get() instanceof SymRef, "Can't reset from detached HEAD"); final SymRef headRef = (SymRef) currHead.get(); final String currentBranch = headRef.getTarget(); if (commit == null) { commit = Suppliers.ofInstance(currHead.get().getObjectId()); } Preconditions.checkState(!ObjectId.NULL.equals(commit.get()), "Commit could not be resolved."); Repository repository = repository(); RevCommit oldCommit = repository.getCommit(commit.get()); if (patterns.size() > 0) { for (String pattern : patterns) { DiffTree diffOp = command(DiffTree.class) .setOldTree(repository.index().getTree().getId()) .setNewTree(oldCommit.getTreeId()).setPathFilter(pattern); Iterator<DiffEntry> diff = diffOp.call(); final long numChanges = Iterators.size(diffOp.call()); if (numChanges == 0) { // We are reseting to the current version, so there is nothing to do. However, // if we are in a conflict state, the conflict should be removed and calling // stage() will not do it, so we do it here repository.stagingDatabase().removeConflict(null, pattern); } else { repository.index().stage(subProgress((1.f / patterns.size()) * 100.f), diff, numChanges); } } } else { if (mode == ResetMode.NONE) { mode = ResetMode.MIXED; } switch (mode) { case HARD: // Update the index and the working tree to the target tree index().updateStageHead(oldCommit.getTreeId()); workingTree().updateWorkHead(oldCommit.getTreeId()); break; case SOFT: // Do not update index or working tree to the target tree break; case MIXED: // Only update the index to the target tree index().updateStageHead(oldCommit.getTreeId()); break; default: throw new UnsupportedOperationException("Unsupported reset mode."); } // Update branch head to the specified commit command(UpdateRef.class).setName(currentBranch).setNewValue(oldCommit.getId()).call(); command(UpdateSymRef.class).setName(Ref.HEAD).setNewValue(currentBranch).call(); Optional<Ref> ref = command(RefParse.class).setName(Ref.MERGE_HEAD).call(); if (ref.isPresent()) { command(UpdateRef.class).setName(Ref.MERGE_HEAD).setDelete(true).call(); } } return true; } }