improve performance of mercurial changesetviewer

This commit is contained in:
Sebastian Sdorra
2011-09-29 14:57:00 +02:00
parent 52527af7f8
commit 860e9367c9
7 changed files with 319 additions and 204 deletions

View File

@@ -38,25 +38,18 @@ package sonia.scm.repository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import sonia.scm.io.Command;
import sonia.scm.io.CommandResult;
import sonia.scm.io.SimpleCommand;
import sonia.scm.util.IOUtil;
import sonia.scm.util.AssertUtil;
import sonia.scm.web.HgUtil;
//~--- JDK imports ------------------------------------------------------------
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.bind.JAXBContext;
/**
*
@@ -65,30 +58,17 @@ import javax.xml.parsers.ParserConfigurationException;
public class HgChangesetViewer implements ChangesetViewer
{
/** Field description */
public static final String ID_TIP = "tip";
/** Field description */
//J-
public static final String TEMPLATE_CHANGESETS =
"\"<changeset>"
+ "<id>{rev}:{node|short}</id>"
+ "<author>{author|escape}</author>"
+ "<description>{desc|escape}</description>"
+ "<date>{date|isodatesec}</date>"
+ "<tags>{tags}</tags>"
+ "<branches>{branches}</branches>"
+ "<files-added>{file_adds}</files-added>"
+ "<files-mods>{file_mods}</files-mods>"
+ "<files-dels>{file_dels}</files-dels>"
+ "</changeset>\"";
//J+
/** Field description */
public static final String ENV_PENDING = "HG_PENDING";
/** Field description */
public static final String TEMPLATE_TOTAL = "{rev}";
public static final String ENV_REVISION_LIMIT = "SCM_REVISION_LIMIT";
/** Field description */
public static final String ENV_REVISION_START = "SCM_REVISION_START";
/** Field description */
public static final String RESOURCE_LOG = "/sonia/scm/hglog.py";
/** the logger for HgChangesetViewer */
private static final Logger logger =
@@ -102,11 +82,16 @@ public class HgChangesetViewer implements ChangesetViewer
*
*
* @param handler
* @param repository
* @param repositoryDirectory
* @param changesetPagingResultContext
*/
public HgChangesetViewer(HgRepositoryHandler handler, Repository repository)
public HgChangesetViewer(HgRepositoryHandler handler,
File repositoryDirectory,
JAXBContext changesetPagingResultContext)
{
this(handler, handler.getDirectory(repository).getAbsolutePath());
this.handler = handler;
this.repositoryDirectory = repositoryDirectory;
this.changesetPagingResultContext = changesetPagingResultContext;
}
/**
@@ -114,12 +99,14 @@ public class HgChangesetViewer implements ChangesetViewer
*
*
* @param handler
* @param repositoryPath
* @param repository
* @param changesetPagingResultContext
*/
public HgChangesetViewer(HgRepositoryHandler handler, String repositoryPath)
public HgChangesetViewer(HgRepositoryHandler handler, Repository repository,
JAXBContext changesetPagingResultContext)
{
this.handler = handler;
this.repositoryPath = repositoryPath;
this(handler, handler.getDirectory(repository),
changesetPagingResultContext);
}
//~--- get methods ----------------------------------------------------------
@@ -131,51 +118,14 @@ public class HgChangesetViewer implements ChangesetViewer
* @param max
*
* @return
*
* @throws IOException
*/
@Override
public ChangesetPagingResult getChangesets(int start, int max)
throws IOException
{
ChangesetPagingResult changesets = null;
InputStream in = null;
try
{
int total = getTotalChangesets(repositoryPath);
int startRev = total - start;
int endRev = total - start - (max - 1);
if (endRev < 0)
{
endRev = 0;
}
List<Changeset> changesetList = getChangesets(Integer.toString(startRev),
Integer.toString(endRev));
if (changesetList != null)
{
if (total == -1)
{
total = 0;
}
else
{
total++;
}
changesets = new ChangesetPagingResult(total, changesetList);
}
}
catch (IOException ex)
{
logger.error("could not load changesets", ex);
}
finally
{
IOUtil.close(in);
}
return changesets;
return getChangesets(String.valueOf(start), String.valueOf(max), false);
}
/**
@@ -183,66 +133,41 @@ public class HgChangesetViewer implements ChangesetViewer
*
*
* @param startRev
* @param endRev
* @param limit
* @param pending
*
* @return
*
* @throws IOException
*/
public List<Changeset> getChangesets(String startRev, String endRev,
public ChangesetPagingResult getChangesets(String startRev, String limit,
boolean pending)
throws IOException
{
List<Changeset> changesetList = null;
InputStream in = null;
AssertUtil.assertIsNotEmpty(startRev);
AssertUtil.assertIsNotEmpty(limit);
try
if (logger.isDebugEnabled())
{
SimpleCommand command =
new SimpleCommand(handler.getConfig().getHgBinary(), "-R",
repositoryPath, "log", "-r", startRev + ":" + endRev,
"--template", TEMPLATE_CHANGESETS);
if (pending)
{
Map<String, String> env = new HashMap<String, String>();
env.put(ENV_PENDING, repositoryPath);
command.setEnvironment(env);
}
CommandResult result = command.execute();
if (result.isSuccessfull())
{
StringBuilder sb = new StringBuilder("<changesets>");
sb.append(result.getOutput()).append("</changesets>");
changesetList = new HgChangesetParser().parse(
new InputSource(new StringReader(sb.toString())));
}
else if (logger.isErrorEnabled())
{
logger.error(
"command for fetching changesets failed with exit code {} and output {}",
result.getReturnCode(), result.getOutput());
}
}
catch (ParserConfigurationException ex)
{
logger.error("could not parse changesets", ex);
}
catch (SAXException ex)
{
logger.error("could not unmarshall changesets", ex);
}
finally
{
IOUtil.close(in);
logger.debug("get changesets for repository {}, start: {}, limit: {}",
new Object[] { repositoryDirectory.getName(),
startRev, limit });
}
return changesetList;
Map<String, String> env = new HashMap<String, String>();
if (pending)
{
env.put(ENV_PENDING, repositoryDirectory.getAbsolutePath());
}
env.put(ENV_REVISION_START, startRev);
env.put(ENV_REVISION_LIMIT, limit);
return HgUtil.getResultFromScript(ChangesetPagingResult.class,
changesetPagingResultContext,
RESOURCE_LOG, handler,
repositoryDirectory, env);
}
/**
@@ -250,55 +175,26 @@ public class HgChangesetViewer implements ChangesetViewer
*
*
* @param startRev
* @param endRev
* @param limit
*
* @return
*
* @throws IOException
*/
public List<Changeset> getChangesets(String startRev, String endRev)
public ChangesetPagingResult getChangesets(String startRev, String limit)
throws IOException
{
return getChangesets(startRev, endRev, false);
}
/**
* Method description
*
*
* @param repositoryPath
*
* @return
*
* @throws IOException
*/
private int getTotalChangesets(String repositoryPath) throws IOException
{
int total = -1;
Command command = new SimpleCommand(handler.getConfig().getHgBinary(),
"-R", repositoryPath, "tip", "--template",
TEMPLATE_TOTAL);
CommandResult result = command.execute();
if (result.isSuccessfull())
{
total = Integer.parseInt(result.getOutput().trim());
}
else if (logger.isErrorEnabled())
{
logger.error(
"could not read tip revision, command returned with exit code {} and content {}",
result.getReturnCode(), result.getOutput());
}
return total;
return getChangesets(startRev, limit, false);
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private JAXBContext changesetPagingResultContext;
/** Field description */
private HgRepositoryHandler handler;
/** Field description */
private String repositoryPath;
private File repositoryDirectory;
}

View File

@@ -50,7 +50,6 @@ import java.io.InputStream;
import java.io.OutputStream;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
/**
*

View File

@@ -115,6 +115,8 @@ public class HgRepositoryHandler
{
this.browserResultContext = JAXBContext.newInstance(BrowserResult.class);
this.blameResultContext = JAXBContext.newInstance(BlameResult.class);
this.changesetPagingResultContext =
JAXBContext.newInstance(ChangesetPagingResult.class);
}
catch (JAXBException ex)
{
@@ -240,7 +242,8 @@ public class HgRepositoryHandler
if (TYPE_NAME.equals(type))
{
changesetViewer = new HgChangesetViewer(this, repository);
changesetViewer = new HgChangesetViewer(this, repository,
changesetPagingResultContext);
}
else
{
@@ -310,6 +313,27 @@ public class HgRepositoryHandler
return TYPE;
}
/**
* Method description
*
*
* @param repositoryDirectory
*
* @return
*/
HgChangesetViewer getChangesetViewer(File repositoryDirectory)
{
AssertUtil.assertIsNotNull(repositoryDirectory);
if (!repositoryDirectory.isDirectory())
{
throw new IllegalStateException("directory not found");
}
return new HgChangesetViewer(this, repositoryDirectory,
changesetPagingResultContext);
}
//~--- methods --------------------------------------------------------------
/**
@@ -552,4 +576,7 @@ public class HgRepositoryHandler
/** Field description */
private JAXBContext browserResultContext;
/** Field description */
private JAXBContext changesetPagingResultContext;
}

View File

@@ -110,8 +110,13 @@ public class HgRepositoryHookEvent extends AbstractRepositoryHookEvent
logger.debug("load {}changesets for hook {}", pendingString, type);
}
changesets = createChangesetViewer().getChangesets(startRev, REV_TIP,
pending);
ChangesetPagingResult result =
createChangesetViewer().getChangesets(startRev, REV_TIP, pending);
if (result != null)
{
changesets = result.getChangesets();
}
}
catch (IOException ex)
{
@@ -147,8 +152,7 @@ public class HgRepositoryHookEvent extends AbstractRepositoryHookEvent
File directory = handler.getConfig().getRepositoryDirectory();
File repositoryDirectory = new File(directory, repositoryName);
return new HgChangesetViewer(handler,
repositoryDirectory.getAbsolutePath());
return handler.getChangesetViewer(repositoryDirectory);
}
//~--- fields ---------------------------------------------------------------

View File

@@ -39,7 +39,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.SCMContext;
import sonia.scm.repository.BrowserResult;
import sonia.scm.repository.HgConfig;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.Repository;
@@ -53,6 +52,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import javax.xml.bind.JAXBContext;
@@ -96,16 +96,15 @@ public class HgUtil
*
*
* @param handler
* @param repository
* @param revision
* @param path
* @param directory
* @param extraEnv
*
* @return
*
* @throws IOException
*/
public static Process createPythonProcess(HgRepositoryHandler handler,
Repository repository, String revision, String path)
File directory, Map<String, String> extraEnv)
throws IOException
{
HgConfig config = handler.getConfig();
@@ -113,12 +112,8 @@ public class HgUtil
Map<String, String> env = pb.environment();
env.put(ENV_PYTHON_PATH, Util.nonNull(config.getPythonPath()));
String directory = handler.getDirectory(repository).getAbsolutePath();
env.put(ENV_REPOSITORY_PATH, directory);
env.put(ENV_REVISION, getRevision(revision));
env.put(ENV_PATH, Util.nonNull(path));
env.put(ENV_REPOSITORY_PATH, directory.getAbsolutePath());
env.putAll(extraEnv);
return pb.start();
}
@@ -145,21 +140,6 @@ public class HgUtil
return new File(cgiDirectory, CGI_NAME);
}
/**
* Method description
*
*
* @param revision
*
* @return
*/
public static String getRevision(String revision)
{
return Util.isEmpty(revision)
? REVISION_TIP
: revision;
}
/**
* Method description
*
@@ -168,23 +148,21 @@ public class HgUtil
* @param context
* @param scriptResource
* @param handler
* @param repository
* @param revision
* @param path
* @param directory
* @param extraEnv
* @param <T>
*
* @return
*
* @throws IOException
*/
public static <T> T getResultFromScript(Class<T> resultType, JAXBContext context,
String scriptResource,
HgRepositoryHandler handler,
Repository repository, String revision,
String path)
public static <T> T getResultFromScript(Class<T> resultType,
JAXBContext context, String scriptResource,
HgRepositoryHandler handler, File directory,
Map<String, String> extraEnv)
throws IOException
{
Process p = createPythonProcess(handler, repository, revision, path);
Process p = createPythonProcess(handler, directory, extraEnv);
T result = null;
InputStream resource = null;
InputStream input = null;
@@ -213,4 +191,79 @@ public class HgUtil
return result;
}
/**
* Method description
*
*
* @param resultType
* @param context
* @param scriptResource
* @param handler
* @param repository
* @param extraEnv
* @param <T>
*
* @return
*
* @throws IOException
*/
public static <T> T getResultFromScript(Class<T> resultType,
JAXBContext context, String scriptResource,
HgRepositoryHandler handler, Repository repository,
Map<String, String> extraEnv)
throws IOException
{
File directory = handler.getDirectory(repository);
return getResultFromScript(resultType, context, scriptResource, handler,
directory, extraEnv);
}
/**
* Method description
*
*
* @param resultType
* @param context
* @param scriptResource
* @param handler
* @param repository
* @param revision
* @param path
* @param <T>
*
* @return
*
* @throws IOException
*/
public static <T> T getResultFromScript(Class<T> resultType,
JAXBContext context, String scriptResource,
HgRepositoryHandler handler, Repository repository, String revision,
String path)
throws IOException
{
Map<String, String> extraEnv = new HashMap<String, String>();
extraEnv.put(ENV_REVISION, getRevision(revision));
extraEnv.put(ENV_PATH, Util.nonNull(path));
return getResultFromScript(resultType, context, scriptResource, handler,
repository, extraEnv);
}
/**
* Method description
*
*
* @param revision
*
* @return
*/
public static String getRevision(String revision)
{
return Util.isEmpty(revision)
? REVISION_TIP
: revision;
}
}

View File

@@ -0,0 +1,131 @@
#
# Copyright (c) 2010, Sebastian Sdorra
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of SCM-Manager; nor the names of its
# contributors may be used to endorse or promote products derived from this
# software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# http://bitbucket.org/sdorra/scm-manager
#
#
import sys, os
pythonPath = os.environ['SCM_PYTHON_PATH']
if len(pythonPath) > 0:
pathParts = pythonPath.split(os.pathsep)
for i in range(len(pathParts)):
sys.path.insert(i, pathParts[i])
from mercurial import hg, ui, commands
from mercurial.node import hex
from xml.sax.saxutils import escape
import datetime, time
repositoryPath = os.environ['SCM_REPOSITORY_PATH']
repo = hg.repository(ui.ui(), path = repositoryPath)
start = os.environ['SCM_REVISION_START']
limit = os.environ['SCM_REVISION_LIMIT']
total = len(repo)
limit = int(limit)
try:
start = int(start)
startRev = total - start - 1
if startRev < 0:
startRev = 0
except ValueError:
startRev = repo[start].rev()
endRev = startRev - limit
if endRev < -1:
endRev = -1
# header
print '<changeset-paging>'
print ' <total>' + str(total) + '</total>'
print ' <changesets>'
# changesets
for i in range(startRev, endRev, -1):
ctx = repo[i]
time = int(ctx.date()[0]) * 1000
branch = ctx.branch()
tags = ctx.tags()
status = repo.status(ctx.p1().node(), ctx.node())
mods = status[0]
added = status[1]
deleted = status[2]
print ' <changeset>'
print ' <id>' + str(i) + ':' + hex(ctx.node()[:6]) + '</id>'
print ' <author>' + escape(ctx.user()) + '</author>'
print ' <description>' + escape(ctx.description()) + '</description>'
print ' <date>' + str(time).split('.')[0] + '</date>'
# branches
if branch != 'default':
print ' <branches>'
print ' <branch>' + branch + '</branch>'
print ' </branches>'
# tags
if tags:
print ' <tags>'
for t in tags:
print ' <tag>' + t + '</tag>'
print ' </tags>'
# modifications
print ' <modifications>'
# files added
if added:
print ' <added>'
for add in added:
print ' <file>' + add + '</file>'
print ' </added>'
# files modified
if mods:
print ' <modified>'
for mod in mods:
print ' <file>' + mod + '</file>'
print ' </modified>'
# files deleted
if deleted:
print ' <deleted>'
for dele in deleted:
print ' <file>' + dele + '</file>'
print ' </deleted>'
print ' </modifications>'
print ' </changeset>'
# footer
print ' </changesets>'
print '</changeset-paging>'