Java tutorial
/* * 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; } }