org.omegat.core.team2.impl.GITRemoteRepository2.java Source code

Java tutorial

Introduction

Here is the source code for org.omegat.core.team2.impl.GITRemoteRepository2.java

Source

/**************************************************************************
 OmegaT - Computer Assisted Translation (CAT) tool 
      with fuzzy matching, translation memory, keyword search, 
      glossaries, and translation leveraging into updated projects.
    
 Copyright (C) 2012 Alex Buloichik
           2014 Alex Buloichik, Aaron Madlon-Kay
           Home page: http://www.omegat.org/
           Support center: http://groups.yahoo.com/group/OmegaT/
    
 This file is part of OmegaT.
    
 OmegaT is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
    
 OmegaT is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
    
 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 **************************************************************************/

package org.omegat.core.team2.impl;

import java.io.File;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import javax.xml.namespace.QName;

import org.eclipse.jgit.api.CloneCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.LsRemoteCommand;
import org.eclipse.jgit.api.ResetCommand.ResetType;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.InvalidRemoteException;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.api.errors.TransportException;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.PushResult;
import org.eclipse.jgit.transport.RemoteRefUpdate;
import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
import org.eclipse.jgit.treewalk.AbstractTreeIterator;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.omegat.core.team2.IRemoteRepository2;
import org.omegat.core.team2.ProjectTeamSettings;
import org.omegat.util.Log;

import gen.core.project.RepositoryDefinition;

/**
 * GIT repository connection implementation.
 * 
 * @author Alex Buloichik (alex73mail@gmail.com)
 * @author Aaron Madlon-Kay
 */
public class GITRemoteRepository2 implements IRemoteRepository2 {
    private static final Logger LOGGER = Logger.getLogger(GITRemoteRepository2.class.getName());

    protected static String LOCAL_BRANCH = "master";
    protected static String REMOTE_BRANCH = "origin/master";
    protected static String REMOTE = "origin";

    String repositoryURL;
    File localDirectory;

    protected Repository repository;

    static {
        CredentialsProvider.setDefault(new GITCredentialsProvider());
    }

    @Override
    public void init(RepositoryDefinition repo, File dir, ProjectTeamSettings teamSettings) throws Exception {
        repositoryURL = repo.getUrl();
        localDirectory = dir;

        String predefinedUser = repo.getOtherAttributes().get(new QName("gitUsername"));
        String predefinedPass = repo.getOtherAttributes().get(new QName("gitPassword"));
        String predefinedFingerprint = repo.getOtherAttributes().get(new QName("gitFingerprint"));
        ((GITCredentialsProvider) CredentialsProvider.getDefault()).setPredefinedCredentials(repositoryURL,
                predefinedUser, predefinedPass, predefinedFingerprint);
        ((GITCredentialsProvider) CredentialsProvider.getDefault()).setTeamSettings(teamSettings);

        File gitDir = new File(localDirectory, ".git");
        if (gitDir.exists() && gitDir.isDirectory()) {
            // already cloned
            repository = Git.open(localDirectory).getRepository();
        } else {
            Log.logInfoRB("GIT_START", "clone");
            CloneCommand c = Git.cloneRepository();
            c.setURI(repositoryURL);
            c.setDirectory(localDirectory);
            try {
                c.call();
            } catch (InvalidRemoteException e) {
                if (localDirectory.exists()) {
                    deleteDirectory(localDirectory);
                }
                Throwable cause = e.getCause();
                if (cause != null && cause instanceof org.eclipse.jgit.errors.NoRemoteRepositoryException) {
                    BadRepositoryException bre = new BadRepositoryException(
                            ((org.eclipse.jgit.errors.NoRemoteRepositoryException) cause).getLocalizedMessage());
                    bre.initCause(e);
                    throw bre;
                }
                throw e;
            }
            repository = Git.open(localDirectory).getRepository();
            try (Git git = new Git(repository)) {
                git.submoduleInit().call();
                git.submoduleUpdate().call();
            }

            // Deal with line endings. A normalized repo has LF line endings.
            // OmegaT uses line endings of OS for storing tmx files.
            // To do auto converting, we need to change a setting:
            StoredConfig config = repository.getConfig();
            if ("\r\n".equals(System.lineSeparator())) {
                // on windows machines, convert text files to CRLF
                config.setBoolean("core", null, "autocrlf", true);
            } else {
                // on Linux/Mac machines (using LF), don't convert text files
                // but use input format, unchanged.
                // NB: I don't know correct setting for OS'es like MacOS <= 9,
                // which uses CR. Git manual only speaks about converting from/to
                // CRLF, so for CR, you probably don't want conversion either.
                config.setString("core", null, "autocrlf", "input");
            }
            config.save();
            Log.logInfoRB("GIT_FINISH", "clone");
        }

        // cleanup repository
        try (Git git = new Git(repository)) {
            git.reset().setMode(ResetType.HARD).call();
        }
    }

    @Override
    public String getFileVersion(String file) throws Exception {
        File f = new File(localDirectory, file);
        if (!f.exists()) {
            return null;
        }
        return getCurrentVersion();
    }

    protected String getCurrentVersion() throws Exception {
        try (RevWalk walk = new RevWalk(repository)) {
            Ref localBranch = repository.findRef("HEAD");
            RevCommit headCommit = walk.lookupCommit(localBranch.getObjectId());
            return headCommit.getName();
        }
    }

    @Override
    public void switchToVersion(String version) throws Exception {
        try (Git git = new Git(repository)) {
            if (version == null) {
                version = REMOTE_BRANCH;
                // TODO fetch
                git.fetch().setRemote(REMOTE).call();
            }
            Log.logDebug(LOGGER, "GIT switchToVersion {0} ", version);
            git.reset().setMode(ResetType.HARD).call();
            git.checkout().setName(version).call();
            git.branchDelete().setForce(true).setBranchNames(LOCAL_BRANCH).call();
            git.checkout().setCreateBranch(true).setName(LOCAL_BRANCH).setStartPoint(version).call();
        }
    }

    @Override
    public void addForCommit(String path) throws Exception {
        Log.logInfoRB("GIT_START", "addForCommit");
        try (Git git = new Git(repository)) {
            git.add().addFilepattern(path).call();
            Log.logInfoRB("GIT_FINISH", "addForCommit");
        } catch (Exception ex) {
            Log.logErrorRB("GIT_ERROR", "addForCommit", ex.getMessage());
            throw ex;
        }
    }

    @Override
    public String commit(String[] onVersions, String comment) throws Exception {
        if (onVersions != null) {
            // check versions
            String currentVersion = getCurrentVersion();
            boolean hasVersion = false;
            for (String v : onVersions) {
                if (v != null) {
                    hasVersion = true;
                    break;
                }
            }
            if (hasVersion) {
                boolean found = false;
                for (String v : onVersions) {
                    if (v != null) {
                        if (v.equals(currentVersion)) {
                            found = true;
                            break;
                        }
                    }
                }
                if (!found) {
                    throw new RuntimeException(
                            "Version changed from " + Arrays.toString(onVersions) + " to " + currentVersion);
                }
            }
        }
        if (indexIsEmpty(DirCache.read(repository))) {
            // Nothing was actually added to the index so we can just return.
            Log.logInfoRB("GIT_NO_CHANGES", "upload");
            return null;
        }
        Log.logInfoRB("GIT_START", "upload");
        try (Git git = new Git(repository)) {
            RevCommit commit = git.commit().setMessage(comment).call();
            Iterable<PushResult> results = git.push().setRemote(REMOTE).add(LOCAL_BRANCH).call();
            List<Status> statuses = StreamSupport.stream(results.spliterator(), false)
                    .flatMap(r -> r.getRemoteUpdates().stream()).map(RemoteRefUpdate::getStatus)
                    .collect(Collectors.toList());
            String result;
            if (statuses.isEmpty() || statuses.stream().anyMatch(s -> s != RemoteRefUpdate.Status.OK)) {
                Log.logWarningRB("GIT_CONFLICT");
                result = null;
            } else {
                result = commit.getName();
            }
            Log.logDebug(LOGGER, "GIT committed into new version {0} ", result);
            Log.logInfoRB("GIT_FINISH", "upload");
            return result;
        } catch (Exception ex) {
            Log.logErrorRB("GIT_ERROR", "upload", ex.getMessage());
            if (ex instanceof TransportException) {
                throw new NetworkException(ex);
            } else {
                throw ex;
            }
        }
    }

    private boolean indexIsEmpty(DirCache dc) throws Exception {
        DirCacheIterator dci = new DirCacheIterator(dc);
        AbstractTreeIterator old = prepareTreeParser(repository, repository.resolve(Constants.HEAD));
        try (Git git = new Git(repository)) {
            List<DiffEntry> diffs = git.diff().setOldTree(old).setNewTree(dci).call();
            return diffs.isEmpty();
        }
    }

    private static AbstractTreeIterator prepareTreeParser(Repository repository, ObjectId objId) throws Exception {
        // from the commit we can build the tree which allows us to construct
        // the TreeParser
        try (RevWalk walk = new RevWalk(repository)) {
            RevCommit commit = walk.parseCommit(objId);
            RevTree tree = walk.parseTree(commit.getTree().getId());
            CanonicalTreeParser treeParser = new CanonicalTreeParser();
            ObjectReader reader = repository.newObjectReader();
            treeParser.reset(reader, tree.getId());
            return treeParser;
        }
    }

    static public boolean deleteDirectory(File path) {
        if (path.exists()) {
            File[] files = path.listFiles();
            for (int i = 0; i < files.length; i++) {
                if (files[i].isDirectory()) {
                    deleteDirectory(files[i]);
                } else {
                    files[i].delete();
                }
            }
        }
        return (path.delete());
    }

    /**
     * Determines whether or not the supplied URL represents a valid Git repository.
     * 
     * <p>
     * Does the equivalent of <code>git ls-remote <i>url</i></code>.
     * 
     * @param url
     *            URL of supposed remote repository
     * @return true if repository appears to be valid, false otherwise
     */
    public static boolean isGitRepository(String url) {
        // Heuristics to save some waiting time
        try {
            Collection<Ref> result = new LsRemoteCommand(null).setRemote(url).call();
            return !result.isEmpty();
        } catch (TransportException ex) {
            String message = ex.getMessage();
            if (message.endsWith("not authorized") || message.endsWith("Auth fail")
                    || message.contains("Too many authentication failures")
                    || message.contains("Authentication is required")) {
                return true;
            }
            return false;
        } catch (GitAPIException ex) {
            return false;
        } catch (JGitInternalException ex) {
            // Happens if the URL is a Subversion URL like svn://...
            return false;
        }
    }
}