Mercurial > hg
view mercurial/templatefilters.py @ 20182:04036798ebed
branches: avoid unnecessary changectx.branch() calls
This requires reading from the changelog, which can be costly over NFS.
Note that this does not totally remove reading from the changelog; we
still do that when calling changectx.closesbranch(). That call will be
removed in a later patch.
Running hg branches on the PyPy repo (with 996) over a busy NFS server,
before this change:
$ time hg --profile branches > /dev/null
CallCount Recursive Total(s) Inline(s) module:lineno(function)
2042 0 2.2827 2.2827 <open>
2036 0 0.9840 0.9840 <method 'close' of 'file' objects>
2036 0 0.0464 0.0464 <method 'read' of 'file' objects>
5233 0 0.1985 0.0453 mercurial.repoview:161(changelog)
10462 0 0.0791 0.0314 mercurial.changelog:133(tip)
5233 0 0.0388 0.0176 mercurial.localrepo:26(__get__)
10462 0 0.0250 0.0126 <len>
5233 0 0.0059 0.0039 mercurial.repoview:112(filterrevs)
10462 0 0.0029 0.0029 <hash>
2034 0 0.0444 0.0444 <method 'seek' of 'file' objects>
5340 0 0.0390 0.0390 mercurial.revlog:296(rev)
2582 0 0.0371 0.0371 <zlib.decompress>
3155 0 0.1963 0.0366 mercurial.context:202(__init__)
3155 0 0.1238 0.0306 mercurial.repoview:161(changelog)
3155 0 0.0261 0.0080 mercurial.changelog:183(rev)
9465 0 0.0061 0.0061 <isinstance>
1096 0 0.0023 0.0023 <binascii.unhexlify>
4251 0 0.0014 0.0014 <len>
2059 0 3.7341 0.0332 mercurial.changelog:270(read)
2059 0 3.6304 0.0307 mercurial.revlog:907(revision)
2057 0 0.0262 0.0137 mercurial.changelog:28(decodeextra)
4118 0 0.0094 0.0094 <method 'split' of 'str' objects>
4118 0 0.0270 0.0048 mercurial.encoding:61(tolocal)
2059 0 0.0040 0.0040 <method 'index' of 'str' objects>
10462 0 0.0791 0.0314 mercurial.changelog:133(tip)
10462 0 0.0289 0.0207 mercurial.changelog:190(node)
10462 0 0.0188 0.0091 <len>
52433 20932 0.0478 0.0310 <len>
20932 0 0.0221 0.0168 mercurial.revlog:262(__len__)
2059 0 3.6304 0.0307 mercurial.revlog:907(revision)
real 0m4.361s
user 0m0.986s
sys 0m0.237s
After this change:
$ time hg --profile branches > /dev/null
CallCount Recursive Total(s) Inline(s) module:lineno(function)
1069 0 1.1098 1.1098 <open>
1063 0 0.4865 0.4865 <method 'close' of 'file' objects>
4122 0 0.1811 0.0404 mercurial.repoview:161(changelog)
8240 0 0.0712 0.0272 mercurial.changelog:133(tip)
4122 0 0.0378 0.0177 mercurial.localrepo:26(__get__)
8240 0 0.0221 0.0115 <len>
4122 0 0.0057 0.0033 mercurial.repoview:112(filterrevs)
8240 0 0.0025 0.0025 <hash>
3029 0 0.1979 0.0371 mercurial.context:202(__init__)
3029 0 0.1278 0.0310 mercurial.repoview:161(changelog)
3029 0 0.0230 0.0081 mercurial.changelog:183(rev)
9087 0 0.0061 0.0061 <isinstance>
1096 0 0.0026 0.0026 <binascii.unhexlify>
4125 0 0.0014 0.0014 <len>
4229 0 0.0337 0.0337 mercurial.revlog:296(rev)
1061 0 0.0296 0.0296 <method 'seek' of 'file' objects>
1063 0 0.0292 0.0292 <method 'read' of 'file' objects>
8240 0 0.0712 0.0272 mercurial.changelog:133(tip)
8240 0 0.0271 0.0196 mercurial.changelog:190(node)
8240 0 0.0169 0.0083 <len>
40476 16488 0.0422 0.0271 <len>
16488 0 0.0193 0.0152 mercurial.revlog:262(__len__)
1342 0 0.0241 0.0241 <zlib.decompress>
9445 0 0.0336 0.0224 mercurial.changelog:190(node)
9445 0 0.0112 0.0112 mercurial.revlog:317(node)
1074 0 1.9102 0.0224 mercurial.changelog:270(read)
1074 0 1.8397 0.0202 mercurial.revlog:907(revision)
1073 0 0.0187 0.0099 mercurial.changelog:28(decodeextra)
2148 0 0.0061 0.0061 <method 'split' of 'str' objects>
2148 0 0.0184 0.0034 mercurial.encoding:61(tolocal)
real 0m2.402s
user 0m0.735s
sys 0m0.177s
author | Brodie Rao <brodie@sf.io> |
---|---|
date | Fri, 15 Nov 2013 23:18:08 -0500 |
parents | e828975722c8 |
children | cce404b0c918 829f2dd99f5c |
line wrap: on
line source
# template-filters.py - common template expansion filters # # Copyright 2005-2008 Matt Mackall <mpm@selenic.com> # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. import cgi, re, os, time, urllib import encoding, node, util import hbisect def addbreaks(text): """:addbreaks: Any text. Add an XHTML "<br />" tag before the end of every line except the last. """ return text.replace('\n', '<br/>\n') agescales = [("year", 3600 * 24 * 365, 'Y'), ("month", 3600 * 24 * 30, 'M'), ("week", 3600 * 24 * 7, 'W'), ("day", 3600 * 24, 'd'), ("hour", 3600, 'h'), ("minute", 60, 'm'), ("second", 1, 's')] def age(date, abbrev=False): """:age: Date. Returns a human-readable date/time difference between the given date/time and the current date/time. """ def plural(t, c): if c == 1: return t return t + "s" def fmt(t, c, a): if abbrev: return "%d%s" % (c, a) return "%d %s" % (c, plural(t, c)) now = time.time() then = date[0] future = False if then > now: future = True delta = max(1, int(then - now)) if delta > agescales[0][1] * 30: return 'in the distant future' else: delta = max(1, int(now - then)) if delta > agescales[0][1] * 2: return util.shortdate(date) for t, s, a in agescales: n = delta // s if n >= 2 or s == 1: if future: return '%s from now' % fmt(t, n, a) return '%s ago' % fmt(t, n, a) def basename(path): """:basename: Any text. Treats the text as a path, and returns the last component of the path after splitting by the path separator (ignoring trailing separators). For example, "foo/bar/baz" becomes "baz" and "foo/bar//" becomes "bar". """ return os.path.basename(path) def datefilter(text): """:date: Date. Returns a date in a Unix date format, including the timezone: "Mon Sep 04 15:13:13 2006 0700". """ return util.datestr(text) def domain(author): """:domain: Any text. Finds the first string that looks like an email address, and extracts just the domain component. Example: ``User <user@example.com>`` becomes ``example.com``. """ f = author.find('@') if f == -1: return '' author = author[f + 1:] f = author.find('>') if f >= 0: author = author[:f] return author def email(text): """:email: Any text. Extracts the first string that looks like an email address. Example: ``User <user@example.com>`` becomes ``user@example.com``. """ return util.email(text) def escape(text): """:escape: Any text. Replaces the special XML/XHTML characters "&", "<" and ">" with XML entities, and filters out NUL characters. """ return cgi.escape(text.replace('\0', ''), True) para_re = None space_re = None def fill(text, width, initindent='', hangindent=''): '''fill many paragraphs with optional indentation.''' global para_re, space_re if para_re is None: para_re = re.compile('(\n\n|\n\\s*[-*]\\s*)', re.M) space_re = re.compile(r' +') def findparas(): start = 0 while True: m = para_re.search(text, start) if not m: uctext = unicode(text[start:], encoding.encoding) w = len(uctext) while 0 < w and uctext[w - 1].isspace(): w -= 1 yield (uctext[:w].encode(encoding.encoding), uctext[w:].encode(encoding.encoding)) break yield text[start:m.start(0)], m.group(1) start = m.end(1) return "".join([util.wrap(space_re.sub(' ', util.wrap(para, width)), width, initindent, hangindent) + rest for para, rest in findparas()]) def fill68(text): """:fill68: Any text. Wraps the text to fit in 68 columns.""" return fill(text, 68) def fill76(text): """:fill76: Any text. Wraps the text to fit in 76 columns.""" return fill(text, 76) def firstline(text): """:firstline: Any text. Returns the first line of text.""" try: return text.splitlines(True)[0].rstrip('\r\n') except IndexError: return '' def hexfilter(text): """:hex: Any text. Convert a binary Mercurial node identifier into its long hexadecimal representation. """ return node.hex(text) def hgdate(text): """:hgdate: Date. Returns the date as a pair of numbers: "1157407993 25200" (Unix timestamp, timezone offset). """ return "%d %d" % text def isodate(text): """:isodate: Date. Returns the date in ISO 8601 format: "2009-08-18 13:00 +0200". """ return util.datestr(text, '%Y-%m-%d %H:%M %1%2') def isodatesec(text): """:isodatesec: Date. Returns the date in ISO 8601 format, including seconds: "2009-08-18 13:00:13 +0200". See also the rfc3339date filter. """ return util.datestr(text, '%Y-%m-%d %H:%M:%S %1%2') def indent(text, prefix): '''indent each non-empty line of text after first with prefix.''' lines = text.splitlines() num_lines = len(lines) endswithnewline = text[-1:] == '\n' def indenter(): for i in xrange(num_lines): l = lines[i] if i and l.strip(): yield prefix yield l if i < num_lines - 1 or endswithnewline: yield '\n' return "".join(indenter()) def json(obj): if obj is None or obj is False or obj is True: return {None: 'null', False: 'false', True: 'true'}[obj] elif isinstance(obj, int) or isinstance(obj, float): return str(obj) elif isinstance(obj, str): u = unicode(obj, encoding.encoding, 'replace') return '"%s"' % jsonescape(u) elif isinstance(obj, unicode): return '"%s"' % jsonescape(obj) elif util.safehasattr(obj, 'keys'): out = [] for k, v in obj.iteritems(): s = '%s: %s' % (json(k), json(v)) out.append(s) return '{' + ', '.join(out) + '}' elif util.safehasattr(obj, '__iter__'): out = [] for i in obj: out.append(json(i)) return '[' + ', '.join(out) + ']' else: raise TypeError('cannot encode type %s' % obj.__class__.__name__) def _uescape(c): if ord(c) < 0x80: return c else: return '\\u%04x' % ord(c) _escapes = [ ('\\', '\\\\'), ('"', '\\"'), ('\t', '\\t'), ('\n', '\\n'), ('\r', '\\r'), ('\f', '\\f'), ('\b', '\\b'), ('<', '\\u003c'), ('>', '\\u003e') ] def jsonescape(s): for k, v in _escapes: s = s.replace(k, v) return ''.join(_uescape(c) for c in s) def localdate(text): """:localdate: Date. Converts a date to local date.""" return (util.parsedate(text)[0], util.makedate()[1]) def nonempty(str): """:nonempty: Any text. Returns '(none)' if the string is empty.""" return str or "(none)" def obfuscate(text): """:obfuscate: Any text. Returns the input text rendered as a sequence of XML entities. """ text = unicode(text, encoding.encoding, 'replace') return ''.join(['&#%d;' % ord(c) for c in text]) def permissions(flags): if "l" in flags: return "lrwxrwxrwx" if "x" in flags: return "-rwxr-xr-x" return "-rw-r--r--" def person(author): """:person: Any text. Returns the name before an email address, interpreting it as per RFC 5322. >>> person('foo@bar') 'foo' >>> person('Foo Bar <foo@bar>') 'Foo Bar' >>> person('"Foo Bar" <foo@bar>') 'Foo Bar' >>> person('"Foo \"buz\" Bar" <foo@bar>') 'Foo "buz" Bar' >>> # The following are invalid, but do exist in real-life ... >>> person('Foo "buz" Bar <foo@bar>') 'Foo "buz" Bar' >>> person('"Foo Bar <foo@bar>') 'Foo Bar' """ if '@' not in author: return author f = author.find('<') if f != -1: return author[:f].strip(' "').replace('\\"', '"') f = author.find('@') return author[:f].replace('.', ' ') def rfc3339date(text): """:rfc3339date: Date. Returns a date using the Internet date format specified in RFC 3339: "2009-08-18T13:00:13+02:00". """ return util.datestr(text, "%Y-%m-%dT%H:%M:%S%1:%2") def rfc822date(text): """:rfc822date: Date. Returns a date using the same format used in email headers: "Tue, 18 Aug 2009 13:00:13 +0200". """ return util.datestr(text, "%a, %d %b %Y %H:%M:%S %1%2") def short(text): """:short: Changeset hash. Returns the short form of a changeset hash, i.e. a 12 hexadecimal digit string. """ return text[:12] def shortbisect(text): """:shortbisect: Any text. Treats `text` as a bisection status, and returns a single-character representing the status (G: good, B: bad, S: skipped, U: untested, I: ignored). Returns single space if `text` is not a valid bisection status. """ return hbisect.shortlabel(text) or ' ' def shortdate(text): """:shortdate: Date. Returns a date like "2006-09-18".""" return util.shortdate(text) def stringescape(text): return text.encode('string_escape') def stringify(thing): """:stringify: Any type. Turns the value into text by converting values into text and concatenating them. """ if util.safehasattr(thing, '__iter__') and not isinstance(thing, str): return "".join([stringify(t) for t in thing if t is not None]) return str(thing) def strip(text): """:strip: Any text. Strips all leading and trailing whitespace.""" return text.strip() def stripdir(text): """:stripdir: Treat the text as path and strip a directory level, if possible. For example, "foo" and "foo/bar" becomes "foo". """ dir = os.path.dirname(text) if dir == "": return os.path.basename(text) else: return dir def tabindent(text): """:tabindent: Any text. Returns the text, with every non-empty line except the first starting with a tab character. """ return indent(text, '\t') def urlescape(text): """:urlescape: Any text. Escapes all "special" characters. For example, "foo bar" becomes "foo%20bar". """ return urllib.quote(text) def userfilter(text): """:user: Any text. Returns a short representation of a user name or email address.""" return util.shortuser(text) def emailuser(text): """:emailuser: Any text. Returns the user portion of an email address.""" return util.emailuser(text) def xmlescape(text): text = (text .replace('&', '&') .replace('<', '<') .replace('>', '>') .replace('"', '"') .replace("'", ''')) # ' invalid in HTML return re.sub('[\x00-\x08\x0B\x0C\x0E-\x1F]', ' ', text) filters = { "addbreaks": addbreaks, "age": age, "basename": basename, "date": datefilter, "domain": domain, "email": email, "escape": escape, "fill68": fill68, "fill76": fill76, "firstline": firstline, "hex": hexfilter, "hgdate": hgdate, "isodate": isodate, "isodatesec": isodatesec, "json": json, "jsonescape": jsonescape, "localdate": localdate, "nonempty": nonempty, "obfuscate": obfuscate, "permissions": permissions, "person": person, "rfc3339date": rfc3339date, "rfc822date": rfc822date, "short": short, "shortbisect": shortbisect, "shortdate": shortdate, "stringescape": stringescape, "stringify": stringify, "strip": strip, "stripdir": stripdir, "tabindent": tabindent, "urlescape": urlescape, "user": userfilter, "emailuser": emailuser, "xmlescape": xmlescape, } def websub(text, websubtable): """:websub: Any text. Only applies to hgweb. Applies the regular expression replacements defined in the websub section. """ if websubtable: for regexp, format in websubtable: text = regexp.sub(format, text) return text # tell hggettext to extract docstrings from these functions: i18nfunctions = filters.values()