org.eclipse.jgit.pgm.Main.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.jgit.pgm.Main.java

Source

/*
 * Copyright (C) 2006, Robin Rosenberg <robin.rosenberg@dewire.com>
 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
 * and other copyright owners as documented in the project's IP log.
 *
 * This program and the accompanying materials are made available
 * under the terms of the Eclipse Distribution License v1.0 which
 * accompanies this distribution, is reproduced below, and is
 * available at http://www.eclipse.org/org/documents/edl-v10.php
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above
 *   copyright notice, this list of conditions and the following
 *   disclaimer in the documentation and/or other materials provided
 *   with the distribution.
 *
 * - Neither the name of the Eclipse Foundation, Inc. nor the
 *   names of its contributors may be used to endorse or promote
 *   products derived from this software without specific prior
 *   written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.eclipse.jgit.pgm;

import static java.nio.charset.StandardCharsets.UTF_8;

import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

import org.eclipse.jgit.awtui.AwtAuthenticator;
import org.eclipse.jgit.awtui.AwtCredentialsProvider;
import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.lfs.BuiltinLFS;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RepositoryBuilder;
import org.eclipse.jgit.pgm.internal.CLIText;
import org.eclipse.jgit.pgm.opt.CmdLineParser;
import org.eclipse.jgit.pgm.opt.SubcommandHandler;
import org.eclipse.jgit.transport.HttpTransport;
import org.eclipse.jgit.transport.http.apache.HttpClientConnectionFactory;
import org.eclipse.jgit.util.CachedAuthenticator;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.Option;
import org.kohsuke.args4j.OptionHandlerFilter;

/**
 * Command line entry point.
 */
public class Main {
    @Option(name = "--help", usage = "usage_displayThisHelpText", aliases = { "-h" })
    private boolean help;

    @Option(name = "--version", usage = "usage_displayVersion")
    private boolean version;

    @Option(name = "--show-stack-trace", usage = "usage_displayThejavaStackTraceOnExceptions")
    private boolean showStackTrace;

    @Option(name = "--git-dir", metaVar = "metaVar_gitDir", usage = "usage_setTheGitRepositoryToOperateOn")
    private String gitdir;

    @Argument(index = 0, metaVar = "metaVar_command", required = true, handler = SubcommandHandler.class)
    private TextBuiltin subcommand;

    @Argument(index = 1, metaVar = "metaVar_arg")
    private List<String> arguments = new ArrayList<>();

    PrintWriter writer;

    private ExecutorService gcExecutor;

    /**
     * <p>Constructor for Main.</p>
     */
    public Main() {
        HttpTransport.setConnectionFactory(new HttpClientConnectionFactory());
        BuiltinLFS.register();
        gcExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() {
            private final ThreadFactory baseFactory = Executors.defaultThreadFactory();

            @Override
            public Thread newThread(Runnable taskBody) {
                Thread thr = baseFactory.newThread(taskBody);
                thr.setName("JGit-autoGc"); //$NON-NLS-1$
                return thr;
            }
        });
    }

    /**
     * Execute the command line.
     *
     * @param argv
     *            arguments.
     * @throws java.lang.Exception
     */
    public static void main(String[] argv) throws Exception {
        // make sure built-in filters are registered
        BuiltinLFS.register();

        new Main().run(argv);
    }

    /**
     * Parse the command line and execute the requested action.
     *
     * Subclasses should allocate themselves and then invoke this method:
     *
     * <pre>
     * class ExtMain {
     *    public static void main(String[] argv) {
     *       new ExtMain().run(argv);
     *    }
     * }
     * </pre>
     *
     * @param argv
     *            arguments.
     * @throws java.lang.Exception
     */
    protected void run(String[] argv) throws Exception {
        writer = createErrorWriter();
        try {
            if (!installConsole()) {
                AwtAuthenticator.install();
                AwtCredentialsProvider.install();
            }
            configureHttpProxy();
            execute(argv);
        } catch (Die err) {
            if (err.isAborted()) {
                exit(1, err);
            }
            writer.println(CLIText.fatalError(err.getMessage()));
            if (showStackTrace) {
                err.printStackTrace(writer);
            }
            exit(128, err);
        } catch (Exception err) {
            // Try to detect errno == EPIPE and exit normally if that happens
            // There may be issues with operating system versions and locale,
            // but we can probably assume that these messages will not be thrown
            // under other circumstances.
            if (err.getClass() == IOException.class) {
                // Linux, OS X
                if (err.getMessage().equals("Broken pipe")) { //$NON-NLS-1$
                    exit(0, err);
                }
                // Windows
                if (err.getMessage().equals("The pipe is being closed")) { //$NON-NLS-1$
                    exit(0, err);
                }
            }
            if (!showStackTrace && err.getCause() != null && err instanceof TransportException) {
                writer.println(CLIText.fatalError(err.getCause().getMessage()));
            }

            if (err.getClass().getName().startsWith("org.eclipse.jgit.errors.")) { //$NON-NLS-1$
                writer.println(CLIText.fatalError(err.getMessage()));
                if (showStackTrace) {
                    err.printStackTrace();
                }
                exit(128, err);
            }
            err.printStackTrace();
            exit(1, err);
        }
        if (System.out.checkError()) {
            writer.println(CLIText.get().unknownIoErrorStdout);
            exit(1, null);
        }
        if (writer.checkError()) {
            // No idea how to present an error here, most likely disk full or
            // broken pipe
            exit(1, null);
        }
        gcExecutor.shutdown();
        gcExecutor.awaitTermination(10, TimeUnit.MINUTES);
    }

    PrintWriter createErrorWriter() {
        return new PrintWriter(new OutputStreamWriter(System.err, UTF_8));
    }

    private void execute(String[] argv) throws Exception {
        final CmdLineParser clp = new SubcommandLineParser(this);

        try {
            clp.parseArgument(argv);
        } catch (CmdLineException err) {
            if (argv.length > 0 && !help && !version) {
                writer.println(CLIText.fatalError(err.getMessage()));
                writer.flush();
                exit(1, err);
            }
        }

        if (argv.length == 0 || help) {
            final String ex = clp.printExample(OptionHandlerFilter.ALL, CLIText.get().resourceBundle());
            writer.println("jgit" + ex + " command [ARG ...]"); //$NON-NLS-1$ //$NON-NLS-2$
            if (help) {
                writer.println();
                clp.printUsage(writer, CLIText.get().resourceBundle());
                writer.println();
            } else if (subcommand == null) {
                writer.println();
                writer.println(CLIText.get().mostCommonlyUsedCommandsAre);
                final CommandRef[] common = CommandCatalog.common();
                int width = 0;
                for (CommandRef c : common) {
                    width = Math.max(width, c.getName().length());
                }
                width += 2;

                for (CommandRef c : common) {
                    writer.print(' ');
                    writer.print(c.getName());
                    for (int i = c.getName().length(); i < width; i++) {
                        writer.print(' ');
                    }
                    writer.print(CLIText.get().resourceBundle().getString(c.getUsage()));
                    writer.println();
                }
                writer.println();
            }
            writer.flush();
            exit(1, null);
        }

        if (version) {
            String cmdId = Version.class.getSimpleName().toLowerCase(Locale.ROOT);
            subcommand = CommandCatalog.get(cmdId).create();
        }

        final TextBuiltin cmd = subcommand;
        init(cmd);
        try {
            cmd.execute(arguments.toArray(new String[0]));
        } finally {
            if (cmd.outw != null) {
                cmd.outw.flush();
            }
            if (cmd.errw != null) {
                cmd.errw.flush();
            }
        }
    }

    void init(TextBuiltin cmd) throws IOException {
        if (cmd.requiresRepository()) {
            cmd.init(openGitDir(gitdir), null);
        } else {
            cmd.init(null, gitdir);
        }
    }

    /**
     * @param status
     * @param t
     *            can be {@code null}
     * @throws Exception
     */
    void exit(int status, Exception t) throws Exception {
        writer.flush();
        System.exit(status);
    }

    /**
     * Evaluate the {@code --git-dir} option and open the repository.
     *
     * @param aGitdir
     *            the {@code --git-dir} option given on the command line. May be
     *            null if it was not supplied.
     * @return the repository to operate on.
     * @throws java.io.IOException
     *             the repository cannot be opened.
     */
    protected Repository openGitDir(String aGitdir) throws IOException {
        RepositoryBuilder rb = new RepositoryBuilder() //
                .setGitDir(aGitdir != null ? new File(aGitdir) : null) //
                .readEnvironment() //
                .findGitDir();
        if (rb.getGitDir() == null)
            throw new Die(CLIText.get().cantFindGitDirectory);
        return rb.build();
    }

    private static boolean installConsole() {
        try {
            install("org.eclipse.jgit.console.ConsoleAuthenticator"); //$NON-NLS-1$
            install("org.eclipse.jgit.console.ConsoleCredentialsProvider"); //$NON-NLS-1$
            return true;
        } catch (ClassNotFoundException | NoClassDefFoundError | UnsupportedClassVersionError e) {
            return false;
        } catch (IllegalArgumentException | SecurityException | IllegalAccessException | InvocationTargetException
                | NoSuchMethodException e) {
            throw new RuntimeException(CLIText.get().cannotSetupConsole, e);
        }
    }

    private static void install(String name) throws IllegalAccessException, InvocationTargetException,
            NoSuchMethodException, ClassNotFoundException {
        try {
            Class.forName(name).getMethod("install").invoke(null); //$NON-NLS-1$
        } catch (InvocationTargetException e) {
            if (e.getCause() instanceof RuntimeException)
                throw (RuntimeException) e.getCause();
            if (e.getCause() instanceof Error)
                throw (Error) e.getCause();
            throw e;
        }
    }

    /**
     * Configure the JRE's standard HTTP based on <code>http_proxy</code>.
     * <p>
     * The popular libcurl library honors the <code>http_proxy</code>,
     * <code>https_proxy</code> environment variables as a means of specifying
     * an HTTP/S proxy for requests made behind a firewall. This is not natively
     * recognized by the JRE, so this method can be used by command line
     * utilities to configure the JRE before the first request is sent. The
     * information found in the environment variables is copied to the
     * associated system properties. This is not done when the system properties
     * are already set. The default way of telling java programs about proxies
     * (the system properties) takes precedence over environment variables.
     *
     * @throws MalformedURLException
     *             the value in <code>http_proxy</code> or
     *             <code>https_proxy</code> is unsupportable.
     */
    static void configureHttpProxy() throws MalformedURLException {
        for (String protocol : new String[] { "http", "https" }) { //$NON-NLS-1$ //$NON-NLS-2$
            if (System.getProperty(protocol + ".proxyHost") != null) { //$NON-NLS-1$
                continue;
            }
            String s = System.getenv(protocol + "_proxy"); //$NON-NLS-1$
            if (s == null && protocol.equals("https")) { //$NON-NLS-1$
                s = System.getenv("HTTPS_PROXY"); //$NON-NLS-1$
            }
            if (s == null || s.isEmpty()) {
                continue;
            }

            final URL u = new URL((!s.contains("://")) ? protocol + "://" + s : s); //$NON-NLS-1$ //$NON-NLS-2$
            if (!u.getProtocol().startsWith("http")) //$NON-NLS-1$
                throw new MalformedURLException(
                        MessageFormat.format(CLIText.get().invalidHttpProxyOnlyHttpSupported, s));

            final String proxyHost = u.getHost();
            final int proxyPort = u.getPort();

            System.setProperty(protocol + ".proxyHost", proxyHost); //$NON-NLS-1$
            if (proxyPort > 0)
                System.setProperty(protocol + ".proxyPort", //$NON-NLS-1$
                        String.valueOf(proxyPort));

            final String userpass = u.getUserInfo();
            if (userpass != null && userpass.contains(":")) { //$NON-NLS-1$
                final int c = userpass.indexOf(':');
                final String user = userpass.substring(0, c);
                final String pass = userpass.substring(c + 1);
                CachedAuthenticator
                        .add(new CachedAuthenticator.CachedAuthentication(proxyHost, proxyPort, user, pass));
            }
        }
    }

    /**
     * Parser for subcommands which doesn't stop parsing on help options and so
     * proceeds all specified options
     */
    static class SubcommandLineParser extends CmdLineParser {
        public SubcommandLineParser(Object bean) {
            super(bean);
        }

        @Override
        protected boolean containsHelp(String... args) {
            return false;
        }
    }
}