Mercurial > hg
changeset 15093:2ca855126091
merge with stable
author | Sune Foldager <cryo@cyanite.org> |
---|---|
date | Wed, 14 Sep 2011 13:57:56 +0200 |
parents | cbba7fca7c4d (diff) 106f89299da6 (current diff) |
children | 868282fa29d8 |
files | |
diffstat | 123 files changed, 1763 insertions(+), 633 deletions(-) [+] |
line wrap: on
line diff
--- a/contrib/check-code.py Wed Sep 14 13:51:50 2011 +0200 +++ b/contrib/check-code.py Wed Sep 14 13:57:56 2011 +0200 @@ -148,7 +148,7 @@ (r'(?<!def)\s+(any|all|format)\(', "any/all/format not available in Python 2.4"), (r'(?<!def)\s+(callable)\(', - "callable not available in Python 3, use hasattr(f, '__call__')"), + "callable not available in Python 3, use getattr(f, '__call__', None)"), (r'if\s.*\selse', "if ... else form not available in Python 2.4"), (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist), "gratuitous whitespace after Python keyword"), @@ -168,6 +168,8 @@ "comparison with singleton, use 'is' or 'is not' instead"), (r'^\s*(while|if) [01]:', "use True/False for constant Boolean expression"), + (r'(?<!def)\s+hasattr', + 'hasattr(foo, bar) is broken, use util.safehasattr(foo, bar) instead'), (r'opener\([^)]*\).read\(', "use opener.read() instead"), (r'opener\([^)]*\).write\(',
--- a/contrib/setup3k.py Wed Sep 14 13:51:50 2011 +0200 +++ b/contrib/setup3k.py Wed Sep 14 13:57:56 2011 +0200 @@ -8,7 +8,7 @@ from lib2to3.refactor import get_fixers_from_package as getfixers import sys -if not hasattr(sys, 'version_info') or sys.version_info < (2, 4, 0, 'final'): +if getattr(sys, 'version_info', (0, 0, 0)) < (2, 4, 0, 'final'): raise SystemExit("Mercurial requires Python 2.4 or later.") if sys.version_info[0] >= 3: @@ -236,7 +236,7 @@ try: build_ext.build_extension(self, ext) except CCompilerError: - if not hasattr(ext, 'optional') or not ext.optional: + if getattr(ext, 'optional', False): raise log.warn("Failed to build optional extension '%s' (skipping)", ext.name)
--- a/contrib/win32/hgwebdir_wsgi.py Wed Sep 14 13:51:50 2011 +0200 +++ b/contrib/win32/hgwebdir_wsgi.py Wed Sep 14 13:57:56 2011 +0200 @@ -50,7 +50,7 @@ #sys.path.insert(0, r'c:\path\to\python\lib') # Enable tracing. Run 'python -m win32traceutil' to debug -if hasattr(sys, 'isapidllhandle'): +if getattr(sys, 'isapidllhandle', None) is not None: import win32traceutil # To serve pages in local charset instead of UTF-8, remove the two lines below
--- a/doc/gendoc.py Wed Sep 14 13:51:50 2011 +0200 +++ b/doc/gendoc.py Wed Sep 14 13:57:56 2011 +0200 @@ -9,6 +9,7 @@ from mercurial.i18n import _ from mercurial.help import helptable from mercurial import extensions +from mercurial import util def get_desc(docstr): if not docstr: @@ -95,7 +96,7 @@ ui.write(".. _%s:\n" % name) ui.write("\n") section(ui, sec) - if hasattr(doc, '__call__'): + if util.safehasattr(doc, '__call__'): doc = doc() ui.write(doc) ui.write("\n")
--- a/hgext/color.py Wed Sep 14 13:51:50 2011 +0200 +++ b/hgext/color.py Wed Sep 14 13:57:56 2011 +0200 @@ -68,6 +68,9 @@ branches.current = green branches.inactive = none + tags.normal = green + tags.local = black bold + The available effects in terminfo mode are 'blink', 'bold', 'dim', 'inverse', 'invisible', 'italic', 'standout', and 'underline'; in ECMA-48 mode, the options are 'bold', 'inverse', 'italic', and @@ -257,7 +260,9 @@ 'status.ignored': 'black bold', 'status.modified': 'blue bold', 'status.removed': 'red bold', - 'status.unknown': 'magenta bold underline'} + 'status.unknown': 'magenta bold underline', + 'tags.normal': 'green', + 'tags.local': 'black bold'} def _effect_str(effect):
--- a/hgext/convert/cvsps.py Wed Sep 14 13:51:50 2011 +0200 +++ b/hgext/convert/cvsps.py Wed Sep 14 13:57:56 2011 +0200 @@ -11,6 +11,7 @@ from mercurial import util from mercurial.i18n import _ from mercurial import hook +from mercurial import util class logentry(object): '''Class logentry has the following attributes: @@ -513,8 +514,8 @@ e.comment == c.comment and e.author == c.author and e.branch == c.branch and - (not hasattr(e, 'branchpoints') or - not hasattr (c, 'branchpoints') or + (not util.safehasattr(e, 'branchpoints') or + not util.safehasattr (c, 'branchpoints') or e.branchpoints == c.branchpoints) and ((c.date[0] + c.date[1]) <= (e.date[0] + e.date[1]) <=
--- a/hgext/convert/git.py Wed Sep 14 13:51:50 2011 +0200 +++ b/hgext/convert/git.py Wed Sep 14 13:57:56 2011 +0200 @@ -16,7 +16,7 @@ # Windows does not support GIT_DIR= construct while other systems # cannot remove environment variable. Just assume none have # both issues. - if hasattr(os, 'unsetenv'): + if util.safehasattr(os, 'unsetenv'): def gitopen(self, s, noerr=False): prevgitdir = os.environ.get('GIT_DIR') os.environ['GIT_DIR'] = self.path
--- a/hgext/convert/hg.py Wed Sep 14 13:51:50 2011 +0200 +++ b/hgext/convert/hg.py Wed Sep 14 13:57:56 2011 +0200 @@ -70,10 +70,10 @@ self.wlock.release() def revmapfile(self): - return os.path.join(self.path, ".hg", "shamap") + return self.repo.join("shamap") def authorfile(self): - return os.path.join(self.path, ".hg", "authormap") + return self.repo.join("authormap") def getheads(self): h = self.repo.changelog.heads() @@ -364,8 +364,7 @@ def converted(self, rev, destrev): if self.convertfp is None: - self.convertfp = open(os.path.join(self.path, '.hg', 'shamap'), - 'a') + self.convertfp = open(self.repo.join('shamap'), 'a') self.convertfp.write('%s %s\n' % (destrev, rev)) self.convertfp.flush()
--- a/hgext/convert/transport.py Wed Sep 14 13:51:50 2011 +0200 +++ b/hgext/convert/transport.py Wed Sep 14 13:57:56 2011 +0200 @@ -54,7 +54,7 @@ if p: providers.append(p) else: - if hasattr(svn.client, 'get_windows_simple_provider'): + if util.safehasattr(svn.client, 'get_windows_simple_provider'): providers.append(svn.client.get_windows_simple_provider(pool)) return svn.core.svn_auth_open(providers, pool) @@ -73,7 +73,7 @@ self.password = '' # Only Subversion 1.4 has reparent() - if ra is None or not hasattr(svn.ra, 'reparent'): + if ra is None or not util.safehasattr(svn.ra, 'reparent'): self.client = svn.client.create_context(self.pool) ab = _create_auth_baton(self.pool) if False:
--- a/hgext/eol.py Wed Sep 14 13:51:50 2011 +0200 +++ b/hgext/eol.py Wed Sep 14 13:57:56 2011 +0200 @@ -52,9 +52,10 @@ The rules will first apply when files are touched in the working copy, e.g. by updating to null and back to tip to touch all files. -The extension uses an optional ``[eol]`` section in your hgrc file -(not the ``.hgeol`` file) for settings that control the overall -behavior. There are two settings: +The extension uses an optional ``[eol]`` section read from both the +normal Mercurial configuration files and the ``.hgeol`` file, with the +latter overriding the former. You can use that section to control the +overall behavior. There are three settings: - ``eol.native`` (default ``os.linesep``) can be set to ``LF`` or ``CRLF`` to override the default interpretation of ``native`` for @@ -67,6 +68,10 @@ Such files are normally not touched under the assumption that they have mixed EOLs on purpose. +- ``eol.fix-trailing-newline`` (default False) can be set to True to + ensure that converted files end with a EOL character (either ``\\n`` + or ``\\r\\n`` as per the configured patterns). + The extension provides ``cleverencode:`` and ``cleverdecode:`` filters like the deprecated win32text extension does. This means that you can disable win32text and enable eol and your filters will still work. You @@ -106,6 +111,8 @@ return s if ui.configbool('eol', 'only-consistent', True) and inconsistenteol(s): return s + if ui.configbool('eol', 'fix-trailing-newline', False) and s and s[-1] != '\n': + s = s + '\n' return eolre.sub('\n', s) def tocrlf(s, params, ui, **kwargs): @@ -114,6 +121,8 @@ return s if ui.configbool('eol', 'only-consistent', True) and inconsistenteol(s): return s + if ui.configbool('eol', 'fix-trailing-newline', False) and s and s[-1] != '\n': + s = s + '\n' return eolre.sub('\r\n', s) def isbinary(s, params): @@ -158,7 +167,7 @@ # about inconsistent newlines. self.match = match.match(root, '', [], include, exclude) - def setfilters(self, ui): + def copytoui(self, ui): for pattern, style in self.cfg.items('patterns'): key = style.upper() try: @@ -167,6 +176,9 @@ except KeyError: ui.warn(_("ignoring unknown EOL style '%s' from %s\n") % (style, self.cfg.source('patterns', pattern))) + # eol.only-consistent can be specified in ~/.hgrc or .hgeol + for k, v in self.cfg.items('eol'): + ui.setconfig('eol', k, v) def checkrev(self, repo, ctx, files): failed = [] @@ -273,7 +285,7 @@ eol = parseeol(self.ui, self, nodes) if eol is None: return None - eol.setfilters(self.ui) + eol.copytoui(self.ui) return eol.match def _hgcleardirstate(self):
--- a/hgext/inotify/__init__.py Wed Sep 14 13:51:50 2011 +0200 +++ b/hgext/inotify/__init__.py Wed Sep 14 13:57:56 2011 +0200 @@ -11,6 +11,7 @@ # todo: socket permissions from mercurial.i18n import _ +from mercurial import util import server from client import client, QueryFailed @@ -31,7 +32,7 @@ ui.write((' %s/\n') % path) def reposetup(ui, repo): - if not hasattr(repo, 'dirstate'): + if not util.safehasattr(repo, 'dirstate'): return class inotifydirstate(repo.dirstate.__class__):
--- a/hgext/keyword.py Wed Sep 14 13:51:50 2011 +0200 +++ b/hgext/keyword.py Wed Sep 14 13:57:56 2011 +0200 @@ -249,10 +249,14 @@ kwcmd = self.restrict and lookup # kwexpand/kwshrink if self.restrict or expand and lookup: mf = ctx.manifest() - lctx = ctx - re_kw = (self.restrict or rekw) and self.rekw or self.rekwexp - msg = (expand and _('overwriting %s expanding keywords\n') - or _('overwriting %s shrinking keywords\n')) + if self.restrict or rekw: + re_kw = self.rekw + else: + re_kw = self.rekwexp + if expand: + msg = _('overwriting %s expanding keywords\n') + else: + msg = _('overwriting %s shrinking keywords\n') for f in candidates: if self.restrict: data = self.repo.file(f).read(mf[f]) @@ -262,18 +266,17 @@ continue if expand: if lookup: - lctx = self.linkctx(f, mf[f]) - data, found = self.substitute(data, f, lctx, re_kw.subn) + ctx = self.linkctx(f, mf[f]) + data, found = self.substitute(data, f, ctx, re_kw.subn) elif self.restrict: found = re_kw.search(data) else: data, found = _shrinktext(data, re_kw.subn) if found: self.ui.note(msg % f) - fpath = self.repo.wjoin(f) - mode = os.lstat(fpath).st_mode - self.repo.wwrite(f, data, ctx.flags(f)) - os.chmod(fpath, mode) + fp = self.repo.wopener(f, "wb", atomictemp=True) + fp.write(data) + fp.close() if kwcmd: self.repo.dirstate.normal(f) elif self.record: @@ -296,7 +299,9 @@ def wread(self, fname, data): '''If in restricted mode returns data read from wdir with keyword substitutions removed.''' - return self.restrict and self.shrink(fname, data) or data + if self.restrict: + return self.shrink(fname, data) + return data class kwfilelog(filelog.filelog): ''' @@ -325,11 +330,11 @@ text = self.kwt.shrink(self.path, text) return super(kwfilelog, self).cmp(node, text) -def _status(ui, repo, kwt, *pats, **opts): +def _status(ui, repo, wctx, kwt, *pats, **opts): '''Bails out if [keyword] configuration is not active. Returns status of working directory.''' if kwt: - return repo.status(match=scmutil.match(repo[None], pats, opts), clean=True, + return repo.status(match=scmutil.match(wctx, pats, opts), clean=True, unknown=opts.get('unknown') or opts.get('all')) if ui.configitems('keyword'): raise util.Abort(_('[keyword] patterns cannot match')) @@ -343,7 +348,7 @@ kwt = kwtools['templater'] wlock = repo.wlock() try: - status = _status(ui, repo, kwt, *pats, **opts) + status = _status(ui, repo, wctx, kwt, *pats, **opts) modified, added, removed, deleted, unknown, ignored, clean = status if modified or added or removed or deleted: raise util.Abort(_('outstanding uncommitted changes')) @@ -415,7 +420,10 @@ ui.setconfig('keywordmaps', k, v) else: ui.status(_('\n\tconfiguration using current keyword template maps\n')) - kwmaps = dict(uikwmaps) or _defaultkwmaps(ui) + if uikwmaps: + kwmaps = dict(uikwmaps) + else: + kwmaps = _defaultkwmaps(ui) uisetup(ui) reposetup(ui, repo) @@ -478,13 +486,13 @@ i = ignored (not tracked) ''' kwt = kwtools['templater'] - status = _status(ui, repo, kwt, *pats, **opts) + wctx = repo[None] + status = _status(ui, repo, wctx, kwt, *pats, **opts) cwd = pats and repo.getcwd() or '' modified, added, removed, deleted, unknown, ignored, clean = status files = [] if not opts.get('unknown') or opts.get('all'): files = sorted(modified + added + clean) - wctx = repo[None] kwfiles = kwt.iskwfile(files, wctx) kwdeleted = kwt.iskwfile(deleted, wctx) kwunknown = kwt.iskwfile(unknown, wctx)
--- a/hgext/mq.py Wed Sep 14 13:51:50 2011 +0200 +++ b/hgext/mq.py Wed Sep 14 13:57:56 2011 +0200 @@ -295,7 +295,8 @@ n, name = entry yield statusentry(bin(n), name) elif l.strip(): - self.ui.warn(_('malformated mq status line: %s\n') % entry) + msg = _('malformated mq status line: %s\n') % entry + self.ui.warn(msg) # else we ignore empty lines lines = self.opener.read(self.statuspath).splitlines() return list(parselines(lines)) @@ -626,6 +627,7 @@ self.ui.note(str(inst) + '\n') if not self.ui.verbose: self.ui.warn(_("patch failed, unable to continue (try -v)\n")) + self.ui.traceback() return (False, list(files), False) def apply(self, repo, series, list=False, update_status=True, @@ -938,7 +940,7 @@ p.write("# User " + user + "\n") if date: p.write("# Date %s %s\n\n" % date) - if hasattr(msg, '__call__'): + if util.safehasattr(msg, '__call__'): msg = msg() commitmsg = msg and msg or ("[mq]: %s" % patchfn) n = repo.commit(commitmsg, user, date, match=match, force=True) @@ -1492,7 +1494,7 @@ n = repo.commit(message, user, ph.date, match=match, force=True) # only write patch after a successful commit - patchf.rename() + patchf.close() self.applied.append(statusentry(n, patchfn)) except: ctx = repo[cparents[0]] @@ -2915,6 +2917,7 @@ @command("qqueue", [('l', 'list', False, _('list all available queues')), + ('', 'active', False, _('print name of active queue')), ('c', 'create', False, _('create new queue')), ('', 'rename', False, _('rename active queue')), ('', 'delete', False, _('delete reference to queue')), @@ -2929,7 +2932,8 @@ Omitting a queue name or specifying -l/--list will show you the registered queues - by default the "normal" patches queue is registered. The currently - active queue will be marked with "(active)". + active queue will be marked with "(active)". Specifying --active will print + only the name of the active queue. To create a new queue, use -c/--create. The queue is automatically made active, except in the case where there are applied patches from the @@ -3022,8 +3026,11 @@ fh.close() util.rename(repo.join('patches.queues.new'), repo.join(_allqueues)) - if not name or opts.get('list'): + if not name or opts.get('list') or opts.get('active'): current = _getcurrent() + if opts.get('active'): + ui.write('%s\n' % (current,)) + return for queue in _getqueues(): ui.write('%s' % (queue,)) if queue == current and not ui.quiet:
--- a/hgext/notify.py Wed Sep 14 13:51:50 2011 +0200 +++ b/hgext/notify.py Wed Sep 14 13:57:56 2011 +0200 @@ -5,71 +5,115 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. -'''hooks for sending email notifications at commit/push time +'''hooks for sending email push notifications -Subscriptions can be managed through a hgrc file. Default mode is to -print messages to stdout, for testing and configuring. +This extension let you run hooks sending email notifications when +changesets are being pushed, from the sending or receiving side. -To use, configure the notify extension and enable it in hgrc like -this:: - - [extensions] - notify = +First, enable the extension as explained in :hg:`help extensions`, and +register the hook you want to run. ``incoming`` and ``outgoing`` hooks +are run by the changesets receiver while the ``outgoing`` one is for +the sender:: [hooks] # one email for each incoming changeset incoming.notify = python:hgext.notify.hook - # batch emails when many changesets incoming at one time + # one email for all incoming changesets changegroup.notify = python:hgext.notify.hook - # batch emails when many changesets outgoing at one time (client side) + + # one email for all outgoing changesets outgoing.notify = python:hgext.notify.hook - [notify] - # config items go here - -Required configuration items:: - - config = /path/to/file # file containing subscriptions - -Optional configuration items:: - - test = True # print messages to stdout for testing - strip = 3 # number of slashes to strip for url paths - domain = example.com # domain to use if committer missing domain - style = ... # style file to use when formatting email - template = ... # template to use when formatting email - incoming = ... # template to use when run as incoming hook - outgoing = ... # template to use when run as outgoing hook - changegroup = ... # template to use when run as changegroup hook - maxdiff = 300 # max lines of diffs to include (0=none, -1=all) - maxsubject = 67 # truncate subject line longer than this - diffstat = True # add a diffstat before the diff content - sources = serve # notify if source of incoming changes in this list - # (serve == ssh or http, push, pull, bundle) - merge = False # send notification for merges (default True) - [email] - from = user@host.com # email address to send as if none given - [web] - baseurl = http://hgserver/... # root of hg web site for browsing commits - -The notify config file has same format as a regular hgrc file. It has -two sections so you can express subscriptions in whatever way is -handier for you. - -:: +Now the hooks are running, subscribers must be assigned to +repositories. Use the ``[usersubs]`` section to map repositories to a +given email or the ``[reposubs]`` section to map emails to a single +repository:: [usersubs] - # key is subscriber email, value is ","-separated list of glob patterns + # key is subscriber email, value is a comma-separated list of glob + # patterns user@host = pattern [reposubs] - # key is glob pattern, value is ","-separated list of subscriber emails + # key is glob pattern, value is a comma-separated list of subscriber + # emails pattern = user@host -Glob patterns are matched against path to repository root. +Glob patterns are matched against absolute path to repository +root. The subscriptions can be defined in their own file and +referenced with:: + + [notify] + config = /path/to/subscriptionsfile + +Alternatively, they can be added to Mercurial configuration files by +setting the previous entry to an empty value. + +At this point, notifications should be generated but will not be sent until you +set the ``notify.test`` entry to ``False``. + +Notifications content can be tweaked with the following configuration entries: + +notify.test + If ``True``, print messages to stdout instead of sending them. Default: True. + +notify.sources + Space separated list of change sources. Notifications are sent only + if it includes the incoming or outgoing changes source. Incoming + sources can be ``serve`` for changes coming from http or ssh, + ``pull`` for pulled changes, ``unbundle`` for changes added by + :hg:`unbundle` or ``push`` for changes being pushed + locally. Outgoing sources are the same except for ``unbundle`` which + is replaced by ``bundle``. Default: serve. + +notify.strip + Number of leading slashes to strip from url paths. By default, notifications + references repositories with their absolute path. ``notify.strip`` let you + turn them into relative paths. For example, ``notify.strip=3`` will change + ``/long/path/repository`` into ``repository``. Default: 0. + +notify.domain + If subscribers emails or the from email have no domain set, complete them + with this value. -If you like, you can put notify config file in repository that users -can push changes to, they can manage their own subscriptions. +notify.style + Style file to use when formatting emails. + +notify.template + Template to use when formatting emails. + +notify.incoming + Template to use when run as incoming hook, override ``notify.template``. + +notify.outgoing + Template to use when run as outgoing hook, override ``notify.template``. + +notify.changegroup + Template to use when running as changegroup hook, override + ``notify.template``. + +notify.maxdiff + Maximum number of diff lines to include in notification email. Set to 0 + to disable the diff, -1 to include all of it. Default: 300. + +notify.maxsubject + Maximum number of characters in emails subject line. Default: 67. + +notify.diffstat + Set to True to include a diffstat before diff content. Default: True. + +notify.merge + If True, send notifications for merge changesets. Default: True. + +If set, the following entries will also be used to customize the notifications: + +email.from + Email ``From`` address to use if none can be found in generated email content. + +web.baseurl + Root repository browsing URL to combine with repository paths when making + references. See also ``notify.strip``. + ''' from mercurial.i18n import _ @@ -167,9 +211,6 @@ return [mail.addressencode(self.ui, s, self.charsets, self.test) for s in sorted(subs)] - def url(self, path=None): - return self.ui.config('web', 'baseurl') + (path or self.root) - def node(self, ctx, **props): '''format one changeset, unless it is a suppressed merge.''' if not self.merge and len(ctx.parents()) > 1:
--- a/hgext/pager.py Wed Sep 14 13:51:50 2011 +0200 +++ b/hgext/pager.py Wed Sep 14 13:57:56 2011 +0200 @@ -58,7 +58,7 @@ from mercurial.i18n import _ def _runpager(p): - if not hasattr(os, 'fork'): + if not util.safehasattr(os, 'fork'): sys.stdout = util.popen(p, 'wb') if util.isatty(sys.stderr): sys.stderr = sys.stdout
--- a/hgext/progress.py Wed Sep 14 13:51:50 2011 +0200 +++ b/hgext/progress.py Wed Sep 14 13:57:56 2011 +0200 @@ -27,6 +27,9 @@ [progress] delay = 3 # number of seconds (float) before showing the progress bar + changedelay = 1 # changedelay: minimum delay before showing a new topic. + # If set to less than 3 * refresh, that value will + # be used instead. refresh = 0.1 # time in seconds between refreshes of the progress bar format = topic bar number estimate # format of the progress bar width = <none> # if set, the maximum width of the progress information @@ -53,7 +56,7 @@ return ' '.join(s for s in args if s) def shouldprint(ui): - return (util.isatty(sys.stderr) or ui.configbool('progress', 'assume-tty')) + return util.isatty(sys.stderr) or ui.configbool('progress', 'assume-tty') def fmtremaining(seconds): if seconds < 60: @@ -105,9 +108,13 @@ self.printed = False self.lastprint = time.time() + float(self.ui.config( 'progress', 'delay', default=3)) + self.lasttopic = None self.indetcount = 0 self.refresh = float(self.ui.config( 'progress', 'refresh', default=0.1)) + self.changedelay = max(3 * self.refresh, + float(self.ui.config( + 'progress', 'changedelay', default=1))) self.order = self.ui.configlist( 'progress', 'format', default=['topic', 'bar', 'number', 'estimate']) @@ -184,6 +191,7 @@ else: out = spacejoin(head, tail) sys.stderr.write('\r' + out[:termwidth]) + self.lasttopic = topic sys.stderr.flush() def clear(self): @@ -248,10 +256,18 @@ self.topics.append(topic) self.topicstates[topic] = pos, item, unit, total if now - self.lastprint >= self.refresh and self.topics: - self.lastprint = now - self.show(now, topic, *self.topicstates[topic]) + if (self.lasttopic is None # first time we printed + # not a topic change + or topic == self.lasttopic + # it's been long enough we should print anyway + or now - self.lastprint >= self.changedelay): + self.lastprint = now + self.show(now, topic, *self.topicstates[topic]) + +_singleton = None def uisetup(ui): + global _singleton class progressui(ui.__class__): _progbar = None @@ -278,7 +294,9 @@ # we instantiate one globally shared progress bar to avoid # competing progress bars when multiple UI objects get created if not progressui._progbar: - progressui._progbar = progbar(ui) + if _singleton is None: + _singleton = progbar(ui) + progressui._progbar = _singleton def reposetup(ui, repo): uisetup(repo.ui)
--- a/hgext/relink.py Wed Sep 14 13:51:50 2011 +0200 +++ b/hgext/relink.py Wed Sep 14 13:57:56 2011 +0200 @@ -36,7 +36,8 @@ command is running. (Both repositories will be locked against writes.) """ - if not hasattr(util, 'samefile') or not hasattr(util, 'samedevice'): + if (not util.safehasattr(util, 'samefile') or + not util.safehasattr(util, 'samedevice')): raise util.Abort(_('hardlinks are not supported on this system')) src = hg.repository(ui, ui.expandpath(origin or 'default-relink', origin or 'default'))
--- a/hgext/share.py Wed Sep 14 13:51:50 2011 +0200 +++ b/hgext/share.py Wed Sep 14 13:57:56 2011 +0200 @@ -6,7 +6,7 @@ '''share a common history between several working directories''' from mercurial.i18n import _ -from mercurial import hg, commands +from mercurial import hg, commands, util def share(ui, source, dest=None, noupdate=False): """create a new shared repository @@ -28,11 +28,46 @@ return hg.share(ui, source, dest, not noupdate) +def unshare(ui, repo): + """convert a shared repository to a normal one + + Copy the store data to the repo and remove the sharedpath data. + """ + + if repo.sharedpath == repo.path: + raise util.Abort(_("this is not a shared repo")) + + destlock = lock = None + lock = repo.lock() + try: + # we use locks here because if we race with commit, we + # can end up with extra data in the cloned revlogs that's + # not pointed to by changesets, thus causing verify to + # fail + + destlock = hg.copystore(ui, repo, repo.path) + + sharefile = repo.join('sharedpath') + util.rename(sharefile, sharefile + '.old') + + repo.requirements.discard('sharedpath') + repo._writerequirements() + finally: + destlock and destlock.release() + lock and lock.release() + + # update store, spath, sopener and sjoin of repo + repo.__init__(ui, repo.root) + cmdtable = { "share": (share, [('U', 'noupdate', None, _('do not create a working copy'))], _('[-U] SOURCE [DEST]')), + "unshare": + (unshare, + [], + ''), } commands.norepo += " share"
--- a/mercurial/archival.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/archival.py Wed Sep 14 13:57:56 2011 +0200 @@ -195,7 +195,7 @@ return f = self.opener(name, "w", atomictemp=True) f.write(data) - f.rename() + f.close() destfile = os.path.join(self.basedir, name) os.chmod(destfile, mode)
--- a/mercurial/bookmarks.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/bookmarks.py Wed Sep 14 13:57:56 2011 +0200 @@ -26,7 +26,13 @@ bookmarks = {} try: for line in repo.opener('bookmarks'): - sha, refspec = line.strip().split(' ', 1) + line = line.strip() + if not line: + continue + if ' ' not in line: + repo.ui.warn(_('malformed line in .hg/bookmarks: %r\n') % line) + continue + sha, refspec = line.split(' ', 1) refspec = encoding.tolocal(refspec) try: bookmarks[refspec] = repo.changelog.lookup(sha) @@ -84,7 +90,7 @@ file = repo.opener('bookmarks', 'w', atomictemp=True) for refspec, node in refs.iteritems(): file.write("%s %s\n" % (hex(node), encoding.fromlocal(refspec))) - file.rename() + file.close() # touch 00changelog.i so hgweb reloads bookmarks (no lock needed) try: @@ -115,7 +121,7 @@ try: file = repo.opener('bookmarks.current', 'w', atomictemp=True) file.write(encoding.fromlocal(mark)) - file.rename() + file.close() finally: wlock.release() repo._bookmarkcurrent = mark @@ -145,11 +151,10 @@ def listbookmarks(repo): # We may try to list bookmarks on a repo type that does not # support it (e.g., statichttprepository). - if not hasattr(repo, '_bookmarks'): - return {} + marks = getattr(repo, '_bookmarks', {}) d = {} - for k, v in repo._bookmarks.iteritems(): + for k, v in marks.iteritems(): d[k] = hex(v) return d
--- a/mercurial/byterange.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/byterange.py Wed Sep 14 13:57:56 2011 +0200 @@ -103,9 +103,7 @@ """This effectively allows us to wrap at the instance level. Any attribute not found in _this_ object will be searched for in self.fo. This includes methods.""" - if hasattr(self.fo, name): - return getattr(self.fo, name) - raise AttributeError(name) + return getattr(self.fo, name) def tell(self): """Return the position within the range. @@ -170,10 +168,8 @@ offset is relative to the current position (self.realpos). """ assert offset >= 0 - if not hasattr(self.fo, 'seek'): - self._poor_mans_seek(offset) - else: - self.fo.seek(self.realpos + offset) + seek = getattr(self.fo, 'seek', self._poor_mans_seek) + seek(self.realpos + offset) self.realpos += offset def _poor_mans_seek(self, offset):
--- a/mercurial/cmdutil.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/cmdutil.py Wed Sep 14 13:57:56 2011 +0200 @@ -109,12 +109,13 @@ limit = None return limit -def makefilename(repo, pat, node, +def makefilename(repo, pat, node, desc=None, total=None, seqno=None, revwidth=None, pathname=None): node_expander = { 'H': lambda: hex(node), 'R': lambda: str(repo.changelog.rev(node)), 'h': lambda: short(node), + 'm': lambda: re.sub('[^\w]', '_', str(desc)) } expander = { '%': lambda: '%', @@ -154,14 +155,14 @@ raise util.Abort(_("invalid format spec '%%%s' in output filename") % inst.args[0]) -def makefileobj(repo, pat, node=None, total=None, +def makefileobj(repo, pat, node=None, desc=None, total=None, seqno=None, revwidth=None, mode='wb', pathname=None): writable = mode not in ('r', 'rb') if not pat or pat == '-': fp = writable and repo.ui.fout or repo.ui.fin - if hasattr(fp, 'fileno'): + if util.safehasattr(fp, 'fileno'): return os.fdopen(os.dup(fp.fileno()), mode) else: # if this fp can't be duped properly, return @@ -177,11 +178,11 @@ return getattr(self.f, attr) return wrappedfileobj(fp) - if hasattr(pat, 'write') and writable: + if util.safehasattr(pat, 'write') and writable: return pat - if hasattr(pat, 'read') and 'r' in mode: + if util.safehasattr(pat, 'read') and 'r' in mode: return pat - return open(makefilename(repo, pat, node, total, seqno, revwidth, + return open(makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname), mode) @@ -516,11 +517,13 @@ shouldclose = False if not fp: - fp = makefileobj(repo, template, node, total=total, seqno=seqno, - revwidth=revwidth, mode='ab') + desc_lines = ctx.description().rstrip().split('\n') + desc = desc_lines[0] #Commit always has a first line. + fp = makefileobj(repo, template, node, desc=desc, total=total, + seqno=seqno, revwidth=revwidth, mode='ab') if fp != template: shouldclose = True - if fp != sys.stdout and hasattr(fp, 'name'): + if fp != sys.stdout and util.safehasattr(fp, 'name'): repo.ui.note("%s\n" % fp.name) fp.write("# HG changeset patch\n")
--- a/mercurial/commands.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/commands.py Wed Sep 14 13:57:56 2011 +0200 @@ -119,6 +119,10 @@ ('', 'stat', None, _('output diffstat-style summary of changes')), ] +mergetoolopts = [ + ('t', 'tool', '', _('specify merge tool')), +] + similarityopts = [ ('s', 'similarity', '', _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY')) @@ -349,9 +353,8 @@ @command('backout', [('', 'merge', None, _('merge with old dirstate parent after backout')), ('', 'parent', '', _('parent to choose when backing out merge'), _('REV')), - ('t', 'tool', '', _('specify merge tool')), ('r', 'rev', '', _('revision to backout'), _('REV')), - ] + walkopts + commitopts + commitopts2, + ] + mergetoolopts + walkopts + commitopts + commitopts2, _('[OPTION]... [-r] REV')) def backout(ui, repo, node=None, rev=None, **opts): '''reverse effect of earlier changeset @@ -1102,8 +1105,8 @@ ctx = repo[node] parents = ctx.parents() - if bheads and not [x for x in parents - if x.node() in bheads and x.branch() == branch]: + if (bheads and node not in bheads and not + [x for x in parents if x.node() in bheads and x.branch() == branch]): ui.status(_('created new head\n')) # The message is not printed for initial roots. For the other # changesets, it is printed in the following situations: @@ -1656,8 +1659,9 @@ def debugignore(ui, repo, *values, **opts): """display the combined ignore pattern""" ignore = repo.dirstate._ignore - if hasattr(ignore, 'includepat'): - ui.write("%s\n" % ignore.includepat) + includepat = getattr(ignore, 'includepat', None) + if includepat is not None: + ui.write("%s\n" % includepat) else: raise util.Abort(_("no ignore patterns found")) @@ -2225,6 +2229,7 @@ :``%R``: changeset revision number :``%b``: basename of the exporting repository :``%h``: short-form changeset hash (12 hexadecimal digits) + :``%m``: first line of the commit message (only alphanumeric characters) :``%n``: zero-padded sequence number, starting at 1 :``%r``: zero-padded changeset revision number @@ -2576,7 +2581,7 @@ [('e', 'extension', None, _('show only help for extensions')), ('c', 'command', None, _('show only help for commands'))], _('[-ec] [TOPIC]')) -def help_(ui, name=None, with_version=False, unknowncmd=False, full=True, **opts): +def help_(ui, name=None, unknowncmd=False, full=True, **opts): """show help for a given topic or a help overview With no arguments, print a list of commands with short help messages. @@ -2586,14 +2591,71 @@ Returns 0 if successful. """ - option_lists = [] + + optlist = [] textwidth = min(ui.termwidth(), 80) - 2 + # list all option lists + def opttext(optlist, width): + out = [] + multioccur = False + for title, options in optlist: + out.append(("\n%s" % title, None)) + 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 + if isinstance(default, list): + numqualifier = " %s [+]" % optlabel + multioccur = True + elif (default is not None) and not isinstance(default, bool): + numqualifier = " %s" % optlabel + else: + numqualifier = "" + out.append(("%2s%s" % + (shortopt and "-%s" % shortopt, + longopt and " --%s%s" % + (longopt, numqualifier)), + "%s%s" % (desc, + default + and _(" (default: %s)") % default + or ""))) + if multioccur: + msg = _("\n[+] marked option can be specified multiple times") + if ui.verbose and name != 'shortlist': + out.append((msg, None)) + else: + out.insert(-1, (msg, None)) + + text = "" + if out: + colwidth = encoding.colwidth + # normalize: (opt or message, desc or None, width of opt) + entries = [desc and (opt, desc, colwidth(opt)) or (opt, None, 0) + for opt, desc in out] + hanging = max([e[2] for e in entries]) + for opt, desc, width in entries: + if desc: + initindent = ' %s%s ' % (opt, ' ' * (hanging - width)) + hangindent = ' ' * (hanging + 3) + text += '%s\n' % (util.wrap(desc, width, + initindent=initindent, + hangindent=hangindent)) + else: + text += "%s\n" % opt + + return text + def addglobalopts(aliases): if ui.verbose: - option_lists.append((_("global options:"), globalopts)) + optlist.append((_("global options:"), globalopts)) if name == 'shortlist': - option_lists.append((_('use "hg help" for the full list ' + optlist.append((_('use "hg help" for the full list ' 'of commands'), ())) else: if name == 'shortlist': @@ -2606,13 +2668,9 @@ 'global options') % (name and " " + name or "") else: msg = _('use "hg -v help %s" to show global options') % name - option_lists.append((msg, ())) + optlist.append((msg, ())) def helpcmd(name): - if with_version: - version_(ui) - ui.write('\n') - try: aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd) except error.AmbiguousCommand, inst: @@ -2646,7 +2704,7 @@ doc = gettext(entry[0].__doc__) if not doc: doc = _("(no help text available)") - if hasattr(entry[0], 'definition'): # aliased command + if util.safehasattr(entry[0], 'definition'): # aliased command if entry[0].definition.startswith('!'): # shell alias doc = _('shell alias for::\n\n %s') % entry[0].definition[1:] else: @@ -2662,7 +2720,7 @@ if not ui.quiet: # options if entry[1]: - option_lists.append((_("options:\n"), entry[1])) + optlist.append((_("options:\n"), entry[1])) addglobalopts(False) @@ -2731,7 +2789,7 @@ # description if not doc: doc = _("(no help text available)") - if hasattr(doc, '__call__'): + if util.safehasattr(doc, '__call__'): doc = doc() ui.write("%s\n\n" % header) @@ -2806,10 +2864,7 @@ else: # program name - if ui.verbose or with_version: - version_(ui) - else: - ui.status(_("Mercurial Distributed SCM\n")) + ui.status(_("Mercurial Distributed SCM\n")) ui.status('\n') # list of commands @@ -2824,42 +2879,6 @@ if text: ui.write("\n%s\n" % minirst.format(text, textwidth)) - # list all option lists - opt_output = [] - multioccur = False - for title, options in option_lists: - opt_output.append(("\n%s" % title, None)) - 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 - if isinstance(default, list): - numqualifier = " %s [+]" % optlabel - multioccur = True - elif (default is not None) and not isinstance(default, bool): - numqualifier = " %s" % optlabel - else: - numqualifier = "" - opt_output.append(("%2s%s" % - (shortopt and "-%s" % shortopt, - longopt and " --%s%s" % - (longopt, numqualifier)), - "%s%s" % (desc, - default - and _(" (default: %s)") % default - or ""))) - if multioccur: - msg = _("\n[+] marked option can be specified multiple times") - if ui.verbose and name != 'shortlist': - opt_output.append((msg, None)) - else: - opt_output.insert(-1, (msg, None)) - if not name: ui.write(_("\nadditional help topics:\n\n")) topics = [] @@ -2869,21 +2888,7 @@ for t, desc in topics: ui.write(" %-*s %s\n" % (topics_len, t, desc)) - if opt_output: - colwidth = encoding.colwidth - # normalize: (opt or message, desc or None, width of opt) - entries = [desc and (opt, desc, colwidth(opt)) or (opt, None, 0) - for opt, desc in opt_output] - hanging = max([e[2] for e in entries]) - for opt, desc, width in entries: - if desc: - initindent = ' %s%s ' % (opt, ' ' * (hanging - width)) - hangindent = ' ' * (hanging + 3) - ui.write('%s\n' % (util.wrap(desc, textwidth, - initindent=initindent, - hangindent=hangindent))) - else: - ui.write("%s\n" % opt) + ui.write(opttext(optlist, textwidth)) @command('identify|id', [('r', 'rev', '', @@ -3507,10 +3512,10 @@ @command('^merge', [('f', 'force', None, _('force a merge with outstanding changes')), - ('t', 'tool', '', _('specify merge tool')), ('r', 'rev', '', _('revision to merge'), _('REV')), ('P', 'preview', None, - _('review revisions to merge (no merge is performed)'))], + _('review revisions to merge (no merge is performed)')) + ] + mergetoolopts, _('[-P] [-f] [[-r] REV]')) def merge(ui, repo, node=None, **opts): """merge working directory with another revision @@ -3589,7 +3594,7 @@ try: # ui.forcemerge is an internal variable, do not document - ui.setconfig('ui', 'forcemerge', opts.get('tool', '')) + repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', '')) return hg.merge(repo, node, force=opts.get('force')) finally: ui.setconfig('ui', 'forcemerge', '') @@ -3947,13 +3952,16 @@ file states (columns) and option combinations (rows). The file states are Added [A], Clean [C], Modified [M] and Missing [!] (as reported by :hg:`status`). The actions are Warn, Remove (from - branch) and Delete (from disk):: - - A C M ! - none W RD W R - -f R RD RD R - -A W W W R - -Af R R R R + branch) and Delete (from disk): + + ======= == == == == + A C M ! + ======= == == == == + none W RD W R + -f R RD RD R + -A W W W R + -Af R R R R + ======= == == == == Note that remove never deletes files in Added [A] state from the working directory, not even if option --force is specified. @@ -4051,9 +4059,8 @@ ('l', 'list', None, _('list state of files needing merge')), ('m', 'mark', None, _('mark files as resolved')), ('u', 'unmark', None, _('mark files as unresolved')), - ('t', 'tool', '', _('specify merge tool')), ('n', 'no-status', None, _('hide status prefix'))] - + walkopts, + + mergetoolopts + walkopts, _('[OPTION]... [FILE]...')) def resolve(ui, repo, *pats, **opts): """redo merges or set/view the merge status of files @@ -4145,7 +4152,7 @@ [('a', 'all', None, _('revert all changes when no arguments given')), ('d', 'date', '', _('tipmost revision matching date'), _('DATE')), ('r', 'rev', '', _('revert to the specified revision'), _('REV')), - ('', 'no-backup', None, _('do not save backup copies of files')), + ('C', 'no-backup', None, _('do not save backup copies of files')), ] + walkopts + dryrunopts, _('[OPTION]... [-r REV] [NAME]...')) def revert(ui, repo, *pats, **opts): @@ -4727,6 +4734,7 @@ ctx = repo[None] parents = ctx.parents() pnode = parents[0].node() + marks = [] for p in parents: # label with log.changeset (instead of log.parent) since this @@ -4735,7 +4743,7 @@ label='log.changeset') ui.write(' '.join(p.tags()), label='log.tag') if p.bookmarks(): - ui.write(' ' + ' '.join(p.bookmarks()), label='log.bookmark') + marks.extend(p.bookmarks()) if p.rev() == -1: if not len(repo): ui.write(_(' (empty repository)')) @@ -4754,6 +4762,20 @@ else: ui.status(m, label='log.branch') + if marks: + current = repo._bookmarkcurrent + ui.write(_('bookmarks:'), label='log.bookmark') + if current is not None: + try: + marks.remove(current) + ui.write(' *' + current, label='bookmarks.current') + except ValueError: + # current bookmark not in parent ctx marks + pass + for m in marks: + ui.write(' ' + m, label='log.bookmark') + ui.write('\n', label='log.bookmark') + st = list(repo.status(unknown=True))[:6] c = repo.dirstate.copies() @@ -4988,19 +5010,22 @@ for t, n in reversed(repo.tagslist()): if ui.quiet: - ui.write("%s\n" % t) + ui.write("%s\n" % t, label='tags.normal') continue hn = hexfunc(n) r = "%5d:%s" % (repo.changelog.rev(n), hn) + rev = ui.label(r, 'log.changeset') spaces = " " * (30 - encoding.colwidth(t)) + tag = ui.label(t, 'tags.normal') if ui.verbose: if repo.tagtype(t) == 'local': tagtype = " local" + tag = ui.label(t, 'tags.local') else: tagtype = "" - ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype)) + ui.write("%s%s %s%s\n" % (tag, spaces, rev, tagtype)) @command('tip', [('p', 'patch', None, _('show patch')),
--- a/mercurial/commandserver.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/commandserver.py Wed Sep 14 13:57:56 2011 +0200 @@ -185,6 +185,7 @@ copiedui = self.ui.copy() self.repo.baseui = copiedui self.repo.ui = self.repo.dirstate._ui = self.repoui.copy() + self.repo.invalidate() req = dispatch.request(args[:], copiedui, self.repo, self.cin, self.cout, self.cerr)
--- a/mercurial/demandimport.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/demandimport.py Wed Sep 14 13:57:56 2011 +0200 @@ -27,6 +27,8 @@ import __builtin__ _origimport = __import__ +nothing = object() + class _demandmod(object): """module demand-loader and proxy""" def __init__(self, name, globals, locals): @@ -50,7 +52,7 @@ h, t = p, None if '.' in p: h, t = p.split('.', 1) - if not hasattr(mod, h): + if getattr(mod, h, nothing) is nothing: setattr(mod, h, _demandmod(p, mod.__dict__, mod.__dict__)) elif t: subload(getattr(mod, h), t) @@ -109,12 +111,12 @@ mod = _origimport(name, globals, locals) # recurse down the module chain for comp in name.split('.')[1:]: - if not hasattr(mod, comp): + if getattr(mod, comp, nothing) is nothing: setattr(mod, comp, _demandmod(comp, mod.__dict__, mod.__dict__)) mod = getattr(mod, comp) for x in fromlist: # set requested submodules for demand load - if not hasattr(mod, x): + if getattr(mod, x, nothing) is nothing: setattr(mod, x, _demandmod(x, mod.__dict__, locals)) return mod @@ -137,6 +139,8 @@ # raise ImportError if x not defined '__main__', '_ssl', # conditional imports in the stdlib, issue1964 + 'rfc822', + 'mimetools', ] def enable(): @@ -146,4 +150,3 @@ def disable(): "disable global demand-loading of modules" __builtin__.__import__ = _origimport -
--- a/mercurial/dirstate.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/dirstate.py Wed Sep 14 13:57:56 2011 +0200 @@ -453,7 +453,7 @@ write(e) write(f) st.write(cs.getvalue()) - st.rename() + st.close() self._lastnormaltime = None self._dirty = self._dirtypl = False
--- a/mercurial/dispatch.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/dispatch.py Wed Sep 14 13:57:56 2011 +0200 @@ -123,6 +123,9 @@ else: ui.warn(_("hg: %s\n") % inst.args[1]) commands.help_(ui, 'shortlist') + except error.OutOfBandError, inst: + ui.warn("abort: remote error:\n") + ui.warn(''.join(inst.args)) except error.RepoError, inst: ui.warn(_("abort: %s!\n") % inst) if inst.hint: @@ -159,16 +162,16 @@ elif m in "zlib".split(): ui.warn(_("(is your Python install correct?)\n")) except IOError, inst: - if hasattr(inst, "code"): + if util.safehasattr(inst, "code"): ui.warn(_("abort: %s\n") % inst) - elif hasattr(inst, "reason"): + elif util.safehasattr(inst, "reason"): try: # usually it is in the form (errno, strerror) reason = inst.reason.args[1] except (AttributeError, IndexError): # it might be anything, for example a string reason = inst.reason ui.warn(_("abort: error: %s\n") % reason) - elif hasattr(inst, "args") and inst.args[0] == errno.EPIPE: + elif util.safehasattr(inst, "args") and inst.args[0] == errno.EPIPE: if ui.debugflag: ui.warn(_("broken pipe\n")) elif getattr(inst, "strerror", None): @@ -338,7 +341,7 @@ ui.debug("alias '%s' shadows command '%s'\n" % (self.name, self.cmdname)) - if hasattr(self, 'shell'): + if util.safehasattr(self, 'shell'): return self.fn(ui, *args, **opts) else: try: @@ -483,15 +486,14 @@ lui = ui.copy() lui.readconfig(os.path.join(path, ".hg", "hgrc"), path) - if rpath: + if rpath and rpath[-1]: path = lui.expandpath(rpath[-1]) lui = ui.copy() lui.readconfig(os.path.join(path, ".hg", "hgrc"), path) return path, lui -def _checkshellalias(ui, args): - cwd = os.getcwd() +def _checkshellalias(lui, ui, args): norepo = commands.norepo options = {} @@ -503,12 +505,6 @@ if not args: return - _parseconfig(ui, options['config']) - if options['cwd']: - os.chdir(options['cwd']) - - path, lui = _getlocal(ui, [options['repository']]) - cmdtable = commands.table.copy() addaliases(lui, cmdtable) @@ -517,28 +513,22 @@ aliases, entry = cmdutil.findcmd(cmd, cmdtable, lui.config("ui", "strict")) except (error.AmbiguousCommand, error.UnknownCommand): commands.norepo = norepo - os.chdir(cwd) return cmd = aliases[0] fn = entry[0] - if cmd and hasattr(fn, 'shell'): + if cmd and util.safehasattr(fn, 'shell'): d = lambda: fn(ui, *args[1:]) return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d, [], {}) commands.norepo = norepo - os.chdir(cwd) _loaded = set() def _dispatch(req): args = req.args ui = req.ui - shellaliasfn = _checkshellalias(ui, args) - if shellaliasfn: - return shellaliasfn() - # read --config before doing anything else # (e.g. to change trust settings for reading .hg/hgrc) cfgs = _parseconfig(ui, _earlygetopt(['--config'], args)) @@ -551,6 +541,12 @@ rpath = _earlygetopt(["-R", "--repository", "--repo"], args) path, lui = _getlocal(ui, rpath) + # Now that we're operating in the right directory/repository with + # the right config settings, check for shell aliases + shellaliasfn = _checkshellalias(lui, ui, args) + if shellaliasfn: + return shellaliasfn() + # Configure extensions in phases: uisetup, extsetup, cmdtable, and # reposetup. Programs like TortoiseHg will call _dispatch several # times so we keep track of configured extensions in _loaded. @@ -635,10 +631,10 @@ for ui_ in uis: ui_.setconfig('web', 'cacerts', '') + if options['version']: + return commands.version_(ui) if options['help']: - return commands.help_(ui, cmd, options['version']) - elif options['version']: - return commands.version_(ui) + return commands.help_(ui, cmd) elif not cmd: return commands.help_(ui, 'shortlist')
--- a/mercurial/error.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/error.py Wed Sep 14 13:57:56 2011 +0200 @@ -39,6 +39,9 @@ class ConfigError(Abort): 'Exception raised when parsing config files' +class OutOfBandError(Exception): + 'Exception raised when a remote repo reports failure' + class ParseError(Exception): 'Exception raised when parsing config files (msg[, pos])'
--- a/mercurial/extensions.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/extensions.py Wed Sep 14 13:57:56 2011 +0200 @@ -124,7 +124,7 @@ where orig is the original (wrapped) function, and *args, **kwargs are the arguments passed to it. ''' - assert hasattr(wrapper, '__call__') + assert util.safehasattr(wrapper, '__call__') aliases, entry = cmdutil.findcmd(command, table) for alias, e in table.iteritems(): if e is entry: @@ -177,12 +177,12 @@ your end users, you should play nicely with others by using the subclass trick. ''' - assert hasattr(wrapper, '__call__') + assert util.safehasattr(wrapper, '__call__') def wrap(*args, **kwargs): return wrapper(origfn, *args, **kwargs) origfn = getattr(container, funcname) - assert hasattr(origfn, '__call__') + assert util.safehasattr(origfn, '__call__') setattr(container, funcname, wrap) return origfn
--- a/mercurial/fancyopts.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/fancyopts.py Wed Sep 14 13:57:56 2011 +0200 @@ -75,7 +75,7 @@ # copy defaults to state if isinstance(default, list): state[name] = default[:] - elif hasattr(default, '__call__'): + elif getattr(default, '__call__', False): state[name] = None else: state[name] = default
--- a/mercurial/hbisect.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/hbisect.py Wed Sep 14 13:57:56 2011 +0200 @@ -35,17 +35,18 @@ # build visit array ancestors = [None] * (len(changelog) + 1) # an extra for [-1] - # set nodes descended from goodrev - ancestors[goodrev] = [] + # set nodes descended from goodrevs + for rev in goodrevs: + ancestors[rev] = [] for rev in xrange(goodrev + 1, len(changelog)): for prev in clparents(rev): if ancestors[prev] == []: ancestors[rev] = [] # clear good revs from array - for node in goodrevs: - ancestors[node] = None - for rev in xrange(len(changelog), -1, -1): + for rev in goodrevs: + ancestors[rev] = None + for rev in xrange(len(changelog), goodrev, -1): if ancestors[rev] is None: for prev in clparents(rev): ancestors[prev] = None @@ -149,7 +150,7 @@ for kind in state: for node in state[kind]: f.write("%s %s\n" % (kind, hex(node))) - f.rename() + f.close() finally: wlock.release()
--- a/mercurial/help.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/help.py Wed Sep 14 13:57:56 2011 +0200 @@ -31,7 +31,7 @@ """Return a delayed loader for help/topic.txt.""" def loader(): - if hasattr(sys, 'frozen'): + if util.mainfrozen(): module = sys.executable else: module = __file__
--- a/mercurial/help/config.txt Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/help/config.txt Wed Sep 14 13:57:56 2011 +0200 @@ -223,6 +223,10 @@ ``$HG_ARGS`` expand to the arguments given to Mercurial. In the ``hg echo foo`` call above, ``$HG_ARGS`` would expand to ``echo foo``. +.. note:: Some global configuration options such as ``-R`` are + processed before shell aliases and will thus not be passed to + aliases. + ``auth`` """""""" @@ -1261,6 +1265,12 @@ ``ipv6`` Whether to use IPv6. Default is False. +``logoimg`` + File name of the logo image that some templates display on each page. + The file name is relative to ``staticurl``. That is, the full path to + the logo image is "staticurl/logoimg". + If unset, ``hglogo.png`` will be used. + ``logourl`` Base URL to use for logos. If unset, ``http://mercurial.selenic.com/`` will be used.
--- a/mercurial/hg.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/hg.py Wed Sep 14 13:57:56 2011 +0200 @@ -98,9 +98,9 @@ hook(ui, repo) return repo -def peer(ui, opts, path, create=False): +def peer(uiorrepo, opts, path, create=False): '''return a repository peer for the specified path''' - rui = remoteui(ui, opts) + rui = remoteui(uiorrepo, opts) return repository(rui, path, create) def defaultdest(source): @@ -174,6 +174,36 @@ continue _update(r, uprev) +def copystore(ui, srcrepo, destpath): + '''copy files from store of srcrepo in destpath + + returns destlock + ''' + destlock = None + try: + hardlink = None + num = 0 + for f in srcrepo.store.copylist(): + src = os.path.join(srcrepo.sharedpath, f) + dst = os.path.join(destpath, f) + dstbase = os.path.dirname(dst) + if dstbase and not os.path.exists(dstbase): + os.mkdir(dstbase) + if os.path.exists(src): + if dst.endswith('data'): + # lock to avoid premature writing to the target + destlock = lock.lock(os.path.join(dstbase, "lock")) + hardlink, n = util.copyfiles(src, dst, hardlink) + num += n + if hardlink: + ui.debug("linked %d files\n" % num) + else: + ui.debug("copied %d files\n" % num) + return destlock + except: + release(destlock) + raise + def clone(ui, peeropts, source, dest=None, pull=False, rev=None, update=True, stream=False, branch=None): """Make a copy of an existing repository. @@ -287,24 +317,7 @@ % dest) raise - hardlink = None - num = 0 - for f in srcrepo.store.copylist(): - src = os.path.join(srcrepo.sharedpath, f) - dst = os.path.join(destpath, f) - dstbase = os.path.dirname(dst) - if dstbase and not os.path.exists(dstbase): - os.mkdir(dstbase) - if os.path.exists(src): - if dst.endswith('data'): - # lock to avoid premature writing to the target - destlock = lock.lock(os.path.join(dstbase, "lock")) - hardlink, n = util.copyfiles(src, dst, hardlink) - num += n - if hardlink: - ui.debug("linked %d files\n" % num) - else: - ui.debug("copied %d files\n" % num) + destlock = copystore(ui, srcrepo, destpath) # we need to re-init the repo after manually copying the data # into it @@ -537,7 +550,7 @@ def remoteui(src, opts): 'build a remote ui from ui or repo and opts' - if hasattr(src, 'baseui'): # looks like a repository + if util.safehasattr(src, 'baseui'): # looks like a repository dst = src.baseui.copy() # drop repo-specific config src = src.ui # copy target options from repo else: # assume it's a global ui object
--- a/mercurial/hgweb/hgweb_mod.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/hgweb/hgweb_mod.py Wed Sep 14 13:57:56 2011 +0200 @@ -7,7 +7,7 @@ # GNU General Public License version 2 or any later version. import os -from mercurial import ui, hg, hook, error, encoding, templater +from mercurial import ui, hg, hook, error, encoding, templater, util from common import get_stat, ErrorResponse, permhooks, caching from common import HTTP_OK, HTTP_NOT_MODIFIED, HTTP_BAD_REQUEST from common import HTTP_NOT_FOUND, HTTP_SERVER_ERROR @@ -148,7 +148,7 @@ cmd = cmd[style + 1:] # avoid accepting e.g. style parameter as command - if hasattr(webcommands, cmd): + if util.safehasattr(webcommands, cmd): req.form['cmd'] = [cmd] else: cmd = '' @@ -236,6 +236,7 @@ port = port != default_port and (":" + port) or "" urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port) logourl = self.config("web", "logourl", "http://mercurial.selenic.com/") + logoimg = self.config("web", "logoimg", "hglogo.png") staticurl = self.config("web", "staticurl") or req.url + 'static/' if not staticurl.endswith('/'): staticurl += '/' @@ -276,6 +277,7 @@ tmpl = templater.templater(mapfile, defaults={"url": req.url, "logourl": logourl, + "logoimg": logoimg, "staticurl": staticurl, "urlbase": urlbase, "repo": self.reponame,
--- a/mercurial/hgweb/hgwebdir_mod.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/hgweb/hgwebdir_mod.py Wed Sep 14 13:57:56 2011 +0200 @@ -51,6 +51,33 @@ yield (prefix + '/' + util.pconvert(path[len(roothead):]).lstrip('/')).strip('/'), path +def geturlcgivars(baseurl, port): + """ + Extract CGI variables from baseurl + + >>> geturlcgivars("http://host.org/base", "80") + ('host.org', '80', '/base') + >>> geturlcgivars("http://host.org:8000/base", "80") + ('host.org', '8000', '/base') + >>> geturlcgivars('/base', 8000) + ('', '8000', '/base') + >>> geturlcgivars("base", '8000') + ('', '8000', '/base') + >>> geturlcgivars("http://host", '8000') + ('host', '8000', '/') + >>> geturlcgivars("http://host/", '8000') + ('host', '8000', '/') + """ + u = util.url(baseurl) + name = u.host or '' + if u.port: + port = u.port + path = u.path or "" + if not path.startswith('/'): + path = '/' + path + + return name, str(port), path + class hgwebdir(object): refreshinterval = 20 @@ -348,6 +375,7 @@ start = url[-1] == '?' and '&' or '?' sessionvars = webutil.sessionvars(vars, start) logourl = config('web', 'logourl', 'http://mercurial.selenic.com/') + logoimg = config('web', 'logoimg', 'hglogo.png') staticurl = config('web', 'staticurl') or url + 'static/' if not staticurl.endswith('/'): staticurl += '/' @@ -358,17 +386,14 @@ "motd": motd, "url": url, "logourl": logourl, + "logoimg": logoimg, "staticurl": staticurl, "sessionvars": sessionvars}) return tmpl def updatereqenv(self, env): if self._baseurl is not None: - u = util.url(self._baseurl) - env['SERVER_NAME'] = u.host - if u.port: - env['SERVER_PORT'] = u.port - path = u.path or "" - if not path.startswith('/'): - path = '/' + path + name, port, path = geturlcgivars(self._baseurl, env['SERVER_PORT']) + env['SERVER_NAME'] = name + env['SERVER_PORT'] = port env['SCRIPT_NAME'] = path
--- a/mercurial/hgweb/protocol.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/hgweb/protocol.py Wed Sep 14 13:57:56 2011 +0200 @@ -10,6 +10,7 @@ from common import HTTP_OK HGTYPE = 'application/mercurial-0.1' +HGERRTYPE = 'application/hg-error' class webproto(object): def __init__(self, req, ui): @@ -90,3 +91,7 @@ rsp = '0\n%s\n' % rsp.res req.respond(HTTP_OK, HGTYPE, length=len(rsp)) return [rsp] + elif isinstance(rsp, wireproto.ooberror): + rsp = rsp.message + req.respond(HTTP_OK, HGERRTYPE, length=len(rsp)) + return [rsp]
--- a/mercurial/hgweb/request.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/hgweb/request.py Wed Sep 14 13:57:56 2011 +0200 @@ -101,7 +101,7 @@ self.headers = [] def write(self, thing): - if hasattr(thing, "__iter__"): + if util.safehasattr(thing, "__iter__"): for part in thing: self.write(part) else:
--- a/mercurial/hgweb/server.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/hgweb/server.py Wed Sep 14 13:57:56 2011 +0200 @@ -248,7 +248,7 @@ from threading import activeCount _mixin = SocketServer.ThreadingMixIn except ImportError: - if hasattr(os, "fork"): + if util.safehasattr(os, "fork"): _mixin = SocketServer.ForkingMixIn else: class _mixin(object):
--- a/mercurial/hgweb/webutil.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/hgweb/webutil.py Wed Sep 14 13:57:56 2011 +0200 @@ -72,7 +72,7 @@ d['date'] = s.date() d['description'] = s.description() d['branch'] = s.branch() - if hasattr(s, 'path'): + if util.safehasattr(s, 'path'): d['file'] = s.path() yield d
--- a/mercurial/hgweb/wsgicgi.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/hgweb/wsgicgi.py Wed Sep 14 13:57:56 2011 +0200 @@ -78,5 +78,4 @@ for chunk in content: write(chunk) finally: - if hasattr(content, 'close'): - content.close() + getattr(content, 'close', lambda : None)()
--- a/mercurial/hook.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/hook.py Wed Sep 14 13:57:56 2011 +0200 @@ -21,14 +21,14 @@ ui.note(_("calling hook %s: %s\n") % (hname, funcname)) obj = funcname - if not hasattr(obj, '__call__'): + if not util.safehasattr(obj, '__call__'): d = funcname.rfind('.') if d == -1: raise util.Abort(_('%s hook is invalid ("%s" not in ' 'a module)') % (hname, funcname)) modname = funcname[:d] oldpaths = sys.path - if hasattr(sys, "frozen"): + if util.mainfrozen(): # binary installs require sys.path manipulation modpath, modfile = os.path.split(modname) if modpath and modfile: @@ -60,7 +60,7 @@ raise util.Abort(_('%s hook is invalid ' '("%s" is not defined)') % (hname, funcname)) - if not hasattr(obj, '__call__'): + if not util.safehasattr(obj, '__call__'): raise util.Abort(_('%s hook is invalid ' '("%s" is not callable)') % (hname, funcname)) @@ -99,7 +99,7 @@ env = {} for k, v in args.iteritems(): - if hasattr(v, '__call__'): + if util.safehasattr(v, '__call__'): v = v() if isinstance(v, dict): # make the dictionary element order stable across Python @@ -149,7 +149,7 @@ for hname, cmd in ui.configitems('hooks'): if hname.split('.')[0] != name or not cmd: continue - if hasattr(cmd, '__call__'): + if util.safehasattr(cmd, '__call__'): r = _pythonhook(ui, repo, name, hname, cmd, args, throw) or r elif cmd.startswith('python:'): if cmd.count(':') >= 2:
--- a/mercurial/httprepo.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/httprepo.py Wed Sep 14 13:57:56 2011 +0200 @@ -44,8 +44,7 @@ def __del__(self): for h in self.urlopener.handlers: h.close() - if hasattr(h, "close_all"): - h.close_all() + getattr(h, "close_all", lambda : None)() def url(self): return self.path @@ -137,6 +136,8 @@ proto = resp.headers.get('content-type', '') safeurl = util.hidepassword(self._url) + if proto.startswith('application/hg-error'): + raise error.OutOfBandError(resp.read()) # accept old "text/plain" and "application/hg-changegroup" for now if not (proto.startswith('application/mercurial-') or proto.startswith('text/plain') or
--- a/mercurial/i18n.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/i18n.py Wed Sep 14 13:57:56 2011 +0200 @@ -9,7 +9,7 @@ import gettext, sys, os # modelled after templater.templatepath: -if hasattr(sys, 'frozen'): +if getattr(sys, 'frozen', None) is not None: module = sys.executable else: module = __file__ @@ -61,4 +61,3 @@ _ = lambda message: message else: _ = gettext -
--- a/mercurial/keepalive.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/keepalive.py Wed Sep 14 13:57:56 2011 +0200 @@ -547,13 +547,14 @@ print "send:", repr(str) try: blocksize = 8192 - if hasattr(str,'read') : + read = getattr(str, 'read', None) + if read is not None: if self.debuglevel > 0: print "sendIng a read()able" - data = str.read(blocksize) + data = read(blocksize) while data: self.sock.sendall(data) - data = str.read(blocksize) + data = read(blocksize) else: self.sock.sendall(str) except socket.error, v:
--- a/mercurial/localrepo.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/localrepo.py Wed Sep 14 13:57:56 2011 +0200 @@ -10,13 +10,14 @@ import repo, changegroup, subrepo, discovery, pushkey import changelog, dirstate, filelog, manifest, context, bookmarks import lock, transaction, store, encoding -import scmutil, util, extensions, hook, error +import scmutil, util, extensions, hook, error, revset import match as matchmod import merge as mergemod import tags as tagsmod from lock import release import weakref, errno, os, time, inspect propertycache = util.propertycache +filecache = scmutil.filecache class localrepository(repo.repository): capabilities = set(('lookup', 'changegroupsubset', 'branchmap', 'pushkey', @@ -63,6 +64,7 @@ ) if self.ui.configbool('format', 'generaldelta', False): requirements.append("generaldelta") + requirements = set(requirements) else: raise error.RepoError(_("repository %s not found") % path) elif create: @@ -95,21 +97,19 @@ if create: self._writerequirements() - # These two define the set of tags for this repository. _tags - # maps tag name to node; _tagtypes maps tag name to 'global' or - # 'local'. (Global tags are defined by .hgtags across all - # heads, and local tags are defined in .hg/localtags.) They - # constitute the in-memory cache of tags. - self._tags = None - self._tagtypes = None self._branchcache = None self._branchcachetip = None - self.nodetagscache = None self.filterpats = {} self._datafilters = {} self._transref = self._lockref = self._wlockref = None + # A cache for various files under .hg/ that tracks file changes, + # (used by the filecache decorator) + # + # Maps a property name to its util.filecacheentry + self._filecache = {} + def _applyrequirements(self, requirements): self.requirements = requirements openerreqs = set(('revlogv1', 'generaldelta')) @@ -159,15 +159,15 @@ parts.pop() return False - @util.propertycache + @filecache('bookmarks') def _bookmarks(self): return bookmarks.read(self) - @util.propertycache + @filecache('bookmarks.current') def _bookmarkcurrent(self): return bookmarks.readcurrent(self) - @propertycache + @filecache('00changelog.i', True) def changelog(self): c = changelog.changelog(self.sopener) if 'HG_PENDING' in os.environ: @@ -176,11 +176,11 @@ c.readpending('00changelog.i.a') return c - @propertycache + @filecache('00manifest.i', True) def manifest(self): return manifest.manifest(self.sopener) - @propertycache + @filecache('dirstate') def dirstate(self): warned = [0] def validate(node): @@ -217,6 +217,17 @@ for i in xrange(len(self)): yield i + def set(self, expr, *args): + ''' + Yield a context for each matching revision, after doing arg + replacement via revset.formatspec + ''' + + expr = revset.formatspec(expr, *args) + m = revset.match(None, expr) + for r in m(self, range(len(self))): + yield self[r] + def url(self): return 'file:' + self.root @@ -249,8 +260,8 @@ fp.write('\n') for name in names: m = munge and munge(name) or name - if self._tagtypes and name in self._tagtypes: - old = self._tags.get(name, nullid) + if self._tagscache.tagtypes and name in self._tagscache.tagtypes: + old = self.tags().get(name, nullid) fp.write('%s %s\n' % (hex(old), m)) fp.write('%s %s\n' % (hex(node), m)) fp.close() @@ -325,12 +336,31 @@ self.tags() # instantiate the cache self._tag(names, node, message, local, user, date) + @propertycache + def _tagscache(self): + '''Returns a tagscache object that contains various tags related caches.''' + + # This simplifies its cache management by having one decorated + # function (this one) and the rest simply fetch things from it. + class tagscache(object): + def __init__(self): + # These two define the set of tags for this repository. tags + # maps tag name to node; tagtypes maps tag name to 'global' or + # 'local'. (Global tags are defined by .hgtags across all + # heads, and local tags are defined in .hg/localtags.) + # They constitute the in-memory cache of tags. + self.tags = self.tagtypes = None + + self.nodetagscache = self.tagslist = None + + cache = tagscache() + cache.tags, cache.tagtypes = self._findtags() + + return cache + def tags(self): '''return a mapping of tag to node''' - if self._tags is None: - (self._tags, self._tagtypes) = self._findtags() - - return self._tags + return self._tagscache.tags def _findtags(self): '''Do the hard work of finding tags. Return a pair of dicts @@ -379,27 +409,29 @@ None : tag does not exist ''' - self.tags() - - return self._tagtypes.get(tagname) + return self._tagscache.tagtypes.get(tagname) def tagslist(self): '''return a list of tags ordered by revision''' - l = [] - for t, n in self.tags().iteritems(): - r = self.changelog.rev(n) - l.append((r, t, n)) - return [(t, n) for r, t, n in sorted(l)] + if not self._tagscache.tagslist: + l = [] + for t, n in self.tags().iteritems(): + r = self.changelog.rev(n) + l.append((r, t, n)) + self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)] + + return self._tagscache.tagslist def nodetags(self, node): '''return the tags associated with a node''' - if not self.nodetagscache: - self.nodetagscache = {} + if not self._tagscache.nodetagscache: + nodetagscache = {} for t, n in self.tags().iteritems(): - self.nodetagscache.setdefault(n, []).append(t) - for tags in self.nodetagscache.itervalues(): + nodetagscache.setdefault(n, []).append(t) + for tags in nodetagscache.itervalues(): tags.sort() - return self.nodetagscache.get(node, []) + self._tagscache.nodetagscache = nodetagscache + return self._tagscache.nodetagscache.get(node, []) def nodebookmarks(self, node): marks = [] @@ -489,7 +521,7 @@ for label, nodes in branches.iteritems(): for node in nodes: f.write("%s %s\n" % (hex(node), encoding.fromlocal(label))) - f.rename() + f.close() except (IOError, OSError): pass @@ -773,16 +805,38 @@ release(lock, wlock) def invalidatecaches(self): - self._tags = None - self._tagtypes = None - self.nodetagscache = None + try: + delattr(self, '_tagscache') + except AttributeError: + pass + self._branchcache = None # in UTF-8 self._branchcachetip = None + def invalidatedirstate(self): + '''Invalidates the dirstate, causing the next call to dirstate + to check if it was modified since the last time it was read, + rereading it if it has. + + This is different to dirstate.invalidate() that it doesn't always + rereads the dirstate. Use dirstate.invalidate() if you want to + explicitly read the dirstate again (i.e. restoring it to a previous + known good state).''' + try: + delattr(self, 'dirstate') + except AttributeError: + pass + def invalidate(self): - for a in ("changelog", "manifest", "_bookmarks", "_bookmarkcurrent"): - if a in self.__dict__: - delattr(self, a) + for k in self._filecache: + # dirstate is invalidated separately in invalidatedirstate() + if k == 'dirstate': + continue + + try: + delattr(self, k) + except AttributeError: + pass self.invalidatecaches() def _lock(self, lockname, wait, releasefn, acquirefn, desc): @@ -809,7 +863,14 @@ l.lock() return l - l = self._lock(self.sjoin("lock"), wait, self.store.write, + def unlock(): + self.store.write() + for k, ce in self._filecache.items(): + if k == 'dirstate': + continue + ce.refresh() + + l = self._lock(self.sjoin("lock"), wait, unlock, self.invalidate, _('repository %s') % self.origroot) self._lockref = weakref.ref(l) return l @@ -823,8 +884,14 @@ l.lock() return l - l = self._lock(self.join("wlock"), wait, self.dirstate.write, - self.dirstate.invalidate, _('working directory of %s') % + def unlock(): + self.dirstate.write() + ce = self._filecache.get('dirstate') + if ce: + ce.refresh() + + l = self._lock(self.join("wlock"), wait, unlock, + self.invalidatedirstate, _('working directory of %s') % self.origroot) self._wlockref = weakref.ref(l) return l
--- a/mercurial/lsprof.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/lsprof.py Wed Sep 14 13:57:56 2011 +0200 @@ -86,9 +86,7 @@ for k, v in list(sys.modules.iteritems()): if v is None: continue - if not hasattr(v, '__file__'): - continue - if not isinstance(v.__file__, str): + if not isinstance(getattr(v, '__file__', None), str): continue if v.__file__.startswith(code.co_filename): mname = _fn2mod[code.co_filename] = k
--- a/mercurial/mail.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/mail.py Wed Sep 14 13:57:56 2011 +0200 @@ -37,7 +37,7 @@ # backward compatible: when tls = true, we use starttls. starttls = tls == 'starttls' or util.parsebool(tls) smtps = tls == 'smtps' - if (starttls or smtps) and not hasattr(socket, 'ssl'): + if (starttls or smtps) and not util.safehasattr(socket, 'ssl'): raise util.Abort(_("can't use TLS: Python SSL support not installed")) if smtps: ui.note(_('(using smtps)\n'))
--- a/mercurial/match.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/match.py Wed Sep 14 13:57:56 2011 +0200 @@ -49,7 +49,6 @@ '<something>' - a pattern of the specified default type """ - self._ctx = None self._root = root self._cwd = cwd self._files = []
--- a/mercurial/merge.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/merge.py Wed Sep 14 13:57:56 2011 +0200 @@ -273,7 +273,6 @@ action.sort(key=actionkey) # prescan for merges - u = repo.ui for a in action: f, m = a[:2] if m == 'm': # merge @@ -308,8 +307,8 @@ numupdates = len(action) for i, a in enumerate(action): f, m = a[:2] - u.progress(_('updating'), i + 1, item=f, total=numupdates, - unit=_('files')) + repo.ui.progress(_('updating'), i + 1, item=f, total=numupdates, + unit=_('files')) if f and f[0] == "/": continue if m == "r": # remove @@ -377,7 +376,7 @@ repo.wopener.audit(f) util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags) ms.commit() - u.progress(_('updating'), None, total=numupdates, unit=_('files')) + repo.ui.progress(_('updating'), None, total=numupdates, unit=_('files')) return updated, merged, removed, unresolved
--- a/mercurial/minirst.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/minirst.py Wed Sep 14 13:57:56 2011 +0200 @@ -18,7 +18,7 @@ when adding support for new constructs. """ -import re, sys +import re import util, encoding from i18n import _ @@ -39,7 +39,7 @@ has an 'indent' field and a 'lines' field. """ blocks = [] - for b in _blockre.split(text.strip()): + for b in _blockre.split(text.lstrip('\n').rstrip()): lines = b.splitlines() indent = min((len(l) - len(l.lstrip())) for l in lines) lines = [l[indent:] for l in lines] @@ -103,6 +103,7 @@ r'((.*) +)(.*)$') _fieldre = re.compile(r':(?![: ])([^:]*)(?<! ):[ ]+(.*)') _definitionre = re.compile(r'[^ ]') +_tablere = re.compile(r'(=+\s+)*=+') def splitparagraphs(blocks): """Split paragraphs into lists.""" @@ -251,6 +252,46 @@ _sectionre = re.compile(r"""^([-=`:.'"~^_*+#])\1+$""") +def findtables(blocks): + '''Find simple tables + + Only simple one-line table elements are supported + ''' + + for block in blocks: + # Searching for a block that looks like this: + # + # === ==== === + # A B C + # === ==== === <- optional + # 1 2 3 + # x y z + # === ==== === + if (block['type'] == 'paragraph' and + len(block['lines']) > 4 and + _tablere.match(block['lines'][0]) and + block['lines'][0] == block['lines'][-1]): + block['type'] = 'table' + block['header'] = False + div = block['lines'][0] + columns = [x for x in xrange(len(div)) + if div[x] == '=' and (x == 0 or div[x - 1] == ' ')] + rows = [] + for l in block['lines'][1:-1]: + if l == div: + block['header'] = True + continue + row = [] + for n, start in enumerate(columns): + if n + 1 < len(columns): + row.append(l[start:columns[n + 1]].strip()) + else: + row.append(l[start:].strip()) + rows.append(row) + block['table'] = rows + + return blocks + def findsections(blocks): """Finds sections. @@ -392,6 +433,24 @@ if block['type'] == 'section': underline = encoding.colwidth(block['lines'][0]) * block['underline'] return "%s%s\n%s%s" % (indent, block['lines'][0],indent, underline) + if block['type'] == 'table': + table = block['table'] + # compute column widths + widths = [max([encoding.colwidth(e) for e in c]) for c in zip(*table)] + text = '' + span = sum(widths) + len(widths) - 1 + indent = ' ' * block['indent'] + hang = ' ' * (len(indent) + span - widths[-1]) + f = ' '.join('%%-%ds' % n for n in widths) + + for row in table: + l = f % tuple(row) + l = util.wrap(l, width=width, initindent=indent, hangindent=hang) + if not text and block['header']: + text = l + '\n' + indent + '-' * (min(width, span)) + '\n' + else: + text += l + "\n" + return text if block['type'] == 'definition': term = indent + block['lines'][0] hang = len(block['lines'][-1]) - len(block['lines'][-1].lstrip()) @@ -433,13 +492,14 @@ initindent=indent, hangindent=subindent) - -def format(text, width, indent=0, keep=None): - """Parse and format the text according to width.""" +def parse(text, indent=0, keep=None): + """Parse text into a list of blocks""" + pruned = [] blocks = findblocks(text) for b in blocks: b['indent'] += indent blocks = findliteralblocks(blocks) + blocks = findtables(blocks) blocks, pruned = prunecontainers(blocks, keep or []) blocks = findsections(blocks) blocks = inlineliterals(blocks) @@ -450,33 +510,62 @@ blocks = addmargins(blocks) blocks = prunecomments(blocks) blocks = findadmonitions(blocks) + return blocks, pruned + +def formatblocks(blocks, width): + text = '\n'.join(formatblock(b, width) for b in blocks) + return text + +def format(text, width, indent=0, keep=None): + """Parse and format the text according to width.""" + blocks, pruned = parse(text, indent, keep or []) text = '\n'.join(formatblock(b, width) for b in blocks) if keep is None: return text else: return text, pruned - -if __name__ == "__main__": - from pprint import pprint - - def debug(func, *args): - blocks = func(*args) - print "*** after %s:" % func.__name__ - pprint(blocks) - print - return blocks +def getsections(blocks): + '''return a list of (section name, nesting level, blocks) tuples''' + nest = "" + level = 0 + secs = [] + for b in blocks: + if b['type'] == 'section': + i = b['underline'] + if i not in nest: + nest += i + level = nest.index(i) + 1 + nest = nest[:level] + secs.append((b['lines'][0], level, [b])) + else: + if not secs: + # add an initial empty section + secs = [('', 0, [])] + secs[-1][2].append(b) + return secs - text = sys.stdin.read() - blocks = debug(findblocks, text) - blocks = debug(findliteralblocks, blocks) - blocks, pruned = debug(prunecontainers, blocks, sys.argv[1:]) - blocks = debug(inlineliterals, blocks) - blocks = debug(splitparagraphs, blocks) - blocks = debug(updatefieldlists, blocks) - blocks = debug(updateoptionlists, blocks) - blocks = debug(findsections, blocks) - blocks = debug(addmargins, blocks) - blocks = debug(prunecomments, blocks) - blocks = debug(findadmonitions, blocks) - print '\n'.join(formatblock(b, 30) for b in blocks) +def decorateblocks(blocks, width): + '''generate a list of (section name, line text) pairs for search''' + lines = [] + for s in getsections(blocks): + section = s[0] + text = formatblocks(s[2], width) + lines.append([(section, l) for l in text.splitlines(True)]) + return lines + +def maketable(data, indent=0, header=False): + '''Generate an RST table for the given table data''' + + widths = [max(encoding.colwidth(e) for e in c) for c in zip(*data)] + indent = ' ' * indent + f = indent + ' '.join('%%-%ds' % w for w in widths) + '\n' + div = indent + ' '.join('=' * w for w in widths) + '\n' + + out = [div] + for row in data: + out.append(f % tuple(row)) + if header and len(data) > 1: + out.insert(2, div) + out.append(div) + return ''.join(out)
--- a/mercurial/osutil.c Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/osutil.c Wed Sep 14 13:57:56 2011 +0200 @@ -12,6 +12,7 @@ #include <fcntl.h> #include <stdio.h> #include <string.h> +#include <errno.h> #ifdef _WIN32 #include <windows.h> @@ -288,7 +289,8 @@ #endif if (pathlen >= PATH_MAX) { - PyErr_SetString(PyExc_ValueError, "path too long"); + errno = ENAMETOOLONG; + PyErr_SetFromErrnoWithFilename(PyExc_OSError, path); goto error_value; } strncpy(fullpath, path, PATH_MAX);
--- a/mercurial/patch.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/patch.py Wed Sep 14 13:57:56 2011 +0200 @@ -126,7 +126,7 @@ mimeheaders = ['content-type'] - if not hasattr(stream, 'next'): + if not util.safehasattr(stream, 'next'): # http responses, for example, have readline but not next stream = fiter(stream)
--- a/mercurial/posix.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/posix.py Wed Sep 14 13:57:56 2011 +0200 @@ -84,6 +84,21 @@ # Turn off all +x bits os.chmod(f, s & 0666) +def copymode(src, dst, mode=None): + '''Copy the file mode from the file at path src to dst. + If src doesn't exist, we're using mode instead. If mode is None, we're + using umask.''' + try: + st_mode = os.lstat(src).st_mode & 0777 + except OSError, inst: + if inst.errno != errno.ENOENT: + raise + st_mode = mode + if st_mode is None: + st_mode = ~umask + st_mode &= 0666 + os.chmod(dst, st_mode) + def checkexec(path): """ Check whether the given path is on a filesystem with UNIX-like exec flags @@ -241,7 +256,9 @@ for path in os.environ.get('PATH', '').split(os.pathsep): executable = findexisting(os.path.join(path, command)) if executable is not None: - return executable + st = os.stat(executable) + if (st.st_mode & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)): + return executable return None def setsignalhandler(): @@ -325,3 +342,45 @@ except ImportError: pass return 80 + +def makedir(path, notindexed): + os.mkdir(path) + +def unlinkpath(f): + """unlink and remove the directory if it is empty""" + os.unlink(f) + # try removing directories that might now be empty + try: + os.removedirs(os.path.dirname(f)) + except OSError: + pass + +def lookupreg(key, name=None, scope=None): + return None + +def hidewindow(): + """Hide current shell window. + + Used to hide the window opened when starting asynchronous + child process under Windows, unneeded on other systems. + """ + pass + +class cachestat(object): + def __init__(self, path): + self.stat = os.stat(path) + + def cacheable(self): + return bool(self.stat.st_ino) + + def __eq__(self, other): + try: + return self.stat == other.stat + except AttributeError: + return False + + def __ne__(self, other): + return not self == other + +def executablepath(): + return None # available on Windows only
--- a/mercurial/pure/parsers.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/pure/parsers.py Wed Sep 14 13:57:56 2011 +0200 @@ -36,7 +36,7 @@ s = struct.calcsize(indexformatng) index = [] cache = None - n = off = 0 + off = 0 l = len(data) - s append = index.append @@ -45,7 +45,6 @@ while off <= l: e = _unpack(indexformatng, data[off:off + s]) append(e) - n += 1 if e[1] < 0: break off += e[1] + s @@ -53,7 +52,6 @@ while off <= l: e = _unpack(indexformatng, data[off:off + s]) append(e) - n += 1 off += s if off != len(data):
--- a/mercurial/repair.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/repair.py Wed Sep 14 13:57:56 2011 +0200 @@ -11,9 +11,8 @@ from mercurial.i18n import _ import os -def _bundle(repo, bases, heads, node, suffix, compress=True): +def _bundle(repo, cg, node, suffix, compress=True): """create a bundle with the specified revisions as a backup""" - cg = repo.changegroupsubset(bases, heads, 'strip') backupdir = repo.join("strip-backup") if not os.path.isdir(backupdir): os.mkdir(backupdir) @@ -83,11 +82,9 @@ saveheads.add(r) saveheads = [cl.node(r) for r in saveheads] - # compute base nodes - if saverevs: - descendants = set(cl.descendants(*saverevs)) - saverevs.difference_update(descendants) - savebases = [cl.node(r) for r in saverevs] + # compute common nodes + savecommon = set(cl.node(p) for r in saverevs for p in cl.parentrevs(r) + if p not in saverevs and p not in tostrip) bm = repo._bookmarks updatebm = [] @@ -99,12 +96,14 @@ # create a changegroup for all the branches we need to keep backupfile = None if backup == "all": - backupfile = _bundle(repo, [node], cl.heads(), node, 'backup') + allnodes=[cl.node(r) for r in xrange(striprev, len(cl))] + cg = repo._changegroup(allnodes, 'strip') + backupfile = _bundle(repo, cg, node, 'backup') repo.ui.status(_("saved backup bundle to %s\n") % backupfile) - if saveheads or savebases: + if saveheads or savecommon: # do not compress partial bundle if we remove it from disk later - chgrpfile = _bundle(repo, savebases, saveheads, node, 'temp', - compress=keeppartialbundle) + cg = repo.getbundle('strip', common=savecommon, heads=saveheads) + chgrpfile = _bundle(repo, cg, node, 'temp', compress=keeppartialbundle) mfst = repo.manifest @@ -128,7 +127,7 @@ tr.abort() raise - if saveheads or savebases: + if saveheads or savecommon: ui.note(_("adding branch\n")) f = open(chgrpfile, "rb") gen = changegroup.readbundle(f, chgrpfile)
--- a/mercurial/revlog.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/revlog.py Wed Sep 14 13:57:56 2011 +0200 @@ -226,9 +226,10 @@ self._nodepos = None v = REVLOG_DEFAULT_VERSION - if hasattr(opener, 'options'): - if 'revlogv1' in opener.options: - if 'generaldelta' in opener.options: + opts = getattr(opener, 'options', None) + if opts is not None: + if 'revlogv1' in opts: + if 'generaldelta' in opts: v |= REVLOGGENERALDELTA else: v = 0 @@ -945,9 +946,9 @@ e = self._io.packentry(self.index[i], self.node, self.version, i) fp.write(e) - # if we don't call rename, the temp file will never replace the + # if we don't call close, the temp file will never replace the # real index - fp.rename() + fp.close() tr.replace(self.indexfile, trindex * self._io.size) self._chunkclear()
--- a/mercurial/revset.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/revset.py Wed Sep 14 13:57:56 2011 +0200 @@ -6,7 +6,7 @@ # GNU General Public License version 2 or any later version. import re -import parser, util, error, discovery, hbisect +import parser, util, error, discovery, hbisect, node import bookmarks as bookmarksmod import match as matchmod from i18n import _ @@ -1019,11 +1019,69 @@ tree, pos = parse(spec) if (pos != len(spec)): raise error.ParseError(_("invalid token"), pos) - tree = findaliases(ui, tree) + if ui: + tree = findaliases(ui, tree) weight, tree = optimize(tree, True) def mfunc(repo, subset): return getset(repo, subset, tree) return mfunc +def formatspec(expr, *args): + ''' + This is a convenience function for using revsets internally, and + escapes arguments appropriately. Aliases are intentionally ignored + so that intended expression behavior isn't accidentally subverted. + + Supported arguments: + + %d = int(arg), no quoting + %s = string(arg), escaped and single-quoted + %b = arg.branch(), escaped and single-quoted + %n = hex(arg), single-quoted + %% = a literal '%' + + >>> formatspec('%d:: and not %d::', 10, 20) + '10:: and not 20::' + >>> formatspec('keyword(%s)', 'foo\\xe9') + "keyword('foo\\\\xe9')" + >>> b = lambda: 'default' + >>> b.branch = b + >>> formatspec('branch(%b)', b) + "branch('default')" + ''' + + def quote(s): + return repr(str(s)) + + ret = '' + pos = 0 + arg = 0 + while pos < len(expr): + c = expr[pos] + if c == '%': + pos += 1 + d = expr[pos] + if d == '%': + ret += d + elif d == 'd': + ret += str(int(args[arg])) + arg += 1 + elif d == 's': + ret += quote(args[arg]) + arg += 1 + elif d == 'n': + ret += quote(node.hex(args[arg])) + arg += 1 + elif d == 'b': + ret += quote(args[arg].branch()) + arg += 1 + else: + raise util.Abort('unexpected revspec format character %s' % d) + else: + ret += c + pos += 1 + + return ret + # tell hggettext to extract docstrings from these functions: i18nfunctions = symbols.values()
--- a/mercurial/scmutil.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/scmutil.py Wed Sep 14 13:57:56 2011 +0200 @@ -324,10 +324,10 @@ def errhandler(err): if err.filename == path: raise err - if followsym and hasattr(os.path, 'samestat'): + samestat = getattr(os.path, 'samestat', None) + if followsym and samestat is not None: def adddir(dirlst, dirname): match = False - samestat = os.path.samestat dirstat = os.stat(dirname) for lstdirstat in dirlst: if samestat(dirstat, lstdirstat): @@ -709,3 +709,95 @@ raise error.RequirementError(_("unknown repository format: " "requires features '%s' (upgrade Mercurial)") % "', '".join(missings)) return requirements + +class filecacheentry(object): + def __init__(self, path): + self.path = path + self.cachestat = filecacheentry.stat(self.path) + + if self.cachestat: + self._cacheable = self.cachestat.cacheable() + else: + # None means we don't know yet + self._cacheable = None + + def refresh(self): + if self.cacheable(): + self.cachestat = filecacheentry.stat(self.path) + + def cacheable(self): + if self._cacheable is not None: + return self._cacheable + + # we don't know yet, assume it is for now + return True + + def changed(self): + # no point in going further if we can't cache it + if not self.cacheable(): + return True + + newstat = filecacheentry.stat(self.path) + + # we may not know if it's cacheable yet, check again now + if newstat and self._cacheable is None: + self._cacheable = newstat.cacheable() + + # check again + if not self._cacheable: + return True + + if self.cachestat != newstat: + self.cachestat = newstat + return True + else: + return False + + @staticmethod + def stat(path): + try: + return util.cachestat(path) + except OSError, e: + if e.errno != errno.ENOENT: + raise + +class filecache(object): + '''A property like decorator that tracks a file under .hg/ for updates. + + Records stat info when called in _filecache. + + On subsequent calls, compares old stat info with new info, and recreates + the object when needed, updating the new stat info in _filecache. + + Mercurial either atomic renames or appends for files under .hg, + so to ensure the cache is reliable we need the filesystem to be able + to tell us if a file has been replaced. If it can't, we fallback to + recreating the object on every call (essentially the same behaviour as + propertycache).''' + def __init__(self, path, instore=False): + self.path = path + self.instore = instore + + def __call__(self, func): + self.func = func + self.name = func.__name__ + return self + + def __get__(self, obj, type=None): + entry = obj._filecache.get(self.name) + + if entry: + if entry.changed(): + entry.obj = self.func(obj) + else: + path = self.instore and obj.sjoin(self.path) or obj.join(self.path) + + # We stat -before- creating the object so our cache doesn't lie if + # a writer modified between the time we read and stat + entry = filecacheentry(path) + entry.obj = self.func(obj) + + obj._filecache[self.name] = entry + + setattr(obj, self.name, entry.obj) + return entry.obj
--- a/mercurial/simplemerge.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/simplemerge.py Wed Sep 14 13:57:56 2011 +0200 @@ -445,7 +445,7 @@ out.write(line) if not opts.get('print'): - out.rename() + out.close() if m3.conflicts: if not opts.get('quiet'):
--- a/mercurial/sshrepo.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/sshrepo.py Wed Sep 14 13:57:56 2011 +0200 @@ -164,6 +164,17 @@ def _recv(self): l = self.pipei.readline() + if l == '\n': + err = [] + while True: + line = self.pipee.readline() + if line == '-\n': + break + err.extend([line]) + if len(err) > 0: + # strip the trailing newline added to the last line server-side + err[-1] = err[-1][:-1] + self._abort(error.OutOfBandError(*err)) self.readerr() try: l = int(l)
--- a/mercurial/sshserver.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/sshserver.py Wed Sep 14 13:57:56 2011 +0200 @@ -82,6 +82,12 @@ def sendpusherror(self, rsp): self.sendresponse(rsp.res) + def sendooberror(self, rsp): + self.ui.ferr.write('%s\n-\n' % rsp.message) + self.ui.ferr.flush() + self.fout.write('\n') + self.fout.flush() + def serve_forever(self): try: while self.serve_one(): @@ -96,6 +102,7 @@ wireproto.streamres: sendstream, wireproto.pushres: sendpushresponse, wireproto.pusherr: sendpusherror, + wireproto.ooberror: sendooberror, } def serve_one(self):
--- a/mercurial/statichttprepo.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/statichttprepo.py Wed Sep 14 13:57:56 2011 +0200 @@ -31,15 +31,11 @@ try: f = self.opener.open(req) data = f.read() - if hasattr(f, 'getcode'): - # python 2.6+ - code = f.getcode() - elif hasattr(f, 'code'): - # undocumented attribute, seems to be set in 2.4 and 2.5 - code = f.code - else: - # Don't know how to check, hope for the best. - code = 206 + # Python 2.6+ defines a getcode() function, and 2.4 and + # 2.5 appear to always have an undocumented code attribute + # set. If we can't read either of those, fall back to 206 + # and hope for the best. + code = getattr(f, 'getcode', lambda : getattr(f, 'code', 206))() except urllib2.HTTPError, inst: num = inst.code == 404 and errno.ENOENT or None raise IOError(num, inst) @@ -125,6 +121,7 @@ self.encodepats = None self.decodepats = None self.capabilities.difference_update(["pushkey"]) + self._filecache = {} def url(self): return self._url
--- a/mercurial/store.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/store.py Wed Sep 14 13:57:56 2011 +0200 @@ -345,7 +345,7 @@ fp = self.opener('fncache', mode='wb', atomictemp=True) for p in self.entries: fp.write(encodedir(p) + '\n') - fp.rename() + fp.close() self._dirty = False def add(self, fn):
--- a/mercurial/subrepo.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/subrepo.py Wed Sep 14 13:57:56 2011 +0200 @@ -181,22 +181,22 @@ def reporelpath(repo): """return path to this (sub)repo as seen from outermost repo""" parent = repo - while hasattr(parent, '_subparent'): + while util.safehasattr(parent, '_subparent'): parent = parent._subparent return repo.root[len(parent.root)+1:] def subrelpath(sub): """return path to this subrepo as seen from outermost repo""" - if hasattr(sub, '_relpath'): + if util.safehasattr(sub, '_relpath'): return sub._relpath - if not hasattr(sub, '_repo'): + if not util.safehasattr(sub, '_repo'): return sub._path return reporelpath(sub._repo) def _abssource(repo, push=False, abort=True): """return pull/push path of repo - either based on parent repo .hgsub info or on the top repo config. Abort or return None if no source found.""" - if hasattr(repo, '_subparent'): + if util.safehasattr(repo, '_subparent'): source = util.url(repo._subsource) if source.isabs(): return str(source) @@ -208,7 +208,7 @@ parent.path = posixpath.normpath(parent.path) return str(parent) else: # recursion reached top repo - if hasattr(repo, '_subtoppath'): + if util.safehasattr(repo, '_subtoppath'): return repo._subtoppath if push and repo.ui.config('paths', 'default-push'): return repo.ui.config('paths', 'default-push')
--- a/mercurial/tags.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/tags.py Wed Sep 14 13:57:56 2011 +0200 @@ -287,6 +287,6 @@ cachefile.write("%s %s\n" % (hex(node), name)) try: - cachefile.rename() + cachefile.close() except (OSError, IOError): pass
--- a/mercurial/templatefilters.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/templatefilters.py Wed Sep 14 13:57:56 2011 +0200 @@ -188,13 +188,13 @@ return '"%s"' % jsonescape(u) elif isinstance(obj, unicode): return '"%s"' % jsonescape(obj) - elif hasattr(obj, 'keys'): + 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 hasattr(obj, '__iter__'): + elif util.safehasattr(obj, '__iter__'): out = [] for i in obj: out.append(json(i)) @@ -279,7 +279,7 @@ """:stringify: Any type. Turns the value into text by converting values into text and concatenating them. """ - if hasattr(thing, '__iter__') and not isinstance(thing, str): + 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)
--- a/mercurial/templater.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/templater.py Wed Sep 14 13:57:56 2011 +0200 @@ -135,7 +135,7 @@ v = mapping.get(key) if v is None: v = context._defaults.get(key, '') - if hasattr(v, '__call__'): + if util.safehasattr(v, '__call__'): return v(**mapping) return v @@ -172,14 +172,14 @@ def buildfunc(exp, context): n = getsymbol(exp[1]) args = [compileexp(x, context) for x in getlist(exp[2])] + if n in funcs: + f = funcs[n] + return (f, args) if n in context._filters: if len(args) != 1: raise error.ParseError(_("filter %s expects one argument") % n) f = context._filters[n] return (runfilter, (args[0][0], args[0][1], f)) - elif n in context._funcs: - f = context._funcs[n] - return (f, args) methods = { "string": lambda e, c: (runstring, e[1]), @@ -191,6 +191,9 @@ "func": buildfunc, } +funcs = { +} + # template engine path = ['templates', '../templates'] @@ -200,14 +203,14 @@ '''yield a single stream from a possibly nested set of iterators''' if isinstance(thing, str): yield thing - elif not hasattr(thing, '__iter__'): + elif not util.safehasattr(thing, '__iter__'): if thing is not None: yield str(thing) else: for i in thing: if isinstance(i, str): yield i - elif not hasattr(i, '__iter__'): + elif not util.safehasattr(i, '__iter__'): if i is not None: yield str(i) elif i is not None: @@ -338,7 +341,7 @@ normpaths = [] # executable version (py2exe) doesn't support __file__ - if hasattr(sys, 'frozen'): + if util.mainfrozen(): module = sys.executable else: module = __file__
--- a/mercurial/templates/monoblue/footer.tmpl Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/templates/monoblue/footer.tmpl Wed Sep 14 13:57:56 2011 +0200 @@ -9,7 +9,7 @@ </div> <div id="powered-by"> - <p><a href="{logourl}" title="Mercurial"><img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a></p> + <p><a href="{logourl}" title="Mercurial"><img src="{staticurl}{logoimg}" width=75 height=90 border=0 alt="mercurial"></a></p> </div> <div id="corner-top-left"></div>
--- a/mercurial/templates/monoblue/index.tmpl Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/templates/monoblue/index.tmpl Wed Sep 14 13:57:56 2011 +0200 @@ -26,7 +26,7 @@ </div> <div id="powered-by"> - <p><a href="{logourl}" title="Mercurial"><img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a></p> + <p><a href="{logourl}" title="Mercurial"><img src="{staticurl}{logoimg}" width=75 height=90 border=0 alt="mercurial"></a></p> </div> <div id="corner-top-left"></div>
--- a/mercurial/templates/paper/bookmarks.tmpl Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/templates/paper/bookmarks.tmpl Wed Sep 14 13:57:56 2011 +0200 @@ -11,7 +11,7 @@ <div class="menu"> <div class="logo"> <a href="{logourl}"> -<img src="{staticurl}hglogo.png" alt="mercurial" /></a> +<img src="{staticurl}{logoimg}" alt="mercurial" /></a> </div> <ul> <li><a href="{url}shortlog{sessionvars%urlparameter}">log</a></li>
--- a/mercurial/templates/paper/branches.tmpl Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/templates/paper/branches.tmpl Wed Sep 14 13:57:56 2011 +0200 @@ -11,7 +11,7 @@ <div class="menu"> <div class="logo"> <a href="{logourl}"> -<img src="{staticurl}hglogo.png" alt="mercurial" /></a> +<img src="{staticurl}{logoimg}" alt="mercurial" /></a> </div> <ul> <li><a href="{url}shortlog{sessionvars%urlparameter}">log</a></li>
--- a/mercurial/templates/paper/changeset.tmpl Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/templates/paper/changeset.tmpl Wed Sep 14 13:57:56 2011 +0200 @@ -6,7 +6,7 @@ <div class="menu"> <div class="logo"> <a href="{logourl}"> -<img src="{staticurl}hglogo.png" alt="mercurial" /></a> +<img src="{staticurl}{logoimg}" alt="mercurial" /></a> </div> <ul> <li><a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">log</a></li>
--- a/mercurial/templates/paper/error.tmpl Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/templates/paper/error.tmpl Wed Sep 14 13:57:56 2011 +0200 @@ -7,7 +7,7 @@ <div class="menu"> <div class="logo"> <a href="{logourl}"> -<img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a> +<img src="{staticurl}{logoimg}" width=75 height=90 border=0 alt="mercurial" /></a> </div> <ul> <li><a href="{url}shortlog{sessionvars%urlparameter}">log</a></li>
--- a/mercurial/templates/paper/fileannotate.tmpl Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/templates/paper/fileannotate.tmpl Wed Sep 14 13:57:56 2011 +0200 @@ -7,7 +7,7 @@ <div class="menu"> <div class="logo"> <a href="{logourl}"> -<img src="{staticurl}hglogo.png" alt="mercurial" /></a> +<img src="{staticurl}{logoimg}" alt="mercurial" /></a> </div> <ul> <li><a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">log</a></li>
--- a/mercurial/templates/paper/filediff.tmpl Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/templates/paper/filediff.tmpl Wed Sep 14 13:57:56 2011 +0200 @@ -7,7 +7,7 @@ <div class="menu"> <div class="logo"> <a href="{logourl}"> -<img src="{staticurl}hglogo.png" alt="mercurial" /></a> +<img src="{staticurl}{logoimg}" alt="mercurial" /></a> </div> <ul> <li><a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">log</a></li>
--- a/mercurial/templates/paper/filelog.tmpl Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/templates/paper/filelog.tmpl Wed Sep 14 13:57:56 2011 +0200 @@ -11,7 +11,7 @@ <div class="menu"> <div class="logo"> <a href="{logourl}"> -<img src="{staticurl}hglogo.png" alt="mercurial" /></a> +<img src="{staticurl}{logoimg}" alt="mercurial" /></a> </div> <ul> <li><a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">log</a></li>
--- a/mercurial/templates/paper/filerevision.tmpl Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/templates/paper/filerevision.tmpl Wed Sep 14 13:57:56 2011 +0200 @@ -7,7 +7,7 @@ <div class="menu"> <div class="logo"> <a href="{logourl}"> -<img src="{staticurl}hglogo.png" alt="mercurial" /></a> +<img src="{staticurl}{logoimg}" alt="mercurial" /></a> </div> <ul> <li><a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">log</a></li>
--- a/mercurial/templates/paper/graph.tmpl Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/templates/paper/graph.tmpl Wed Sep 14 13:57:56 2011 +0200 @@ -12,7 +12,7 @@ <div class="menu"> <div class="logo"> <a href="{logourl}"> -<img src="{staticurl}hglogo.png" alt="mercurial" /></a> +<img src="{staticurl}{logoimg}" alt="mercurial" /></a> </div> <ul> <li><a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">log</a></li>
--- a/mercurial/templates/paper/help.tmpl Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/templates/paper/help.tmpl Wed Sep 14 13:57:56 2011 +0200 @@ -11,7 +11,7 @@ <div class="menu"> <div class="logo"> <a href="{logourl}"> -<img src="{staticurl}hglogo.png" alt="mercurial" /></a> +<img src="{staticurl}{logoimg}" alt="mercurial" /></a> </div> <ul> <li><a href="{url}shortlog{sessionvars%urlparameter}">log</a></li>
--- a/mercurial/templates/paper/helptopics.tmpl Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/templates/paper/helptopics.tmpl Wed Sep 14 13:57:56 2011 +0200 @@ -11,7 +11,7 @@ <div class="menu"> <div class="logo"> <a href="{logourl}"> -<img src="{staticurl}hglogo.png" alt="mercurial" /></a> +<img src="{staticurl}{logoimg}" alt="mercurial" /></a> </div> <ul> <li><a href="{url}shortlog{sessionvars%urlparameter}">log</a></li>
--- a/mercurial/templates/paper/index.tmpl Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/templates/paper/index.tmpl Wed Sep 14 13:57:56 2011 +0200 @@ -6,7 +6,7 @@ <div class="container"> <div class="menu"> <a href="{logourl}"> -<img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a> +<img src="{staticurl}{logoimg}" width=75 height=90 border=0 alt="mercurial" /></a> </div> <div class="main"> <h2>Mercurial Repositories</h2>
--- a/mercurial/templates/paper/manifest.tmpl Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/templates/paper/manifest.tmpl Wed Sep 14 13:57:56 2011 +0200 @@ -7,7 +7,7 @@ <div class="menu"> <div class="logo"> <a href="{logourl}"> -<img src="{staticurl}hglogo.png" alt="mercurial" /></a> +<img src="{staticurl}{logoimg}" alt="mercurial" /></a> </div> <ul> <li><a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">log</a></li>
--- a/mercurial/templates/paper/search.tmpl Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/templates/paper/search.tmpl Wed Sep 14 13:57:56 2011 +0200 @@ -7,7 +7,7 @@ <div class="menu"> <div class="logo"> <a href="{logourl}"> -<img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a> +<img src="{staticurl}{logoimg}" width=75 height=90 border=0 alt="mercurial"></a> </div> <ul> <li><a href="{url}shortlog{sessionvars%urlparameter}">log</a></li>
--- a/mercurial/templates/paper/shortlog.tmpl Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/templates/paper/shortlog.tmpl Wed Sep 14 13:57:56 2011 +0200 @@ -11,7 +11,7 @@ <div class="menu"> <div class="logo"> <a href="{logourl}"> -<img src="{staticurl}hglogo.png" alt="mercurial" /></a> +<img src="{staticurl}{logoimg}" alt="mercurial" /></a> </div> <ul> <li class="active">log</li>
--- a/mercurial/templates/paper/tags.tmpl Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/templates/paper/tags.tmpl Wed Sep 14 13:57:56 2011 +0200 @@ -11,7 +11,7 @@ <div class="menu"> <div class="logo"> <a href="{logourl}"> -<img src="{staticurl}hglogo.png" alt="mercurial" /></a> +<img src="{staticurl}{logoimg}" alt="mercurial" /></a> </div> <ul> <li><a href="{url}shortlog{sessionvars%urlparameter}">log</a></li>
--- a/mercurial/templates/spartan/footer.tmpl Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/templates/spartan/footer.tmpl Wed Sep 14 13:57:56 2011 +0200 @@ -2,7 +2,7 @@ {motd} <div class="logo"> <a href="{logourl}"> -<img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a> +<img src="{staticurl}{logoimg}" width=75 height=90 border=0 alt="mercurial"></a> </div> </body>
--- a/mercurial/ui.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/ui.py Wed Sep 14 13:57:56 2011 +0200 @@ -46,7 +46,7 @@ def copy(self): return self.__class__(self) - def _is_trusted(self, fp, f): + def _trusted(self, fp, f): st = util.fstat(fp) if util.isowner(st): return True @@ -75,7 +75,7 @@ raise cfg = config.config() - trusted = sections or trust or self._is_trusted(fp, filename) + trusted = sections or trust or self._trusted(fp, filename) try: cfg.read(filename, fp, sections=sections, remap=remap) @@ -155,7 +155,19 @@ return self._data(untrusted).source(section, name) or 'none' def config(self, section, name, default=None, untrusted=False): - value = self._data(untrusted).get(section, name, default) + if isinstance(name, list): + alternates = name + else: + alternates = [name] + + for n in alternates: + value = self._data(untrusted).get(section, name, None) + if value is not None: + name = n + break + else: + value = default + if self.debugflag and not untrusted and self._reportuntrusted: uvalue = self._ucfg.get(section, name) if uvalue is not None and uvalue != value: @@ -164,12 +176,14 @@ return value def configpath(self, section, name, default=None, untrusted=False): - 'get a path config item, expanded relative to config file' + 'get a path config item, expanded relative to repo root or config file' v = self.config(section, name, default, untrusted) + if v is None: + return None if not os.path.isabs(v) or "://" not in v: src = self.configsource(section, name, untrusted) if ':' in src: - base = os.path.dirname(src.rsplit(':')) + base = os.path.dirname(src.rsplit(':')[0]) v = os.path.join(base, os.path.expanduser(v)) return v
--- a/mercurial/url.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/url.py Wed Sep 14 13:57:56 2011 +0200 @@ -135,7 +135,7 @@ orgsend(self, data) return _sendfile -has_https = hasattr(urllib2, 'HTTPSHandler') +has_https = util.safehasattr(urllib2, 'HTTPSHandler') if has_https: try: _create_connection = socket.create_connection @@ -192,8 +192,8 @@ # general transaction handler to support different ways to handle # HTTPS proxying before and after Python 2.6.3. def _generic_start_transaction(handler, h, req): - if hasattr(req, '_tunnel_host') and req._tunnel_host: - tunnel_host = req._tunnel_host + tunnel_host = getattr(req, '_tunnel_host', None) + if tunnel_host: if tunnel_host[:7] not in ['http://', 'https:/']: tunnel_host = 'https://' + tunnel_host new_tunnel = True
--- a/mercurial/util.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/util.py Wed Sep 14 13:57:56 2011 +0200 @@ -19,11 +19,67 @@ import os, time, calendar, textwrap, signal import imp, socket, urllib +if os.name == 'nt': + import windows as platform +else: + import posix as platform + +cachestat = platform.cachestat +checkexec = platform.checkexec +checklink = platform.checklink +copymode = platform.copymode +executablepath = platform.executablepath +expandglobs = platform.expandglobs +explainexit = platform.explainexit +findexe = platform.findexe +gethgcmd = platform.gethgcmd +getuser = platform.getuser +groupmembers = platform.groupmembers +groupname = platform.groupname +hidewindow = platform.hidewindow +isexec = platform.isexec +isowner = platform.isowner +localpath = platform.localpath +lookupreg = platform.lookupreg +makedir = platform.makedir +nlinks = platform.nlinks +normpath = platform.normpath +nulldev = platform.nulldev +openhardlinks = platform.openhardlinks +oslink = platform.oslink +parsepatchoutput = platform.parsepatchoutput +pconvert = platform.pconvert +popen = platform.popen +posixfile = platform.posixfile +quotecommand = platform.quotecommand +realpath = platform.realpath +rename = platform.rename +samedevice = platform.samedevice +samefile = platform.samefile +samestat = platform.samestat +setbinary = platform.setbinary +setflags = platform.setflags +setsignalhandler = platform.setsignalhandler +shellquote = platform.shellquote +spawndetached = platform.spawndetached +sshargs = platform.sshargs +statfiles = platform.statfiles +termwidth = platform.termwidth +testpid = platform.testpid +umask = platform.umask +unlink = platform.unlink +unlinkpath = platform.unlinkpath +username = platform.username + # Python compatibility def sha1(s): return _fastsha1(s) +_notset = object() +def safehasattr(thing, attr): + return getattr(thing, attr, _notset) is not _notset + def _fastsha1(s): # This function will import sha1 from hashlib or sha (whichever is # available) and overwrite itself with it on the first call. @@ -303,8 +359,8 @@ The code supports py2exe (most common, Windows only) and tools/freeze (portable, not much used). """ - return (hasattr(sys, "frozen") or # new py2exe - hasattr(sys, "importers") or # old py2exe + return (safehasattr(sys, "frozen") or # new py2exe + safehasattr(sys, "importers") or # old py2exe imp.is_frozen("__main__")) # tools/freeze def hgexecutable(): @@ -318,6 +374,8 @@ _sethgexecutable(hg) elif mainfrozen(): _sethgexecutable(sys.executable) + elif getattr(sys.modules['__main__'], '__file__', '').endswith('hg'): + _sethgexecutable(sys.modules['__main__'].__file__) else: exe = findexe('hg') or os.path.basename(sys.argv[0]) _sethgexecutable(exe) @@ -390,18 +448,6 @@ return check -def makedir(path, notindexed): - os.mkdir(path) - -def unlinkpath(f): - """unlink and remove the directory if it is empty""" - os.unlink(f) - # try removing directories that might now be empty - try: - os.removedirs(os.path.dirname(f)) - except OSError: - pass - def copyfile(src, dest): "copy a file, preserving mode and atime/mtime" if os.path.islink(src): @@ -487,22 +533,10 @@ return _("filename ends with '%s', which is not allowed " "on Windows") % t -def lookupreg(key, name=None, scope=None): - return None - -def hidewindow(): - """Hide current shell window. - - Used to hide the window opened when starting asynchronous - child process under Windows, unneeded on other systems. - """ - pass - if os.name == 'nt': checkosfilename = checkwinfilename - from windows import * else: - from posix import * + checkosfilename = platform.checkosfilename def makelock(info, pathname): try: @@ -686,16 +720,7 @@ # Temporary files are created with mode 0600, which is usually not # what we want. If the original file already exists, just copy # its mode. Otherwise, manually obey umask. - try: - st_mode = os.lstat(name).st_mode & 0777 - except OSError, inst: - if inst.errno != errno.ENOENT: - raise - st_mode = createmode - if st_mode is None: - st_mode = ~umask - st_mode &= 0666 - os.chmod(temp, st_mode) + copymode(name, temp, createmode) if emptyok: return temp try: @@ -722,11 +747,10 @@ '''writeable file object that atomically updates a file All writes will go to a temporary copy of the original file. Call - rename() when you are done writing, and atomictempfile will rename - the temporary copy to the original name, making the changes visible. - - Unlike other file-like objects, close() discards your writes by - simply deleting the temporary file. + close() when you are done writing, and atomictempfile will rename + the temporary copy to the original name, making the changes + visible. If the object is destroyed without being closed, all your + writes are discarded. ''' def __init__(self, name, mode='w+b', createmode=None): self.__name = name # permanent name @@ -738,12 +762,12 @@ self.write = self._fp.write self.fileno = self._fp.fileno - def rename(self): + def close(self): if not self._fp.closed: self._fp.close() rename(self._tempname, localpath(self.__name)) - def close(self): + def discard(self): if not self._fp.closed: try: os.unlink(self._tempname) @@ -752,24 +776,25 @@ self._fp.close() def __del__(self): - if hasattr(self, '_fp'): # constructor actually did something - self.close() + if safehasattr(self, '_fp'): # constructor actually did something + self.discard() def makedirs(name, mode=None): """recursive directory creation with parent mode inheritance""" - parent = os.path.abspath(os.path.dirname(name)) try: os.mkdir(name) - if mode is not None: - os.chmod(name, mode) - return except OSError, err: if err.errno == errno.EEXIST: return - if not name or parent == name or err.errno != errno.ENOENT: + if err.errno != errno.ENOENT or not name: + raise + parent = os.path.dirname(os.path.abspath(name)) + if parent == name: raise - makedirs(parent, mode) - makedirs(name, mode) + makedirs(parent, mode) + os.mkdir(name) + if mode is not None: + os.chmod(name, mode) def readfile(path): fp = open(path, 'rb') @@ -1294,8 +1319,9 @@ def handler(signum, frame): terminated.add(os.wait()) prevhandler = None - if hasattr(signal, 'SIGCHLD'): - prevhandler = signal.signal(signal.SIGCHLD, handler) + SIGCHLD = getattr(signal, 'SIGCHLD', None) + if SIGCHLD is not None: + prevhandler = signal.signal(SIGCHLD, handler) try: pid = spawndetached(args) while not condfn(): @@ -1639,8 +1665,10 @@ self.user, self.passwd = user, passwd if not self.user: return (s, None) - # authinfo[1] is passed to urllib2 password manager, and its URIs - # must not contain credentials. + # authinfo[1] is passed to urllib2 password manager, and its + # URIs must not contain credentials. The host is passed in the + # URIs list because Python < 2.4.3 uses only that to search for + # a password. return (s, (None, (s, self.host), self.user, self.passwd or ''))
--- a/mercurial/windows.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/windows.py Wed Sep 14 13:57:56 2011 +0200 @@ -9,6 +9,22 @@ import osutil import errno, msvcrt, os, re, sys +import win32 +executablepath = win32.executablepath +getuser = win32.getuser +hidewindow = win32.hidewindow +lookupreg = win32.lookupreg +makedir = win32.makedir +nlinks = win32.nlinks +oslink = win32.oslink +samedevice = win32.samedevice +samefile = win32.samefile +setsignalhandler = win32.setsignalhandler +spawndetached = win32.spawndetached +termwidth = win32.termwidth +testpid = win32.testpid +unlink = win32.unlink + nulldev = 'NUL:' umask = 002 @@ -90,6 +106,9 @@ def setflags(f, l, x): pass +def copymode(src, dst, mode=None): + pass + def checkexec(path): return False @@ -99,8 +118,9 @@ def setbinary(fd): # When run without console, pipes may expose invalid # fileno(), usually set to -1. - if hasattr(fd, 'fileno') and fd.fileno() >= 0: - msvcrt.setmode(fd.fileno(), os.O_BINARY) + fno = getattr(fd, 'fileno', None) + if fno is not None and fno() >= 0: + msvcrt.setmode(fno(), os.O_BINARY) def pconvert(path): return '/'.join(path.split(os.sep)) @@ -281,6 +301,14 @@ # Don't support groups on Windows for now raise KeyError() -from win32 import * +def isexec(f): + return False + +class cachestat(object): + def __init__(self, path): + pass + + def cacheable(self): + return False expandglobs = True
--- a/mercurial/wireproto.py Wed Sep 14 13:51:50 2011 +0200 +++ b/mercurial/wireproto.py Wed Sep 14 13:57:56 2011 +0200 @@ -17,7 +17,7 @@ class future(object): '''placeholder for a value to be set later''' def set(self, value): - if hasattr(self, 'value'): + if util.safehasattr(self, 'value'): raise error.RepoError("future is already set") self.value = value @@ -58,8 +58,9 @@ req, rsp = [], [] for name, args, opts, resref in self.calls: mtd = getattr(self.remote, name) - if hasattr(mtd, 'batchable'): - batchable = getattr(mtd, 'batchable')(mtd.im_self, *args, **opts) + batchablefn = getattr(mtd, 'batchable', None) + if batchablefn is not None: + batchable = batchablefn(mtd.im_self, *args, **opts) encargsorres, encresref = batchable.next() if encresref: req.append((name, encargsorres,)) @@ -334,6 +335,10 @@ def __init__(self, res): self.res = res +class ooberror(object): + def __init__(self, message): + self.message = message + def dispatch(repo, proto, command): func, spec = commands[command] args = proto.getargs(spec) @@ -375,6 +380,8 @@ result = func(repo, proto, *[data[k] for k in keys]) else: result = func(repo, proto) + if isinstance(result, ooberror): + return result res.append(escapearg(result)) return ';'.join(res)
--- a/setup.py Wed Sep 14 13:51:50 2011 +0200 +++ b/setup.py Wed Sep 14 13:57:56 2011 +0200 @@ -5,7 +5,7 @@ # 'python setup.py --help' for more options import sys, platform -if not hasattr(sys, 'version_info') or sys.version_info < (2, 4, 0, 'final'): +if getattr(sys, 'version_info', (0, 0, 0)) < (2, 4, 0, 'final'): raise SystemExit("Mercurial requires Python 2.4 or later.") if sys.version_info[0] >= 3:
--- a/tests/hghave Wed Sep 14 13:51:50 2011 +0200 +++ b/tests/hghave Wed Sep 14 13:57:56 2011 +0200 @@ -101,6 +101,16 @@ def has_fifo(): return hasattr(os, "mkfifo") +def has_cacheable_fs(): + from mercurial import util + + fd, path = tempfile.mkstemp(prefix=tempprefix) + os.close(fd) + try: + return util.cachestat(path).cacheable() + finally: + os.remove(path) + def has_lsprof(): try: import _lsprof @@ -200,6 +210,7 @@ "baz": (has_baz, "GNU Arch baz client"), "bzr": (has_bzr, "Canonical's Bazaar client"), "bzr114": (has_bzr114, "Canonical's Bazaar client >= 1.14"), + "cacheable": (has_cacheable_fs, "cacheable filesystem"), "cvs": (has_cvs, "cvs client/server"), "darcs": (has_darcs, "darcs client"), "docutils": (has_docutils, "Docutils text processing library"),
--- a/tests/run-tests.py Wed Sep 14 13:51:50 2011 +0200 +++ b/tests/run-tests.py Wed Sep 14 13:57:56 2011 +0200 @@ -340,10 +340,7 @@ """Terminate subprocess (with fallback for Python versions < 2.6)""" vlog('# Terminating process %d' % proc.pid) try: - if hasattr(proc, 'terminate'): - proc.terminate() - else: - os.kill(proc.pid, signal.SIGTERM) + getattr(proc, 'terminate', lambda : os.kill(proc.pid, signal.SIGTERM))() except OSError: pass @@ -726,6 +723,7 @@ rename(testpath + ".err", testpath) else: rename(testpath + ".err", testpath + ".out") + result('p', test) return result('f', (test, msg))
--- a/tests/sitecustomize.py Wed Sep 14 13:51:50 2011 +0200 +++ b/tests/sitecustomize.py Wed Sep 14 13:57:56 2011 +0200 @@ -1,6 +1,5 @@ try: import coverage - if hasattr(coverage, 'process_startup'): - coverage.process_startup() + getattr(coverage, 'process_startup', lambda: None)() except ImportError: pass
--- a/tests/test-alias.t Wed Sep 14 13:51:50 2011 +0200 +++ b/tests/test-alias.t Wed Sep 14 13:57:56 2011 +0200 @@ -251,7 +251,7 @@ $ hg --cwd .. count 'branch(default)' 2 $ hg echo --cwd .. - --cwd .. + repo specific shell aliases
--- a/tests/test-atomictempfile.py Wed Sep 14 13:51:50 2011 +0200 +++ b/tests/test-atomictempfile.py Wed Sep 14 13:57:56 2011 +0200 @@ -12,22 +12,21 @@ assert basename in glob.glob('.foo-*') file.write('argh\n') - file.rename() + file.close() assert os.path.isfile('foo') assert basename not in glob.glob('.foo-*') print 'OK' -# close() removes the temp file but does not make the write -# permanent -- essentially discards your work (WTF?!) -def test2_close(): +# discard() removes the temp file without making the write permanent +def test2_discard(): if os.path.exists('foo'): os.remove('foo') file = atomictempfile('foo') (dir, basename) = os.path.split(file._tempname) file.write('yo\n') - file.close() + file.discard() assert not os.path.isfile('foo') assert basename not in os.listdir('.') @@ -45,5 +44,5 @@ if __name__ == '__main__': test1_simple() - test2_close() + test2_discard() test3_oops()
--- a/tests/test-bisect2.t Wed Sep 14 13:51:50 2011 +0200 +++ b/tests/test-bisect2.t Wed Sep 14 13:57:56 2011 +0200 @@ -431,3 +431,22 @@ date: Thu Jan 01 00:00:09 1970 +0000 summary: 9 + +user adds irrelevant but consistent information (here: -g 2) to bisect state + + $ hg bisect -r + $ hg bisect -b 13 + $ hg bisect -g 8 + Testing changeset 11:82ca6f06eccd (3 changesets remaining, ~1 tests) + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg bisect -g 2 + Testing changeset 11:82ca6f06eccd (3 changesets remaining, ~1 tests) + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg bisect -b + The first bad revision is: + changeset: 11:82ca6f06eccd + parent: 8:dab8161ac8fc + user: test + date: Thu Jan 01 00:00:11 1970 +0000 + summary: 11 +
--- a/tests/test-bookmarks.t Wed Sep 14 13:51:50 2011 +0200 +++ b/tests/test-bookmarks.t Wed Sep 14 13:57:56 2011 +0200 @@ -239,9 +239,10 @@ test summary $ hg summary - parent: 2:db815d6d32e6 tip Y Z x y + parent: 2:db815d6d32e6 tip 2 branch: default + bookmarks: *Z Y x y commit: (clean) update: 1 new changesets, 2 branch heads (merge) @@ -342,3 +343,19 @@ * Z 3:125c9a1d6df6 x y 2:db815d6d32e6 +test wrongly formated bookmark + + $ echo '' >> .hg/bookmarks + $ hg bookmarks + X2 1:925d80f479bb + Y 2:db815d6d32e6 + * Z 3:125c9a1d6df6 + x y 2:db815d6d32e6 + $ echo "Ican'thasformatedlines" >> .hg/bookmarks + $ hg bookmarks + malformed line in .hg/bookmarks: "Ican'thasformatedlines" + X2 1:925d80f479bb + Y 2:db815d6d32e6 + * Z 3:125c9a1d6df6 + x y 2:db815d6d32e6 +
--- a/tests/test-check-pyflakes.t Wed Sep 14 13:51:50 2011 +0200 +++ b/tests/test-check-pyflakes.t Wed Sep 14 13:57:56 2011 +0200 @@ -7,8 +7,5 @@ mercurial/commands.py:*: 'mpatch' imported but unused (glob) mercurial/commands.py:*: 'osutil' imported but unused (glob) hgext/inotify/linux/__init__.py:*: 'from _inotify import *' used; unable to detect undefined names (glob) - mercurial/util.py:*: 'from posix import *' used; unable to detect undefined names (glob) - mercurial/windows.py:*: 'from win32 import *' used; unable to detect undefined names (glob) - mercurial/util.py:*: 'from windows import *' used; unable to detect undefined names (glob)
--- a/tests/test-commandserver.py Wed Sep 14 13:51:50 2011 +0200 +++ b/tests/test-commandserver.py Wed Sep 14 13:57:56 2011 +0200 @@ -154,6 +154,30 @@ 'hooks.pre-identify=python:test-commandserver.hook', 'id'], input=cStringIO.StringIO('some input')) +def outsidechanges(server): + readchannel(server) + os.system('echo a >> a && hg ci -Am2') + runcommand(server, ['tip']) + +def bookmarks(server): + readchannel(server) + runcommand(server, ['bookmarks']) + + # changes .hg/bookmarks + os.system('hg bookmark -i bm1') + os.system('hg bookmark -i bm2') + runcommand(server, ['bookmarks']) + + # changes .hg/bookmarks.current + os.system('hg upd bm1 -q') + runcommand(server, ['bookmarks']) + +def tagscache(server): + readchannel(server) + runcommand(server, ['id', '-t', '-r', '0']) + os.system('hg tag -r 0 foo') + runcommand(server, ['id', '-t', '-r', '0']) + if __name__ == '__main__': os.system('hg init') @@ -169,3 +193,6 @@ hgrc.close() check(localhgrc) check(hookoutput) + check(outsidechanges) + check(bookmarks) + check(tagscache)
--- a/tests/test-commandserver.py.out Wed Sep 14 13:51:50 2011 +0200 +++ b/tests/test-commandserver.py.out Wed Sep 14 13:57:56 2011 +0200 @@ -52,3 +52,16 @@ hook talking now try to read something: 'some input' eff892de26ec tip +changeset: 1:d3a0a68be6de +tag: tip +user: test +date: Thu Jan 01 00:00:00 1970 +0000 +summary: 2 + +no bookmarks set + bm1 1:d3a0a68be6de + bm2 1:d3a0a68be6de + * bm1 1:d3a0a68be6de + bm2 1:d3a0a68be6de + +foo
--- a/tests/test-convert-authormap.t Wed Sep 14 13:51:50 2011 +0200 +++ b/tests/test-convert-authormap.t Wed Sep 14 13:57:56 2011 +0200 @@ -27,7 +27,7 @@ sorting... converting... 0 foo - Writing author map file new/.hg/authormap + Writing author map file $TESTTMP/new/.hg/authormap $ cat new/.hg/authormap user name=Long User Name $ hg -Rnew log @@ -44,7 +44,7 @@ $ hg init new $ mv authormap.txt new/.hg/authormap $ hg convert orig new - Ignoring bad line in author map file new/.hg/authormap: this line is ignored + Ignoring bad line in author map file $TESTTMP/new/.hg/authormap: this line is ignored scanning source... sorting... converting...
--- a/tests/test-convert-svn-move.t Wed Sep 14 13:51:50 2011 +0200 +++ b/tests/test-convert-svn-move.t Wed Sep 14 13:57:56 2011 +0200 @@ -167,6 +167,7 @@ > [progress] > assume-tty = 1 > delay = 0 + > changedelay = 0 > format = topic bar number > refresh = 0 > width = 60
--- a/tests/test-debugcomplete.t Wed Sep 14 13:51:50 2011 +0200 +++ b/tests/test-debugcomplete.t Wed Sep 14 13:57:56 2011 +0200 @@ -196,7 +196,7 @@ forget: include, exclude init: ssh, remotecmd, insecure log: follow, follow-first, date, copies, keyword, rev, removed, only-merges, user, only-branch, branch, prune, hidden, patch, git, limit, no-merges, stat, style, template, include, exclude - merge: force, tool, rev, preview + merge: force, rev, preview, tool pull: update, force, rev, bookmark, branch, ssh, remotecmd, insecure push: force, rev, bookmark, branch, new-branch, ssh, remotecmd, insecure remove: after, force, include, exclude @@ -206,7 +206,7 @@ update: clean, check, date, rev addremove: similarity, include, exclude, dry-run archive: no-decode, prefix, rev, type, subrepos, include, exclude - backout: merge, parent, tool, rev, include, exclude, message, logfile, date, user + backout: merge, parent, rev, tool, include, exclude, message, logfile, date, user bisect: reset, good, bad, skip, extend, command, noupdate bookmarks: force, rev, delete, rename, inactive branch: force, clean @@ -255,7 +255,7 @@ paths: recover: rename: after, force, include, exclude, dry-run - resolve: all, list, mark, unmark, tool, no-status, include, exclude + resolve: all, list, mark, unmark, no-status, tool, include, exclude revert: all, date, rev, no-backup, include, exclude, dry-run rollback: dry-run root:
--- a/tests/test-doctest.py Wed Sep 14 13:51:50 2011 +0200 +++ b/tests/test-doctest.py Wed Sep 14 13:57:56 2011 +0200 @@ -33,3 +33,6 @@ import hgext.convert.cvsps doctest.testmod(hgext.convert.cvsps) + +import mercurial.revset +doctest.testmod(mercurial.revset)
--- a/tests/test-eol.t Wed Sep 14 13:51:50 2011 +0200 +++ b/tests/test-eol.t Wed Sep 14 13:57:56 2011 +0200 @@ -441,3 +441,82 @@ warning: ignoring .hgeol file due to parse error at .hgeol:1: bad $ hg status ? .hgeol.orig + +Test eol.only-consistent can be specified in .hgeol + + $ cd $TESTTMP + $ hg init only-consistent + $ cd only-consistent + $ printf "first\nsecond\r\n" > a.txt + $ hg add a.txt + $ cat > .hgeol << EOF + > [eol] + > only-consistent = True + > EOF + $ hg commit -m 'inconsistent' + abort: inconsistent newline style in a.txt + + [255] + $ cat > .hgeol << EOF + > [eol] + > only-consistent = False + > EOF + $ hg commit -m 'consistent' + + +Test trailing newline + + $ cat >> $HGRCPATH <<EOF + > [extensions] + > eol= + > EOF + +setup repository + + $ cd $TESTTMP + $ hg init trailing + $ cd trailing + $ cat > .hgeol <<EOF + > [patterns] + > **.txt = native + > [eol] + > fix-trailing-newline = False + > EOF + +add text without trailing newline + + $ printf "first\nsecond" > a.txt + $ hg commit --addremove -m 'checking in' + adding .hgeol + adding a.txt + $ rm a.txt + $ hg update -C -q + $ cat a.txt + first + second (no-eol) + + $ cat > .hgeol <<EOF + > [patterns] + > **.txt = native + > [eol] + > fix-trailing-newline = True + > EOF + $ printf "third\nfourth" > a.txt + $ hg commit -m 'checking in with newline fix' + $ rm a.txt + $ hg update -C -q + $ cat a.txt + third + fourth + +append a line without trailing newline + + $ printf "fifth" >> a.txt + $ hg commit -m 'adding another line line' + $ rm a.txt + $ hg update -C -q + $ cat a.txt + third + fourth + fifth +
--- a/tests/test-export.t Wed Sep 14 13:51:50 2011 +0200 +++ b/tests/test-export.t Wed Sep 14 13:57:56 2011 +0200 @@ -7,7 +7,7 @@ > hg ci -m "foo-$i" > done - $ for out in "%nof%N" "%%%H" "%b-%R" "%h" "%r"; do + $ for out in "%nof%N" "%%%H" "%b-%R" "%h" "%r" "%m"; do > echo > echo "# foo-$out.patch" > hg export -v -o "foo-$out.patch" 2:tip @@ -77,6 +77,19 @@ foo-09.patch foo-10.patch foo-11.patch + + # foo-%m.patch + exporting patches: + foo-foo_2.patch + foo-foo_3.patch + foo-foo_4.patch + foo-foo_5.patch + foo-foo_6.patch + foo-foo_7.patch + foo-foo_8.patch + foo-foo_9.patch + foo-foo_10.patch + foo-foo_11.patch Exporting 4 changesets to a file: @@ -108,3 +121,11 @@ foo-9 +foo-10 +Checking if only alphanumeric characters are used in the file name (%m option): + + $ echo "line" >> foo + $ hg commit -m " !\"#$%&(,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_\`abcdefghijklmnopqrstuvwxyz{|}~" + $ hg export -v -o %m.patch tip + exporting patch: + ____________0123456789_______ABCDEFGHIJKLMNOPQRSTUVWXYZ______abcdefghijklmnopqrstuvwxyz____.patch +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-filecache.py Wed Sep 14 13:57:56 2011 +0200 @@ -0,0 +1,94 @@ +import sys, os, subprocess + +if subprocess.call(['%s/hghave' % os.environ['TESTDIR'], 'cacheable']): + sys.exit(80) + +from mercurial import util, scmutil, extensions + +filecache = scmutil.filecache + +class fakerepo(object): + def __init__(self): + self._filecache = {} + + def join(self, p): + return p + + def sjoin(self, p): + return p + + @filecache('x') + def cached(self): + print 'creating' + + def invalidate(self): + for k in self._filecache: + try: + delattr(self, k) + except AttributeError: + pass + +def basic(repo): + # file doesn't exist, calls function + repo.cached + + repo.invalidate() + # file still doesn't exist, uses cache + repo.cached + + # create empty file + f = open('x', 'w') + f.close() + repo.invalidate() + # should recreate the object + repo.cached + + f = open('x', 'w') + f.write('a') + f.close() + repo.invalidate() + # should recreate the object + repo.cached + + repo.invalidate() + # stats file again, nothing changed, reuses object + repo.cached + + # atomic replace file, size doesn't change + # hopefully st_mtime doesn't change as well so this doesn't use the cache + # because of inode change + f = scmutil.opener('.')('x', 'w', atomictemp=True) + f.write('b') + f.close() + + repo.invalidate() + repo.cached + +def fakeuncacheable(): + def wrapcacheable(orig, *args, **kwargs): + return False + + def wrapinit(orig, *args, **kwargs): + pass + + originit = extensions.wrapfunction(util.cachestat, '__init__', wrapinit) + origcacheable = extensions.wrapfunction(util.cachestat, 'cacheable', + wrapcacheable) + + try: + os.remove('x') + except: + pass + + basic(fakerepo()) + + util.cachestat.cacheable = origcacheable + util.cachestat.__init__ = originit + +print 'basic:' +print +basic(fakerepo()) +print +print 'fakeuncacheable:' +print +fakeuncacheable()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-filecache.py.out Wed Sep 14 13:57:56 2011 +0200 @@ -0,0 +1,15 @@ +basic: + +creating +creating +creating +creating + +fakeuncacheable: + +creating +creating +creating +creating +creating +creating
--- a/tests/test-help.t Wed Sep 14 13:51:50 2011 +0200 +++ b/tests/test-help.t Wed Sep 14 13:57:56 2011 +0200 @@ -199,12 +199,7 @@ Test short command list with verbose option $ hg -v help shortlist - Mercurial Distributed SCM (version *) (glob) - (see http://mercurial.selenic.com for more information) - - Copyright (C) 2005-2011 Matt Mackall and others - This is free software; see the source for copying conditions. There is NO - warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + Mercurial Distributed SCM basic commands: @@ -359,32 +354,6 @@ Copyright (C) 2005-2011 Matt Mackall and others This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - - hg add [OPTION]... [FILE]... - - add the specified files on the next commit - - Schedule files to be version controlled and added to the repository. - - The files will be added to the repository at the next commit. To undo an - add before that, see "hg forget". - - If no names are given, add all files to the repository. - - Returns 0 if all files are successfully added. - - use "hg -v help add" to show verbose help - - options: - - -I --include PATTERN [+] include names matching the given patterns - -X --exclude PATTERN [+] exclude names matching the given patterns - -S --subrepos recurse into subrepositories - -n --dry-run do not perform actions, just print output - - [+] marked option can be specified multiple times - - use "hg -v help add" to show global options $ hg add --skjdfks hg add: option --skjdfks not recognized
--- a/tests/test-init.t Wed Sep 14 13:51:50 2011 +0200 +++ b/tests/test-init.t Wed Sep 14 13:57:56 2011 +0200 @@ -19,8 +19,8 @@ store created 00changelog.i created revlogv1 + fncache store - fncache dotencode $ echo this > local/foo $ hg ci --cwd local -A -m "init" @@ -48,8 +48,8 @@ store created 00changelog.i created revlogv1 + fncache store - fncache test failure @@ -145,8 +145,8 @@ store created 00changelog.i created revlogv1 + fncache store - fncache dotencode prepare test of init of url configured from paths @@ -162,8 +162,8 @@ store created 00changelog.i created revlogv1 + fncache store - fncache dotencode verify that clone also expand urls @@ -175,8 +175,8 @@ store created 00changelog.i created revlogv1 + fncache store - fncache dotencode clone bookmarks
--- a/tests/test-minirst.py Wed Sep 14 13:51:50 2011 +0200 +++ b/tests/test-minirst.py Wed Sep 14 13:57:56 2011 +0200 @@ -231,3 +231,14 @@ """ debugformat('comments', comments, 30) + + +data = [['a', 'b', 'c'], + ['1', '2', '3'], + ['foo', 'bar', 'baz this list is very very very long man']] + +table = minirst.maketable(data, 2, True) + +print table + +debugformat('table', table, 30)
--- a/tests/test-minirst.py.out Wed Sep 14 13:51:50 2011 +0200 +++ b/tests/test-minirst.py.out Wed Sep 14 13:57:56 2011 +0200 @@ -388,3 +388,21 @@ Empty comment above ---------------------------------------------------------------------- + === === ======================================== + a b c + === === ======================================== + 1 2 3 + foo bar baz this list is very very very long man + === === ======================================== + +table formatted to fit within 30 characters: +---------------------------------------------------------------------- + a b c + ------------------------------ + 1 2 3 + foo bar baz this list is + very very very long + man + +---------------------------------------------------------------------- +
--- a/tests/test-notify-changegroup.t Wed Sep 14 13:51:50 2011 +0200 +++ b/tests/test-notify-changegroup.t Wed Sep 14 13:57:56 2011 +0200 @@ -72,4 +72,57 @@ @@ -0,0 +1,2 @@ +a +a + $ hg --cwd a rollback + repository tip rolled back to revision -1 (undo push) + working directory now based on revision -1 +unbundle with unrelated source + + $ hg --cwd b bundle ../test.hg ../a + searching for changes + 2 changesets found + $ hg --cwd a unbundle ../test.hg + adding changesets + adding manifests + adding file changes + added 2 changesets with 2 changes to 1 files + (run 'hg update' to get a working copy) + $ hg --cwd a rollback + repository tip rolled back to revision -1 (undo unbundle) + working directory now based on revision -1 + +unbundle with correct source + + $ hg --config notify.sources=unbundle --cwd a unbundle ../test.hg 2>&1 | + > python -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),' + adding changesets + adding manifests + adding file changes + added 2 changesets with 2 changes to 1 files + Content-Type: text/plain; charset="us-ascii" + MIME-Version: 1.0 + Content-Transfer-Encoding: 7bit + Date: * (glob) + Subject: * (glob) + From: test + X-Hg-Notification: changeset cb9a9f314b8b + Message-Id: <*> (glob) + To: baz, foo@bar + + changeset cb9a9f314b8b in $TESTTMP/a + details: $TESTTMP/a?cmd=changeset;node=cb9a9f314b8b + summary: a + + changeset ba677d0156c1 in $TESTTMP/a + details: $TESTTMP/a?cmd=changeset;node=ba677d0156c1 + summary: b + + diffs (6 lines): + + diff -r 000000000000 -r ba677d0156c1 a + --- /dev/null Thu Jan 01 00:00:00 1970 +0000 + +++ b/a Thu Jan 01 00:00:00 1970 +0000 + @@ -0,0 +1,2 @@ + +a + +a + (run 'hg update' to get a working copy)
--- a/tests/test-notify.t Wed Sep 14 13:51:50 2011 +0200 +++ b/tests/test-notify.t Wed Sep 14 13:57:56 2011 +0200 @@ -17,67 +17,111 @@ > * = baz > EOF $ hg help notify - notify extension - hooks for sending email notifications at commit/push time - - Subscriptions can be managed through a hgrc file. Default mode is to print - messages to stdout, for testing and configuring. + notify extension - hooks for sending email push notifications - To use, configure the notify extension and enable it in hgrc like this: + This extension let you run hooks sending email notifications when changesets + are being pushed, from the sending or receiving side. - [extensions] - notify = + First, enable the extension as explained in "hg help extensions", and register + the hook you want to run. "incoming" and "outgoing" hooks are run by the + changesets receiver while the "outgoing" one is for the sender: [hooks] # one email for each incoming changeset incoming.notify = python:hgext.notify.hook - # batch emails when many changesets incoming at one time + # one email for all incoming changesets changegroup.notify = python:hgext.notify.hook - # batch emails when many changesets outgoing at one time (client side) + + # one email for all outgoing changesets outgoing.notify = python:hgext.notify.hook - [notify] - # config items go here - - Required configuration items: - - config = /path/to/file # file containing subscriptions - - Optional configuration items: - - test = True # print messages to stdout for testing - strip = 3 # number of slashes to strip for url paths - domain = example.com # domain to use if committer missing domain - style = ... # style file to use when formatting email - template = ... # template to use when formatting email - incoming = ... # template to use when run as incoming hook - outgoing = ... # template to use when run as outgoing hook - changegroup = ... # template to use when run as changegroup hook - maxdiff = 300 # max lines of diffs to include (0=none, -1=all) - maxsubject = 67 # truncate subject line longer than this - diffstat = True # add a diffstat before the diff content - sources = serve # notify if source of incoming changes in this list - # (serve == ssh or http, push, pull, bundle) - merge = False # send notification for merges (default True) - [email] - from = user@host.com # email address to send as if none given - [web] - baseurl = http://hgserver/... # root of hg web site for browsing commits - - The notify config file has same format as a regular hgrc file. It has two - sections so you can express subscriptions in whatever way is handier for you. + Now the hooks are running, subscribers must be assigned to repositories. Use + the "[usersubs]" section to map repositories to a given email or the + "[reposubs]" section to map emails to a single repository: [usersubs] - # key is subscriber email, value is ","-separated list of glob patterns + # key is subscriber email, value is a comma-separated list of glob + # patterns user@host = pattern [reposubs] - # key is glob pattern, value is ","-separated list of subscriber emails + # key is glob pattern, value is a comma-separated list of subscriber + # emails pattern = user@host - Glob patterns are matched against path to repository root. + Glob patterns are matched against absolute path to repository root. The + subscriptions can be defined in their own file and referenced with: + + [notify] + config = /path/to/subscriptionsfile + + Alternatively, they can be added to Mercurial configuration files by setting + the previous entry to an empty value. + + At this point, notifications should be generated but will not be sent until + you set the "notify.test" entry to "False". + + Notifications content can be tweaked with the following configuration entries: + + notify.test + If "True", print messages to stdout instead of sending them. Default: True. + + notify.sources + Space separated list of change sources. Notifications are sent only if it + includes the incoming or outgoing changes source. Incoming sources can be + "serve" for changes coming from http or ssh, "pull" for pulled changes, + "unbundle" for changes added by "hg unbundle" or "push" for changes being + pushed locally. Outgoing sources are the same except for "unbundle" which is + replaced by "bundle". Default: serve. + + notify.strip + Number of leading slashes to strip from url paths. By default, notifications + references repositories with their absolute path. "notify.strip" let you + turn them into relative paths. For example, "notify.strip=3" will change + "/long/path/repository" into "repository". Default: 0. + + notify.domain + If subscribers emails or the from email have no domain set, complete them + with this value. - If you like, you can put notify config file in repository that users can push - changes to, they can manage their own subscriptions. + notify.style + Style file to use when formatting emails. + + notify.template + Template to use when formatting emails. + + notify.incoming + Template to use when run as incoming hook, override "notify.template". + + notify.outgoing + Template to use when run as outgoing hook, override "notify.template". + + notify.changegroup + Template to use when running as changegroup hook, override + "notify.template". + + notify.maxdiff + Maximum number of diff lines to include in notification email. Set to 0 to + disable the diff, -1 to include all of it. Default: 300. + + notify.maxsubject + Maximum number of characters in emails subject line. Default: 67. + + notify.diffstat + Set to True to include a diffstat before diff content. Default: True. + + notify.merge + If True, send notifications for merge changesets. Default: True. + + If set, the following entries will also be used to customize the + notifications: + + email.from + Email "From" address to use if none can be found in generated email content. + + web.baseurl + Root repository browsing URL to combine with repository paths when making + references. See also "notify.strip". no commands defined $ hg init a
--- a/tests/test-progress.t Wed Sep 14 13:51:50 2011 +0200 +++ b/tests/test-progress.t Wed Sep 14 13:57:56 2011 +0200 @@ -9,16 +9,28 @@ > total = loops > if opts.get('total', None): > total = int(opts.get('total')) + > nested = False + > if opts.get('nested', None): + > nested = True > loops = abs(loops) > > for i in range(loops): > ui.progress('loop', i, 'loop.%d' % i, 'loopnum', total) + > if opts.get('parallel'): + > ui.progress('other', i, 'other.%d' % i, 'othernum', total) + > if nested: + > for j in range(2): + > ui.progress('nested', j, 'nested.%d' % j, 'nestnum', 2) + > ui.progress('nested', None, 'nested.done', 'nestnum', 2) > ui.progress('loop', None, 'loop.done', 'loopnum', total) > > commands.norepo += " loop" > > cmdtable = { - > "loop": (loop, [('', 'total', '', 'override for total')], + > "loop": (loop, [('', 'total', '', 'override for total'), + > ('', 'nested', False, 'show nested results'), + > ('', 'parallel', False, 'show parallel sets of results'), + > ], > 'hg loop LOOPS'), > } > EOF @@ -47,6 +59,42 @@ loop [===============================> ] 2/3 \r (esc) + +test nested short-lived topics (which shouldn't display with nestdelay): + + $ hg -y loop 3 --nested 2>&1 | \ + > python $TESTDIR/filtercr.py + + loop [ ] 0/3 + loop [===============> ] 1/3 + loop [===============================> ] 2/3 + \r (esc) + + + $ hg --config progress.changedelay=0 -y loop 3 --nested 2>&1 | \ + > python $TESTDIR/filtercr.py + + loop [ ] 0/3 + nested [ ] 0/2 + nested [======================> ] 1/2 + loop [===============> ] 1/3 + nested [ ] 0/2 + nested [======================> ] 1/2 + loop [===============================> ] 2/3 + nested [ ] 0/2 + nested [======================> ] 1/2 + \r (esc) + + +test two topics being printed in parallel (as when we're doing a local +--pull clone, where you get the unbundle and bundle progress at the +same time): + $ hg loop 3 --parallel 2>&1 | python $TESTDIR/filtercr.py + + loop [ ] 0/3 + loop [===============> ] 1/3 + loop [===============================> ] 2/3 + \r (esc) test refresh is taken in account $ hg -y --config progress.refresh=100 loop 3 2>&1 | $TESTDIR/filtercr.py
--- a/tests/test-setdiscovery.t Wed Sep 14 13:51:50 2011 +0200 +++ b/tests/test-setdiscovery.t Wed Sep 14 13:57:56 2011 +0200 @@ -288,7 +288,7 @@ reading DAG from stdin $ hg heads -t --template . | wc -c - *261 (re) + \s*261 (re) $ hg clone -b a . a adding changesets
--- a/tests/test-share.t Wed Sep 14 13:51:50 2011 +0200 +++ b/tests/test-share.t Wed Sep 14 13:57:56 2011 +0200 @@ -97,3 +97,21 @@ -rw-r--r-- 2 b + +test unshare command + + $ hg unshare + $ test -d .hg/store + $ test -f .hg/sharedpath + [1] + $ hg unshare + abort: this is not a shared repo + [255] + +check that a change does not propagate + + $ echo b >> b + $ hg commit -m'change in unshared' + $ cd ../repo1 + $ hg id -r tip + c2e0ac586386 tip
--- a/tests/test-symlink-os-yes-fs-no.py Wed Sep 14 13:51:50 2011 +0200 +++ b/tests/test-symlink-os-yes-fs-no.py Wed Sep 14 13:57:56 2011 +0200 @@ -5,7 +5,7 @@ BUNDLEPATH = os.path.join(TESTDIR, 'bundles', 'test-no-symlinks.hg') # only makes sense to test on os which supports symlinks -if not hasattr(os, "symlink"): +if not getattr(os, "symlink", False): sys.exit(80) # SKIPPED_STATUS defined in run-tests.py # clone with symlink support
--- a/tests/test-walkrepo.py Wed Sep 14 13:51:50 2011 +0200 +++ b/tests/test-walkrepo.py Wed Sep 14 13:57:56 2011 +0200 @@ -5,7 +5,7 @@ from os.path import join as pjoin u = ui.ui() -sym = hasattr(os, 'symlink') and hasattr(os.path, 'samestat') +sym = getattr(os, 'symlink', False) and getattr(os.path, 'samestat', False) hg.repository(u, 'top1', create=1) mkdir('subdir')
--- a/tests/tinyproxy.py Wed Sep 14 13:51:50 2011 +0200 +++ b/tests/tinyproxy.py Wed Sep 14 13:57:56 2011 +0200 @@ -23,7 +23,8 @@ def handle(self): (ip, port) = self.client_address - if hasattr(self, 'allowed_clients') and ip not in self.allowed_clients: + allowed = getattr(self, 'allowed_clients', None) + if allowed is not None and ip not in allowed: self.raw_requestline = self.rfile.readline() if self.parse_request(): self.send_error(403)