Package org.locationtech.geogig.cli

Source Code of org.locationtech.geogig.cli.GeogigCLI

/* 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;

import static com.google.common.base.Preconditions.checkNotNull;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.ServiceLoader;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;

import javax.annotation.Nullable;

import jline.console.ConsoleReader;
import jline.console.CursorBuffer;

import org.locationtech.geogig.api.Context;
import org.locationtech.geogig.api.DefaultPlatform;
import org.locationtech.geogig.api.DefaultProgressListener;
import org.locationtech.geogig.api.GeoGIG;
import org.locationtech.geogig.api.GlobalContextBuilder;
import org.locationtech.geogig.api.Platform;
import org.locationtech.geogig.api.ProgressListener;
import org.locationtech.geogig.api.hooks.CannotRunGeogigOperationException;
import org.locationtech.geogig.api.plumbing.ResolveGeogigDir;
import org.locationtech.geogig.api.porcelain.ConfigException;
import org.locationtech.geogig.api.porcelain.ConfigGet;
import org.locationtech.geogig.cli.annotation.ObjectDatabaseReadOnly;
import org.locationtech.geogig.cli.annotation.ReadOnly;
import org.locationtech.geogig.cli.annotation.RemotesReadOnly;
import org.locationtech.geogig.cli.annotation.RequiresRepository;
import org.locationtech.geogig.cli.annotation.StagingDatabaseReadOnly;
import org.locationtech.geogig.repository.Hints;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.beust.jcommander.JCommander;
import com.beust.jcommander.ParameterException;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.inject.Binding;
import com.google.inject.Guice;
import com.google.inject.Key;
import com.google.inject.Module;

//import org.python.core.exceptions;

/**
* Command Line Interface for geogig.
* <p>
* Looks up and executes {@link CLICommand} implementations provided by any {@link Guice}
* {@link Module} that implements {@link CLIModule} declared in any classpath's
* {@code META-INF/services/com.google.inject.Module} file.
*/
public class GeogigCLI {

    private static final Logger LOGGER = LoggerFactory.getLogger(GeogigCLI.class);

    static {
        GlobalContextBuilder.builder = new CLIContextBuilder();
    }

    private static final com.google.inject.Injector commandsInjector;
    static {
        Iterable<CLIModule> plugins = ServiceLoader.load(CLIModule.class);
        commandsInjector = Guice.createInjector(plugins);
    }

    private Context geogigInjector;

    private Platform platform;

    private GeoGIG geogig;

    private final GeoGIG providedGeogig;

    private final ConsoleReader consoleReader;

    protected ProgressListener progressListener;

    private boolean exitOnFinish = true;

    private static final Hints READ_WRITE = Hints.readWrite();

    private Hints hints = READ_WRITE;

    private boolean progressListenerDisabled;

    /**
     * Construct a GeogigCLI with the given console reader.
     *
     * @param consoleReader
     */
    public GeogigCLI(final ConsoleReader consoleReader) {
        this(null, consoleReader);
    }

    /**
     * Constructor to use the provided {@code GeoGIG} instance and never try to close it.
     */
    public GeogigCLI(final GeoGIG geogig, final ConsoleReader consoleReader) {
        this.consoleReader = consoleReader;
        this.platform = new DefaultPlatform();
        this.providedGeogig = geogig;
    }

    /**
     * @return the platform being used by the geogig command line interface.
     * @see Platform
     */
    public Platform getPlatform() {
        return platform;
    }

    /**
     * Sets the platform for the command line interface to use.
     *
     * @param platform the platform to use
     * @see Platform
     */
    public void setPlatform(Platform platform) {
        checkNotNull(platform);
        this.platform = platform;
    }

    public void disableProgressListener() {
        this.progressListenerDisabled = true;
    }

    /**
     * Provides a GeoGIG facade configured for the current repository if inside a repository,
     * {@code null} otherwise.
     * <p>
     * Note the repository is lazily loaded and cached afterwards to simplify the execution of
     * commands or command options that do not need a live repository.
     *
     * @return the GeoGIG facade associated with the current repository, or {@code null} if there's
     *         no repository in the current {@link Platform#pwd() working directory}
     * @see ResolveGeogigDir
     */
    @Nullable
    public synchronized GeoGIG getGeogig() {
        if (providedGeogig != null) {
            return providedGeogig;
        }
        if (geogig == null) {
            GeoGIG geogig = loadRepository();
            setGeogig(geogig);
        }
        return geogig;
    }

    @VisibleForTesting
    public synchronized GeoGIG getGeogig(Hints hints) {
        close();
        GeoGIG geogig = loadRepository(hints);
        setGeogig(geogig);
        return geogig;
    }

    /**
     * Gives the command line interface a GeoGIG facade to use.
     *
     * @param geogig
     */
    public void setGeogig(@Nullable GeoGIG geogig) {
        this.geogig = geogig;
    }

    /**
     * Sets flag controlling whether the cli will call {@link System#exit(int)} when done running
     * the command.
     * <p>
     * Commands should call this method only in cases where the starts a server or creates
     * additional threads.
     * </p>
     *
     * @param exit <tt>true</tt> will cause the cli to exit.
     */
    public void setExitOnFinish(boolean exit) {
        this.exitOnFinish = exit;
    }

    /**
     * Returns flag controlling whether cli will exit on completion.
     *
     * @see {@link #setExitOnFinish(boolean)}
     */
    public boolean isExitOnFinish() {
        return exitOnFinish;
    }

    /**
     * Loads the repository _if_ inside a geogig repository and returns a configured {@link GeoGIG}
     * facade.
     *
     * @return a geogig for the current repository or {@code null} if not inside a geogig repository
     *         directory.
     */
    @Nullable
    private GeoGIG loadRepository() {
        return loadRepository(this.hints);
    }

    @Nullable
    private GeoGIG loadRepository(Hints hints) {
        GeoGIG geogig = newGeoGIG(hints);

        if (geogig.command(ResolveGeogigDir.class).call().isPresent()) {
            geogig.getRepository();
            return geogig;
        }
        geogig.close();

        return null;
    }

    /**
     * Constructs and returns a new read-write geogig facade, which will not be managed by this
     * GeogigCLI instance, so the calling code is responsible for closing/disposing it after usage
     *
     * @return the constructed GeoGIG.
     */
    public GeoGIG newGeoGIG() {
        return newGeoGIG(Hints.readWrite());
    }

    public GeoGIG newGeoGIG(Hints hints) {
        Context inj = newGeogigInjector(hints);
        GeoGIG geogig = new GeoGIG(inj, platform.pwd());
        try {
            geogig.getRepository();
        } catch (Exception e) {
            throw Throwables.propagate(e);
        }
        return geogig;
    }

    /**
     * @return the Guice injector being used by the command line interface. If one hasn't been made,
     *         it will be created.
     */
    public Context getGeogigInjector() {
        return getGeogigInjector(this.hints);
    }

    private Context getGeogigInjector(Hints hints) {
        if (this.geogigInjector == null || !Objects.equal(this.hints, hints)) {
            // System.err.println("Injector hints: " + hints);
            geogigInjector = newGeogigInjector(hints);
        }
        return geogigInjector;
    }

    private Context newGeogigInjector(Hints hints) {
        Context geogigInjector = GlobalContextBuilder.builder.build(hints);
        return geogigInjector;
    }

    /**
     * @return the console reader being used by the command line interface.
     */
    public ConsoleReader getConsole() {
        return consoleReader;
    }

    /**
     * Closes the GeoGIG facade if it exists.
     */
    public synchronized void close() {
        if (providedGeogig != null) {
            return;
        }
        if (geogig != null) {
            geogig.close();
            geogig = null;
        }
        this.hints = READ_WRITE;
        this.geogigInjector = null;
    }

    /**
     * @return true if a command is being ran
     */
    public synchronized boolean isRunning() {
        return geogig != null;
    }

    /**
     * Entry point for the command line interface.
     *
     * @param args
     */
    public static void main(String[] args) {
        Logging.tryConfigureLogging();
        ConsoleReader consoleReader;
        try {
            consoleReader = new ConsoleReader(System.in, System.out);
            // needed for CTRL+C not to let the console broken
            consoleReader.getTerminal().setEchoEnabled(true);
        } catch (Exception e) {
            throw Throwables.propagate(e);
        }

        final GeogigCLI cli = new GeogigCLI(consoleReader);
        addShutdownHook(cli);
        int exitCode = cli.execute(args);

        try {
            cli.close();
        } finally {
            try {
                consoleReader.getTerminal().restore();
            } catch (Exception e) {
                LOGGER.error(e.getMessage(), e);
                exitCode = -1;
            }
            consoleReader.shutdown();
        }

        if (exitCode != 0 || cli.isExitOnFinish()) {
            System.exit(exitCode);
        }
    }

    /**
     * Finds all commands that are bound do the command injector.
     *
     * @return a collection of keys, one for each command
     */
    private Collection<Key<?>> findCommands() {
        Map<Key<?>, Binding<?>> commands = commandsInjector.getBindings();
        return commands.keySet();
    }

    public JCommander newCommandParser() {
        JCommander jc = new JCommander(this);
        jc.setProgramName("geogig");
        for (Key<?> cmd : findCommands()) {
            Object obj = commandsInjector.getInstance(cmd);
            if (obj instanceof CLICommand || obj instanceof CLICommandExtension) {
                jc.addCommand(obj);
            }
        }
        return jc;
    }

    @VisibleForTesting
    public Exception exception;

    /**
     * Processes a command, catching any exceptions and printing their messages to the console.
     *
     * @param args
     * @return 0 for normal exit, -1 if there was an exception.
     */
    public int execute(String... args) {
        exception = null;
        String consoleMessage = null;
        boolean printError = true;
        try {
            executeInternal(args);
            return 0;
        } catch (ParameterException paramParseException) {
            exception = paramParseException;
            consoleMessage = paramParseException.getMessage() + ". See geogig --help";

        } catch (InvalidParameterException paramValidationError) {
            exception = paramValidationError;
            consoleMessage = paramValidationError.getMessage();

        } catch (CannotRunGeogigOperationException cannotRun) {

            consoleMessage = cannotRun.getMessage();

        } catch (CommandFailedException cmdFailed) {
            exception = cmdFailed;
            if (null == cmdFailed.getMessage()) {
                // this is intentional, see the javadoc for CommandFailedException
                printError = false;
            } else {
                LOGGER.error(consoleMessage, cmdFailed.getCause());
                consoleMessage = cmdFailed.getMessage();
            }
        } catch (RuntimeException e) {
            exception = e;
            // e.printStackTrace();
            consoleMessage = String.format(
                    "An unhandled error occurred: %s. See the log for more details.",
                    e.getMessage());
            LOGGER.error(consoleMessage, e);
        } catch (IOException ioe) {
            exception = ioe;
            // can't write to the console, see the javadocs for CLICommand.run().
            LOGGER.error(
                    "An IOException was caught, should only happen if an error occurred while writing to the console",
                    ioe);
        } finally {
            // close after executing a command for the next one to reopen with its own hints and not
            // to keep the db's open for write meanwhile
            close();
        }
        if (printError) {
            try {
                consoleReader.println(Optional.fromNullable(consoleMessage).or("Unknown Error"));
                consoleReader.flush();
            } catch (IOException e) {
                LOGGER.error("Error writing to the console. Original error: {}", consoleMessage, e);
            }
        }
        return -1;
    }

    /**
     * Executes a command.
     *
     * @param args
     * @throws exceptions thrown by the executed commands.
     */
    private void executeInternal(String... args) throws ParameterException, CommandFailedException,
            IOException, CannotRunGeogigOperationException {

        JCommander mainCommander = newCommandParser();
        if (null == args || args.length == 0) {
            printShortCommandList(mainCommander);
            return;
        }
        {
            args = unalias(args);
            final String commandName = args[0];
            JCommander commandParser = mainCommander.getCommands().get(commandName);

            if (commandParser == null) {
                consoleReader.println(args[0] + " is not a geogig command. See geogig --help.");
                // check for similar commands
                Map<String, JCommander> candidates = spellCheck(mainCommander.getCommands(),
                        commandName);
                if (!candidates.isEmpty()) {
                    String msg = candidates.size() == 1 ? "Did you mean this?"
                            : "Did you mean one of these?";
                    consoleReader.println();
                    consoleReader.println(msg);
                    for (String name : candidates.keySet()) {
                        consoleReader.println("\t" + name);
                    }
                }
                consoleReader.flush();
                throw new CommandFailedException(String.format("'%s' is not a command.",
                        commandName));
            }

            Object object = commandParser.getObjects().get(0);
            if (object instanceof CLICommandExtension) {
                args = Arrays.asList(args).subList(1, args.length)
                        .toArray(new String[args.length - 1]);
                mainCommander = ((CLICommandExtension) object).getCommandParser();
                if (Lists.newArrayList(args).contains("--help")) {
                    printUsage(mainCommander);
                    return;
                }
            }
        }

        mainCommander.parse(args);
        final String parsedCommand = mainCommander.getParsedCommand();
        if (null == parsedCommand) {
            if (mainCommander.getObjects().size() == 0) {
                printUsage(mainCommander);
            } else if (mainCommander.getObjects().get(0) instanceof CLICommandExtension) {
                CLICommandExtension extension = (CLICommandExtension) mainCommander.getObjects()
                        .get(0);
                printUsage(extension.getCommandParser());
            } else {
                printUsage(mainCommander);
                throw new CommandFailedException();
            }
        } else {
            JCommander jCommander = mainCommander.getCommands().get(parsedCommand);
            List<Object> objects = jCommander.getObjects();
            CLICommand cliCommand = (CLICommand) objects.get(0);
            Class<? extends CLICommand> cmdClass = cliCommand.getClass();
            if (cliCommand instanceof AbstractCommand && ((AbstractCommand) cliCommand).help) {
                ((AbstractCommand) cliCommand).printUsage(this);
                getConsole().flush();
                return;
            }
            Hints hints = gatherHints(cmdClass);
            this.hints = hints;

            if (cmdClass.isAnnotationPresent(RequiresRepository.class)
                    && cmdClass.getAnnotation(RequiresRepository.class).value()) {
                String workingDir;
                Platform platform = getPlatform();
                if (platform == null || platform.pwd() == null) {
                    workingDir = "Couln't determine working directory.";
                } else {
                    workingDir = platform.pwd().getAbsolutePath();
                }
                if (getGeogig() == null) {
                    throw new CommandFailedException("Not in a geogig repository: " + workingDir);
                }
            }

            cliCommand.run(this);
            getConsole().flush();
        }
    }

    /**
     * This method should be used instead of {@link JCommander#usage()} so the help string is
     * printed to the cli's {@link #getConsole() console} (and hence to wherever its output is sent)
     * instead of directly to {@code System.out}
     */
    public void printUsage(JCommander mainCommander) {
        StringBuilder out = new StringBuilder();
        mainCommander.usage(out);
        ConsoleReader console = getConsole();
        try {
            console.println(out.toString());
            console.flush();
        } catch (IOException e) {
            throw Throwables.propagate(e);
        }
    }

    private Hints gatherHints(Class<? extends CLICommand> cmdClass) {
        Hints hints = new Hints();

        checkAnnotationHint(cmdClass, ReadOnly.class, Hints.OBJECTS_READ_ONLY, hints);
        checkAnnotationHint(cmdClass, ReadOnly.class, Hints.STAGING_READ_ONLY, hints);

        checkAnnotationHint(cmdClass, ObjectDatabaseReadOnly.class, Hints.OBJECTS_READ_ONLY, hints);
        checkAnnotationHint(cmdClass, StagingDatabaseReadOnly.class, Hints.STAGING_READ_ONLY, hints);
        checkAnnotationHint(cmdClass, RemotesReadOnly.class, Hints.REMOTES_READ_ONLY, hints);

        return hints;
    }

    private void checkAnnotationHint(Class<? extends CLICommand> cmdClass,
            Class<? extends Annotation> annotation, String key, Hints hints) {

        if (cmdClass.isAnnotationPresent(annotation)) {
            hints.set(key, Boolean.TRUE);
        }
    }

    /**
     * If the passed arguments contains an alias, it replaces it by the full command corresponding
     * to that alias and returns anew set of arguments
     *
     * IF not, it returns the passed arguments
     *
     * @param args
     * @return
     */
    private String[] unalias(String[] args) {
        final String aliasedCommand = args[0];
        String configParam = "alias." + aliasedCommand;
        boolean closeGeogig = false;
        GeoGIG geogig = this.providedGeogig == null ? this.geogig : this.providedGeogig;
        if (geogig == null) { // in case the repo is not initialized yet
            closeGeogig = true;
            geogig = newGeoGIG(Hints.readOnly());
        }
        try {
            Optional<String> unaliased = Optional.absent();
            if (geogig.command(ResolveGeogigDir.class).call().isPresent()) {
                unaliased = geogig.command(ConfigGet.class).setName(configParam).call();
            }
            if (!unaliased.isPresent()) {
                unaliased = geogig.command(ConfigGet.class).setGlobal(true).setName(configParam)
                        .call();
            }
            if (!unaliased.isPresent()) {
                return args;
            }
            Iterable<String> tokens = Splitter.on(" ").split(unaliased.get());
            List<String> allArgs = Lists.newArrayList(tokens);
            allArgs.addAll(Lists.newArrayList(Arrays.copyOfRange(args, 1, args.length)));
            return allArgs.toArray(new String[0]);
        } catch (ConfigException e) {
            return args;
        } finally {
            if (closeGeogig) {
                geogig.close();
            }
        }
    }

    /**
     * Return all commands with a command name at a levenshtein distance of less than 3, as
     * potential candidates for a mistyped command
     *
     * @param commands the list of all available commands
     * @param commandName the command name
     * @return a map filtered according to distance between command names
     */
    private Map<String, JCommander> spellCheck(Map<String, JCommander> commands,
            final String commandName) {
        Map<String, JCommander> candidates = Maps.filterEntries(commands,
                new Predicate<Map.Entry<String, JCommander>>() {
                    @Override
                    public boolean apply(@Nullable Entry<String, JCommander> entry) {
                        char[] s1 = entry.getKey().toCharArray();
                        char[] s2 = commandName.toCharArray();
                        int[] prev = new int[s2.length + 1];
                        for (int j = 0; j < s2.length + 1; j++) {
                            prev[j] = j;
                        }
                        for (int i = 1; i < s1.length + 1; i++) {
                            int[] curr = new int[s2.length + 1];
                            curr[0] = i;
                            for (int j = 1; j < s2.length + 1; j++) {
                                int d1 = prev[j] + 1;
                                int d2 = curr[j - 1] + 1;
                                int d3 = prev[j - 1];
                                if (s1[i - 1] != s2[j - 1]) {
                                    d3 += 1;
                                }
                                curr[j] = Math.min(Math.min(d1, d2), d3);
                            }
                            prev = curr;
                        }
                        return prev[s2.length] < 3;
                    }
                });
        return candidates;
    }

    /**
     * This prints out only porcelain commands
     *
     * @param mainCommander
     *
     * @throws IOException
     */
    public void printShortCommandList(JCommander mainCommander) {
        TreeSet<String> commandNames = Sets.newTreeSet();
        int longestCommandLenght = 0;
        // do this to ignore aliases
        for (String name : mainCommander.getCommands().keySet()) {
            JCommander command = mainCommander.getCommands().get(name);
            Class<? extends Object> clazz = command.getObjects().get(0).getClass();
            String packageName = clazz.getPackage().getName();
            if (!packageName.startsWith("org.locationtech.geogig.cli.plumbing")) {
                commandNames.add(name);
                longestCommandLenght = Math.max(longestCommandLenght, name.length());
            }
        }
        ConsoleReader console = getConsole();
        try {
            console.println("usage: geogig <command> [<args>]");
            console.println();
            console.println("The most commonly used geogig commands are:");
            for (String cmd : commandNames) {
                console.print(Strings.padEnd(cmd, longestCommandLenght, ' '));
                console.print("\t");
                console.println(mainCommander.getCommandDescription(cmd));
            }
            console.flush();
        } catch (IOException e) {
            throw Throwables.propagate(e);
        }
    }

    /**
     * This prints out all commands, including plumbing ones, without description
     *
     * @param mainCommander
     * @throws IOException
     */
    public void printCommandList(JCommander mainCommander) {
        TreeSet<String> commandNames = Sets.newTreeSet();
        int longestCommandLenght = 0;
        // do this to ignore aliases
        for (String name : mainCommander.getCommands().keySet()) {
            commandNames.add(name);
            longestCommandLenght = Math.max(longestCommandLenght, name.length());
        }
        ConsoleReader console = getConsole();
        try {
            console.println("usage: geogig <command> [<args>]");
            console.println();
            int i = 0;
            for (String cmd : commandNames) {
                console.print(Strings.padEnd(cmd, longestCommandLenght, ' '));
                i++;
                if (i % 3 == 0) {
                    console.println();
                } else {
                    console.print("\t");
                }
            }
            console.flush();
        } catch (IOException e) {
            throw Throwables.propagate(e);
        }
    }

    /**
     * /**
     *
     * @return the ProgressListener for the command line interface. If it doesn't exist, a new one
     *         will be constructed.
     * @see ProgressListener
     */
    public synchronized ProgressListener getProgressListener() {
        if (this.progressListener == null) {
            if (progressListenerDisabled) {
                this.progressListener = new DefaultProgressListener();
                return this.progressListener;
            }
            this.progressListener = new DefaultProgressListener() {

                private final Platform platform = getPlatform();

                private final ConsoleReader console = getConsole();

                private final NumberFormat percentFormat = NumberFormat.getPercentInstance();

                private final NumberFormat numberFormat = NumberFormat.getIntegerInstance();

                private final long delayNanos = TimeUnit.NANOSECONDS.convert(100,
                        TimeUnit.MILLISECONDS);

                // Don't skip the first update
                private volatile long lastRun = 0;

                @Override
                public void started() {
                    super.started();
                    lastRun = -(delayNanos + 1);
                }

                @Override
                public void setDescription(String s) {
                    try {
                        console.println();
                        console.println(s);
                        console.flush();
                    } catch (IOException e) {
                        Throwables.propagate(e);
                    }
                }

                @Override
                public synchronized void complete() {
                    // avoid double logging if caller missbehaves
                    if (super.isCompleted()) {
                        return;
                    }
                    super.complete();
                    super.dispose();
                    try {
                        log(getProgress());
                        console.println();
                        console.flush();
                    } catch (IOException e) {
                        Throwables.propagate(e);
                    }
                }

                @Override
                public synchronized void setProgress(float percent) {
                    super.setProgress(percent);
                    long nanoTime = platform.nanoTime();
                    if ((nanoTime - lastRun) > delayNanos) {
                        lastRun = nanoTime;
                        log(percent);
                    }
                }

                private void log(float percent) {
                    CursorBuffer cursorBuffer = console.getCursorBuffer();
                    cursorBuffer.clear();
                    String description = getDescription();
                    if (description != null) {
                        cursorBuffer.write(description);
                    }
                    if (percent > 100) {
                        cursorBuffer.write(numberFormat.format(percent));
                    } else {
                        cursorBuffer.write(percentFormat.format(percent / 100f));
                    }
                    try {
                        console.redrawLine();
                        console.flush();
                    } catch (IOException e) {
                        Throwables.propagate(e);
                    }
                }
            };

        }
        return this.progressListener;
    }

    static void addShutdownHook(final GeogigCLI cli) {
        // try to grafefully shutdown upon CTRL+C
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                if (cli.isRunning()) {
                    System.err.println("Forced shut down, wait for geogig to be closed...");
                    System.err.flush();
                    cli.close();
                    System.err.println("geogig closed.");
                    System.err.flush();
                }
            }
        });
    }

    @VisibleForTesting
    public void tryConfigureLogging() {
        Logging.tryConfigureLogging(getPlatform());
    }
}
TOP

Related Classes of org.locationtech.geogig.cli.GeogigCLI

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.