--- a/contrib/hg-ssh Wed May 23 21:34:29 2012 +0200
+++ b/contrib/hg-ssh Wed May 30 14:31:39 2012 -0500
@@ -33,25 +33,31 @@
import sys, os, shlex
-cwd = os.getcwd()
-allowed_paths = [os.path.normpath(os.path.join(cwd, os.path.expanduser(path)))
- for path in sys.argv[1:]]
-orig_cmd = os.getenv('SSH_ORIGINAL_COMMAND', '?')
-try:
- cmdargv = shlex.split(orig_cmd)
-except ValueError, e:
- sys.stderr.write('Illegal command "%s": %s\n' % (orig_cmd, e))
- sys.exit(255)
+def main():
+ cwd = os.getcwd()
+ allowed_paths = [os.path.normpath(os.path.join(cwd,
+ os.path.expanduser(path)))
+ for path in sys.argv[1:]]
+ orig_cmd = os.getenv('SSH_ORIGINAL_COMMAND', '?')
+ try:
+ cmdargv = shlex.split(orig_cmd)
+ except ValueError, e:
+ sys.stderr.write('Illegal command "%s": %s\n' % (orig_cmd, e))
+ sys.exit(255)
-if cmdargv[:2] == ['hg', '-R'] and cmdargv[3:] == ['serve', '--stdio']:
- path = cmdargv[2]
- repo = os.path.normpath(os.path.join(cwd, os.path.expanduser(path)))
- if repo in allowed_paths:
- dispatch.dispatch(dispatch.request(['-R', repo, 'serve', '--stdio']))
+ if cmdargv[:2] == ['hg', '-R'] and cmdargv[3:] == ['serve', '--stdio']:
+ path = cmdargv[2]
+ repo = os.path.normpath(os.path.join(cwd, os.path.expanduser(path)))
+ if repo in allowed_paths:
+ dispatch.dispatch(dispatch.request(['-R', repo,
+ 'serve',
+ '--stdio']))
+ else:
+ sys.stderr.write('Illegal repository "%s"\n' % repo)
+ sys.exit(255)
else:
- sys.stderr.write('Illegal repository "%s"\n' % repo)
+ sys.stderr.write('Illegal command "%s"\n' % orig_cmd)
sys.exit(255)
-else:
- sys.stderr.write('Illegal command "%s"\n' % orig_cmd)
- sys.exit(255)
+if __name__ == '__main__':
+ main()
--- a/contrib/perf.py Wed May 23 21:34:29 2012 +0200
+++ b/contrib/perf.py Wed May 30 14:31:39 2012 -0500
@@ -46,8 +46,21 @@
# False))))
timer(lambda: sum(map(len, repo.status())))
+def clearcaches(cl):
+ # behave somewhat consistently across internal API changes
+ if util.safehasattr(cl, 'clearcaches'):
+ cl.clearcaches()
+ elif util.safehasattr(cl, '_nodecache'):
+ from mercurial.node import nullid, nullrev
+ cl._nodecache = {nullid: nullrev}
+ cl._nodepos = None
+
def perfheads(ui, repo):
- timer(lambda: len(repo.changelog.headrevs()))
+ cl = repo.changelog
+ def d():
+ len(cl.headrevs())
+ clearcaches(cl)
+ timer(d)
def perftags(ui, repo):
import mercurial.changelog, mercurial.manifest
@@ -72,6 +85,14 @@
del repo.dirstate._dirs
timer(d)
+def perfdirstatewrite(ui, repo):
+ ds = repo.dirstate
+ "a" in ds
+ def d():
+ ds._dirty = True
+ ds.write()
+ timer(d)
+
def perfmanifest(ui, repo):
def d():
t = repo.manifest.tip()
@@ -126,20 +147,9 @@
mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
n = repo[rev].node()
cl = mercurial.revlog.revlog(repo.sopener, "00changelog.i")
- # behave somewhat consistently across internal API changes
- if util.safehasattr(cl, 'clearcaches'):
- clearcaches = cl.clearcaches
- elif util.safehasattr(cl, '_nodecache'):
- from mercurial.node import nullid, nullrev
- def clearcaches():
- cl._nodecache = {nullid: nullrev}
- cl._nodepos = None
- else:
- def clearcaches():
- pass
def d():
cl.rev(n)
- clearcaches()
+ clearcaches(cl)
timer(d)
def perflog(ui, repo, **opts):
@@ -218,6 +228,7 @@
'perftags': (perftags, []),
'perfdirstate': (perfdirstate, []),
'perfdirstatedirs': (perfdirstate, []),
+ 'perfdirstatewrite': (perfdirstatewrite, []),
'perflog': (perflog,
[('', 'rename', False, 'ask log to follow renames')]),
'perftemplating': (perftemplating, []),
--- a/mercurial/commands.py Wed May 23 21:34:29 2012 +0200
+++ b/mercurial/commands.py Wed May 30 14:31:39 2012 -0500
@@ -3089,78 +3089,6 @@
textwidth = min(ui.termwidth(), 80) - 2
- def optrst(options):
- data = []
- multioccur = False
- for option in options:
- if len(option) == 5:
- shortopt, longopt, default, desc, optlabel = option
- else:
- shortopt, longopt, default, desc = option
- optlabel = _("VALUE") # default label
-
- if _("DEPRECATED") in desc and not ui.verbose:
- continue
-
- so = ''
- if shortopt:
- so = '-' + shortopt
- lo = '--' + longopt
- if default:
- desc += _(" (default: %s)") % default
-
- if isinstance(default, list):
- lo += " %s [+]" % optlabel
- multioccur = True
- elif (default is not None) and not isinstance(default, bool):
- lo += " %s" % optlabel
-
- data.append((so, lo, desc))
-
- rst = minirst.maketable(data, 1)
-
- if multioccur:
- rst += _("\n[+] marked option can be specified multiple times\n")
-
- return rst
-
- # list all option lists
- def opttext(optlist, width):
- rst = ''
- if not optlist:
- return ''
-
- for title, options in optlist:
- rst += '\n%s\n' % title
- if options:
- rst += "\n"
- rst += optrst(options)
- rst += '\n'
-
- return '\n' + minirst.format(rst, width)
-
- def addglobalopts(optlist, aliases):
- if ui.quiet:
- return []
-
- if ui.verbose:
- optlist.append((_("global options:"), globalopts))
- if name == 'shortlist':
- optlist.append((_('use "hg help" for the full list '
- 'of commands'), ()))
- else:
- if name == 'shortlist':
- msg = _('use "hg help" for the full list of commands '
- 'or "hg -v" for details')
- elif name and not full:
- msg = _('use "hg help %s" to show the full help text') % name
- elif aliases:
- msg = _('use "hg -v help%s" to show builtin aliases and '
- 'global options') % (name and " " + name or "")
- else:
- msg = _('use "hg -v help %s" to show more info') % name
- optlist.append((msg, ()))
-
def helpcmd(name):
try:
aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
@@ -3223,13 +3151,13 @@
rst += '\n'
rst += _("options:")
rst += '\n\n'
- rst += optrst(entry[1])
+ rst += help.optrst(entry[1], ui.verbose)
if ui.verbose:
rst += '\n'
rst += _("global options:")
rst += '\n\n'
- rst += optrst(globalopts)
+ rst += help.optrst(globalopts, ui.verbose)
keep = ui.verbose and ['verbose'] or []
formatted, pruned = minirst.format(rst, textwidth, keep=keep)
@@ -3303,8 +3231,25 @@
ui.write(" %-*s %s\n" % (topics_len, t, desc))
optlist = []
- addglobalopts(optlist, True)
- ui.write(opttext(optlist, textwidth))
+ if not ui.quiet:
+ if ui.verbose:
+ optlist.append((_("global options:"), globalopts))
+ if name == 'shortlist':
+ optlist.append((_('use "hg help" for the full list '
+ 'of commands'), ()))
+ else:
+ if name == 'shortlist':
+ msg = _('use "hg help" for the full list of commands '
+ 'or "hg -v" for details')
+ elif name and not full:
+ msg = _('use "hg help %s" to show the full help '
+ 'text') % name
+ else:
+ msg = _('use "hg -v help%s" to show builtin aliases and '
+ 'global options') % (name and " " + name or "")
+ optlist.append((msg, ()))
+
+ ui.write(help.opttext(optlist, textwidth, ui.verbose))
def helptopic(name):
for names, header, doc in help.helptable:
--- a/mercurial/help.py Wed May 23 21:34:29 2012 +0200
+++ b/mercurial/help.py Wed May 30 14:31:39 2012 -0500
@@ -8,7 +8,7 @@
from i18n import gettext, _
import itertools, sys, os
import extensions, revset, fileset, templatekw, templatefilters, filemerge
-import encoding, util
+import encoding, util, minirst
def listexts(header, exts, indent=1):
'''return a text listing of the given extensions'''
@@ -27,6 +27,56 @@
doc += listexts(_('disabled extensions:'), extensions.disabled())
return doc
+def optrst(options, verbose):
+ data = []
+ multioccur = False
+ for option in options:
+ if len(option) == 5:
+ shortopt, longopt, default, desc, optlabel = option
+ else:
+ shortopt, longopt, default, desc = option
+ optlabel = _("VALUE") # default label
+
+ if _("DEPRECATED") in desc and not verbose:
+ continue
+
+ so = ''
+ if shortopt:
+ so = '-' + shortopt
+ lo = '--' + longopt
+ if default:
+ desc += _(" (default: %s)") % default
+
+ if isinstance(default, list):
+ lo += " %s [+]" % optlabel
+ multioccur = True
+ elif (default is not None) and not isinstance(default, bool):
+ lo += " %s" % optlabel
+
+ data.append((so, lo, desc))
+
+ rst = minirst.maketable(data, 1)
+
+ if multioccur:
+ rst += _("\n[+] marked option can be specified multiple times\n")
+
+ return rst
+
+# list all option lists
+def opttext(optlist, width, verbose):
+ rst = ''
+ if not optlist:
+ return ''
+
+ for title, options in optlist:
+ rst += '\n%s\n' % title
+ if options:
+ rst += "\n"
+ rst += optrst(options, verbose)
+ rst += '\n'
+
+ return '\n' + minirst.format(rst, width)
+
def topicmatch(kw):
"""Return help topics matching kw.
--- a/mercurial/match.py Wed May 23 21:34:29 2012 +0200
+++ b/mercurial/match.py Wed May 30 14:31:39 2012 -0500
@@ -62,7 +62,10 @@
pats = _normalize(exclude, 'glob', root, cwd, auditor)
self.excludepat, em = _buildmatch(ctx, pats, '(?:/|$)')
if exact:
- self._files = patterns
+ if isinstance(patterns, list):
+ self._files = patterns
+ else:
+ self._files = list(patterns)
pm = self.exact
elif patterns:
pats = _normalize(patterns, default, root, cwd, auditor)
--- a/mercurial/parsers.c Wed May 23 21:34:29 2012 +0200
+++ b/mercurial/parsers.c Wed May 30 14:31:39 2012 -0500
@@ -246,6 +246,7 @@
Py_ssize_t raw_length; /* original number of elements */
Py_ssize_t length; /* current number of elements */
PyObject *added; /* populated on demand */
+ PyObject *headrevs; /* cache, invalidated on changes */
nodetree *nt; /* base-16 trie */
int ntlength; /* # nodes in use */
int ntcapacity; /* # nodes allocated */
@@ -463,6 +464,7 @@
if (self->nt)
nt_insert(self, node, (int)offset);
+ Py_CLEAR(self->headrevs);
Py_RETURN_NONE;
}
@@ -484,6 +486,7 @@
free(self->nt);
self->nt = NULL;
}
+ Py_CLEAR(self->headrevs);
}
static PyObject *index_clearcaches(indexObject *self)
@@ -534,6 +537,107 @@
return NULL;
}
+/*
+ * When we cache a list, we want to be sure the caller can't mutate
+ * the cached copy.
+ */
+static PyObject *list_copy(PyObject *list)
+{
+ Py_ssize_t len = PyList_GET_SIZE(list);
+ PyObject *newlist = PyList_New(len);
+ Py_ssize_t i;
+
+ if (newlist == NULL)
+ return NULL;
+
+ for (i = 0; i < len; i++) {
+ PyObject *obj = PyList_GET_ITEM(list, i);
+ Py_INCREF(obj);
+ PyList_SET_ITEM(newlist, i, obj);
+ }
+
+ return newlist;
+}
+
+static PyObject *index_headrevs(indexObject *self)
+{
+ Py_ssize_t i, len, addlen;
+ char *nothead = NULL;
+ PyObject *heads;
+
+ if (self->headrevs)
+ return list_copy(self->headrevs);
+
+ len = index_length(self) - 1;
+ heads = PyList_New(0);
+ if (heads == NULL)
+ goto bail;
+ if (len == 0) {
+ PyObject *nullid = PyInt_FromLong(-1);
+ if (nullid == NULL || PyList_Append(heads, nullid) == -1) {
+ Py_XDECREF(nullid);
+ goto bail;
+ }
+ goto done;
+ }
+
+ nothead = calloc(len, 1);
+ if (nothead == NULL)
+ goto bail;
+
+ for (i = 0; i < self->raw_length; i++) {
+ const char *data = index_deref(self, i);
+ int parent_1 = getbe32(data + 24);
+ int parent_2 = getbe32(data + 28);
+ if (parent_1 >= 0)
+ nothead[parent_1] = 1;
+ if (parent_2 >= 0)
+ nothead[parent_2] = 1;
+ }
+
+ addlen = self->added ? PyList_GET_SIZE(self->added) : 0;
+
+ for (i = 0; i < addlen; i++) {
+ PyObject *rev = PyList_GET_ITEM(self->added, i);
+ PyObject *p1 = PyTuple_GET_ITEM(rev, 5);
+ PyObject *p2 = PyTuple_GET_ITEM(rev, 6);
+ long parent_1, parent_2;
+
+ if (!PyInt_Check(p1) || !PyInt_Check(p2)) {
+ PyErr_SetString(PyExc_TypeError,
+ "revlog parents are invalid");
+ goto bail;
+ }
+ parent_1 = PyInt_AS_LONG(p1);
+ parent_2 = PyInt_AS_LONG(p2);
+ if (parent_1 >= 0)
+ nothead[parent_1] = 1;
+ if (parent_2 >= 0)
+ nothead[parent_2] = 1;
+ }
+
+ for (i = 0; i < len; i++) {
+ PyObject *head;
+
+ if (nothead[i])
+ continue;
+ head = PyInt_FromLong(i);
+ if (head == NULL || PyList_Append(heads, head) == -1) {
+ Py_XDECREF(head);
+ goto bail;
+ }
+ }
+
+done:
+ self->headrevs = heads;
+ free(nothead);
+ return list_copy(self->headrevs);
+bail:
+ Py_XDECREF(heads);
+ free(nothead);
+ return NULL;
+}
+
static inline int nt_level(const char *node, Py_ssize_t level)
{
int v = node[level>>1];
@@ -930,6 +1034,7 @@
{
Py_ssize_t start, stop, step, slicelength;
Py_ssize_t length = index_length(self);
+ int ret = 0;
if (PySlice_GetIndicesEx((PySliceObject*)item, length,
&start, &stop, &step, &slicelength) < 0)
@@ -975,7 +1080,9 @@
self->ntrev = (int)start;
}
self->length = start + 1;
- return 0;
+ if (start < self->raw_length)
+ self->raw_length = start;
+ goto done;
}
if (self->nt) {
@@ -983,10 +1090,12 @@
if (self->ntrev > start)
self->ntrev = (int)start;
}
- return self->added
- ? PyList_SetSlice(self->added, start - self->length + 1,
- PyList_GET_SIZE(self->added), NULL)
- : 0;
+ if (self->added)
+ ret = PyList_SetSlice(self->added, start - self->length + 1,
+ PyList_GET_SIZE(self->added), NULL);
+done:
+ Py_CLEAR(self->headrevs);
+ return ret;
}
/*
@@ -1076,6 +1185,7 @@
self->cache = NULL;
self->added = NULL;
+ self->headrevs = NULL;
self->offsets = NULL;
self->nt = NULL;
self->ntlength = self->ntcapacity = 0;
@@ -1140,6 +1250,8 @@
"clear the index caches"},
{"get", (PyCFunction)index_m_get, METH_VARARGS,
"get an index entry"},
+ {"headrevs", (PyCFunction)index_headrevs, METH_NOARGS,
+ "get head revisions"},
{"insert", (PyCFunction)index_insert, METH_VARARGS,
"insert an index entry"},
{"partialmatch", (PyCFunction)index_partialmatch, METH_VARARGS,
--- a/mercurial/revlog.py Wed May 23 21:34:29 2012 +0200
+++ b/mercurial/revlog.py Wed May 30 14:31:39 2012 -0500
@@ -635,6 +635,10 @@
return (orderedout, roots, heads)
def headrevs(self):
+ try:
+ return self.index.headrevs()
+ except AttributeError:
+ pass
count = len(self)
if not count:
return [nullrev]
--- a/mercurial/scmutil.py Wed May 23 21:34:29 2012 +0200
+++ b/mercurial/scmutil.py Wed May 30 14:31:39 2012 -0500
@@ -525,9 +525,11 @@
l = revrange(repo, revs)
if len(l) == 0:
+ if revs:
+ raise util.Abort(_('empty revision range'))
return repo.dirstate.p1(), None
- if len(l) == 1:
+ if len(l) == 1 and len(revs) == 1 and _revrangesep not in revs[0]:
return repo.lookup(l[0]), None
return repo.lookup(l[0]), repo.lookup(l[-1])
--- a/tests/test-diff-change.t Wed May 23 21:34:29 2012 +0200
+++ b/tests/test-diff-change.t Wed May 30 14:31:39 2012 -0500
@@ -29,6 +29,12 @@
-first
+second
+Test dumb revspecs (issue3474)
+
+ $ hg diff -r 2:2
+ $ hg diff -r "2 and 1"
+ abort: empty revision range
+ [255]
Testing diff --change when merge: