net.sourceforge.vulcan.git.GitRepository.java Source code

Java tutorial

Introduction

Here is the source code for net.sourceforge.vulcan.git.GitRepository.java

Source

/*
 * Vulcan Build Manager
 * Copyright (C) 2005-2012 Chris Eldredge
 * 
 * This program 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 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
package net.sourceforge.vulcan.git;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import net.sourceforge.vulcan.RepositoryAdaptor;
import net.sourceforge.vulcan.core.BuildDetailCallback;
import net.sourceforge.vulcan.core.support.FileSystem;
import net.sourceforge.vulcan.core.support.FileSystemImpl;
import net.sourceforge.vulcan.dto.ChangeLogDto;
import net.sourceforge.vulcan.dto.ProjectConfigDto;
import net.sourceforge.vulcan.dto.ProjectStatusDto;
import net.sourceforge.vulcan.dto.RepositoryTagDto;
import net.sourceforge.vulcan.dto.RevisionTokenDto;
import net.sourceforge.vulcan.exception.RepositoryException;

import org.apache.commons.io.filefilter.NameFileFilter;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.SystemUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class GitRepository implements RepositoryAdaptor {
    protected static enum Command {
        clone(true), fetch(true), pull(true), log(false), diff(false), update(false), purge(false), branch(
                false), tag(false), heads(false);

        private final boolean remote;

        Command(boolean remote) {
            this.remote = remote;
        }

        public boolean isRemote() {
            return remote;
        }
    }

    private static final Log LOG = LogFactory.getLog(GitRepository.class);

    private final static Pattern tagWithRevisionPattern = Pattern.compile("^(.*)\\s+(\\d+:\\w+)$",
            Pattern.MULTILINE);

    private final ProjectConfigDto projectConfig;
    private final GitProjectConfig settings;
    private final GitConfig globals;

    private FileSystem fileSystem = new FileSystemImpl();
    private String changeLogTemplatePath = "xml";

    public GitRepository(ProjectConfigDto projectConfig, GitConfig globals) {
        this.projectConfig = projectConfig;
        this.settings = (GitProjectConfig) projectConfig.getRepositoryAdaptorConfig().copy();
        this.globals = globals;
    }

    @Override
    public boolean hasIncomingChanges(ProjectStatusDto mostRecentBuildInSameWorkDir) throws RepositoryException {
        try {
            final RevisionTokenDto latestRevision = getLatestRevision(mostRecentBuildInSameWorkDir.getRevision());

            if (!latestRevision.equals(mostRecentBuildInSameWorkDir.getRevision())) {
                return true;
            }

            tryInvoke(Command.fetch);

            RevisionTokenDto latestRevision0 = getLatestRevision0(true);
            return !latestRevision.equals(latestRevision0);

        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void prepareRepository(BuildDetailCallback buildDetailCallback)
            throws RepositoryException, InterruptedException {
        final File workDir = getLocalRepositoryPath();

        if (!isWorkingCopy()) {
            clone(workDir, buildDetailCallback);
        } else {
            pull(buildDetailCallback);
        }
    }

    public void clone(File workDir, BuildDetailCallback buildDetailCallback) throws RepositoryException {
        if (!isRemoteRepositoryConfigured()) {
            throw new RepositoryException("hg.errors.no.repo.and.no.remote", null, workDir);
        }

        buildDetailCallback.setDetailMessage("hg.activity.clone", null);

        List<String> args = new ArrayList<String>();

        args.add("--progress");
        args.add("-v");
        if (SystemUtils.IS_OS_WINDOWS) {
            args.add("\"" + settings.getRemoteRepositoryUrl() + "\"");
            args.add("\"" + workDir.getAbsolutePath() + "\"");
        } else {
            args.add(settings.getRemoteRepositoryUrl());
            args.add(workDir.getAbsolutePath());
        }

        tryInvoke(Command.clone, args.toArray(new String[args.size()]));
    }

    public void pull(BuildDetailCallback buildDetailCallback) throws RepositoryException {
        if (!isRemoteRepositoryConfigured()) {
            return;
        }

        List<String> args = new ArrayList<String>();
        args.add("--progress");
        args.add("-v");
        if (SystemUtils.IS_OS_WINDOWS) {
            args.add("\"origin\"");
        } else {
            args.add("origin");
        }

        if (buildDetailCallback != null) {
            buildDetailCallback.setDetailMessage("hg.activity.pull", null);
        }

        tryInvoke(Command.pull, args.toArray(new String[args.size()]));
    }

    @Override
    public boolean isWorkingCopy() throws RepositoryException {
        if (!fileSystem.directoryExists(getLocalRepositoryPath())) {
            return false;
        }

        return new File(getLocalRepositoryPath(), ".git").exists();
    }

    @Override
    public RevisionTokenDto getLatestRevision(RevisionTokenDto previousRevision)
            throws RepositoryException, InterruptedException {
        return getLatestRevision0(false);
    }

    public RevisionTokenDto getLatestRevision0(boolean origin) throws RepositoryException {
        InvocationResult result = tryInvoke(Command.log, "-1",
                origin ? "origin/" + getSelectedBranch() : getSelectedBranch());

        final String[] output = result.getOutput().split("\n");
        for (String s : output) {
            if (s.startsWith("commit")) {
                String[] split = s.split(" ");
                return new RevisionTokenDto(Long.valueOf(split[1].hashCode()), split[1]);
            }
        }

        throw new RepositoryException("Wrong result\n" + result.getOutput(), new InterruptedException());
    }

    boolean isRemoteRepositoryConfigured() {
        return !StringUtils.isBlank(settings.getRemoteRepositoryUrl());
    }

    String getSelectedBranch() {
        if (StringUtils.isBlank(settings.getBranch())) {
            return "master";
        }

        return settings.getBranch();
    }

    File getLocalRepositoryPath() {
        return new File(projectConfig.getWorkDir());
    }

    @Override
    public void createPristineWorkingCopy(BuildDetailCallback buildDetailCallback)
            throws RepositoryException, InterruptedException {
        if (globals.isPurgeEnabled()) {
            buildDetailCallback.setDetailMessage("hg.activity.update", null);
            tryInvoke(Command.update, "--clean", getSelectedBranch());

            buildDetailCallback.setDetailMessage("hg.activity.purge", null);
            tryInvoke(Command.purge, "--all", "--config", "extensions.purge=");
        } else {
            buildDetailCallback.setDetailMessage("hg.activity.remove.working.copy", null);
            tryInvoke(Command.update, "--clean", "null");

            buildDetailCallback.setDetailMessage("hg.activity.purge", null);

            try {
                fileSystem.cleanDirectory(getLocalRepositoryPath(), new NameFileFilter(".hg"));
            } catch (IOException e) {
                throw new RepositoryException("hg.errors.delete.files", e);
            }

            buildDetailCallback.setDetailMessage("hg.activity.update", null);
            tryInvoke(Command.update, getSelectedBranch());
        }
    }

    @Override
    public void updateWorkingCopy(BuildDetailCallback buildDetailCallback) throws RepositoryException {
        tryInvoke(Command.update, getSelectedBranch());
    }

    @Override
    public ChangeLogDto getChangeLog(RevisionTokenDto previousRevision, RevisionTokenDto currentRevision,
            OutputStream diffOutputStream) throws RepositoryException, InterruptedException {
        return new ChangeLogDto();
    }

    @Override
    public List<RepositoryTagDto> getAvailableTagsAndBranches() throws RepositoryException {
        final List<RepositoryTagDto> results = new ArrayList<RepositoryTagDto>();
        final List<String> namedRevisions = new ArrayList<String>();

        addAvailableTags(Command.branch, results, namedRevisions);
        addAvailableTags(Command.tag, results, namedRevisions);

        addAnonymousHeads(results, namedRevisions);

        return results;
    }

    private void addAvailableTags(Command command, List<RepositoryTagDto> results, List<String> namedRevisions)
            throws RepositoryException {
        String[] args = new String[0];
        if (command == Command.branch) {
            args = new String[] { "--active" };
        }

        final InvocationResult result = tryInvoke(command, args);

        final Matcher matcher = tagWithRevisionPattern.matcher(result.getOutput());

        while (matcher.find()) {
            final String name = matcher.group(1).trim();
            final String revision = matcher.group(2);

            results.add(new RepositoryTagDto(name, name));
            namedRevisions.add(revision);
        }
    }

    private void addAnonymousHeads(List<RepositoryTagDto> results, List<String> namedRevisions)
            throws RepositoryException {
        final InvocationResult result = tryInvoke(Command.heads, "--quiet", "--topo");

        for (String s : result.getOutput().split("\n")) {
            s = s.trim();
            if (s.equals("") || namedRevisions.contains(s)) {
                continue;
            }

            results.add(new RepositoryTagDto(s, s));
        }
    }

    protected InvocationResult tryInvoke(Command command, String... args) throws RepositoryException {
        return tryInvokeWithStream(command, null, args);
    }

    protected InvocationResult tryInvokeWithStream(Command command, OutputStream output, String... args)
            throws RepositoryException {
        final File workDir = getLocalRepositoryPath();

        if (!fileSystem.directoryExists(workDir)) {
            try {
                fileSystem.createDirectory(workDir);
            } catch (IOException e) {
                throw new RepositoryException("hg.errors.mkdir", e, workDir);
            }
        }

        final Invoker invoker = createInvoker();

        if (output != null) {
            invoker.setOutputStream(output);
        }

        try {
            return invoker.invoke(command.name(), workDir, args);
        } catch (IOException e) {
            final String errorText = invoker.getErrorText();

            StringBuilder sb = new StringBuilder();
            sb.append("Unexpected exception invoking hg: stderr: ");
            sb.append(errorText);
            if (output == null) {
                final String stdout = invoker.getOutputText();
                if (StringUtils.isNotBlank(stdout)) {
                    sb.append("\nstdout: ");
                    sb.append(stdout);
                }
            }

            LOG.error(sb.toString(), e);

            throw new RepositoryException("hg.errors.invocation", e, errorText, invoker.getExitCode());
        }
    }

    protected Invoker createInvoker() {
        final ProcessInvoker invoker = new ProcessInvoker();

        invoker.setExecutable(globals.getExecutable());

        return invoker;
    }

    @Override
    public String getRepositoryUrl() {
        return settings.getRemoteRepositoryUrl();
    }

    @Override
    public String getTagOrBranch() {
        return getSelectedBranch();
    }

    @Override
    public void setTagOrBranch(String tagName) {
        settings.setBranch(tagName);
    }

    public void setFileSystem(FileSystem fileSystem) {
        this.fileSystem = fileSystem;
    }

    public String getChangeLogTemplatePath() {
        return changeLogTemplatePath;
    }

    public void setChangeLogTemplatePath(String changeLogTemplatePath) {
        this.changeLogTemplatePath = changeLogTemplatePath;
    }

    protected ProjectConfigDto getProjectConfig() {
        return projectConfig;
    }

    protected GitProjectConfig getSettings() {
        return settings;
    }

    protected GitConfig getGlobals() {
        return globals;
    }
}