--- 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