org.z2env.impl.helper.GitTools.java Source code

Java tutorial

Introduction

Here is the source code for org.z2env.impl.helper.GitTools.java

Source

/*
 * z2env.org - (c) ZFabrik Software KG
 * 
 * Licensed under Apache 2.
 * 
 * www.z2-environment.net
 */
package org.z2env.impl.helper;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;

import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.transport.RemoteConfig;
import org.eclipse.jgit.transport.RemoteSession;
import org.eclipse.jgit.transport.SshSessionFactory;
import org.eclipse.jgit.transport.URIish;
import org.eclipse.jgit.util.FS;

/**
 * Provides some useful static helpers like
 * {@link #isOriginReachable(URIish)}, {@link #isValidRepository(URIish)}, {@link #cloneRepository(URIish, File, CredentialsProvider, int)}, ...
 * 
 * @author Udo Offermann
 *
 */
public class GitTools {

    /* network timeout in milliseconds */
    private final static int NETWORK_TIMEOUT_MSEC = 5000;

    public enum ValidationResult {
        valid, invalid, cannotTell
    };

    /**
     * Returns true if repoUri is local. If repoUri is remote the method returns true if the host is reachable.
     *   
     * @param repoUri a Git-URIish
     * @return true if the host addressed by repoUri is reachable
     */
    public static boolean isOriginReachable(URIish repoUri) {
        boolean result;
        if (repoUri.isRemote()) {
            try {
                result = InetAddress.getByName(repoUri.getHost()).isReachable(NETWORK_TIMEOUT_MSEC);
            } catch (Exception e) {
                result = false;
            }
        } else {
            // local is definitely reachable ;-)
            result = true;
        }
        return result;
    }

    /**
     * Checks whether the given repository is valid.
     * This works for local as well as for remote repositories. However only 'http' and 'ssh' based git-urls are supported at the moment. 
     * The method returns {@link ValidationResult#cannotTell} for all other schemas.
     * 
     * @param repoUri repository uri
     * 
     * @return
     *    <ul> 
     *      <li>{@link ValidationResult#valid}: repoUri points to a valid repository</li>
     *      <li>{@link ValidationResult#invalid}: repoUri points to an invalid repository, or the remote side is not reachable</li>
     *      <li>{@link ValidationResult#cannotTell}: repoUri cannot be validated (because there is no validation for repoUri's scheme available)</li>
     */
    public static ValidationResult isValidRepository(URIish repoUri) {
        if (repoUri.isRemote()) {
            return isValidRemoteRepository(repoUri);
        } else {
            return isValidLocalRepository(repoUri);
        }
    }

    private static ValidationResult isValidLocalRepository(URIish repoUri) {
        ValidationResult result;
        try {
            if (Git.open(new File(repoUri.getPath())).getRepository().getObjectDatabase().exists()) {
                result = ValidationResult.valid;
            } else {
                result = ValidationResult.invalid;
            }
        } catch (IOException e) {
            result = ValidationResult.invalid;
        }
        return result;
    }

    private final static String INFO_REFS_PATH = "info/refs";

    private static ValidationResult isValidRemoteRepository(URIish repoUri) {
        ValidationResult result;

        if (repoUri.getScheme().toLowerCase().startsWith("http")) {
            String path = repoUri.getPath();
            String newPath = path.endsWith("/") ? path + INFO_REFS_PATH : path + "/" + INFO_REFS_PATH;
            URIish checkUri = repoUri.setPath(newPath);

            InputStream ins = null;
            try {
                URLConnection conn = new URL(checkUri.toString()).openConnection();
                conn.setReadTimeout(NETWORK_TIMEOUT_MSEC);
                ins = conn.getInputStream();
                result = ValidationResult.valid;

            } catch (Exception e) {
                result = ValidationResult.invalid;

            } finally {
                try {
                    ins.close();
                } catch (Exception e) {
                    /* ignore */ }
            }

        } else if (repoUri.getScheme().toLowerCase().startsWith("ssh")) {

            RemoteSession ssh = null;
            Process exec = null;

            try {
                ssh = SshSessionFactory.getInstance().getSession(repoUri, null, FS.detect(), 5000);
                exec = ssh.exec("cd " + repoUri.getPath() + "; git rev-parse --git-dir", 5000);

                Integer exitValue = null;
                do {
                    try {
                        exitValue = exec.exitValue();
                    } catch (Exception e) {
                        try {
                            Thread.sleep(1000);
                        } catch (Exception ee) {
                        }
                    }
                } while (exitValue == null);

                result = exitValue == 0 ? ValidationResult.valid : ValidationResult.invalid;

            } catch (Exception e) {
                result = ValidationResult.invalid;

            } finally {
                try {
                    exec.destroy();
                } catch (Exception e) {
                    /* ignore */ }
                try {
                    ssh.disconnect();
                } catch (Exception e) {
                    /* ignore */ }
            }

        } else {

            // TODO need to implement tests for other schemas
            result = ValidationResult.cannotTell;
        }
        return result;
    }

    /**
     * Clones the given remote repository into the given destination folder. The method clones all branches but doesn't perform a checkout.
     *   
     * @param remoteUri URI of the remote repository
     * @param destFolder local destination folder
     * @param credentials user credentials
     * @return the cloned repository
     * @throws IOException if something went wrong
     */
    public static Repository cloneRepository(URIish remoteUri, File destFolder, CredentialsProvider credentials,
            int timeout) throws IOException {

        // workaround for http://redmine.z2-environment.net/issues/902:
        // split clone into its piece in order to get the chance to set "core.autocrlf"
        Git gitResult;
        try {
            gitResult = Git.init().setBare(false).setDirectory(destFolder).call();
        } catch (Exception e) {
            throw new IOException("Failed to initialize a new Git repository at " + destFolder.getAbsolutePath(),
                    e);
        }

        Repository repo = gitResult.getRepository();

        // setting "core.autocrlf=false" helps to solve http://redmine.z2-environment.net/issues/902
        StoredConfig config = repo.getConfig();
        config.setString(ConfigConstants.CONFIG_CORE_SECTION, null, ConfigConstants.CONFIG_KEY_AUTOCRLF,
                String.valueOf(false));

        // add origin - clone all branches
        RemoteConfig remoteCfg = null;
        try {
            remoteCfg = new RemoteConfig(config, "origin");
        } catch (URISyntaxException e) {
            throw new IOException("Failed to configure origin repository", e);
        }
        remoteCfg.addURI(remoteUri);
        remoteCfg.addFetchRefSpec(new RefSpec("+refs/heads/*:refs/remotes/origin/*"));
        remoteCfg.update(config);
        config.save();

        // fetch all branches from origin
        try {
            gitResult.fetch().setRemote("origin").setCredentialsProvider(credentials).setTimeout(timeout).call();
        } catch (Exception e) {
            throw new IOException("Failed to fetch from origin!", e);
        }

        return repo;
    }

    /**
     * Switches to the given target branch in the given repository. If such a local branch exists the method performs a checkout on this branch. 
     * Otherwise the branch name can be a remote branch in which case a local tracking branch is created. 
     * @param repo the repository
     * @param targetBranch a local or remote branch name.
     * @throws IOException if something went wrong
     */
    public static void switchBranch(Repository repo, String targetBranch) throws IOException {

        if (hasLocalBranch(repo, targetBranch)) {

            if (!targetBranch.equals(repo.getBranch())) {
                try {
                    new Git(repo).checkout().setName(targetBranch).call();
                } catch (Exception e) {
                    throw new IOException("Failed to switch to branch '" + targetBranch + "' inside " + repo + "!");
                }
            }

        } else if (hasRemoteBranch(repo, targetBranch)) {

            // create tracking branch 
            try {
                new Git(repo).branchCreate().setName(targetBranch).setUpstreamMode(SetupUpstreamMode.TRACK)
                        .setStartPoint(Constants.R_REMOTES + Constants.DEFAULT_REMOTE_NAME + "/" + targetBranch)
                        .call();
            } catch (Exception e) {
                throw new IOException(
                        "Failed to create tracking branch '" + targetBranch + "' inside " + repo + "!");
            }

            // switch to new branch
            try {
                new Git(repo).checkout().setName(targetBranch).call();
            } catch (Exception e) {
                throw new IOException("Failed to switch to branch '" + targetBranch + "' inside " + repo + "!");
            }

        } else {
            throw new IOException("No such branch '" + targetBranch + "' inside " + repo + "!");
        }
    }

    private static boolean hasLocalBranch(Repository repo, String b) throws IOException {
        boolean result = null != repo.getRef("refs/heads/" + b);
        return result;
    }

    private static boolean hasRemoteBranch(Repository repo, String b) throws IOException {
        boolean result = null != repo.getRef("refs/remotes/origin/" + b);
        return result;
    }
}