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

Java tutorial

Introduction

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

Source

/*
 * Copyright (C) 2007, 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 static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_LOG_OUTPUT_ENCODING;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_SECTION_I18N;
import static org.eclipse.jgit.lib.Constants.R_HEADS;
import static org.eclipse.jgit.lib.Constants.R_REMOTES;
import static org.eclipse.jgit.lib.Constants.R_TAGS;

import java.io.BufferedWriter;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.text.MessageFormat;
import java.util.ResourceBundle;

import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.pgm.internal.CLIText;
import org.eclipse.jgit.pgm.internal.SshDriver;
import org.eclipse.jgit.pgm.opt.CmdLineParser;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.SshSessionFactory;
import org.eclipse.jgit.transport.sshd.DefaultProxyDataFactory;
import org.eclipse.jgit.transport.sshd.JGitKeyCache;
import org.eclipse.jgit.transport.sshd.SshdSessionFactory;
import org.eclipse.jgit.util.io.ThrowingPrintWriter;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.Option;

/**
 * Abstract command which can be invoked from the command line.
 * <p>
 * Commands are configured with a single "current" repository and then the
 * {@link #execute(String[])} method is invoked with the arguments that appear
 * on the command line after the command name.
 * <p>
 * Command constructors should perform as little work as possible as they may be
 * invoked very early during process loading, and the command may not execute
 * even though it was constructed.
 */
public abstract class TextBuiltin {
    private String commandName;

    @Option(name = "--help", usage = "usage_displayThisHelpText", aliases = { "-h" })
    private boolean help;

    @Option(name = "--ssh", usage = "usage_sshDriver")
    private SshDriver sshDriver = SshDriver.JSCH;

    /**
     * Input stream, typically this is standard input.
     *
     * @since 3.4
     */
    protected InputStream ins;

    /**
     * Writer to output to, typically this is standard output.
     *
     * @since 2.2
     */
    protected ThrowingPrintWriter outw;

    /**
     * Stream to output to, typically this is standard output.
     *
     * @since 2.2
     */
    protected OutputStream outs;

    /**
     * Error writer, typically this is standard error.
     *
     * @since 3.4
     */
    protected ThrowingPrintWriter errw;

    /**
     * Error output stream, typically this is standard error.
     *
     * @since 3.4
     */
    protected OutputStream errs;

    /** Git repository the command was invoked within. */
    protected Repository db;

    /** Directory supplied via --git-dir command line option. */
    protected String gitdir;

    /** RevWalk used during command line parsing, if it was required. */
    protected RevWalk argWalk;

    final void setCommandName(String name) {
        commandName = name;
    }

    /**
     * If this command requires a repository.
     *
     * @return true if {@link #db}/{@link #getRepository()} is required
     */
    protected boolean requiresRepository() {
        return true;
    }

    /**
     * Initializes the command to work with a repository, including setting the
     * output and error streams.
     *
     * @param repository
     *            the opened repository that the command should work on.
     * @param gitDir
     *            value of the {@code --git-dir} command line option, if
     *            {@code repository} is null.
     * @param input
     *            input stream from which input will be read
     * @param output
     *            output stream to which output will be written
     * @param error
     *            error stream to which errors will be written
     * @since 4.9
     */
    public void initRaw(final Repository repository, final String gitDir, InputStream input, OutputStream output,
            OutputStream error) {
        this.ins = input;
        this.outs = output;
        this.errs = error;
        init(repository, gitDir);
    }

    /**
     * Get the log output encoding specified in the repository's
     * {@code i18n.logOutputEncoding} configuration.
     *
     * @param repository
     *            the repository.
     * @return Charset corresponding to {@code i18n.logOutputEncoding}, or
     *         {@code UTF_8}.
     */
    private Charset getLogOutputEncodingCharset(Repository repository) {
        if (repository != null) {
            String logOutputEncoding = repository.getConfig().getString(CONFIG_SECTION_I18N, null,
                    CONFIG_KEY_LOG_OUTPUT_ENCODING);
            if (logOutputEncoding != null) {
                try {
                    return Charset.forName(logOutputEncoding);
                } catch (IllegalArgumentException e) {
                    throw die(CLIText.get().cannotCreateOutputStream, e);
                }
            }
        }
        return UTF_8;
    }

    /**
     * Initialize the command to work with a repository.
     *
     * @param repository
     *            the opened repository that the command should work on.
     * @param gitDir
     *            value of the {@code --git-dir} command line option, if
     *            {@code repository} is null.
     */
    protected void init(Repository repository, String gitDir) {
        Charset charset = getLogOutputEncodingCharset(repository);

        if (ins == null)
            ins = new FileInputStream(FileDescriptor.in);
        if (outs == null)
            outs = new FileOutputStream(FileDescriptor.out);
        if (errs == null)
            errs = new FileOutputStream(FileDescriptor.err);
        outw = new ThrowingPrintWriter(new BufferedWriter(new OutputStreamWriter(outs, charset)));
        errw = new ThrowingPrintWriter(new BufferedWriter(new OutputStreamWriter(errs, charset)));

        if (repository != null && repository.getDirectory() != null) {
            db = repository;
            gitdir = repository.getDirectory().getAbsolutePath();
        } else {
            db = repository;
            gitdir = gitDir;
        }
    }

    /**
     * Parse arguments and run this command.
     *
     * @param args
     *            command line arguments passed after the command name.
     * @throws java.lang.Exception
     *             an error occurred while processing the command. The main
     *             framework will catch the exception and print a message on
     *             standard error.
     */
    public final void execute(String[] args) throws Exception {
        parseArguments(args);
        switch (sshDriver) {
        case APACHE: {
            SshdSessionFactory factory = new SshdSessionFactory(new JGitKeyCache(), new DefaultProxyDataFactory());
            Runtime.getRuntime().addShutdownHook(new Thread(() -> factory.close()));
            SshSessionFactory.setInstance(factory);
            break;
        }
        case JSCH:
        default:
            SshSessionFactory.setInstance(null);
            break;
        }
        run();
    }

    /**
     * Parses the command line arguments prior to running.
     * <p>
     * This method should only be invoked by {@link #execute(String[])}, prior
     * to calling {@link #run()}. The default implementation parses all
     * arguments into this object's instance fields.
     *
     * @param args
     *            the arguments supplied on the command line, if any.
     * @throws java.io.IOException
     */
    protected void parseArguments(String[] args) throws IOException {
        final CmdLineParser clp = new CmdLineParser(this);
        help = containsHelp(args);
        try {
            clp.parseArgument(args);
        } catch (CmdLineException err) {
            this.errw.println(CLIText.fatalError(err.getMessage()));
            if (help) {
                printUsage("", clp); //$NON-NLS-1$
            }
            throw die(true, err);
        }

        if (help) {
            printUsage("", clp); //$NON-NLS-1$
            throw new TerminatedByHelpException();
        }

        argWalk = clp.getRevWalkGently();
    }

    /**
     * Print the usage line
     *
     * @param clp
     *            a {@link org.eclipse.jgit.pgm.opt.CmdLineParser} object.
     * @throws java.io.IOException
     */
    public void printUsageAndExit(CmdLineParser clp) throws IOException {
        printUsageAndExit("", clp); //$NON-NLS-1$
    }

    /**
     * Print an error message and the usage line
     *
     * @param message
     *            a {@link java.lang.String} object.
     * @param clp
     *            a {@link org.eclipse.jgit.pgm.opt.CmdLineParser} object.
     * @throws java.io.IOException
     */
    public void printUsageAndExit(String message, CmdLineParser clp) throws IOException {
        printUsage(message, clp);
        throw die(true);
    }

    /**
     * Print usage help text.
     *
     * @param message
     *            non null
     * @param clp
     *            parser used to print options
     * @throws java.io.IOException
     * @since 4.2
     */
    protected void printUsage(String message, CmdLineParser clp) throws IOException {
        errw.println(message);
        errw.print("jgit "); //$NON-NLS-1$
        errw.print(commandName);
        clp.printSingleLineUsage(errw, getResourceBundle());
        errw.println();

        errw.println();
        clp.printUsage(errw, getResourceBundle());
        errw.println();

        errw.flush();
    }

    /**
     * Get error writer
     *
     * @return error writer, typically this is standard error.
     * @since 4.2
     */
    public ThrowingPrintWriter getErrorWriter() {
        return errw;
    }

    /**
     * Get output writer
     *
     * @return output writer, typically this is standard output.
     * @since 4.9
     */
    public ThrowingPrintWriter getOutputWriter() {
        return outw;
    }

    /**
     * Get resource bundle with localized texts
     *
     * @return the resource bundle that will be passed to args4j for purpose of
     *         string localization
     */
    protected ResourceBundle getResourceBundle() {
        return CLIText.get().resourceBundle();
    }

    /**
     * Perform the actions of this command.
     * <p>
     * This method should only be invoked by {@link #execute(String[])}.
     *
     * @throws java.lang.Exception
     *             an error occurred while processing the command. The main
     *             framework will catch the exception and print a message on
     *             standard error.
     */
    protected abstract void run() throws Exception;

    /**
     * Get the repository
     *
     * @return the repository this command accesses.
     */
    public Repository getRepository() {
        return db;
    }

    ObjectId resolve(String s) throws IOException {
        final ObjectId r = db.resolve(s);
        if (r == null)
            throw die(MessageFormat.format(CLIText.get().notARevision, s));
        return r;
    }

    /**
     * Exit the command with an error message
     *
     * @param why
     *            textual explanation
     * @return a runtime exception the caller is expected to throw
     */
    protected static Die die(String why) {
        return new Die(why);
    }

    /**
     * Exit the command with an error message and an exception
     *
     * @param why
     *            textual explanation
     * @param cause
     *            why the command has failed.
     * @return a runtime exception the caller is expected to throw
     */
    protected static Die die(String why, Throwable cause) {
        return new Die(why, cause);
    }

    /**
     * Exit the command
     *
     * @param aborted
     *            boolean indicating that the execution has been aborted before
     *            running
     * @return a runtime exception the caller is expected to throw
     * @since 3.4
     */
    protected static Die die(boolean aborted) {
        return new Die(aborted);
    }

    /**
     * Exit the command
     *
     * @param aborted
     *            boolean indicating that the execution has been aborted before
     *            running
     * @param cause
     *            why the command has failed.
     * @return a runtime exception the caller is expected to throw
     * @since 4.2
     */
    protected static Die die(boolean aborted, Throwable cause) {
        return new Die(aborted, cause);
    }

    String abbreviateRef(String dst, boolean abbreviateRemote) {
        if (dst.startsWith(R_HEADS))
            dst = dst.substring(R_HEADS.length());
        else if (dst.startsWith(R_TAGS))
            dst = dst.substring(R_TAGS.length());
        else if (abbreviateRemote && dst.startsWith(R_REMOTES))
            dst = dst.substring(R_REMOTES.length());
        return dst;
    }

    /**
     * Check if the arguments contain a help option
     *
     * @param args
     *            non null
     * @return true if the given array contains help option
     * @since 4.2
     */
    public static boolean containsHelp(String[] args) {
        for (String str : args) {
            if (str.equals("-h") || str.equals("--help")) { //$NON-NLS-1$ //$NON-NLS-2$
                return true;
            }
        }
        return false;
    }

    /**
     * Exception thrown by {@link TextBuiltin} if it proceeds 'help' option
     *
     * @since 4.2
     */
    public static class TerminatedByHelpException extends Die {
        private static final long serialVersionUID = 1L;

        /**
         * Default constructor
         */
        public TerminatedByHelpException() {
            super(true);
        }

    }
}