merge with stable
authorMatt Mackall <mpm@selenic.com>
Fri, 26 Aug 2011 16:08:24 -0500
changeset 15061 86380f24e697
parent 15058 81f33be0ea79 (diff)
parent 15060 01cdfba22f0c (current diff)
child 15067 cc16323e748d
merge with stable
mercurial/subrepo.py
--- a/contrib/check-code.py	Fri Aug 26 16:07:16 2011 -0500
+++ b/contrib/check-code.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/contrib/setup3k.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/contrib/win32/hgwebdir_wsgi.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/doc/gendoc.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/hgext/color.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/hgext/convert/cvsps.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/hgext/convert/git.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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/transport.py	Fri Aug 26 16:07:16 2011 -0500
+++ b/hgext/convert/transport.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/hgext/eol.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/hgext/inotify/__init__.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/hgext/keyword.py	Fri Aug 26 16:08:24 2011 -0500
@@ -249,10 +249,15 @@
         kwcmd = self.restrict and lookup # kwexpand/kwshrink
         if self.restrict or expand and lookup:
             mf = ctx.manifest()
+        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')
         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'))
         for f in candidates:
             if self.restrict:
                 data = self.repo.file(f).read(mf[f])
@@ -293,7 +298,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):
     '''
@@ -322,11 +329,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'))
@@ -340,7 +347,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'))
@@ -412,7 +419,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)
@@ -475,13 +485,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	Fri Aug 26 16:07:16 2011 -0500
+++ b/hgext/mq.py	Fri Aug 26 16:08:24 2011 -0500
@@ -938,7 +938,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 +1492,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 +2915,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 +2930,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 +3024,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	Fri Aug 26 16:07:16 2011 -0500
+++ b/hgext/notify.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/hgext/pager.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/hgext/progress.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/hgext/relink.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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/mercurial/archival.py	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/archival.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/bookmarks.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/byterange.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/cmdutil.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/commands.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/commandserver.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/demandimport.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/dirstate.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/dispatch.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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/encoding.py	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/encoding.py	Fri Aug 26 16:08:24 2011 -0500
@@ -140,12 +140,12 @@
 def colwidth(s):
     "Find the column width of a UTF-8 string for display"
     d = s.decode(encoding, 'replace')
-    if hasattr(unicodedata, 'east_asian_width'):
+    eaw = getattr(unicodedata, 'east_asian_width', None)
+    if eaw is not None:
         wide = "WF"
         if ambiguous == "wide":
             wide = "WFA"
-        w = unicodedata.east_asian_width
-        return sum([w(c) in wide and 2 or 1 for c in d])
+        return sum([eaw(c) in wide and 2 or 1 for c in d])
     return len(d)
 
 def lower(s):
--- a/mercurial/error.py	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/error.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/extensions.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/fancyopts.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/hbisect.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/help.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/help/config.txt	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/hg.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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):
@@ -537,7 +537,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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/hgweb/hgweb_mod.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/hgweb/hgwebdir_mod.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/hgweb/protocol.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/hgweb/request.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/hgweb/server.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/hgweb/webutil.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/hgweb/wsgicgi.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/hook.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/httprepo.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/i18n.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/keepalive.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/localrepo.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/lsprof.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/mail.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/match.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/merge.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/minirst.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/osutil.c	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/patch.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/posix.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/pure/parsers.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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/revlog.py	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/revlog.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/revset.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/scmutil.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/simplemerge.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/sshrepo.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/sshserver.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/statichttprepo.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/store.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/subrepo.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/tags.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/templatefilters.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/templater.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/templates/monoblue/footer.tmpl	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/templates/monoblue/index.tmpl	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/templates/paper/bookmarks.tmpl	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/templates/paper/branches.tmpl	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/templates/paper/changeset.tmpl	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/templates/paper/error.tmpl	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/templates/paper/fileannotate.tmpl	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/templates/paper/filediff.tmpl	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/templates/paper/filelog.tmpl	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/templates/paper/filerevision.tmpl	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/templates/paper/graph.tmpl	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/templates/paper/help.tmpl	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/templates/paper/helptopics.tmpl	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/templates/paper/index.tmpl	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/templates/paper/manifest.tmpl	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/templates/paper/search.tmpl	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/templates/paper/shortlog.tmpl	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/templates/paper/tags.tmpl	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/templates/spartan/footer.tmpl	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/ui.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/url.py	Fri Aug 26 16:08:24 2011 -0500
@@ -133,7 +133,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
@@ -190,8 +190,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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/util.py	Fri Aug 26 16:08:24 2011 -0500
@@ -19,11 +19,67 @@
 import os, time, calendar, textwrap, unicodedata, 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():
@@ -390,18 +446,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 +531,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 +718,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 +745,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 +760,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 +774,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')
@@ -1148,16 +1171,14 @@
         def __init__(self, **kwargs):
             textwrap.TextWrapper.__init__(self, **kwargs)
 
-        def _cutdown(self, str, space_left):
+        def _cutdown(self, ucstr, space_left):
             l = 0
-            ucstr = unicode(str, encoding.encoding)
             colwidth = unicodedata.east_asian_width
             for i in xrange(len(ucstr)):
                 l += colwidth(ucstr[i]) in 'WFA' and 2 or 1
                 if space_left < l:
-                    return (ucstr[:i].encode(encoding.encoding),
-                            ucstr[i:].encode(encoding.encoding))
-            return str, ''
+                    return (ucstr[:i], ucstr[i:])
+            return ucstr, ''
 
         # overriding of base class
         def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
@@ -1179,10 +1200,13 @@
     if width <= maxindent:
         # adjust for weird terminal size
         width = max(78, maxindent + 1)
+    line = line.decode(encoding.encoding, encoding.encodingmode)
+    initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
+    hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
     wrapper = MBTextWrapper(width=width,
                             initial_indent=initindent,
                             subsequent_indent=hangindent)
-    return wrapper.fill(line)
+    return wrapper.fill(line).encode(encoding.encoding)
 
 def iterlines(iterator):
     for chunk in iterator:
@@ -1223,8 +1247,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():
@@ -1565,8 +1590,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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/windows.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/mercurial/wireproto.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/setup.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/tests/hghave	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/tests/run-tests.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/tests/sitecustomize.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/tests/test-alias.t	Fri Aug 26 16:08:24 2011 -0500
@@ -251,7 +251,7 @@
   $ hg --cwd .. count 'branch(default)'
   2
   $ hg echo --cwd ..
-  --cwd ..
+  
 
 
 repo specific shell aliases
--- a/tests/test-atomictempfile.py	Fri Aug 26 16:07:16 2011 -0500
+++ b/tests/test-atomictempfile.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/tests/test-bisect2.t	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/tests/test-bookmarks.t	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/tests/test-check-pyflakes.t	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/tests/test-commandserver.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/tests/test-commandserver.py.out	Fri Aug 26 16:08:24 2011 -0500
@@ -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-svn-move.t	Fri Aug 26 16:07:16 2011 -0500
+++ b/tests/test-convert-svn-move.t	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/tests/test-debugcomplete.t	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/tests/test-doctest.py	Fri Aug 26 16:08:24 2011 -0500
@@ -33,3 +33,6 @@
 
 import hgext.convert.cvsps
 doctest.testmod(hgext.convert.cvsps)
+
+import mercurial.revset
+doctest.testmod(mercurial.revset)
--- a/tests/test-encoding-align.t	Fri Aug 26 16:07:16 2011 -0500
+++ b/tests/test-encoding-align.t	Fri Aug 26 16:08:24 2011 -0500
@@ -22,14 +22,14 @@
   > cmdtable = {
   >     'showoptlist':
   >         (showoptlist,
-  >          [('s', 'opt1', '', 'short width',  '""" + s + """'),
-  >           ('m', 'opt2', '', 'middle width', '""" + m + """'),
-  >           ('l', 'opt3', '', 'long width',   '""" + l + """')
+  >          [('s', 'opt1', '', 'short width'  + ' %(s)s' * 8, '%(s)s'),
+  >           ('m', 'opt2', '', 'middle width' + ' %(m)s' * 8, '%(m)s'),
+  >           ('l', 'opt3', '', 'long width'   + ' %(l)s' * 8, '%(l)s')
   >          ],
   >          ""
   >         )
   > }
-  > """)
+  > """ % globals())
   > f.close()
   > EOF
   $ S=`cat s`
@@ -52,9 +52,11 @@
   
   options:
   
-   -s --opt1 \xe7\x9f\xad\xe5\x90\x8d          short width (esc)
-   -m --opt2 MIDDLE_       middle width
-   -l --opt3 \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d  long width (esc)
+   -s --opt1 \xe7\x9f\xad\xe5\x90\x8d          short width \xe7\x9f\xad\xe5\x90\x8d \xe7\x9f\xad\xe5\x90\x8d \xe7\x9f\xad\xe5\x90\x8d \xe7\x9f\xad\xe5\x90\x8d \xe7\x9f\xad\xe5\x90\x8d \xe7\x9f\xad\xe5\x90\x8d \xe7\x9f\xad\xe5\x90\x8d \xe7\x9f\xad\xe5\x90\x8d (esc)
+   -m --opt2 MIDDLE_       middle width MIDDLE_ MIDDLE_ MIDDLE_ MIDDLE_ MIDDLE_
+                           MIDDLE_ MIDDLE_ MIDDLE_
+   -l --opt3 \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d  long width \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d (esc)
+                           \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d (esc)
   
   use "hg -v help showoptlist" to show global options
 
--- a/tests/test-eol.t	Fri Aug 26 16:07:16 2011 -0500
+++ b/tests/test-eol.t	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/tests/test-export.t	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:08:24 2011 -0500
@@ -0,0 +1,15 @@
+basic:
+
+creating
+creating
+creating
+creating
+
+fakeuncacheable:
+
+creating
+creating
+creating
+creating
+creating
+creating
--- a/tests/test-help.t	Fri Aug 26 16:07:16 2011 -0500
+++ b/tests/test-help.t	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/tests/test-init.t	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/tests/test-minirst.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/tests/test-minirst.py.out	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/tests/test-notify-changegroup.t	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/tests/test-notify.t	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/tests/test-progress.t	Fri Aug 26 16:08:24 2011 -0500
@@ -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-symlink-os-yes-fs-no.py	Fri Aug 26 16:07:16 2011 -0500
+++ b/tests/test-symlink-os-yes-fs-no.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/tests/test-walkrepo.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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	Fri Aug 26 16:07:16 2011 -0500
+++ b/tests/tinyproxy.py	Fri Aug 26 16:08:24 2011 -0500
@@ -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)