org.openengsb.connector.git.internal.GitServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.openengsb.connector.git.internal.GitServiceImpl.java

Source

/**
 * Licensed to the Austrian Association for Software Tool Integration (AASTI)
 * under one or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information regarding copyright
 * ownership. The AASTI licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.openengsb.connector.git.internal;

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.ArchiveOutputStream;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.eclipse.jgit.api.AddCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.LogCommand;
import org.eclipse.jgit.api.RmCommand;
import org.eclipse.jgit.api.TagCommand;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheCheckout;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTag;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.storage.file.FileRepository;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import org.eclipse.jgit.transport.FetchResult;
import org.eclipse.jgit.transport.RemoteConfig;
import org.eclipse.jgit.transport.Transport;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.openengsb.connector.git.domain.GitCommitRef;
import org.openengsb.connector.git.domain.GitTagRef;
import org.openengsb.core.api.AliveState;
import org.openengsb.core.common.AbstractOpenEngSBService;
import org.openengsb.domain.scm.CommitRef;
import org.openengsb.domain.scm.ScmDomain;
import org.openengsb.domain.scm.ScmException;
import org.openengsb.domain.scm.TagRef;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class GitServiceImpl extends AbstractOpenEngSBService implements ScmDomain {
    private static final Logger LOGGER = LoggerFactory.getLogger(GitServiceImpl.class);

    private String remoteLocation;
    private File localWorkspace;
    private String watchBranch;
    private FileRepository repository;

    public GitServiceImpl(String instanceId) {
        super(instanceId);
    }

    @Override
    public AliveState getAliveState() {
        return AliveState.OFFLINE;
    }

    @Override
    public List<CommitRef> update() {
        List<CommitRef> commits = new ArrayList<CommitRef>();
        try {
            if (repository == null) {
                prepareWorkspace();
                initRepository();
            }
            Git git = new Git(repository);
            AnyObjectId oldHead = repository.resolve(Constants.HEAD);
            if (oldHead == null) {
                LOGGER.debug("Local repository is empty. Fetching remote repository.");
                FetchResult fetchResult = doRemoteUpdate();
                if (fetchResult.getTrackingRefUpdate(Constants.R_REMOTES + "origin/" + watchBranch) == null) {
                    LOGGER.debug("Nothing to fetch from remote repository.");
                    return null;
                }
                doCheckout(fetchResult);
            } else {
                LOGGER.debug("Local repository exists. Pulling remote repository.");
                git.pull().call();
            }
            AnyObjectId newHead = repository.resolve(Constants.HEAD);
            if (newHead == null) {
                LOGGER.debug("New HEAD of local repository doesnt exist.");
                return null;
            }
            if (newHead != oldHead) {
                LogCommand logCommand = git.log();
                if (oldHead == null) {
                    LOGGER.debug("Retrieving revisions from HEAD [{}] on", newHead.name());
                    logCommand.add(newHead);
                } else {
                    LOGGER.debug("Retrieving revisions in range [{}, {}]", newHead.name(), oldHead.name());
                    logCommand.addRange(oldHead, newHead);
                }
                Iterable<RevCommit> revisions = logCommand.call();
                for (RevCommit revision : revisions) {
                    commits.add(new GitCommitRef(revision));
                }
            }
        } catch (Exception e) {
            throw new ScmException(e);
        }
        return commits;
    }

    /**
     * Checks if the {@code localWorkspace} is set and creates the relevant
     * directories if necessary.
     */
    private void prepareWorkspace() {
        if (localWorkspace == null) {
            throw new ScmException("Local workspace not set.");
        }
        if (!localWorkspace.isDirectory()) {
            tryCreateLocalWorkspace();
        }
    }

    /**
     * Creates the directories necessary for the workspace.
     */
    private void tryCreateLocalWorkspace() {
        if (!localWorkspace.exists()) {
            localWorkspace.mkdirs();
        }
        if (!localWorkspace.exists()) {
            throw new ScmException(
                    "Local workspace directory '" + localWorkspace + "' does not exist and cannot be created.");
        }
        if (!localWorkspace.isDirectory()) {
            throw new ScmException("Local workspace directory '" + localWorkspace + "' is not a valid directory.");
        }
    }

    /**
     * Initializes the {@link FileRepository} or creates a new own if it does
     * not exist.
     */
    private void initRepository() throws IOException {
        FileRepositoryBuilder builder = new FileRepositoryBuilder();
        builder.setWorkTree(localWorkspace);
        repository = builder.build();
        if (!new File(localWorkspace, ".git").isDirectory()) {
            repository.create();
            repository.getConfig().setString("remote", "origin", "fetch", "+refs/heads/*:refs/remotes/origin/*");
            repository.getConfig().setString("remote", "origin", "url", remoteLocation);
            repository.getConfig().setString("branch", watchBranch, "remote", "origin");
            repository.getConfig().setString("branch", watchBranch, "merge", "refs/heads/" + watchBranch);
            repository.getConfig().save();
        }
    }

    protected void doCheckout(FetchResult fetchResult) throws IOException {
        final Ref head = fetchResult.getAdvertisedRef(Constants.R_HEADS + watchBranch);
        final RevWalk rw = new RevWalk(repository);
        final RevCommit mapCommit;
        try {
            LOGGER.debug("Mapping received reference to respective commit.");
            mapCommit = rw.parseCommit(head.getObjectId());
        } finally {
            rw.release();
        }

        final RefUpdate u;

        boolean detached = !head.getName().startsWith(Constants.R_HEADS);
        LOGGER.debug("Updating HEAD reference to revision [{}]", mapCommit.getId().name());
        u = repository.updateRef(Constants.HEAD, detached);
        u.setNewObjectId(mapCommit.getId());
        u.forceUpdate();

        DirCacheCheckout dirCacheCheckout = new DirCacheCheckout(repository, null, repository.lockDirCache(),
                mapCommit.getTree());
        dirCacheCheckout.setFailOnConflict(true);
        boolean checkoutResult = dirCacheCheckout.checkout();
        LOGGER.debug("Checked out new repository revision to working directory");
        if (!checkoutResult) {
            throw new IOException("Internal error occured on checking out files");
        }
    }

    protected FetchResult doRemoteUpdate() throws IOException {
        List<RemoteConfig> remoteConfig = null;
        try {
            LOGGER.debug("Fetching remote configurations from repository configuration");
            remoteConfig = RemoteConfig.getAllRemoteConfigs(repository.getConfig());
        } catch (URISyntaxException e) {
            throw new ScmException(e);
        }

        LOGGER.debug("Opening transport to {}", remoteConfig.get(0).getName());
        Transport transport = Transport.open(repository, remoteConfig.get(0));
        try {
            LOGGER.debug("Fetching content from remote repository");
            return transport.fetch(NullProgressMonitor.INSTANCE, null);
        } finally {
            if (transport != null) {
                transport.close();
            }
        }
    }

    @Override
    public File export() {
        try {
            if (repository == null) {
                initRepository();
            }

            LOGGER.debug("Exporting repository to archive");
            File tmp = File.createTempFile("repository", ".zip");
            ZipArchiveOutputStream zos = new ZipArchiveOutputStream(tmp);
            packRepository(localWorkspace, zos);
            zos.close();
            return tmp;
        } catch (IOException e) {
            throw new ScmException(e);
        }
    }

    @Override
    public File export(CommitRef ref) {
        RevWalk rw = null;
        File tmp = null;
        try {
            if (repository == null) {
                initRepository();
            }

            LOGGER.debug("Resolving HEAD and reference [{}]", ref.getStringRepresentation());
            AnyObjectId headId = repository.resolve(Constants.HEAD);
            AnyObjectId refId = repository.resolve(ref.getStringRepresentation());
            if (headId == null || refId == null) {
                throw new ScmException("HEAD or reference [" + ref.getStringRepresentation() + "] doesn't exist.");
            }
            rw = new RevWalk(repository);
            RevCommit head = rw.parseCommit(headId);
            RevCommit commit = rw.parseCommit(refId);

            LOGGER.debug("Checking out working copy of revision");
            checkoutIndex(commit);

            tmp = File.createTempFile("repository", ".zip");
            LOGGER.debug("Exporting repository to archive");
            ZipArchiveOutputStream zos = new ZipArchiveOutputStream(tmp);
            packRepository(localWorkspace, zos);
            zos.close();

            LOGGER.debug("Checking out working copy of former HEAD revision");
            checkoutIndex(head);
        } catch (IOException e) {
            throw new ScmException(e);
        } finally {
            if (rw != null) {
                rw.release();
            }
        }
        return tmp;
    }

    /**
     * Packs the files and directories of a passed {@link File} to a passed
     * {@link ArchiveOutputStream}.
     *
     * @throws IOException
     */
    private void packRepository(File source, ArchiveOutputStream aos) throws IOException {
        int bufferSize = 2048;
        byte[] readBuffer = new byte[bufferSize];
        int bytesIn = 0;
        File[] files = source.listFiles(new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                return !pathname.getName().equals(Constants.DOT_GIT);
            }
        });
        for (File file : files) {
            if (file.isDirectory()) {
                ArchiveEntry ae = aos.createArchiveEntry(file, getRelativePath(file.getAbsolutePath()));
                aos.putArchiveEntry(ae);
                aos.closeArchiveEntry();
                packRepository(file, aos);
            } else {
                FileInputStream fis = new FileInputStream(file);
                ArchiveEntry ae = aos.createArchiveEntry(file, getRelativePath(file.getAbsolutePath()));
                aos.putArchiveEntry(ae);
                while ((bytesIn = fis.read(readBuffer)) != -1) {
                    aos.write(readBuffer, 0, bytesIn);
                }
                aos.closeArchiveEntry();
                fis.close();
            }
        }
    }

    private void checkoutIndex(RevCommit commit) {
        DirCache dc = null;
        try {
            dc = repository.lockDirCache();
            DirCacheCheckout checkout = new DirCacheCheckout(repository, dc, commit.getTree());
            checkout.setFailOnConflict(false);
            checkout.checkout();
        } catch (IOException e) {
            throw new ScmException(e);
        } finally {
            if (dc != null) {
                dc.unlock();
            }
        }
    }

    public void setRemoteLocation(String remoteLocation) {
        // https://bugs.eclipse.org/bugs/show_bug.cgi?id=323571
        if (remoteLocation.startsWith("file:/") && !remoteLocation.startsWith("file:///")) {
            remoteLocation = remoteLocation.replace("file:/", "file:///");
        }
        this.remoteLocation = remoteLocation;
    }

    public void setLocalWorkspace(String localWorkspace) {
        this.localWorkspace = new File(localWorkspace);
    }

    public void setWatchBranch(String watchBranch) {
        this.watchBranch = watchBranch;
    }

    public FileRepository getRepository() {
        if (repository == null) {
            prepareWorkspace();
            try {
                initRepository();
            } catch (IOException e) {
                throw new ScmException(e);
            }
        }
        return repository;
    }

    @Override
    public boolean exists(String arg0) {
        try {
            AnyObjectId id = repository.resolve(Constants.HEAD);
            RevCommit commit = new RevWalk(repository).parseCommit(id);
            LOGGER.debug("Looking up file {} in HEAD revision", arg0);
            TreeWalk treeWalk = TreeWalk.forPath(repository, arg0, new AnyObjectId[] { commit.getTree() });
            if (treeWalk == null) {
                return false;
            }
            ObjectId objectId = treeWalk.getObjectId(treeWalk.getTreeCount() - 1);
            LOGGER.debug("File {} received commit id {} at commit", arg0, objectId.name());
            return !objectId.equals(ObjectId.zeroId());
        } catch (Exception e) {
            throw new ScmException(e);
        }
    }

    @Override
    public File get(String file) {
        try {
            if (repository == null) {
                initRepository();
            }
            AnyObjectId id = repository.resolve(Constants.HEAD);
            RevCommit commit = new RevWalk(repository).parseCommit(id);
            LOGGER.debug("Looking up file {} in HEAD revision", file);
            TreeWalk treeWalk = TreeWalk.forPath(repository, file, new AnyObjectId[] { commit.getTree() });
            if (treeWalk == null) {
                return null;
            }
            ObjectId objectId = treeWalk.getObjectId(treeWalk.getTreeCount() - 1);
            if (objectId == ObjectId.zeroId()) {
                LOGGER.debug("File {} couldn't be found in HEAD revision", file);
                return null;
            }
            String fileName = getFilename(file);
            LOGGER.debug("Creating file from saved repository content");
            File tmp = File.createTempFile(fileName, null);
            tmp.deleteOnExit();
            OutputStream os = new FileOutputStream(tmp);
            os.write(repository.open(objectId).getCachedBytes());
            os.close();
            return tmp;
        } catch (Exception e) {
            throw new ScmException(e);
        }
    }

    @Override
    public boolean exists(String arg0, CommitRef arg1) {
        try {
            AnyObjectId id = repository.resolve(arg1.getStringRepresentation());
            RevCommit commit = new RevWalk(repository).parseCommit(id);
            LOGGER.debug("Looking up file {} in revision {}", arg0, arg1.getStringRepresentation());
            TreeWalk treeWalk = TreeWalk.forPath(repository, arg0, new AnyObjectId[] { commit.getTree() });
            if (treeWalk == null) {
                return false;
            }
            ObjectId objectId = treeWalk.getObjectId(treeWalk.getTreeCount() - 1);
            LOGGER.debug("File {} received commit id {} at commit", arg0, objectId.name());
            return !objectId.equals(ObjectId.zeroId());
        } catch (Exception e) {
            throw new ScmException(e);
        }
    }

    @Override
    public File get(String file, CommitRef ref) {
        try {
            if (repository == null) {
                initRepository();
            }
            AnyObjectId id = repository.resolve(ref.getStringRepresentation());
            RevCommit commit = new RevWalk(repository).parseCommit(id);
            LOGGER.debug("Looking up file {} in revision {}", file, ref.getStringRepresentation());
            TreeWalk treeWalk = TreeWalk.forPath(repository, file, new AnyObjectId[] { commit.getTree() });
            if (treeWalk == null) {
                return null;
            }
            ObjectId objectId = treeWalk.getObjectId(treeWalk.getTreeCount() - 1);
            if (objectId == ObjectId.zeroId()) {
                return null;
            }
            String fileName = getFilename(file);
            LOGGER.debug("Creating file from saved repository content");
            File tmp = File.createTempFile(fileName, null);
            tmp.deleteOnExit();
            OutputStream os = new FileOutputStream(tmp);
            os.write(repository.open(objectId).getCachedBytes());
            os.close();
            return tmp;
        } catch (Exception e) {
            throw new ScmException(e);
        }
    }

    /**
     * Returns the name of a file from a passed repository path.
     */
    private String getFilename(String path) {
        String fileName;
        if (path.contains("/")) {
            fileName = path.substring(path.lastIndexOf("/") + 1);
        } else {
            fileName = path;
        }
        return fileName;
    }

    @Override
    public CommitRef getHead() {
        try {
            if (repository == null) {
                initRepository();
            }
            AnyObjectId id = repository.resolve(Constants.HEAD);
            RevCommit commit = new RevWalk(repository).parseCommit(id);
            LOGGER.debug("Resolved HEAD to commit {}", commit.getId().name());
            return new GitCommitRef(commit);
        } catch (IOException e) {
            if (repository != null) {
                repository.close();
                repository = null;
            }
            throw new ScmException(e);
        }
    }

    @Override
    public CommitRef add(String comment, File... file) {
        if (file.length == 0) {
            LOGGER.debug("No files to add in list");
            return null;
        }
        if (repository == null) {
            prepareWorkspace();
            try {
                initRepository();
            } catch (IOException e) {
                if (repository != null) {
                    repository.close();
                }
                throw new ScmException(e);
            }
        }

        Git git = new Git(repository);
        AddCommand add = git.add();
        try {
            for (File toCommit : file) {
                if (!toCommit.exists()) {
                    throw new ScmException("File " + toCommit + " is not a valid file to commit.");
                }
                String filepattern = getRelativePath(toCommit.getAbsolutePath());
                LOGGER.debug("Adding file {} in working directory to repository", filepattern);
                add.addFilepattern(filepattern);
            }

            add.call();
            LOGGER.debug("Committing added files with comment '{}'", comment);
            return new GitCommitRef(git.commit().setMessage(comment).call());
        } catch (Exception e) {
            throw new ScmException(e);
        }
    }

    /**
     * Returns the relative path of an absolute {@code filePath} in comparison
     * to the working directory of the repository.
     */
    private String getRelativePath(String filePath) {
        final String repoPath = repository.getWorkTree().getAbsolutePath();
        if (filePath.startsWith(repoPath)) {
            return filePath.substring(repoPath.length() + 1);
        } else {
            throw new ScmException("File is not in working directory.");
        }
    }

    @Override
    public CommitRef remove(String comment, File... file) {
        if (file.length == 0) {
            LOGGER.debug("No files to add in list");
            return null;
        }
        if (repository == null) {
            prepareWorkspace();
            try {
                initRepository();
            } catch (IOException e) {
                if (repository != null) {
                    repository.close();
                    repository = null;
                }
                throw new ScmException(e);
            }
        }

        Git git = new Git(repository);
        RmCommand rm = git.rm();
        try {
            for (File toCommit : file) {
                if (!toCommit.exists()) {
                    throw new ScmException("File " + toCommit + " is not a valid file to commit.");
                }
                String filepattern = getRelativePath(toCommit.getAbsolutePath());
                LOGGER.debug("Removing file {} in working directory from repository", filepattern);
                rm.addFilepattern(filepattern);
            }

            rm.call();
            LOGGER.debug("Committing removed files with comment '{}'", comment);
            return new GitCommitRef(git.commit().setMessage(comment).call());
        } catch (Exception e) {
            throw new ScmException(e);
        }
    }

    @Override
    public TagRef tagRepo(String tagName) {
        try {
            if (repository == null) {
                initRepository();
            }
            TagCommand tag = new Git(repository).tag();
            LOGGER.debug("Tagging HEAD with name '{}'", tagName);
            return new GitTagRef(tag.setName(tagName).call());
        } catch (Exception e) {
            throw new ScmException(e);
        }
    }

    @Override
    public TagRef tagRepo(String tagName, CommitRef ref) {
        try {
            if (repository == null) {
                initRepository();
            }
            AnyObjectId commitRef = repository.resolve(ref.getStringRepresentation());
            if (commitRef == null) {
                LOGGER.debug("Couldnt resolve reference {} in repository", ref.getStringRepresentation());
                return null;
            }
            RevWalk walk = new RevWalk(repository);
            RevCommit revCommit = walk.parseCommit(commitRef);
            TagCommand tag = new Git(repository).tag();
            tag.setName(tagName).setObjectId(revCommit);
            LOGGER.debug("Tagging revision {} with name '{}'", ref.getStringRepresentation(), tagName);
            return new GitTagRef(tag.call());
        } catch (Exception e) {
            throw new ScmException(e);
        }
    }

    @Override
    public CommitRef getCommitRefForTag(TagRef ref) {
        try {
            if (repository == null) {
                initRepository();
            }
            AnyObjectId tagRef = repository.resolve(ref.getStringRepresentation());
            if (tagRef == null) {
                LOGGER.debug("Couldnt resolve reference {} in repository", ref.getStringRepresentation());
                return null;
            }
            RevWalk walk = new RevWalk(repository);
            RevTag revTag = walk.parseTag(tagRef);
            CommitRef commitRef = null;
            if (revTag.getObject() instanceof RevCommit) {
                commitRef = new GitCommitRef((RevCommit) revTag.getObject());
                LOGGER.debug("Resolved reference {} to commit {}", ref.getStringRepresentation(),
                        commitRef.getStringRepresentation());
            }
            return commitRef;
        } catch (IOException e) {
            throw new ScmException(e);
        }
    }
}