Package org.locationtech.geogig.cli.porcelain

Source Code of org.locationtech.geogig.cli.porcelain.Log$LogEntryPrinter

/* 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.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import jline.console.ConsoleReader;

import org.fusesource.jansi.Ansi;
import org.fusesource.jansi.Ansi.Color;
import org.geotools.util.Range;
import org.locationtech.geogig.api.GeoGIG;
import org.locationtech.geogig.api.ObjectId;
import org.locationtech.geogig.api.Platform;
import org.locationtech.geogig.api.Ref;
import org.locationtech.geogig.api.RevCommit;
import org.locationtech.geogig.api.RevPerson;
import org.locationtech.geogig.api.SymRef;
import org.locationtech.geogig.api.plumbing.ForEachRef;
import org.locationtech.geogig.api.plumbing.ParseTimestamp;
import org.locationtech.geogig.api.plumbing.RefParse;
import org.locationtech.geogig.api.plumbing.RevParse;
import org.locationtech.geogig.api.plumbing.diff.DiffEntry;
import org.locationtech.geogig.api.porcelain.DiffOp;
import org.locationtech.geogig.api.porcelain.LogOp;
import org.locationtech.geogig.cli.AbstractCommand;
import org.locationtech.geogig.cli.CLICommand;
import org.locationtech.geogig.cli.GeogigCLI;
import org.locationtech.geogig.cli.InvalidParameterException;
import org.locationtech.geogig.cli.annotation.ReadOnly;

import com.beust.jcommander.Parameters;
import com.beust.jcommander.ParametersDelegate;
import com.beust.jcommander.internal.Lists;
import com.beust.jcommander.internal.Maps;
import com.google.common.base.Optional;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;

/**
* Shows the commit logs.
* <p>
* CLI proxy for {@link org.locationtech.geogig.api.porcelain.LogOp}
* <p>
* Usage:
* <ul>
* <li> {@code geogig log [<options>]}
* </ul>
*
* @see org.locationtech.geogig.api.porcelain.LogOp
*/
@ReadOnly
@Parameters(commandNames = "log", commandDescription = "Show commit logs")
public class Log extends AbstractCommand implements CLICommand {

    public enum LOG_DETAIL {
        SUMMARY, NAMES_ONLY, STATS, NOTHING
    };

    @ParametersDelegate
    public final LogArgs args = new LogArgs();

    private Map<ObjectId, String> refs;

    private GeoGIG geogig;

    private ConsoleReader console;

    /**
     * Executes the log command using the provided options.
     *
     * @param cli
     * @throws IOException
     * @see org.locationtech.geogig.cli.AbstractCommand#runInternal(org.locationtech.geogig.cli.GeogigCLI)
     */
    @Override
    public void runInternal(GeogigCLI cli) throws IOException {
        checkParameter(!(args.summary && args.oneline),
                "--summary and --oneline cannot be used together");
        checkParameter(!(args.stats && args.oneline),
                "--stats and --oneline cannot be used together");
        checkParameter(!(args.stats && args.oneline),
                "--name-only and --oneline cannot be used together");

        geogig = cli.getGeogig();

        LogOp op = geogig.command(LogOp.class).setTopoOrder(args.topo)
                .setFirstParentOnly(args.firstParent);

        refs = Maps.newHashMap();
        if (args.decoration) {
            Optional<Ref> head = geogig.command(RefParse.class).setName(Ref.HEAD).call();
            refs.put(head.get().getObjectId(), Ref.HEAD);
            ImmutableSet<Ref> set = geogig.command(ForEachRef.class)
                    .setPrefixFilter(Ref.REFS_PREFIX).call();
            for (Ref ref : set) {
                ObjectId id = ref.getObjectId();
                if (refs.containsKey(id)) {
                    refs.put(id, refs.get(id) + ", " + ref.getName());
                } else {
                    refs.put(id, ref.getName());
                }
            }
        }
        if (args.all) {
            ImmutableSet<Ref> refs = geogig.command(ForEachRef.class)
                    .setPrefixFilter(Ref.REFS_PREFIX).call();
            List<ObjectId> list = Lists.newArrayList();
            for (Ref ref : refs) {
                list.add(ref.getObjectId());
            }
            Optional<Ref> head = geogig.command(RefParse.class).setName(Ref.HEAD).call();
            if (head.isPresent()) {
                Ref ref = head.get();
                if (ref instanceof SymRef) {
                    ObjectId id = ref.getObjectId();
                    list.remove(id);
                    list.add(id);// put the HEAD ref in the last position, to give it preference
                }
            }
            for (ObjectId id : list) {
                op.addCommit(id);
            }
        } else if (args.branch != null) {
            Optional<Ref> obj = geogig.command(RefParse.class).setName(args.branch).call();
            checkParameter(obj.isPresent(), "Wrong branch name: " + args.branch);
            op.addCommit(obj.get().getObjectId());
        }

        if (args.author != null && !args.author.isEmpty()) {
            op.setAuthor(args.author);
        }
        if (args.committer != null && !args.committer.isEmpty()) {
            op.setCommiter(args.committer);
        }
        if (args.skip != null) {
            op.setSkip(args.skip.intValue());
        }
        if (args.limit != null) {
            op.setLimit(args.limit.intValue());
        }
        if (args.since != null || args.until != null) {
            Date since = new Date(0);
            Date until = new Date();
            if (args.since != null) {
                since = new Date(geogig.command(ParseTimestamp.class).setString(args.since).call());
            }
            if (args.until != null) {
                until = new Date(geogig.command(ParseTimestamp.class).setString(args.until).call());
                if (args.all) {
                    throw new InvalidParameterException(
                            "Cannot specify 'until' commit when listing all branches");
                }
            }
            op.setTimeRange(new Range<Date>(Date.class, since, until));
        }
        if (!args.sinceUntilPaths.isEmpty()) {
            List<String> sinceUntil = ImmutableList.copyOf((Splitter.on("..")
                    .split(args.sinceUntilPaths.get(0))));
            checkParameter(sinceUntil.size() == 1 || sinceUntil.size() == 2,
                    "Invalid refSpec format, expected [<until>]|[<since>..<until>]: %s",
                    args.sinceUntilPaths.get(0));

            String sinceRefSpec;
            String untilRefSpec;
            if (sinceUntil.size() == 1) {
                // just until was given
                sinceRefSpec = null;
                untilRefSpec = sinceUntil.get(0);
            } else {
                sinceRefSpec = sinceUntil.get(0);
                untilRefSpec = sinceUntil.get(1);
            }
            if (sinceRefSpec != null) {
                Optional<ObjectId> since;
                since = geogig.command(RevParse.class).setRefSpec(sinceRefSpec).call();
                checkParameter(since.isPresent(), "Object not found '%s'", sinceRefSpec);
                op.setSince(since.get());
            }
            if (untilRefSpec != null) {
                if (args.all) {
                    throw new InvalidParameterException(
                            "Cannot specify 'until' commit when listing all branches");
                }
                Optional<ObjectId> until;
                until = geogig.command(RevParse.class).setRefSpec(untilRefSpec).call();
                checkParameter(until.isPresent(), "Object not found '%s'", sinceRefSpec);
                op.setUntil(until.get());
            }
        }
        if (!args.pathNames.isEmpty()) {
            for (String s : args.pathNames) {
                op.addPath(s);
            }
        }
        Iterator<RevCommit> log = op.call();
        this.console = cli.getConsole();
        if (!log.hasNext()) {
            console.println("No commits to show");
            console.flush();
            return;
        }

        LogEntryPrinter printer;
        if (args.oneline) {
            printer = new OneLineConverter();
        } else {
            LOG_DETAIL detail;
            if (args.summary) {
                detail = LOG_DETAIL.SUMMARY;
            } else if (args.names) {
                detail = LOG_DETAIL.NAMES_ONLY;
            } else if (args.stats) {
                detail = LOG_DETAIL.STATS;
            } else {
                detail = LOG_DETAIL.NOTHING;
            }

            printer = new StandardConverter(detail, geogig.getPlatform());
        }

        while (log.hasNext()) {
            printer.print(log.next());
            console.flush();
        }
    }

    interface LogEntryPrinter {

        /**
         * @param geogig
         * @param console
         * @param entry
         * @throws IOException
         */
        void print(RevCommit commit) throws IOException;

    }

    private class OneLineConverter implements LogEntryPrinter {

        @Override
        public void print(RevCommit commit) throws IOException {
            Ansi ansi = newAnsi(console.getTerminal());
            ansi.fg(Color.YELLOW).a(getIdAsString(commit.getId())).reset();
            String message = Strings.nullToEmpty(commit.getMessage());
            String title = Splitter.on('\n').split(message).iterator().next();
            ansi.a(" ").a(title);
            console.println(ansi.toString());
        }

    }

    private class StandardConverter implements LogEntryPrinter {

        private SimpleDateFormat DATE_FORMAT;

        private long now;

        private LOG_DETAIL detail;

        public StandardConverter(final LOG_DETAIL detail, final Platform platform) {
            now = platform.currentTimeMillis();
            DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
            this.detail = detail;
        }

        @Override
        public void print(RevCommit commit) throws IOException {
            Ansi ansi = newAnsi(console.getTerminal());

            ansi.a("Commit:  ").fg(Color.YELLOW).a(getIdAsString(commit.getId())).reset().newline();
            if (commit.getParentIds().size() > 1) {
                ansi.a("Merge: ");
                for (ObjectId parent : commit.getParentIds()) {
                    ansi.a(parent.toString().substring(0, 7)).a(" ");
                }
                ansi.newline();
            }
            ansi.a("Author:  ").fg(Color.GREEN).a(formatPerson(commit.getAuthor())).reset()
                    .newline();

            final long timestamp = commit.getAuthor().getTimestamp();

            int timeZoneOffset = commit.getAuthor().getTimeZoneOffset();
            if (args.utcDateFormat) {
                timeZoneOffset = 0;
            }
            String friendlyString = estimateSince(now, timestamp);
            DATE_FORMAT.getCalendar().getTimeZone().setRawOffset(timeZoneOffset);
            String formattedDate = DATE_FORMAT.format(timestamp);

            ansi.a("Date:    (").fg(Color.RED).a(friendlyString).reset().a(") ").a(formattedDate)
                    .newline();
            ansi.a("Subject: ").a(commit.getMessage()).newline();
            if ((detail.equals(LOG_DETAIL.NAMES_ONLY)) && commit.getParentIds().size() == 1) {
                ansi.a("Affected paths:").newline();
                Iterator<DiffEntry> diff = geogig.command(DiffOp.class)
                        .setOldVersion(commit.parentN(0).get()).setNewVersion(commit.getId())
                        .call();
                DiffEntry diffEntry;
                while (diff.hasNext()) {
                    diffEntry = diff.next();
                    ansi.a("\t" + diffEntry.newPath()).newline();
                }
            }
            if (detail.equals(LOG_DETAIL.STATS) && commit.getParentIds().size() == 1) {

                Iterator<DiffEntry> diff = geogig.command(DiffOp.class)
                        .setOldVersion(commit.parentN(0).get()).setNewVersion(commit.getId())
                        .call();
                int adds = 0, deletes = 0, changes = 0;
                DiffEntry diffEntry;
                while (diff.hasNext()) {
                    diffEntry = diff.next();
                    switch (diffEntry.changeType()) {
                    case ADDED:
                        ++adds;
                        break;
                    case REMOVED:
                        ++deletes;
                        break;
                    case MODIFIED:
                        ++changes;
                        break;
                    }
                }

                ansi.a("Changes:");
                ansi.fg(Color.GREEN).a(adds).reset().a(" features added, ").fg(Color.YELLOW)
                        .a(changes).reset().a(" changed, ").fg(Color.RED).a(deletes).reset()
                        .a(" deleted.").reset().newline();
            }

            console.println(ansi.toString());
            if (detail.equals(LOG_DETAIL.SUMMARY) && commit.getParentIds().size() == 1) {
                ansi.a("Changes:").newline();
                Iterator<DiffEntry> diff = geogig.command(DiffOp.class)
                        .setOldVersion(commit.parentN(0).get()).setNewVersion(commit.getId())
                        .call();
                DiffEntry diffEntry;
                while (diff.hasNext()) {
                    diffEntry = diff.next();
                    if (detail.equals(LOG_DETAIL.SUMMARY)) {
                        new FullDiffPrinter(true, false).print(geogig, console, diffEntry);
                    }

                }
            }
        }
    }

    /**
     * Converts a RevPerson for into a readable string.
     *
     * @param person the person to format.
     * @return the formatted string
     * @see RevPerson
     */
    private String formatPerson(RevPerson person) {
        StringBuilder sb = new StringBuilder();
        sb.append(person.getName().or("<name not set>"));

        if (person.getEmail().isPresent()) {
            sb.append(" <").append(person.getEmail().get()).append('>');
        }
        return sb.toString();
    }

    /**
     * Converts a timestamp into a readable string that represents the rough time since that
     * timestamp.
     *
     * @param now
     * @param timestamp
     * @return
     */
    private String estimateSince(final long now, long timestamp) {
        long diff = now - timestamp;
        final long seconds = 1000;
        final long minutes = seconds * 60;
        final long hours = minutes * 60;
        final long days = hours * 24;
        final long weeks = days * 7;
        final long months = days * 30;
        final long years = days * 365;

        if (diff > years) {
            return diff / years + " years ago";
        }
        if (diff > months) {
            return diff / months + " months ago";
        }
        if (diff > weeks) {
            return diff / weeks + " weeks ago";
        }
        if (diff > days) {
            return diff / days + " days ago";
        }
        if (diff > hours) {
            return diff / hours + " hours ago";
        }
        if (diff > minutes) {
            return diff / minutes + " minutes ago";
        }
        if (diff > seconds) {
            return diff / seconds + " seconds ago";
        }
        return "just now";
    }

    /**
     * Returns an Id as a string, decorating or abbreviating it if needed
     *
     * @param id
     * @return
     */
    private String getIdAsString(ObjectId id) {
        StringBuilder sb = new StringBuilder();
        if (args.abbrev) {
            sb.append(id.toString().substring(0, 7));
        } else {
            sb.append(id.toString());
        }
        if (refs.containsKey(id)) {
            sb.append(" (");
            sb.append(refs.get(id));
            sb.append(')');
        }

        return sb.toString();
    }
}
TOP

Related Classes of org.locationtech.geogig.cli.porcelain.Log$LogEntryPrinter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.