/* 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.cli.porcelain;
import java.io.IOException;
import java.util.Iterator;
import jline.console.ConsoleReader;
import org.fusesource.jansi.Ansi;
import org.fusesource.jansi.Ansi.Color;
import org.locationtech.geogig.api.GeoGIG;
import org.locationtech.geogig.api.NodeRef;
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.diff.DiffEntry;
import org.locationtech.geogig.api.plumbing.diff.DiffEntry.ChangeType;
import org.locationtech.geogig.api.plumbing.merge.Conflict;
import org.locationtech.geogig.api.porcelain.StatusOp;
import org.locationtech.geogig.api.porcelain.StatusOp.StatusSummary;
import org.locationtech.geogig.cli.AbstractCommand;
import org.locationtech.geogig.cli.CLICommand;
import org.locationtech.geogig.cli.GeogigCLI;
import org.locationtech.geogig.cli.annotation.ReadOnly;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.base.Optional;
/**
* Displays features that have differences between the index and the current HEAD commit and
* features that have differences between the working tree and the index file. The first are what
* you would commit by running geogig commit; the second are what you could commit by running geogig
* add before running geogig commit.
* <p>
* Usage:
* <ul>
* <li> {@code geogig status [<options>]}
* </ul>
*
* @see Commit
* @see Add
*/
@ReadOnly
@Parameters(commandNames = "status", commandDescription = "Show the working tree status")
public class Status extends AbstractCommand implements CLICommand {
@Parameter(names = "--limit", description = "Limit number of displayed changes. Must be >= 0.")
private Integer limit = 50;
@Parameter(names = "--all", description = "Force listing all changes (overrides limit).")
private boolean all = false;
/**
* Executes the status command using the provided options.
*/
@Override
public void runInternal(GeogigCLI cli) throws IOException {
checkParameter(limit >= 0, "Limit must be 0 or greater.");
ConsoleReader console = cli.getConsole();
GeoGIG geogig = cli.getGeogig();
StatusOp op = geogig.command(StatusOp.class);
StatusSummary summary = op.call();
final Optional<Ref> currHead = geogig.command(RefParse.class).setName(Ref.HEAD).call();
checkParameter(currHead.isPresent(), "Repository has no HEAD.");
if (currHead.get() instanceof SymRef) {
final SymRef headRef = (SymRef) currHead.get();
console.println("# On branch " + Ref.localName(headRef.getTarget()));
} else {
console.println("# Not currently on any branch.");
}
print(console, summary);
}
private void print(ConsoleReader console, StatusSummary summary) throws IOException {
long countStaged = summary.getCountStaged();
long countUnstaged = summary.getCountUnstaged();
int countConflicted = summary.getCountConflicts();
if (countStaged + countUnstaged + countConflicted == 0) {
console.println("nothing to commit (working directory clean)");
}
if (countStaged > 0) {
console.println("# Changes to be committed:");
console.println("# (use \"geogig reset HEAD <path/to/fid>...\" to unstage)");
console.println("#");
print(console, summary.getStaged().get(), Color.GREEN, countStaged);
console.println("#");
}
if (countConflicted > 0) {
console.println("# Unmerged paths:");
console.println("# (use \"geogig add/rm <path/to/fid>...\" as appropriate to mark resolution");
console.println("#");
printUnmerged(console, summary.getConflicts().get(), Color.RED, countConflicted);
}
if (countUnstaged > 0) {
console.println("# Changes not staged for commit:");
console.println("# (use \"geogig add <path/to/fid>...\" to update what will be committed");
console.println("# (use \"geogig checkout -- <path/to/fid>...\" to discard changes in working directory");
console.println("#");
print(console, summary.getUnstaged().get(), Color.RED, countUnstaged);
}
}
/**
* Prints the list of changes using the specified options
*
* @param console the output console
* @param changes an iterator of differences to print
* @param color the color to use for the changes if color use is enabled
* @param total the total number of changes
* @throws IOException
* @see DiffEntry
*/
private void print(final ConsoleReader console, final Iterator<DiffEntry> changes,
final Color color, final long total) throws IOException {
final int limit = all || this.limit == null ? Integer.MAX_VALUE : this.limit.intValue();
StringBuilder sb = new StringBuilder();
Ansi ansi = newAnsi(console.getTerminal(), sb);
DiffEntry entry;
ChangeType type;
String path;
int cnt = 0;
if (limit > 0) {
Iterator<DiffEntry> changesIterator = changes;
while (changesIterator.hasNext() && cnt < limit) {
++cnt;
entry = changesIterator.next();
type = entry.changeType();
path = formatPath(entry);
sb.setLength(0);
ansi.a("# ").fg(color).a(type.toString().toLowerCase()).a(" ").a(path)
.reset();
console.println(ansi.toString());
}
}
sb.setLength(0);
ansi.a("# ").a(String.format("%,d", total)).a(" total.");
console.println(ansi.toString());
}
private void printUnmerged(final ConsoleReader console, final Iterable<Conflict> conflicts,
final Color color, final int total) throws IOException {
StringBuilder sb = new StringBuilder();
Ansi ansi = newAnsi(console.getTerminal(), sb);
String path;
for (Conflict c : conflicts) {
path = c.getPath();
sb.setLength(0);
ansi.a("# ").fg(color).a("unmerged").a(" ").a(path).reset();
console.println(ansi.toString());
}
sb.setLength(0);
ansi.a("# ").a(String.format("%,d", total)).a(" total.");
console.println(ansi.toString());
}
/**
* Formats a DiffEntry for display
*
* @param entry the DiffEntry to format
* @return the formatted display string
* @see DiffEntry
*/
private String formatPath(DiffEntry entry) {
String path;
NodeRef oldObject = entry.getOldObject();
NodeRef newObject = entry.getNewObject();
if (oldObject == null) {
path = newObject.path();
} else if (newObject == null) {
path = oldObject.path();
} else {
if (oldObject.path().equals(newObject.path())) {
path = oldObject.path();
} else {
path = oldObject.path() + " -> " + newObject.path();
}
}
return path;
}
}