merge with stable
authorAugie Fackler <augie@google.com>
Thu, 04 May 2017 00:26:55 -0400
changeset 32190 4fadea09feca
parent 32189 f238a483a1fd (diff)
parent 32114 40785ccab410 (current diff)
child 32191 d90ffee93df6
merge with stable
--- a/mercurial/commands.py	Wed May 03 22:56:53 2017 -0400
+++ b/mercurial/commands.py	Thu May 04 00:26:55 2017 -0400
@@ -452,7 +452,7 @@
                                      whitespace=True)
     for abs in ctx.walk(m):
         fctx = ctx[abs]
-        if not opts.get('text') and util.binary(fctx.data()):
+        if not opts.get('text') and fctx.isbinary():
             fm.plain(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
             continue
 
@@ -837,8 +837,6 @@
     elif extra or good + bad + skip + reset + extend + bool(command) > 1:
         raise error.Abort(_('incompatible arguments'))
 
-    cmdutil.checkunfinished(repo)
-
     if reset:
         hbisect.resetstate(repo)
         return
@@ -865,6 +863,7 @@
         """common used update sequence"""
         if noupdate:
             return
+        cmdutil.checkunfinished(repo)
         cmdutil.bailifchanged(repo)
         return hg.clean(repo, node, show_stats=show_stats)
 
--- a/mercurial/dispatch.py	Wed May 03 22:56:53 2017 -0400
+++ b/mercurial/dispatch.py	Thu May 04 00:26:55 2017 -0400
@@ -162,9 +162,13 @@
     ret = None
     try:
         ret = _runcatch(req)
-    except KeyboardInterrupt:
+    except KeyboardInterrupt as inst:
         try:
-            req.ui.warn(_("interrupted!\n"))
+            if isinstance(inst, error.SignalInterrupt):
+                msg = _("killed!\n")
+            else:
+                msg = _("interrupted!\n")
+            req.ui.warn(msg)
         except error.SignalInterrupt:
             # maybe pager would quit without consuming all the output, and
             # SIGPIPE was raised. we cannot print anything in this case.
@@ -179,7 +183,7 @@
         if req.ui.logblockedtimes:
             req.ui._blockedtimes['command_duration'] = duration * 1000
             req.ui.log('uiblocked', 'ui blocked ms', **req.ui._blockedtimes)
-        req.ui.log("commandfinish", "%s exited %s after %0.2f seconds\n",
+        req.ui.log("commandfinish", "%s exited %d after %0.2f seconds\n",
                    msg, ret or 0, duration)
         try:
             req._runexithandlers()
--- a/mercurial/filelog.py	Wed May 03 22:56:53 2017 -0400
+++ b/mercurial/filelog.py	Thu May 04 00:26:55 2017 -0400
@@ -18,7 +18,7 @@
 
 _mdre = re.compile('\1\n')
 def parsemeta(text):
-    """return (metadatadict, keylist, metadatasize)"""
+    """return (metadatadict, metadatasize)"""
     # text can be buffer, so we can't use .startswith or .index
     if text[:2] != '\1\n':
         return None, None
--- a/mercurial/fileset.py	Wed May 03 22:56:53 2017 -0400
+++ b/mercurial/fileset.py	Thu May 04 00:26:55 2017 -0400
@@ -256,7 +256,7 @@
     """
     # i18n: "binary" is a keyword
     getargs(x, 0, 0, _("binary takes no arguments"))
-    return [f for f in mctx.existing() if util.binary(mctx.ctx[f].data())]
+    return [f for f in mctx.existing() if mctx.ctx[f].isbinary()]
 
 @predicate('exec()', callexisting=True)
 def exec_(mctx, x):
--- a/mercurial/help/internals/wireprotocol.txt	Wed May 03 22:56:53 2017 -0400
+++ b/mercurial/help/internals/wireprotocol.txt	Thu May 04 00:26:55 2017 -0400
@@ -632,6 +632,9 @@
 branches
 --------
 
+(Legacy command used for discovery in old clients. Clients with ``getbundle``
+use the ``known`` and ``heads`` commands instead.)
+
 Obtain ancestor changesets of specific nodes back to a branch point.
 
 Despite the name, this command has nothing to do with Mercurial named branches.
--- a/mercurial/hgweb/webcommands.py	Wed May 03 22:56:53 2017 -0400
+++ b/mercurial/hgweb/webcommands.py	Thu May 04 00:26:55 2017 -0400
@@ -808,7 +808,7 @@
         context = parsecontext(web.config('web', 'comparisoncontext', '5'))
 
     def filelines(f):
-        if util.binary(f.data()):
+        if f.isbinary():
             mt = mimetypes.guess_type(f.path())[0]
             if not mt:
                 mt = 'application/octet-stream'
@@ -886,7 +886,7 @@
             yield p
 
     def annotate(**map):
-        if util.binary(fctx.data()):
+        if fctx.isbinary():
             mt = (mimetypes.guess_type(fctx.path())[0]
                   or 'application/octet-stream')
             lines = [((fctx.filectx(fctx.filerev()), 1), '(binary:%s)' % mt)]
--- a/mercurial/localrepo.py	Wed May 03 22:56:53 2017 -0400
+++ b/mercurial/localrepo.py	Thu May 04 00:26:55 2017 -0400
@@ -385,16 +385,6 @@
         # generic mapping between names and nodes
         self.names = namespaces.namespaces()
 
-    @property
-    def wopener(self):
-        self.ui.deprecwarn("use 'repo.wvfs' instead of 'repo.wopener'", '4.2')
-        return self.wvfs
-
-    @property
-    def opener(self):
-        self.ui.deprecwarn("use 'repo.vfs' instead of 'repo.opener'", '4.2')
-        return self.vfs
-
     def close(self):
         self._writecaches()
 
@@ -649,11 +639,6 @@
         """
         return hook.hook(self.ui, self, name, throw, **args)
 
-    def tag(self, names, node, message, local, user, date, editor=False):
-        self.ui.deprecwarn("use 'tagsmod.tag' instead of 'repo.tag'", '4.2')
-        tagsmod.tag(self, names, node, message, local, user, date,
-                    editor=editor)
-
     @filteredpropertycache
     def _tagscache(self):
         '''Returns a tagscache object that contains various tags related
@@ -841,10 +826,6 @@
             return 'store'
         return None
 
-    def join(self, f, *insidef):
-        self.ui.deprecwarn("use 'repo.vfs.join' instead of 'repo.join'", '4.2')
-        return self.vfs.join(os.path.join(f, *insidef))
-
     def wjoin(self, f, *insidef):
         return self.vfs.reljoin(self.root, f, *insidef)
 
@@ -884,15 +865,6 @@
     def pathto(self, f, cwd=None):
         return self.dirstate.pathto(f, cwd)
 
-    def wfile(self, f, mode='r'):
-        self.ui.deprecwarn("use 'repo.wvfs' instead of 'repo.wfile'", '4.2')
-        return self.wvfs(f, mode)
-
-    def _link(self, f):
-        self.ui.deprecwarn("use 'repo.wvfs.islink' instead of 'repo._link'",
-                           '4.2')
-        return self.wvfs.islink(f)
-
     def _loadfilter(self, filter):
         if filter not in self.filterpats:
             l = []
--- a/mercurial/match.py	Wed May 03 22:56:53 2017 -0400
+++ b/mercurial/match.py	Thu May 04 00:26:55 2017 -0400
@@ -52,7 +52,7 @@
     return fset, other
 
 def _expandsubinclude(kindpats, root):
-    '''Returns the list of subinclude matchers and the kindpats without the
+    '''Returns the list of subinclude matcher args and the kindpats without the
     subincludes in it.'''
     relmatchers = []
     other = []
@@ -64,12 +64,12 @@
             path = pathutil.join(sourceroot, pat)
 
             newroot = pathutil.dirname(path)
-            relmatcher = match(newroot, '', [], ['include:%s' % path])
+            matcherargs = (newroot, '', [], ['include:%s' % path])
 
             prefix = pathutil.canonpath(root, root, newroot)
             if prefix:
                 prefix += '/'
-            relmatchers.append((prefix, relmatcher))
+            relmatchers.append((prefix, matcherargs))
         else:
             other.append((kind, pat, source))
 
@@ -584,10 +584,17 @@
 
     subincludes, kindpats = _expandsubinclude(kindpats, root)
     if subincludes:
+        submatchers = {}
         def matchsubinclude(f):
-            for prefix, mf in subincludes:
-                if f.startswith(prefix) and mf(f[len(prefix):]):
-                    return True
+            for prefix, matcherargs in subincludes:
+                if f.startswith(prefix):
+                    mf = submatchers.get(prefix)
+                    if mf is None:
+                        mf = match(*matcherargs)
+                        submatchers[prefix] = mf
+
+                    if mf(f[len(prefix):]):
+                        return True
             return False
         matchfuncs.append(matchsubinclude)
 
--- a/mercurial/posix.py	Wed May 03 22:56:53 2017 -0400
+++ b/mercurial/posix.py	Thu May 04 00:26:55 2017 -0400
@@ -494,7 +494,7 @@
 
 def getuser():
     '''return name of current user'''
-    return getpass.getuser()
+    return pycompat.fsencode(getpass.getuser())
 
 def username(uid=None):
     """Return the name of the user with the given uid.
--- a/mercurial/scmutil.py	Wed May 03 22:56:53 2017 -0400
+++ b/mercurial/scmutil.py	Thu May 04 00:26:55 2017 -0400
@@ -26,7 +26,6 @@
     revsetlang,
     similar,
     util,
-    vfs as vfsmod,
 )
 
 if pycompat.osname == 'nt':
@@ -186,8 +185,6 @@
         ui.warn(_("abort: file censored %s!\n") % inst)
     except error.RevlogError as inst:
         ui.warn(_("abort: %s!\n") % inst)
-    except error.SignalInterrupt:
-        ui.warn(_("killed!\n"))
     except error.InterventionRequired as inst:
         ui.warn("%s\n" % inst)
         if inst.hint:
@@ -335,27 +332,6 @@
         key = s.digest()
     return key
 
-def _deprecated(old, new, func):
-    msg = ('class at mercurial.scmutil.%s moved to mercurial.vfs.%s'
-           % (old, new))
-    def wrapper(*args, **kwargs):
-        util.nouideprecwarn(msg, '4.2')
-        return func(*args, **kwargs)
-    return wrapper
-
-# compatibility layer since all 'vfs' code moved to 'mercurial.vfs'
-#
-# This is hard to instal deprecation warning to this since we do not have
-# access to a 'ui' object.
-opener = _deprecated('opener', 'vfs', vfsmod.vfs)
-vfs = _deprecated('vfs', 'vfs', vfsmod.vfs)
-filteropener = _deprecated('filteropener', 'filtervfs', vfsmod.filtervfs)
-filtervfs = _deprecated('filtervfs', 'filtervfs', vfsmod.filtervfs)
-abstractvfs = _deprecated('abstractvfs', 'abstractvfs', vfsmod.abstractvfs)
-readonlyvfs = _deprecated('readonlyvfs', 'readonlyvfs', vfsmod.readonlyvfs)
-auditvfs = _deprecated('auditvfs', 'auditvfs', vfsmod.auditvfs)
-checkambigatclosing = vfsmod.checkambigatclosing
-
 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
     '''yield every hg repository under path, always recursively.
     The recurse flag will only control recursion into repo working dirs'''
--- a/mercurial/templatefilters.py	Wed May 03 22:56:53 2017 -0400
+++ b/mercurial/templatefilters.py	Thu May 04 00:26:55 2017 -0400
@@ -16,6 +16,7 @@
     encoding,
     hbisect,
     node,
+    pycompat,
     registrar,
     templatekw,
     util,
@@ -24,6 +25,9 @@
 urlerr = util.urlerr
 urlreq = util.urlreq
 
+if pycompat.ispy3:
+    long = int
+
 # filters are callables like:
 #   fn(obj)
 # with:
@@ -226,8 +230,8 @@
     elif obj is True:
         return 'true'
     elif isinstance(obj, (int, long, float)):
-        return str(obj)
-    elif isinstance(obj, str):
+        return pycompat.bytestr(obj)
+    elif isinstance(obj, bytes):
         return '"%s"' % encoding.jsonescape(obj, paranoid=paranoid)
     elif util.safehasattr(obj, 'keys'):
         out = ['%s: %s' % (json(k), json(v))
@@ -351,11 +355,11 @@
     text and concatenating them.
     """
     thing = templatekw.unwraphybrid(thing)
-    if util.safehasattr(thing, '__iter__') and not isinstance(thing, str):
+    if util.safehasattr(thing, '__iter__') and not isinstance(thing, bytes):
         return "".join([stringify(t) for t in thing if t is not None])
     if thing is None:
         return ""
-    return str(thing)
+    return pycompat.bytestr(thing)
 
 @templatefilter('stripdir')
 def stripdir(text):
--- a/mercurial/util.py	Wed May 03 22:56:53 2017 -0400
+++ b/mercurial/util.py	Thu May 04 00:26:55 2017 -0400
@@ -1724,8 +1724,7 @@
     iterator over chunks of arbitrary size."""
 
     def __init__(self, in_iter):
-        """in_iter is the iterator that's iterating over the input chunks.
-        targetsize is how big a buffer to try to maintain."""
+        """in_iter is the iterator that's iterating over the input chunks."""
         def splitbig(chunks):
             for chunk in chunks:
                 if len(chunk) > 2**20:
--- a/mercurial/worker.py	Wed May 03 22:56:53 2017 -0400
+++ b/mercurial/worker.py	Thu May 04 00:26:55 2017 -0400
@@ -134,37 +134,43 @@
             killworkers()
     oldchldhandler = signal.signal(signal.SIGCHLD, sigchldhandler)
     ui.flush()
+    parentpid = os.getpid()
     for pargs in partition(args, workers):
-        pid = os.fork()
-        if pid == 0:
-            signal.signal(signal.SIGINT, oldhandler)
-            signal.signal(signal.SIGCHLD, oldchldhandler)
-
-            def workerfunc():
-                os.close(rfd)
-                for i, item in func(*(staticargs + (pargs,))):
-                    os.write(wfd, '%d %s\n' % (i, item))
-                return 0
+        # make sure we use os._exit in all worker code paths. otherwise the
+        # worker may do some clean-ups which could cause surprises like
+        # deadlock. see sshpeer.cleanup for example.
+        # override error handling *before* fork. this is necessary because
+        # exception (signal) may arrive after fork, before "pid =" assignment
+        # completes, and other exception handler (dispatch.py) can lead to
+        # unexpected code path without os._exit.
+        ret = -1
+        try:
+            pid = os.fork()
+            if pid == 0:
+                signal.signal(signal.SIGINT, oldhandler)
+                signal.signal(signal.SIGCHLD, oldchldhandler)
 
-            # make sure we use os._exit in all code paths. otherwise the worker
-            # may do some clean-ups which could cause surprises like deadlock.
-            # see sshpeer.cleanup for example.
-            ret = 0
-            try:
+                def workerfunc():
+                    os.close(rfd)
+                    for i, item in func(*(staticargs + (pargs,))):
+                        os.write(wfd, '%d %s\n' % (i, item))
+                    return 0
+
+                ret = scmutil.callcatch(ui, workerfunc)
+        except: # parent re-raises, child never returns
+            if os.getpid() == parentpid:
+                raise
+            exctype = sys.exc_info()[0]
+            force = not issubclass(exctype, KeyboardInterrupt)
+            ui.traceback(force=force)
+        finally:
+            if os.getpid() != parentpid:
                 try:
-                    ret = scmutil.callcatch(ui, workerfunc)
-                finally:
                     ui.flush()
-            except KeyboardInterrupt:
-                os._exit(255)
-            except: # never return, therefore no re-raises
-                try:
-                    ui.traceback(force=True)
-                    ui.flush()
+                except: # never returns, no re-raises
+                    pass
                 finally:
-                    os._exit(255)
-            else:
-                os._exit(ret & 255)
+                    os._exit(ret & 255)
         pids.add(pid)
     os.close(wfd)
     fp = os.fdopen(rfd, pycompat.sysstr('rb'), 0)
--- a/setup.py	Wed May 03 22:56:53 2017 -0400
+++ b/setup.py	Thu May 04 00:26:55 2017 -0400
@@ -5,7 +5,7 @@
 # 'python setup.py --help' for more options
 
 import sys, platform
-if getattr(sys, 'version_info', (0, 0, 0)) < (2, 6, 0, 'final'):
+if sys.version_info < (2, 6, 0, 'final'):
     raise SystemExit("Mercurial requires Python 2.6 or later.")
 
 if sys.version_info[0] >= 3:
--- a/tests/test-bisect.t	Wed May 03 22:56:53 2017 -0400
+++ b/tests/test-bisect.t	Thu May 04 00:26:55 2017 -0400
@@ -551,7 +551,14 @@
   date:        Thu Jan 01 00:00:06 1970 +0000
   summary:     msg 6
   
-
+  $ hg graft -q 15
+  warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
+  abort: unresolved conflicts, can't continue
+  (use 'hg resolve' and 'hg graft --continue')
+  [255]
+  $ hg bisect --reset
+  $ hg up -C .
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 Check that bisect does not break on obsolete changesets
 =========================================================
--- a/tests/test-worker.t	Wed May 03 22:56:53 2017 -0400
+++ b/tests/test-worker.t	Thu May 04 00:26:55 2017 -0400
@@ -2,6 +2,7 @@
 
   $ cat > t.py <<EOF
   > from __future__ import absolute_import, print_function
+  > import time
   > from mercurial import (
   >     cmdutil,
   >     error,
@@ -22,6 +23,7 @@
   >     for arg in args:
   >         ui.status('run\n')
   >         yield 1, arg
+  >     time.sleep(0.1) # easier to trigger killworkers code path
   > functable = {
   >     'abort': abort,
   >     'exc': exc,
@@ -74,21 +76,53 @@
 
 Known exception should be caught, but printed if --traceback is enabled
 
-  $ hg --config "extensions.t=$abspath" --config 'worker.numcpus=2' \
-  > test 100000.0 abort
+  $ hg --config "extensions.t=$abspath" --config 'worker.numcpus=8' \
+  > test 100000.0 abort 2>&1
   start
   abort: known exception
   [255]
 
-  $ hg --config "extensions.t=$abspath" --config 'worker.numcpus=2' \
-  > test 100000.0 abort --traceback 2>&1 | grep '^Traceback'
-  Traceback (most recent call last):
-  Traceback (most recent call last):
+  $ hg --config "extensions.t=$abspath" --config 'worker.numcpus=8' \
+  > test 100000.0 abort --traceback 2>&1 | egrep '^(SystemExit|Abort)'
+  Abort: known exception
+  SystemExit: 255
 
 Traceback must be printed for unknown exceptions
 
-  $ hg --config "extensions.t=$abspath" --config 'worker.numcpus=2' \
-  > test 100000.0 exc 2>&1 | grep '^Traceback'
-  Traceback (most recent call last):
+  $ hg --config "extensions.t=$abspath" --config 'worker.numcpus=8' \
+  > test 100000.0 exc 2>&1 | grep '^Exception'
+  Exception: unknown exception
+
+Workers should not do cleanups in all cases
+
+  $ cat > $TESTTMP/detectcleanup.py <<EOF
+  > from __future__ import absolute_import
+  > import atexit
+  > import os
+  > import time
+  > oldfork = os.fork
+  > count = 0
+  > parentpid = os.getpid()
+  > def delayedfork():
+  >     global count
+  >     count += 1
+  >     pid = oldfork()
+  >     # make it easier to test SIGTERM hitting other workers when they have
+  >     # not set up error handling yet.
+  >     if count > 1 and pid == 0:
+  >         time.sleep(0.1)
+  >     return pid
+  > os.fork = delayedfork
+  > def cleanup():
+  >     if os.getpid() != parentpid:
+  >         os.write(1, 'should never happen\n')
+  > atexit.register(cleanup)
+  > EOF
+
+  $ hg --config "extensions.t=$abspath" --config worker.numcpus=8 --config \
+  > "extensions.d=$TESTTMP/detectcleanup.py" test 100000 abort
+  start
+  abort: known exception
+  [255]
 
 #endif