merge with crew.
--- a/.hgignore
+++ b/.hgignore
@@ -9,6 +9,7 @@ syntax: glob
*.swp
*.prof
tests/.coverage*
+tests/annotated
tests/*.err
build
dist
--- a/CONTRIBUTORS
+++ b/CONTRIBUTORS
@@ -14,6 +14,7 @@ Volker Kleinfeld <Volker.Kleinfeld at gm
Vadim Lebedev <vadim at mbdsys.com>
Christopher Li <hg at chrisli.org>
Chris Mason <mason at suse.com>
+Colin McMillen <mcmillen at cs.cmu.edu>
Wojciech Milkowski <wmilkowski at interia.pl>
Chad Netzer <chad.netzer at gmail.com>
Bryan O'Sullivan <bos at serpentine.com>
--- a/doc/hgrc.5.txt
+++ b/doc/hgrc.5.txt
@@ -131,11 +131,11 @@ decode/encode::
**.txt = tempfile: unix2dos -n INFILE OUTFILE
hooks::
- Commands that get automatically executed by various actions such as
- starting or finishing a commit. Multiple commands can be run for
- the same action by appending a suffix to the action. Overriding a
- site-wide hook can be done by changing its value or setting it to
- an empty string.
+ Commands or Python functions that get automatically executed by
+ various actions such as starting or finishing a commit. Multiple
+ hooks can be run for the same action by appending a suffix to the
+ action. Overriding a site-wide hook can be done by changing its
+ value or setting it to an empty string.
Example .hg/hgrc:
@@ -211,6 +211,21 @@ hooks::
the environment for backwards compatibility, but their use is
deprecated, and they will be removed in a future release.
+ The syntax for Python hooks is as follows:
+
+ hookname = python:modulename.submodule.callable
+
+ Python hooks are run within the Mercurial process. Each hook is
+ called with at least three keyword arguments: a ui object (keyword
+ "ui"), a repository object (keyword "repo"), and a "hooktype"
+ keyword that tells what kind of hook is used. Arguments listed as
+ environment variables above are passed as keyword arguments, with no
+ "HG_" prefix, and names in lower case.
+
+ A Python hook must return a "true" value to succeed. Returning a
+ "false" value or raising an exception is treated as failure of the
+ hook.
+
http_proxy::
Used to access web-based Mercurial repositories through a HTTP
proxy.
--- a/hgext/gpg.py
+++ b/hgext/gpg.py
@@ -23,11 +23,11 @@ class gpg:
""" returns of the good and bad signatures"""
try:
# create temporary files
- fd, sigfile = tempfile.mkstemp(prefix="hggpgsig")
+ fd, sigfile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".sig")
fp = os.fdopen(fd, 'wb')
fp.write(sig)
fp.close()
- fd, datafile = tempfile.mkstemp(prefix="hggpgdata")
+ fd, datafile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".txt")
fp = os.fdopen(fd, 'wb')
fp.write(data)
fp.close()
--- a/hgext/patchbomb.py
+++ b/hgext/patchbomb.py
@@ -62,7 +62,7 @@ try:
except ImportError: pass
def diffstat(patch):
- fd, name = tempfile.mkstemp()
+ fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
try:
p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
try:
--- a/mercurial/appendfile.py
+++ b/mercurial/appendfile.py
@@ -38,7 +38,7 @@ class appendfile(object):
self.tmpname = tmpname
self.tmpfp = util.posixfile(self.tmpname, 'ab+')
else:
- fd, self.tmpname = tempfile.mkstemp()
+ fd, self.tmpname = tempfile.mkstemp(prefix="hg-appendfile-")
os.close(fd)
self.tmpfp = util.posixfile(self.tmpname, 'ab+')
self.realfp = fp
--- a/mercurial/archival.py
+++ b/mercurial/archival.py
@@ -80,8 +80,11 @@ class zipit:
def __init__(self, dest, prefix, compress=True):
self.prefix = tidyprefix(dest, prefix, ('.zip',))
- if not isinstance(dest, str) and not hasattr(dest, 'tell'):
- dest = tellable(dest)
+ if not isinstance(dest, str):
+ try:
+ dest.tell()
+ except (AttributeError, IOError):
+ dest = tellable(dest)
self.z = zipfile.ZipFile(dest, 'w',
compress and zipfile.ZIP_DEFLATED or
zipfile.ZIP_STORED)
--- a/mercurial/changelog.py
+++ b/mercurial/changelog.py
@@ -11,7 +11,7 @@ from demandload import demandload
demandload(globals(), "os time util")
class changelog(revlog):
- def __init__(self, opener, defversion=0):
+ def __init__(self, opener, defversion=REVLOGV0):
revlog.__init__(self, opener, "00changelog.i", "00changelog.d",
defversion)
@@ -41,14 +41,15 @@ class changelog(revlog):
if date:
# validate explicit (probably user-specified) date and
# time zone offset. values must fit in signed 32 bits for
- # current 32-bit linux runtimes.
+ # current 32-bit linux runtimes. timezones go from UTC-12
+ # to UTC+14
try:
when, offset = map(int, date.split(' '))
except ValueError:
raise ValueError(_('invalid date: %r') % date)
if abs(when) > 0x7fffffff:
raise ValueError(_('date exceeds 32 bits: %d') % when)
- if abs(offset) >= 43200:
+ if offset < -50400 or offset > 43200:
raise ValueError(_('impossible time zone offset: %d') % offset)
else:
date = "%d %d" % util.makedate()
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -19,6 +19,11 @@ class UnknownCommand(Exception):
class AmbiguousCommand(Exception):
"""Exception raised if command shortcut matches more than one command."""
+def bail_if_changed(repo):
+ modified, added, removed, deleted, unknown = repo.changes()
+ if modified or added or removed or deleted:
+ raise util.Abort(_("outstanding uncommitted changes"))
+
def filterfiles(filters, files):
l = [x for x in files if x in filters]
@@ -298,7 +303,7 @@ def write_bundle(cg, filename=None, comp
raise util.Abort(_("file '%s' already exists"), filename)
fh = open(filename, "wb")
else:
- fd, filename = tempfile.mkstemp(suffix=".hg", prefix="hg-bundle-")
+ fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
fh = os.fdopen(fd, "wb")
cleanup = filename
@@ -926,10 +931,48 @@ def archive(ui, repo, dest, **opts):
prefix = make_filename(repo, repo.changelog, opts['prefix'], node)
if os.path.realpath(dest) == repo.root:
raise util.Abort(_('repository root cannot be destination'))
- _, matchfn, _ = matchpats(repo, [], opts)
+ dummy, matchfn, dummy = matchpats(repo, [], opts)
archival.archive(repo, dest, node, opts.get('type') or 'files',
not opts['no_decode'], matchfn, prefix)
+def backout(ui, repo, rev, **opts):
+ '''reverse effect of earlier changeset
+
+ Commit the backed out changes as a new changeset.
+
+ If you back out a changeset other than the tip, a new head is
+ created. The --merge option remembers the parent of the working
+ directory before starting the backout, then merges the new head
+ with it afterwards, to save you from doing this by hand. The
+ result of this merge is not committed, as for a normal merge.'''
+
+ bail_if_changed(repo)
+ op1, op2 = repo.dirstate.parents()
+ if op2 != nullid:
+ raise util.Abort(_('outstanding uncommitted merge'))
+ node = repo.lookup(rev)
+ parent, p2 = repo.changelog.parents(node)
+ if parent == nullid:
+ raise util.Abort(_('cannot back out a change with no parents'))
+ if p2 != nullid:
+ raise util.Abort(_('cannot back out a merge'))
+ repo.update(node, force=True)
+ revert_opts = opts.copy()
+ revert_opts['rev'] = hex(parent)
+ revert(ui, repo, **revert_opts)
+ commit_opts = opts.copy()
+ commit_opts['addremove'] = False
+ if not commit_opts['message'] and not commit_opts['logfile']:
+ commit_opts['message'] = _("Backed out changeset %s") % (hex(node))
+ commit(ui, repo, **commit_opts)
+ def nice(node):
+ return '%d:%s' % (repo.changelog.rev(node), short(node))
+ ui.status(_('changeset %s backs out changeset %s\n') %
+ (nice(repo.changelog.tip()), nice(node)))
+ if opts['merge'] and op1 != node:
+ ui.status(_('merging with changeset %s\n') % nice(op1))
+ update(ui, repo, hex(op1), **opts)
+
def bundle(ui, repo, fname, dest="default-push", **opts):
"""create a changegroup file
@@ -1572,10 +1615,15 @@ def export(ui, repo, *changesets, **opts
doexport(ui, repo, cset, seqno, total, revwidth, opts)
def forget(ui, repo, *pats, **opts):
- """don't add the specified files on the next commit
-
+ """don't add the specified files on the next commit (DEPRECATED)
+
+ (DEPRECATED)
Undo an 'hg add' scheduled for the next commit.
+
+ This command is now deprecated and will be removed in a future
+ release. Please use revert instead.
"""
+ ui.warn(_("(the forget command is deprecated; use revert instead)\n"))
forget = []
for src, abs, rel, exact in walk(repo, pats, opts):
if repo.dirstate.state(abs) == 'a':
@@ -1792,9 +1840,7 @@ def import_(ui, repo, patch1, *patches,
patches = (patch1,) + patches
if not opts['force']:
- modified, added, removed, deleted, unknown = repo.changes()
- if modified or added or removed or deleted:
- raise util.Abort(_("outstanding uncommitted changes"))
+ bail_if_changed(repo)
d = opts["base"]
strip = opts["strip"]
@@ -2885,7 +2931,7 @@ table = {
('I', 'include', [], _('include names matching the given patterns')),
('X', 'exclude', [], _('exclude names matching the given patterns'))],
_('hg annotate [-r REV] [-a] [-u] [-d] [-n] [-c] FILE...')),
- 'archive':
+ "archive":
(archive,
[('', 'no-decode', None, _('do not pass files through decoders')),
('p', 'prefix', '', _('directory prefix for files in archive')),
@@ -2894,6 +2940,17 @@ table = {
('I', 'include', [], _('include names matching the given patterns')),
('X', 'exclude', [], _('exclude names matching the given patterns'))],
_('hg archive [OPTION]... DEST')),
+ "backout":
+ (backout,
+ [('', 'merge', None,
+ _('merge with old dirstate parent after backout')),
+ ('m', 'message', '', _('use <text> as commit message')),
+ ('l', 'logfile', '', _('read commit message from <file>')),
+ ('d', 'date', '', _('record datecode as commit date')),
+ ('u', 'user', '', _('record user as committer')),
+ ('I', 'include', [], _('include names matching the given patterns')),
+ ('X', 'exclude', [], _('exclude names matching the given patterns'))],
+ _('hg backout [OPTION]... REV')),
"bundle":
(bundle,
[('f', 'force', None,
@@ -2973,7 +3030,7 @@ table = {
('a', 'text', None, _('treat all files as text')),
('', 'switch-parent', None, _('diff against the second parent'))],
_('hg export [-a] [-o OUTFILESPEC] REV...')),
- "forget":
+ "debugforget|forget":
(forget,
[('I', 'include', [], _('include names matching the given patterns')),
('X', 'exclude', [], _('exclude names matching the given patterns'))],
@@ -3255,11 +3312,8 @@ def find(cmd):
raise UnknownCommand(cmd)
-class SignalInterrupt(Exception):
- """Exception raised on SIGTERM and SIGHUP."""
-
def catchterm(*args):
- raise SignalInterrupt
+ raise util.SignalInterrupt
def run():
sys.exit(dispatch(sys.argv[1:]))
@@ -3311,7 +3365,7 @@ def dispatch(args):
if num: signal.signal(num, catchterm)
try:
- u = ui.ui()
+ u = ui.ui(traceback='--traceback' in sys.argv[1:])
except util.Abort, inst:
sys.stderr.write(_("abort: %s\n") % inst)
return -1
@@ -3335,7 +3389,7 @@ def dispatch(args):
external.append(mod)
except Exception, inst:
u.warn(_("*** failed to import extension %s: %s\n") % (x[0], inst))
- if "--traceback" in sys.argv[1:]:
+ if u.traceback:
traceback.print_exc()
return 1
continue
@@ -3363,7 +3417,7 @@ def dispatch(args):
atexit.register(print_time)
u.updateopts(options["verbose"], options["debug"], options["quiet"],
- not options["noninteractive"])
+ not options["noninteractive"], options["traceback"])
# enter the debugger before command execution
if options['debugger']:
@@ -3430,7 +3484,7 @@ def dispatch(args):
# enter the debugger when we hit an exception
if options['debugger']:
pdb.post_mortem(sys.exc_info()[2])
- if options['traceback']:
+ if u.traceback:
traceback.print_exc()
raise
except ParseError, inst:
@@ -3447,7 +3501,7 @@ def dispatch(args):
u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
help_(u, 'shortlist')
except hg.RepoError, inst:
- u.warn(_("abort: "), inst, "!\n")
+ u.warn(_("abort: %s!\n") % inst)
except lock.LockHeld, inst:
if inst.errno == errno.ETIMEDOUT:
reason = _('timed out waiting for lock held by %s') % inst.locker
@@ -3459,7 +3513,7 @@ def dispatch(args):
(inst.desc or inst.filename, inst.strerror))
except revlog.RevlogError, inst:
u.warn(_("abort: "), inst, "!\n")
- except SignalInterrupt:
+ except util.SignalInterrupt:
u.warn(_("killed!\n"))
except KeyboardInterrupt:
try:
--- a/mercurial/hgweb.py
+++ b/mercurial/hgweb.py
@@ -125,7 +125,7 @@ class hgweb(object):
def archivelist(self, nodeid):
for i in self.archives:
if self.repo.ui.configbool("web", "allow" + i, False):
- yield {"type" : i, "node" : nodeid}
+ yield {"type" : i, "node" : nodeid, "url": ""}
def listfiles(self, files, mf):
for f in files[:self.maxfiles]:
@@ -293,7 +293,8 @@ class hgweb(object):
yield self.t('changelog',
changenav=changenav,
manifest=hex(mf),
- rev=pos, changesets=count, entries=changelist)
+ rev=pos, changesets=count, entries=changelist,
+ archives=self.archivelist("tip"))
def search(self, query):
@@ -727,7 +728,9 @@ class hgweb(object):
yield self.t("header", **map)
def footer(**map):
- yield self.t("footer", **map)
+ yield self.t("footer",
+ motd=self.repo.ui.config("web", "motd", ""),
+ **map)
def expand_form(form):
shortcuts = {
@@ -1006,8 +1009,11 @@ class hgwebdir(object):
def cleannames(items):
return [(name.strip(os.sep), path) for name, path in items]
+ self.motd = ""
+ self.repos_sorted = ('name', False)
if isinstance(config, (list, tuple)):
self.repos = cleannames(config)
+ self.repos_sorted = ('', False)
elif isinstance(config, dict):
self.repos = cleannames(config.items())
self.repos.sort()
@@ -1015,6 +1021,8 @@ class hgwebdir(object):
cp = ConfigParser.SafeConfigParser()
cp.read(config)
self.repos = []
+ if cp.has_section('web') and cp.has_option('web', 'motd'):
+ self.motd = cp.get('web', 'motd')
if cp.has_section('paths'):
self.repos.extend(cleannames(cp.items('paths')))
if cp.has_section('collections'):
@@ -1032,14 +1040,20 @@ class hgwebdir(object):
yield tmpl("header", **map)
def footer(**map):
- yield tmpl("footer", **map)
+ yield tmpl("footer", motd=self.motd, **map)
m = os.path.join(templater.templatepath(), "map")
tmpl = templater.templater(m, templater.common_filters,
defaults={"header": header,
"footer": footer})
- def entries(**map):
+ def archivelist(ui, nodeid, url):
+ for i in ['zip', 'gz', 'bz2']:
+ if ui.configbool("web", "allow" + i, False):
+ yield {"type" : i, "node": nodeid, "url": url}
+
+ def entries(sortcolumn="", descending=False, **map):
+ rows = []
parity = 0
for name, path in self.repos:
u = ui.ui()
@@ -1058,16 +1072,37 @@ class hgwebdir(object):
except OSError:
continue
- yield dict(contact=(get("ui", "username") or # preferred
- get("web", "contact") or # deprecated
- get("web", "author", "unknown")), # also
- name=get("web", "name", name),
+ contact = (get("ui", "username") or # preferred
+ get("web", "contact") or # deprecated
+ get("web", "author", "")) # also
+ description = get("web", "description", "")
+ name = get("web", "name", name)
+ row = dict(contact=contact or "unknown",
+ contact_sort=contact.upper() or "unknown",
+ name=name,
+ name_sort=name,
url=url,
- parity=parity,
- shortdesc=get("web", "description", "unknown"),
- lastupdate=d)
-
- parity = 1 - parity
+ description=description or "unknown",
+ description_sort=description.upper() or "unknown",
+ lastchange=d,
+ lastchange_sort=d[1]-d[0],
+ archives=archivelist(u, "tip", url))
+ if (not sortcolumn
+ or (sortcolumn, descending) == self.repos_sorted):
+ # fast path for unsorted output
+ row['parity'] = parity
+ parity = 1 - parity
+ yield row
+ else:
+ rows.append((row["%s_sort" % sortcolumn], row))
+ if rows:
+ rows.sort()
+ if descending:
+ rows.reverse()
+ for key, row in rows:
+ row['parity'] = parity
+ parity = 1 - parity
+ yield row
virtual = req.env.get("PATH_INFO", "").strip('/')
if virtual:
@@ -1088,4 +1123,20 @@ class hgwebdir(object):
req.write(staticfile(static, fname)
or tmpl("error", error="%r not found" % fname))
else:
- req.write(tmpl("index", entries=entries))
+ sortable = ["name", "description", "contact", "lastchange"]
+ sortcolumn, descending = self.repos_sorted
+ if req.form.has_key('sort'):
+ sortcolumn = req.form['sort'][0]
+ descending = sortcolumn.startswith('-')
+ if descending:
+ sortcolumn = sortcolumn[1:]
+ if sortcolumn not in sortable:
+ sortcolumn = ""
+
+ sort = [("sort_%s" % column,
+ "%s%s" % ((not descending and column == sortcolumn)
+ and "-" or "", column))
+ for column in sortable]
+ req.write(tmpl("index", entries=entries,
+ sortcolumn=sortcolumn, descending=descending,
+ **dict(sort)))
--- a/mercurial/httprangereader.py
+++ b/mercurial/httprangereader.py
@@ -18,7 +18,11 @@ class httprangereader(object):
urllib2.install_opener(opener)
req = urllib2.Request(self.url)
end = ''
- if bytes: end = self.pos + bytes
+ if bytes:
+ end = self.pos + bytes - 1
req.add_header('Range', 'bytes=%d-%s' % (self.pos, end))
f = urllib2.urlopen(req)
- return f.read()
+ data = f.read()
+ if bytes:
+ data = data[:bytes]
+ return data
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -11,7 +11,8 @@ from node import *
from i18n import gettext as _
from demandload import *
demandload(globals(), "appendfile changegroup")
-demandload(globals(), "re lock transaction tempfile stat mdiff errno ui revlog")
+demandload(globals(), "re lock transaction tempfile stat mdiff errno ui")
+demandload(globals(), "revlog traceback")
class localrepository(object):
def __del__(self):
@@ -42,7 +43,8 @@ class localrepository(object):
pass
v = self.ui.revlogopts
- self.revlogversion = int(v.get('format', 0))
+ self.revlogversion = int(v.get('format', revlog.REVLOGV0))
+ self.revlogv1 = self.revlogversion != revlog.REVLOGV0
flags = 0
for x in v.get('flags', "").split():
flags |= revlog.flagstr(x)
@@ -71,7 +73,59 @@ class localrepository(object):
os.mkdir(self.join("data"))
self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root)
+
def hook(self, name, throw=False, **args):
+ def callhook(hname, funcname):
+ '''call python hook. hook is callable object, looked up as
+ name in python module. if callable returns "true", hook
+ passes, else fails. if hook raises exception, treated as
+ hook failure. exception propagates if throw is "true".'''
+
+ self.ui.note(_("calling hook %s: %s\n") % (hname, funcname))
+ d = funcname.rfind('.')
+ if d == -1:
+ raise util.Abort(_('%s hook is invalid ("%s" not in a module)')
+ % (hname, funcname))
+ modname = funcname[:d]
+ try:
+ obj = __import__(modname)
+ except ImportError:
+ raise util.Abort(_('%s hook is invalid '
+ '(import of "%s" failed)') %
+ (hname, modname))
+ try:
+ for p in funcname.split('.')[1:]:
+ obj = getattr(obj, p)
+ except AttributeError, err:
+ raise util.Abort(_('%s hook is invalid '
+ '("%s" is not defined)') %
+ (hname, funcname))
+ if not callable(obj):
+ raise util.Abort(_('%s hook is invalid '
+ '("%s" is not callable)') %
+ (hname, funcname))
+ try:
+ r = obj(ui=ui, repo=repo, hooktype=name, **args)
+ except (KeyboardInterrupt, util.SignalInterrupt):
+ raise
+ except Exception, exc:
+ if isinstance(exc, util.Abort):
+ self.ui.warn(_('error: %s hook failed: %s\n') %
+ (hname, exc.args[0] % exc.args[1:]))
+ else:
+ self.ui.warn(_('error: %s hook raised an exception: '
+ '%s\n') % (hname, exc))
+ if throw:
+ raise
+ if self.ui.traceback:
+ traceback.print_exc()
+ return False
+ if not r:
+ if throw:
+ raise util.Abort(_('%s hook failed') % hname)
+ self.ui.warn(_('error: %s hook failed\n') % hname)
+ return r
+
def runhook(name, cmd):
self.ui.note(_("running hook %s: %s\n") % (name, cmd))
env = dict([('HG_' + k.upper(), v) for k, v in args.iteritems()] +
@@ -90,7 +144,10 @@ class localrepository(object):
if hname.split(".", 1)[0] == name and cmd]
hooks.sort()
for hname, cmd in hooks:
- r = runhook(hname, cmd) and r
+ if cmd.startswith('python:'):
+ r = callhook(hname, cmd[7:].strip()) and r
+ else:
+ r = runhook(hname, cmd) and r
return r
def tags(self):
@@ -1320,7 +1377,8 @@ class localrepository(object):
# Signal that no more groups are left.
yield changegroup.closechunk()
- self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source)
+ if msng_cl_lst:
+ self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source)
return util.chunkbuffer(gengroup())
@@ -1766,7 +1824,7 @@ class localrepository(object):
def temp(prefix, node):
pre = "%s~%s." % (os.path.basename(fn), prefix)
- (fd, name) = tempfile.mkstemp("", pre)
+ (fd, name) = tempfile.mkstemp(prefix=pre)
f = os.fdopen(fd, "wb")
self.wwrite(fn, fl.read(node), f)
f.close()
@@ -1803,12 +1861,17 @@ class localrepository(object):
filenodes = {}
changesets = revisions = files = 0
errors = [0]
+ warnings = [0]
neededmanifests = {}
def err(msg):
self.ui.warn(msg + "\n")
errors[0] += 1
+ def warn(msg):
+ self.ui.warn(msg + "\n")
+ warnings[0] += 1
+
def checksize(obj, name):
d = obj.checksize()
if d[0]:
@@ -1816,6 +1879,18 @@ class localrepository(object):
if d[1]:
err(_("%s index contains %d extra bytes") % (name, d[1]))
+ def checkversion(obj, name):
+ if obj.version != revlog.REVLOGV0:
+ if not revlogv1:
+ warn(_("warning: `%s' uses revlog format 1") % name)
+ elif revlogv1:
+ warn(_("warning: `%s' uses revlog format 0") % name)
+
+ revlogv1 = self.revlogversion != revlog.REVLOGV0
+ if self.ui.verbose or revlogv1 != self.revlogv1:
+ self.ui.status(_("repository uses revlog format %d\n") %
+ (revlogv1 and 1 or 0))
+
seen = {}
self.ui.status(_("checking changesets\n"))
checksize(self.changelog, "changelog")
@@ -1850,6 +1925,7 @@ class localrepository(object):
seen = {}
self.ui.status(_("checking manifests\n"))
+ checkversion(self.manifest, "manifest")
checksize(self.manifest, "manifest")
for i in range(self.manifest.count()):
@@ -1914,6 +1990,7 @@ class localrepository(object):
err(_("file without name in manifest %s") % short(n))
continue
fl = self.file(f)
+ checkversion(fl, f)
checksize(fl, f)
nodes = {nullid: 1}
@@ -1962,6 +2039,8 @@ class localrepository(object):
self.ui.status(_("%d files, %d changesets, %d total revisions\n") %
(files, changesets, revisions))
+ if warnings[0]:
+ self.ui.warn(_("%d warnings encountered!\n") % warnings[0])
if errors[0]:
self.ui.warn(_("%d integrity errors encountered!\n") % errors[0])
return 1
--- a/mercurial/manifest.py
+++ b/mercurial/manifest.py
@@ -12,7 +12,7 @@ from demandload import *
demandload(globals(), "bisect array")
class manifest(revlog):
- def __init__(self, opener, defversion=0):
+ def __init__(self, opener, defversion=REVLOGV0):
self.mapcache = None
self.listcache = None
revlog.__init__(self, opener, "00manifest.i", "00manifest.d",
--- a/mercurial/revlog.py
+++ b/mercurial/revlog.py
@@ -293,7 +293,7 @@ class revlog(object):
remove data, and can use some simple techniques to avoid the need
for locking while reading.
"""
- def __init__(self, opener, indexfile, datafile, defversion=0):
+ def __init__(self, opener, indexfile, datafile, defversion=REVLOGV0):
"""
create a revlog object
@@ -333,11 +333,11 @@ class revlog(object):
and st.st_ctime == oldst.st_ctime):
return
self.indexstat = st
- if len(i) > 0:
- v = struct.unpack(versionformat, i)[0]
+ if len(i) > 0:
+ v = struct.unpack(versionformat, i)[0]
flags = v & ~0xFFFF
fmt = v & 0xFFFF
- if fmt == 0:
+ if fmt == REVLOGV0:
if flags:
raise RevlogError(_("index %s invalid flags %x for format v0" %
(self.indexfile, flags)))
@@ -349,7 +349,7 @@ class revlog(object):
raise RevlogError(_("index %s invalid format %d" %
(self.indexfile, fmt)))
self.version = v
- if v == 0:
+ if v == REVLOGV0:
self.indexformat = indexformatv0
shaoffset = v0shaoffset
else:
@@ -369,7 +369,7 @@ class revlog(object):
# we've already got the entire data file read in, save it
# in the chunk data
self.chunkcache = (0, i)
- if self.version != 0:
+ if self.version != REVLOGV0:
e = list(self.index[0])
type = self.ngtype(e[0])
e[0] = self.offset_type(0, type)
@@ -399,7 +399,7 @@ class revlog(object):
def ngoffset(self, q):
if q & 0xFFFF:
raise RevlogError(_('%s: incompatible revision flag %x') %
- (self.indexfile, type))
+ (self.indexfile, q))
return long(q >> 16)
def ngtype(self, q):
@@ -441,13 +441,13 @@ class revlog(object):
if node == nullid: return (nullid, nullid)
r = self.rev(node)
d = self.index[r][-3:-1]
- if self.version == 0:
+ if self.version == REVLOGV0:
return d
return [ self.node(x) for x in d ]
def start(self, rev):
if rev < 0:
return -1
- if self.version != 0:
+ if self.version != REVLOGV0:
return self.ngoffset(self.index[rev][0])
return self.index[rev][0]
@@ -456,7 +456,7 @@ class revlog(object):
def size(self, rev):
"""return the length of the uncompressed text for a given revision"""
l = -1
- if self.version != 0:
+ if self.version != REVLOGV0:
l = self.index[rev][2]
if l >= 0:
return l
@@ -911,7 +911,7 @@ class revlog(object):
if t >= 0:
offset = self.end(t)
- if self.version == 0:
+ if self.version == REVLOGV0:
e = (offset, l, base, link, p1, p2, node)
else:
e = (self.offset_type(offset, 0), l, len(text),
@@ -935,7 +935,7 @@ class revlog(object):
f.seek(0, 2)
transaction.add(self.indexfile, f.tell(), self.count() - 1)
- if len(self.index) == 1 and self.version != 0:
+ if len(self.index) == 1 and self.version != REVLOGV0:
l = struct.pack(versionformat, self.version)
f.write(l)
entry = entry[4:]
@@ -1135,7 +1135,7 @@ class revlog(object):
raise RevlogError(_("consistency error adding group"))
textlen = len(text)
else:
- if self.version == 0:
+ if self.version == REVLOGV0:
e = (end, len(cdelta), base, link, p1, p2, node)
else:
e = (self.offset_type(end, 0), len(cdelta), textlen, base,
--- a/mercurial/sshrepo.py
+++ b/mercurial/sshrepo.py
@@ -9,7 +9,7 @@ from node import *
from remoterepo import *
from i18n import gettext as _
from demandload import *
-demandload(globals(), "hg os re stat")
+demandload(globals(), "hg os re stat util")
class sshrepository(remoterepository):
def __init__(self, ui, path):
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -12,7 +12,7 @@ demandload(globals(), "errno os re socke
class ui(object):
def __init__(self, verbose=False, debug=False, quiet=False,
- interactive=True, parentui=None):
+ interactive=True, traceback=False, parentui=None):
self.overlay = {}
if parentui is None:
# this is the parent of all ui children
@@ -24,6 +24,7 @@ class ui(object):
self.verbose = self.configbool("ui", "verbose")
self.debugflag = self.configbool("ui", "debug")
self.interactive = self.configbool("ui", "interactive", True)
+ self.traceback = traceback
self.updateopts(verbose, debug, quiet, interactive)
self.diffcache = None
@@ -45,11 +46,12 @@ class ui(object):
return getattr(self.parentui, key)
def updateopts(self, verbose=False, debug=False, quiet=False,
- interactive=True):
+ interactive=True, traceback=False):
self.quiet = (self.quiet or quiet) and not verbose and not debug
self.verbose = (self.verbose or verbose) or debug
self.debugflag = (self.debugflag or debug)
self.interactive = (self.interactive and interactive)
+ self.traceback = self.traceback or traceback
def readconfig(self, fn, root=None):
if isinstance(fn, basestring):
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -16,6 +16,9 @@ from demandload import *
demandload(globals(), "cStringIO errno popen2 re shutil sys tempfile")
demandload(globals(), "threading time")
+class SignalInterrupt(Exception):
+ """Exception raised on SIGTERM and SIGHUP."""
+
def pipefilter(s, cmd):
'''filter string S through command CMD, returning its output'''
(pout, pin) = popen2.popen2(cmd, -1, 'b')
@@ -43,11 +46,11 @@ def tempfilter(s, cmd):
the temporary files generated.'''
inname, outname = None, None
try:
- infd, inname = tempfile.mkstemp(prefix='hgfin')
+ infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
fp = os.fdopen(infd, 'wb')
fp.write(s)
fp.close()
- outfd, outname = tempfile.mkstemp(prefix='hgfout')
+ outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
os.close(outfd)
cmd = cmd.replace('INFILE', inname)
cmd = cmd.replace('OUTFILE', outname)
@@ -676,7 +679,7 @@ def opener(base, audit=True):
def mktempcopy(name):
d, fn = os.path.split(name)
- fd, temp = tempfile.mkstemp(prefix=fn, dir=d)
+ fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
os.close(fd)
fp = posixfile(temp, "wb")
try:
--- a/templates/changelog.tmpl
+++ b/templates/changelog.tmpl
@@ -8,6 +8,7 @@
<div class="buttons">
<a href="?cmd=tags">tags</a>
<a href="?mf=#manifest|short#;path=/">manifest</a>
+#archives%archiveentry#
<a type="application/rss+xml" href="?style=rss">rss</a>
</div>
--- a/templates/changelogentry.tmpl
+++ b/templates/changelogentry.tmpl
@@ -1,11 +1,11 @@
-<table class="changelogEntry parity#parity#">
+<table class="logEntry parity#parity#">
<tr>
- <th class="age" width="15%">#date|age# ago:</th>
+ <th class="age">#date|age# ago:</th>
<th class="firstline">#desc|strip|firstline|escape#</th>
</tr>
<tr>
- <th class="changesetRev">changeset #rev#:</th>
- <td class="changesetNode"><a href="?cs=#node|short#">#node|short#</a></td>
+ <th class="revision">changeset #rev#:</th>
+ <td class="node"><a href="?cs=#node|short#">#node|short#</a></td>
</tr>
#parent%changelogparent#
#child%changelogchild#
--- a/templates/filelogentry.tmpl
+++ b/templates/filelogentry.tmpl
@@ -1,20 +1,25 @@
-<table class="parity#parity#" width="100%" cellspacing="0" cellpadding="0">
-<tr>
- <td align="right" width="15%"><b>#date|age# ago: </b></td>
- <td><b><a href="?cs=#node|short#">#desc|strip|firstline|escape#</a></b></td></tr>
-<tr>
- <td align="right">revision #filerev#: </td>
- <td><a href="?f=#filenode|short#;file=#file|urlescape#">#filenode|short#</a>
-<a href="?fd=#node|short#;file=#file|urlescape#">(diff)</a>
-<a href="?fa=#filenode|short#;file=#file|urlescape#">(annotate)</a>
-</td></tr>
-#rename%filelogrename#
-<tr>
- <td align="right">author: </td>
- <td>#author|obfuscate#</td></tr>
-<tr>
- <td align="right">date: </td>
- <td>#date|date# (#date|age# ago)</td></tr>
+<table class="logEntry parity#parity#">
+ <tr>
+ <th class="age">#date|age# ago:</th>
+ <th class="firstline"><a href="?cs=#node|short#">#desc|strip|firstline|escape#</a></th>
+ </tr>
+ <tr>
+ <th class="revision">revision #filerev#:</td>
+ <td class="node">
+ <a href="?f=#filenode|short#;file=#file|urlescape#">#filenode|short#</a>
+ <a href="?fd=#node|short#;file=#file|urlescape#">(diff)</a>
+ <a href="?fa=#filenode|short#;file=#file|urlescape#">(annotate)</a>
+ </td>
+ </tr>
+ #rename%filelogrename#
+ <tr>
+ <th class="author">author:</th>
+ <td class="author">#author|obfuscate#</td>
+ </tr>
+ <tr>
+ <th class="date">date:</th>
+ <td class="date">#date|date#</td>
+ </tr>
</table>
--- a/templates/footer.tmpl
+++ b/templates/footer.tmpl
@@ -1,3 +1,4 @@
+#motd#
<div class="logo">
powered by<br/>
<a href="http://www.selenic.com/mercurial/">mercurial</a>
--- a/templates/index.tmpl
+++ b/templates/index.tmpl
@@ -7,10 +7,10 @@
<table>
<tr>
- <td>Name</td>
- <td>Description</td>
- <td>Contact</td>
- <td>Last change</td>
+ <td><a href="?sort=#sort_name#">Name</a></td>
+ <td><a href="?sort=#sort_description#">Description</a></td>
+ <td><a href="?sort=#sort_contact#">Contact</a></td>
+ <td><a href="?sort=#sort_lastchange#">Last change</a></td>
<td> </td>
<tr>
#entries%indexentry#
--- a/templates/map
+++ b/templates/map
@@ -28,23 +28,23 @@ changelogparent = '<tr><th class="parent
changesetparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="?cs=#node|short#">#node|short#</a></td></tr>'
filerevparent = '<tr><td class="metatag">parent:</td><td><a href="?f=#node|short#;file=#file|urlescape#">#node|short#</a></td></tr>'
filerename = '<tr><td class="metatag">parent:</td><td><a href="?f=#node|short#;file=#file|urlescape#">#file|escape#@#node|short#</a></td></tr>'
-filelogrename = '<tr><td align="right">base: </td><td><a href="?f=#node|short#;file=#file|urlescape#">#file|escape#@#node|short#</a></td></tr>'
+filelogrename = '<tr><th>base:</th><td><a href="?f=#node|short#;file=#file|urlescape#">#file|escape#@#node|short#</a></td></tr>'
fileannotateparent = '<tr><td class="metatag">parent:</td><td><a href="?fa=#filenode|short#;file=#file|urlescape#">#node|short#</a></td></tr>'
changesetchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="?cs=#node|short#">#node|short#</a></td></tr>'
changelogchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="?cs=#node|short#">#node|short#</a></td></tr>'
filerevchild = '<tr><td class="metatag">child:</td><td><a href="?f=#node|short#;file=#file|urlescape#">#node|short#</a></td></tr>'
fileannotatechild = '<tr><td class="metatag">child:</td><td><a href="?fa=#filenode|short#;file=#file|urlescape#">#node|short#</a></td></tr>'
tags = tags.tmpl
-tagentry = '<li class="tagEntry parity#parity#"><span class="node">#node#</span> <a href="?cs=#node|short#">#tag|escape#</a></li>'
+tagentry = '<li class="tagEntry parity#parity#"><tt class="node">#node#</tt> <a href="?cs=#node|short#">#tag|escape#</a></li>'
diffblock = '<pre class="parity#parity#">#lines#</pre>'
changelogtag = '<tr><th class="tag">tag:</th><td class="tag">#tag|escape#</td></tr>'
changesettag = '<tr><th class="tag">tag:</th><td class="tag">#tag|escape#</td></tr>'
filediffparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="?cs=#node|short#">#node|short#</a></td></tr>'
-filelogparent = '<tr><td align="right">parent #rev#: </td><td><a href="?f=#node|short#;file=#file|urlescape#">#node|short#</a></td></tr>'
+filelogparent = '<tr><th>parent #rev#:</th><td><a href="?f=#node|short#;file=#file|urlescape#">#node|short#</a></td></tr>'
filediffchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="?cs=#node|short#">#node|short#</a></td></tr>'
-filelogchild = '<tr><td align="right">child #rev#: </td><td><a href="?f=#node|short#;file=#file|urlescape#">#node|short#</a></td></tr>'
-indexentry = '<tr class="parity#parity#"><td><a href="#url#">#name|escape#</a></td><td>#shortdesc#</td><td>#contact|obfuscate#</td><td>#lastupdate|age# ago</td><td><a href="#url#?cl=tip;style=rss">RSS</a></td></tr>'
+filelogchild = '<tr><th>child #rev#:</th><td><a href="?f=#node|short#;file=#file|urlescape#">#node|short#</a></td></tr>'
+indexentry = '<tr class="parity#parity#"><td><a href="#url#">#name|escape#</a></td><td>#description#</td><td>#contact|obfuscate#</td><td class="age">#lastchange|age# ago</td><td class="indexlinks"><a href="#url#?cl=tip;style=rss">RSS</a> #archives%archiveentry#</td></tr>'
index = index.tmpl
-archiveentry = '<a href="?ca=#node|short#;type=#type|urlescape#">#type|escape#</a> '
+archiveentry = '<a href="#url#?ca=#node|short#;type=#type|urlescape#">#type|escape#</a> '
notfound = notfound.tmpl
error = error.tmpl
--- a/templates/map-gitweb
+++ b/templates/map-gitweb
@@ -36,7 +36,7 @@ changesetchild = '<tr><td>child</td><td
filerevchild = '<tr><td class="metatag">child:</td><td><a href="?cmd=file;file=#file|urlescape#;filenode=#node#;style=gitweb">#node|short#</a></td></tr>'
fileannotatechild = '<tr><td class="metatag">child:</td><td><a href="?cmd=annotate;file=#file|urlescape#;filenode=#node#;style=gitweb">#node|short#</a></td></tr>'
tags = tags-gitweb.tmpl
-tagentry = '<tr class="parity#parity#"><td><i>#date|age# ago</i></td><td><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb"><b>#tag|escape#</b></a></td><td class="link"><a href="?cmd=changeset;node=#node|short#;style=gitweb">changeset</a> | <a href="?cmd=changelog;rev=#node|short#;style=gitweb">changelog</a> | <a href="?mf=#tagmanifest|short#;path=/;style=gitweb">manifest</a></td></tr>'
+tagentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb"><b>#tag|escape#</b></a></td><td class="link"><a href="?cmd=changeset;node=#node|short#;style=gitweb">changeset</a> | <a href="?cmd=changelog;rev=#node|short#;style=gitweb">changelog</a> | <a href="?mf=#tagmanifest|short#;path=/;style=gitweb">manifest</a></td></tr>'
diffblock = '#lines#'
changelogtag = '<tr><th class="tag">tag:</th><td class="tag">#tag|escape#</td></tr>'
changesettag = '<tr><td>tag</td><td>#tag|escape#</td></tr>'
@@ -45,6 +45,6 @@ filelogparent = '<tr><td align="right">p
filediffchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="?cmd=changeset;node=#node#;style=gitweb">#node|short#</a></td></tr>'
filelogchild = '<tr><td align="right">child #rev#: </td><td><a href="?cmd=file;file=#file|urlescape#;filenode=#node#;style=gitweb">#node|short#</a></td></tr>'
shortlog = shortlog-gitweb.tmpl
-shortlogentry = '<tr class="parity#parity#"><td><i>#date|age# ago</i></td><td><i>#author#</i></td><td><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb"><b>#desc|firstline|escape#</b></a></td><td class="link"><a href="?cmd=changeset;node=#node|short#;style=gitweb">changeset</a> | <a href="?cmd=manifest;manifest=#manifest|short#;path=/;style=gitweb">manifest</a></td></tr>'
-filelogentry = '<tr class="parity#parity#"><td><i>#date|age# ago</i></td><td><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb"><b>#desc|firstline|escape#</b></a></td><td class="link"><!-- FIXME: <a href="?fd=#node|short#;file=#file|urlescape#;style=gitweb">diff</a> | --> <a href="?fa=#filenode|short#;file=#file|urlescape#;style=gitweb">annotate</a> #rename%filelogrename#</td></tr>'
+shortlogentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><i>#author#</i></td><td><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb"><b>#desc|firstline|escape#</b></a></td><td class="link"><a href="?cmd=changeset;node=#node|short#;style=gitweb">changeset</a> | <a href="?cmd=manifest;manifest=#manifest|short#;path=/;style=gitweb">manifest</a></td></tr>'
+filelogentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb"><b>#desc|firstline|escape#</b></a></td><td class="link"><!-- FIXME: <a href="?fd=#node|short#;file=#file|urlescape#;style=gitweb">diff</a> | --> <a href="?fa=#filenode|short#;file=#file|urlescape#;style=gitweb">annotate</a> #rename%filelogrename#</td></tr>'
archiveentry = ' | <a href="?ca=#node|short#;type=#type|urlescape#">#type|escape#</a> '
--- a/templates/static/style-gitweb.css
+++ b/templates/static/style-gitweb.css
@@ -17,6 +17,7 @@ div.title, a.title {
a.title:hover { background-color: #d9d8d1; }
div.title_text { padding:6px 0px; border: solid #d9d8d1; border-width:0px 0px 1px; }
div.log_body { padding:8px 8px 8px 150px; }
+.age { white-space:nowrap; }
span.age { position:relative; float:left; width:142px; font-style:italic; }
div.log_link {
padding:0px 8px;
--- a/templates/static/style.css
+++ b/templates/static/style.css
@@ -1,4 +1,6 @@
a { text-decoration:none; }
+.age { white-space:nowrap; }
+.indexlinks { white-space:nowrap; }
.parity0 { background-color: #dddddd; }
.parity1 { background-color: #eeeeee; }
.lineno { width: 60px; color: #aaaaaa; font-size: smaller;
@@ -48,16 +50,16 @@ pre { margin: 0; }
color: #999;
}
-/* Changelog entries */
-.changelogEntry { width: 100%; }
-.changelogEntry th { font-weight: normal; text-align: right; vertical-align: top; }
-.changelogEntry th.age, .changelogEntry th.firstline { font-weight: bold; }
-.changelogEntry th.firstline { text-align: left; width: inherit; }
+/* Changelog/Filelog entries */
+.logEntry { width: 100%; }
+.logEntry .age { width: 15%; }
+.logEntry th { font-weight: normal; text-align: right; vertical-align: top; }
+.logEntry th.age, .logEntry th.firstline { font-weight: bold; }
+.logEntry th.firstline { text-align: left; width: inherit; }
/* Tag entries */
#tagEntries { list-style: none; margin: 0; padding: 0; }
#tagEntries .tagEntry { list-style: none; margin: 0; padding: 0; }
-#tagEntries .tagEntry span.node { font-family: monospace; }
/* Changeset entry */
#changesetEntry { }
--- a/tests/coverage.py
+++ b/tests/coverage.py
@@ -350,6 +350,7 @@ class coverage:
show_missing = settings.get('show-missing')
directory = settings.get('directory')
omit = filter(None, settings.get('omit', '').split(','))
+ omit += ['/<'] # Always skip /<string> etc.
if settings.get('report'):
self.report(args, show_missing, ignore_errors, omit_prefixes=omit)
--- a/tests/run-tests
+++ b/tests/run-tests
@@ -64,7 +64,7 @@ HGTMP="${TMPDIR-/tmp}/hgtests.$RANDOM.$R
exit 1
}
-TESTDIR="$PWD"
+TESTDIR="`pwd`"
export TESTDIR
INST="$HGTMP/install"
PYTHONDIR="$INST/lib/python"
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -15,11 +15,18 @@ from optparse import OptionParser
required_tools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
-parser = OptionParser()
-parser.add_option("-v", "--verbose", action="store_true", dest="verbose",
- default=False, help="output verbose messages")
+parser = OptionParser("%prog [options] [tests]")
+parser.add_option("-v", "--verbose", action="store_true",
+ help="output verbose messages")
+parser.add_option("-c", "--cover", action="store_true",
+ help="print a test coverage report")
+parser.add_option("-s", "--cover_stdlib", action="store_true",
+ help="print a test coverage report inc. standard libraries")
+parser.add_option("-C", "--annotate", action="store_true",
+ help="output files annotated with coverage")
(options, args) = parser.parse_args()
verbose = options.verbose
+coverage = options.cover or options.cover_stdlib or options.annotate
def vlog(*msg):
if verbose:
@@ -40,66 +47,82 @@ def find_program(program):
return name
return None
-# Before we go any further, check for pre-requisite tools
-# stuff from coreutils (cat, rm, etc) are not tested
-for p in required_tools:
- if os.name == 'nt':
- p += '.exe'
- found = find_program(p)
- if found:
- vlog("# Found prerequisite", p, "at", found)
- else:
- print "WARNING: Did not find prerequisite tool: "+p
-
-# Reset some environment variables to well-known values
-os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
-os.environ['TZ'] = 'GMT'
-
-os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
-os.environ["HGMERGE"] = sys.executable + ' -c "import sys; sys.exit(0)"'
-os.environ["HGUSER"] = "test"
-os.environ["HGRCPATH"] = ""
-
-TESTDIR = os.environ["TESTDIR"] = os.getcwd()
-HGTMP = os.environ["HGTMP"] = tempfile.mkdtemp("", "hgtests.")
+def check_required_tools():
+ # Before we go any further, check for pre-requisite tools
+ # stuff from coreutils (cat, rm, etc) are not tested
+ for p in required_tools:
+ if os.name == 'nt':
+ p += '.exe'
+ found = find_program(p)
+ if found:
+ vlog("# Found prerequisite", p, "at", found)
+ else:
+ print "WARNING: Did not find prerequisite tool: "+p
def cleanup_exit():
if verbose:
print "# Cleaning up HGTMP", HGTMP
shutil.rmtree(HGTMP, True)
-vlog("# Using TESTDIR", TESTDIR)
-vlog("# Using HGTMP", HGTMP)
-
-os.umask(022)
+def install_hg():
+ vlog("# Performing temporary installation of HG")
+ installerrs = os.path.join("tests", "install.err")
-vlog("# Performing temporary installation of HG")
-INST = os.path.join(HGTMP, "install")
-BINDIR = os.path.join(INST, "bin")
-PYTHONDIR = os.path.join(INST, "lib", "python")
-installerrs = os.path.join("tests", "install.err")
+ os.chdir("..") # Get back to hg root
+ cmd = '%s setup.py install --home="%s" --install-lib="%s" >%s 2>&1' % \
+ (sys.executable, INST, PYTHONDIR, installerrs)
+ vlog("# Running", cmd)
+ if os.system(cmd) == 0:
+ if not verbose:
+ os.remove(installerrs)
+ else:
+ f = open(installerrs)
+ for line in f:
+ print line,
+ f.close()
+ sys.exit(1)
+ os.chdir(TESTDIR)
+
+ os.environ["PATH"] = "%s%s%s" % (BINDIR, os.pathsep, os.environ["PATH"])
+ os.environ["PYTHONPATH"] = PYTHONDIR
-os.chdir("..") # Get back to hg root
-cmd = '%s setup.py install --home="%s" --install-lib="%s" >%s 2>&1' % \
- (sys.executable, INST, PYTHONDIR, installerrs)
-vlog("# Running", cmd)
-if os.system(cmd) == 0:
- if not verbose:
- os.remove(installerrs)
-else:
- f = open(installerrs)
- for line in f:
- print line,
- f.close()
- cleanup_exit()
- sys.exit(1)
-os.chdir(TESTDIR)
+ if coverage:
+ vlog("# Installing coverage wrapper")
+ os.environ['COVERAGE_FILE'] = COVERAGE_FILE
+ if os.path.exists(COVERAGE_FILE):
+ os.unlink(COVERAGE_FILE)
+ # Create a wrapper script to invoke hg via coverage.py
+ os.rename(os.path.join(BINDIR, "hg"), os.path.join(BINDIR, "_hg.py"))
+ f = open(os.path.join(BINDIR, 'hg'), 'w')
+ f.write('#!' + sys.executable + '\n')
+ f.write('import sys, os; os.execv(sys.executable, [sys.executable, '+ \
+ '"%s", "-x", "%s"] + sys.argv[1:])\n' % (
+ os.path.join(TESTDIR, 'coverage.py'),
+ os.path.join(BINDIR, '_hg.py')))
+ f.close()
+ os.chmod(os.path.join(BINDIR, 'hg'), 0700)
-os.environ["PATH"] = "%s%s%s" % (BINDIR, os.pathsep, os.environ["PATH"])
-os.environ["PYTHONPATH"] = PYTHONDIR
-
-tests = 0
-failed = 0
+def output_coverage():
+ vlog("# Producing coverage report")
+ omit = [BINDIR, TESTDIR, PYTHONDIR]
+ if not options.cover_stdlib:
+ # Exclude as system paths (ignoring empty strings seen on win)
+ omit += [x for x in sys.path if x != '']
+ omit = ','.join(omit)
+ os.chdir(PYTHONDIR)
+ cmd = '"%s" "%s" -r "--omit=%s"' % (
+ sys.executable, os.path.join(TESTDIR, 'coverage.py'), omit)
+ vlog("# Running: "+cmd)
+ os.system(cmd)
+ if options.annotate:
+ adir = os.path.join(TESTDIR, 'annotated')
+ if not os.path.isdir(adir):
+ os.mkdir(adir)
+ cmd = '"%s" "%s" -a "--directory=%s" "--omit=%s"' % (
+ sys.executable, os.path.join(TESTDIR, 'coverage.py'),
+ adir, omit)
+ vlog("# Running: "+cmd)
+ os.system(cmd)
def run(cmd, split_lines=True):
"""Run command in a sub-process, capturing the output (stdout and stderr).
@@ -176,17 +199,52 @@ def run_one(test):
shutil.rmtree(tmpd, True)
return ret == 0
-for test in os.listdir("."):
- if test.startswith("test-"):
- if '~' in test or re.search(r'\.(out|err)$', test):
- continue
- if not run_one(test):
- failed += 1
- tests += 1
+
+os.umask(022)
+
+check_required_tools()
+
+# Reset some environment variables to well-known values so that
+# the tests produce repeatable output.
+os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
+os.environ['TZ'] = 'GMT'
+
+os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
+os.environ["HGMERGE"] = sys.executable + ' -c "import sys; sys.exit(0)"'
+os.environ["HGUSER"] = "test"
+os.environ["HGRCPATH"] = ""
+
+TESTDIR = os.environ["TESTDIR"] = os.getcwd()
+HGTMP = os.environ["HGTMP"] = tempfile.mkdtemp("", "hgtests.")
+vlog("# Using TESTDIR", TESTDIR)
+vlog("# Using HGTMP", HGTMP)
-print "# Ran %d tests, %d failed." % (tests, failed)
+INST = os.path.join(HGTMP, "install")
+BINDIR = os.path.join(INST, "bin")
+PYTHONDIR = os.path.join(INST, "lib", "python")
+COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
+
+try:
+ install_hg()
+
+ tests = 0
+ failed = 0
-cleanup_exit()
+ if len(args) == 0:
+ args = os.listdir(".")
+ for test in args:
+ if test.startswith("test-"):
+ if '~' in test or re.search(r'\.(out|err)$', test):
+ continue
+ if not run_one(test):
+ failed += 1
+ tests += 1
+
+ print "\n# Ran %d tests, %d failed." % (tests, failed)
+ if coverage:
+ output_coverage()
+finally:
+ cleanup_exit()
if failed:
sys.exit(1)
new file mode 100755
--- /dev/null
+++ b/tests/test-backout
@@ -0,0 +1,51 @@
+#!/bin/sh
+
+echo '# basic operation'
+hg init basic
+cd basic
+echo a > a
+hg commit -d '0 0' -A -m a
+echo b >> a
+hg commit -d '1 0' -m b
+
+hg backout -d '2 0' tip
+cat a
+
+echo '# file that was removed is recreated'
+cd ..
+hg init remove
+cd remove
+
+echo content > a
+hg commit -d '0 0' -A -m a
+
+hg rm a
+hg commit -d '1 0' -m b
+
+hg backout -d '2 0' --merge tip
+cat a
+
+echo '# backout of backout is as if nothing happened'
+
+hg backout -d '3 0' --merge tip
+cat a
+
+echo '# backout with merge'
+cd ..
+hg init merge
+cd merge
+
+echo line 1 > a
+hg commit -d '0 0' -A -m a
+
+echo line 2 >> a
+hg commit -d '1 0' -m b
+
+echo line 3 >> a
+hg commit -d '2 0' -m c
+
+hg backout --merge -d '3 0' 1
+hg commit -d '4 0' -m d
+cat a
+
+exit 0
new file mode 100644
--- /dev/null
+++ b/tests/test-backout.out
@@ -0,0 +1,19 @@
+# basic operation
+adding a
+changeset 2:b38a34ddfd9f backs out changeset 1:a820f4f40a57
+a
+# file that was removed is recreated
+adding a
+adding a
+changeset 2:44cd84c7349a backs out changeset 1:76862dcce372
+content
+# backout of backout is as if nothing happened
+removing a
+changeset 3:0dd8a0ed5e99 backs out changeset 2:44cd84c7349a
+cat: a: No such file or directory
+# backout with merge
+adding a
+changeset 3:6c77ecc28460 backs out changeset 1:314f55b1bf23
+merging with changeset 2:b66ea5b77abb
+merging a
+line 1
--- a/tests/test-help.out
+++ b/tests/test-help.out
@@ -42,6 +42,7 @@ list of commands (use "hg help -v" to sh
addremove add all new files, delete all missing files
annotate show changeset information per file line
archive create unversioned archive of a repository revision
+ backout reverse effect of earlier changeset
bundle create a changegroup file
cat output the latest or given revisions of files
clone make a copy of an existing repository
@@ -49,7 +50,6 @@ list of commands (use "hg help -v" to sh
copy mark files as copied for the next commit
diff diff repository (or selected files)
export dump the header and diffs for one or more changesets
- forget don't add the specified files on the next commit
grep search for a pattern in specified files and revisions
heads show current repository heads
help show help for a given command or all commands
@@ -85,6 +85,7 @@ list of commands (use "hg help -v" to sh
addremove add all new files, delete all missing files
annotate show changeset information per file line
archive create unversioned archive of a repository revision
+ backout reverse effect of earlier changeset
bundle create a changegroup file
cat output the latest or given revisions of files
clone make a copy of an existing repository
@@ -92,7 +93,6 @@ list of commands (use "hg help -v" to sh
copy mark files as copied for the next commit
diff diff repository (or selected files)
export dump the header and diffs for one or more changesets
- forget don't add the specified files on the next commit
grep search for a pattern in specified files and revisions
heads show current repository heads
help show help for a given command or all commands
--- a/tests/test-hook
+++ b/tests/test-hook
@@ -87,4 +87,93 @@ hg undo
echo 'preoutgoing.forbid = echo preoutgoing.forbid hook; exit 1' >> ../a/.hg/hgrc
hg pull ../a
+cat > hooktests.py <<EOF
+from mercurial import util
+
+uncallable = 0
+
+def printargs(args):
+ args.pop('ui', None)
+ args.pop('repo', None)
+ a = list(args.items())
+ a.sort()
+ print 'hook args:'
+ for k, v in a:
+ print ' ', k, v
+ return True
+
+def passhook(**args):
+ printargs(args)
+ return True
+
+def failhook(**args):
+ printargs(args)
+
+class LocalException(Exception):
+ pass
+
+def raisehook(**args):
+ raise LocalException('exception from hook')
+
+def aborthook(**args):
+ raise util.Abort('raise abort from hook')
+
+def brokenhook(**args):
+ return 1 + {}
+
+class container:
+ unreachable = 1
+EOF
+
+echo '# test python hooks'
+PYTHONPATH="`pwd`:$PYTHONPATH"
+export PYTHONPATH
+
+echo '[hooks]' > ../a/.hg/hgrc
+echo 'preoutgoing.broken = python:hooktests.brokenhook' >> ../a/.hg/hgrc
+hg pull ../a 2>&1 | grep 'raised an exception'
+
+echo '[hooks]' > ../a/.hg/hgrc
+echo 'preoutgoing.raise = python:hooktests.raisehook' >> ../a/.hg/hgrc
+hg pull ../a 2>&1 | grep 'raised an exception'
+
+echo '[hooks]' > ../a/.hg/hgrc
+echo 'preoutgoing.abort = python:hooktests.aborthook' >> ../a/.hg/hgrc
+hg pull ../a
+
+echo '[hooks]' > ../a/.hg/hgrc
+echo 'preoutgoing.fail = python:hooktests.failhook' >> ../a/.hg/hgrc
+hg pull ../a
+
+echo '[hooks]' > ../a/.hg/hgrc
+echo 'preoutgoing.uncallable = python:hooktests.uncallable' >> ../a/.hg/hgrc
+hg pull ../a
+
+echo '[hooks]' > ../a/.hg/hgrc
+echo 'preoutgoing.nohook = python:hooktests.nohook' >> ../a/.hg/hgrc
+hg pull ../a
+
+echo '[hooks]' > ../a/.hg/hgrc
+echo 'preoutgoing.nomodule = python:nomodule' >> ../a/.hg/hgrc
+hg pull ../a
+
+echo '[hooks]' > ../a/.hg/hgrc
+echo 'preoutgoing.badmodule = python:nomodule.nowhere' >> ../a/.hg/hgrc
+hg pull ../a
+
+echo '[hooks]' > ../a/.hg/hgrc
+echo 'preoutgoing.unreachable = python:hooktests.container.unreachable' >> ../a/.hg/hgrc
+hg pull ../a
+
+echo '[hooks]' > ../a/.hg/hgrc
+echo 'preoutgoing.pass = python:hooktests.passhook' >> ../a/.hg/hgrc
+hg pull ../a
+
+echo '# make sure --traceback works'
+echo '[hooks]' > .hg/hgrc
+echo 'commit.abort = python:hooktests.aborthook' >> .hg/hgrc
+
+echo a >> a
+hg --traceback commit -A -m a 2>&1 | grep '^Traceback'
+
exit 0
--- a/tests/test-hook.out
+++ b/tests/test-hook.out
@@ -89,3 +89,43 @@ preoutgoing.forbid hook
pulling from ../a
searching for changes
abort: preoutgoing.forbid hook exited with status 1
+# test python hooks
+error: preoutgoing.broken hook raised an exception: unsupported operand type(s) for +: 'int' and 'dict'
+error: preoutgoing.raise hook raised an exception: exception from hook
+pulling from ../a
+searching for changes
+error: preoutgoing.abort hook failed: raise abort from hook
+abort: raise abort from hook
+pulling from ../a
+searching for changes
+hook args:
+ hooktype preoutgoing
+ source pull
+abort: preoutgoing.fail hook failed
+pulling from ../a
+searching for changes
+abort: preoutgoing.uncallable hook is invalid ("hooktests.uncallable" is not callable)
+pulling from ../a
+searching for changes
+abort: preoutgoing.nohook hook is invalid ("hooktests.nohook" is not defined)
+pulling from ../a
+searching for changes
+abort: preoutgoing.nomodule hook is invalid ("nomodule" not in a module)
+pulling from ../a
+searching for changes
+abort: preoutgoing.badmodule hook is invalid (import of "nomodule" failed)
+pulling from ../a
+searching for changes
+abort: preoutgoing.unreachable hook is invalid (import of "hooktests.container" failed)
+pulling from ../a
+searching for changes
+hook args:
+ hooktype preoutgoing
+ source pull
+adding changesets
+adding manifests
+adding file changes
+added 1 changesets with 1 changes to 1 files
+(run 'hg update' to get a working copy)
+# make sure --traceback works
+Traceback (most recent call last):
--- a/tests/test-nested-repo
+++ b/tests/test-nested-repo
@@ -14,6 +14,6 @@ hg add b/x
echo '# should print A b/x'
hg st
echo '# should forget b/x'
-hg forget
+hg revert
echo '# should print nothing'
hg st b
--- a/tests/test-revert
+++ b/tests/test-revert
@@ -54,4 +54,18 @@ echo %% should silently add a
hg revert -r0 a
hg st a
+hg update -C
+chmod +x c
+hg revert
+echo %% should print non-executable
+test -x c || echo non-executable
+
+chmod +x c
+hg commit -d '1000001 0' -m exe
+
+chmod -x c
+hg revert
+echo %% should print executable
+test -x c && echo executable
+
true
--- a/tests/test-revert.out
+++ b/tests/test-revert.out
@@ -45,3 +45,9 @@ forgetting z
forgetting a
%% should silently add a
A a
+reverting c
+%% should print non-executable
+non-executable
+reverting c
+%% should print executable
+executable