/* 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:
* Gabriel Roldan (Boundless) - initial implementation
*/
package org.locationtech.geogig.api.porcelain;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;
import org.locationtech.geogig.api.AbstractGeoGigOp;
import org.locationtech.geogig.api.ObjectId;
import org.locationtech.geogig.api.ProgressListener;
import org.locationtech.geogig.api.Ref;
import org.locationtech.geogig.api.RevObject.TYPE;
import org.locationtech.geogig.api.RevTree;
import org.locationtech.geogig.api.plumbing.RevParse;
import org.locationtech.geogig.api.plumbing.UpdateRef;
import org.locationtech.geogig.api.plumbing.diff.DiffEntry;
import org.locationtech.geogig.api.plumbing.merge.Conflict;
import org.locationtech.geogig.di.CanRunDuringConflict;
import org.locationtech.geogig.repository.StagingArea;
import org.locationtech.geogig.repository.WorkingTree;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterators;
/**
* Manipulates the index (staging area) by setting the unstaged changes that match this operation
* criteria as staged.
*
* @see WorkingTree
* @see StagingArea
*/
@CanRunDuringConflict
public class AddOp extends AbstractGeoGigOp<WorkingTree> {
private Set<String> patterns;
private boolean updateOnly;
/**
* Constructs a new {@code AddOp} with the given parameters.
*/
public AddOp() {
patterns = new HashSet<String>();
}
/**
* Executes the add command, staging unstaged changes that match the provided patterns.
*
* @return the modified {@link WorkingTree working tree}.
*/
@Override
protected WorkingTree _call() {
// this is add all, TODO: implement partial adds
String path = null;
if (patterns.size() == 1) {
path = patterns.iterator().next();
}
stage(getProgressListener(), path);
return workingTree();
}
/**
* Stages the object addressed by {@code pathFilter}, or all unstaged objects if
* {@code pathFilter == null} to be added, if it is/they are marked as an unstaged change. Does
* nothing otherwise.
* <p>
* To stage changes not yet staged, a diff tree walk is performed using the current staged
* {@link RevTree} as the old object and the current unstaged {@link RevTree} as the new object.
* Then all the differences are traversed and the staged tree is updated with the changes
* reported by the diff walk (neat).
* </p>
*
* @param progress the progress listener for this process
* @param pathFilter the filter to use
*/
public void stage(final ProgressListener progress, final @Nullable String pathFilter) {
// short cut for the case where the index is empty and we're staging all changes in the
// working tree, so it's just a matter of updating the index ref to working tree RevTree id
if (null == pathFilter && !index().getStaged(null).hasNext() && !updateOnly
&& index().countConflicted(null) == 0) {
progress.started();
Optional<ObjectId> workHead = command(RevParse.class).setRefSpec(Ref.WORK_HEAD).call();
if (workHead.isPresent()) {
command(UpdateRef.class).setName(Ref.STAGE_HEAD).setNewValue(workHead.get()).call();
}
progress.setProgress(100f);
progress.complete();
return;
}
final long numChanges = workingTree().countUnstaged(pathFilter).count();
Iterator<DiffEntry> unstaged = workingTree().getUnstaged(pathFilter);
if (updateOnly) {
unstaged = Iterators.filter(unstaged, new Predicate<DiffEntry>() {
@Override
public boolean apply(@Nullable DiffEntry input) {
// HACK: avoid reporting changed trees
if (input.isChange() && input.getOldObject().getType().equals(TYPE.TREE)) {
return false;
}
return input.getOldObject() != null;
}
});
}
index().stage(progress, unstaged, numChanges);
List<Conflict> conflicts = index().getConflicted(pathFilter);
for (Conflict conflict : conflicts) {
// if we are staging unmerged files, the conflict should get solved. However, if the
// working index object is the same as the staging area one (for instance, after running
// checkout --ours), it will not be reported by the getUnstaged method. We solve that
// here.
stagingDatabase().removeConflict(null, conflict.getPath());
}
}
/**
* @param pattern a regular expression to match what content to be staged
* @return {@code this}
*/
public AddOp addPattern(final String pattern) {
patterns.add(pattern);
return this;
}
/**
* @param updateOnly if {@code true}, only add already tracked features (either for modification
* or deletion), but do not stage any newly added one.
* @return {@code this}
*/
public AddOp setUpdateOnly(final boolean updateOnly) {
this.updateOnly = updateOnly;
return this;
}
}