--- a/.hgsigs Thu Nov 16 03:52:42 2017 +0100
+++ b/.hgsigs Fri Dec 01 15:21:05 2017 -0600
@@ -155,3 +155,4 @@
1e2454b60e5936f5e77498cab2648db469504487 0 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
--- a/.hgtags Thu Nov 16 03:52:42 2017 +0100
+++ b/.hgtags Fri Dec 01 15:21:05 2017 -0600
@@ -168,3 +168,4 @@
1e2454b60e5936f5e77498cab2648db469504487 4.4-rc
0ccb43d4cf01d013ae05917ec4f305509f851b2d 4.4
cabc840ffdee8a72f3689fb77dd74d04fdc2bc04 4.4.1
+a92b9f8e11ba330614cdfd6af0e03b15c1ff3797 4.4.2
--- a/hgext/largefiles/lfcommands.py Thu Nov 16 03:52:42 2017 +0100
+++ b/hgext/largefiles/lfcommands.py Fri Dec 01 15:21:05 2017 -0600
@@ -455,6 +455,7 @@
lfiles = [f for f in lfiles if f in filelist]
update = {}
+ dropped = set()
updated, removed = 0, 0
wvfs = repo.wvfs
wctx = repo[None]
@@ -476,7 +477,11 @@
expecthash = lfutil.readasstandin(wctx[relstandin])
if expecthash != '':
if lfile not in wctx: # not switched to normal file
- wvfs.unlinkpath(rellfile, ignoremissing=True)
+ if repo.dirstate[relstandin] != '?':
+ wvfs.unlinkpath(rellfile, ignoremissing=True)
+ else:
+ dropped.add(rellfile)
+
# use normallookup() to allocate an entry in largefiles
# dirstate to prevent lfilesrepo.status() from reporting
# missing files as removed.
@@ -496,6 +501,15 @@
lfdirstate.write()
if lfiles:
+ lfiles = [f for f in lfiles if f not in dropped]
+
+ for f in dropped:
+ repo.wvfs.unlinkpath(lfutil.standin(f))
+
+ # This needs to happen for dropped files, otherwise they stay in
+ # the M state.
+ lfutil.synclfdirstate(repo, lfdirstate, f, normallookup)
+
statuswriter(_('getting changed largefiles\n'))
cachelfiles(ui, repo, None, lfiles)
--- a/mercurial/chgserver.py Thu Nov 16 03:52:42 2017 +0100
+++ b/mercurial/chgserver.py Fri Dec 01 15:21:05 2017 -0600
@@ -220,8 +220,17 @@
newui._csystem = srcui._csystem
# command line args
- args = args[:]
- dispatch._parseconfig(newui, dispatch._earlygetopt(['--config'], args))
+ options = {}
+ if srcui.plain('strictflags'):
+ options.update(dispatch._earlyparseopts(args))
+ else:
+ args = args[:]
+ options['config'] = dispatch._earlygetopt(['--config'], args)
+ cwds = dispatch._earlygetopt(['--cwd'], args)
+ options['cwd'] = cwds and cwds[-1] or ''
+ rpath = dispatch._earlygetopt(["-R", "--repository", "--repo"], args)
+ options['repository'] = rpath and rpath[-1] or ''
+ dispatch._parseconfig(newui, options['config'])
# stolen from tortoisehg.util.copydynamicconfig()
for section, name, value in srcui.walkconfig():
@@ -232,10 +241,9 @@
newui.setconfig(section, name, value, source)
# load wd and repo config, copied from dispatch.py
- cwds = dispatch._earlygetopt(['--cwd'], args)
- cwd = cwds and os.path.realpath(cwds[-1]) or None
- rpath = dispatch._earlygetopt(["-R", "--repository", "--repo"], args)
- rpath = rpath and rpath[-1] or ''
+ cwd = options['cwd']
+ cwd = cwd and os.path.realpath(cwd) or None
+ rpath = options['repository']
path, newlui = dispatch._getlocal(newui, rpath, wd=cwd)
return (newui, newlui)
--- a/mercurial/dispatch.py Thu Nov 16 03:52:42 2017 +0100
+++ b/mercurial/dispatch.py Fri Dec 01 15:21:05 2017 -0600
@@ -150,6 +150,8 @@
try:
if not req.ui:
req.ui = uimod.ui.load()
+ if req.ui.plain('strictflags'):
+ req.earlyoptions.update(_earlyparseopts(req.args))
if _earlyreqoptbool(req, 'traceback', ['--traceback']):
req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
@@ -644,6 +646,12 @@
return configs
+def _earlyparseopts(args):
+ options = {}
+ fancyopts.fancyopts(args, commands.globalopts, options,
+ gnu=False, early=True)
+ return options
+
def _earlygetopt(aliases, args, strip=True):
"""Return list of values for an option (or aliases).
@@ -732,12 +740,16 @@
def _earlyreqopt(req, name, aliases):
"""Peek a list option without using a full options table"""
+ if req.ui.plain('strictflags'):
+ return req.earlyoptions[name]
values = _earlygetopt(aliases, req.args, strip=False)
req.earlyoptions[name] = values
return values
def _earlyreqoptstr(req, name, aliases):
"""Peek a string option without using a full options table"""
+ if req.ui.plain('strictflags'):
+ return req.earlyoptions[name]
value = (_earlygetopt(aliases, req.args, strip=False) or [''])[-1]
req.earlyoptions[name] = value
return value
@@ -745,13 +757,15 @@
def _earlyreqoptbool(req, name, aliases):
"""Peek a boolean option without using a full options table
- >>> req = request([b'x', b'--debugger'])
+ >>> req = request([b'x', b'--debugger'], uimod.ui())
>>> _earlyreqoptbool(req, b'debugger', [b'--debugger'])
True
- >>> req = request([b'x', b'--', b'--debugger'])
+ >>> req = request([b'x', b'--', b'--debugger'], uimod.ui())
>>> _earlyreqoptbool(req, b'debugger', [b'--debugger'])
"""
+ if req.ui.plain('strictflags'):
+ return req.earlyoptions[name]
try:
argcount = req.args.index("--")
except ValueError:
--- a/mercurial/fancyopts.py Thu Nov 16 03:52:42 2017 +0100
+++ b/mercurial/fancyopts.py Fri Dec 01 15:21:05 2017 -0600
@@ -7,6 +7,8 @@
from __future__ import absolute_import
+import functools
+
from .i18n import _
from . import (
error,
@@ -24,6 +26,179 @@
'version',
}
+def _earlyoptarg(arg, shortlist, namelist):
+ """Check if the given arg is a valid unabbreviated option
+
+ Returns (flag_str, has_embedded_value?, embedded_value, takes_value?)
+
+ >>> def opt(arg):
+ ... return _earlyoptarg(arg, b'R:q', [b'cwd=', b'debugger'])
+
+ long form:
+
+ >>> opt(b'--cwd')
+ ('--cwd', False, '', True)
+ >>> opt(b'--cwd=')
+ ('--cwd', True, '', True)
+ >>> opt(b'--cwd=foo')
+ ('--cwd', True, 'foo', True)
+ >>> opt(b'--debugger')
+ ('--debugger', False, '', False)
+ >>> opt(b'--debugger=') # invalid but parsable
+ ('--debugger', True, '', False)
+
+ short form:
+
+ >>> opt(b'-R')
+ ('-R', False, '', True)
+ >>> opt(b'-Rfoo')
+ ('-R', True, 'foo', True)
+ >>> opt(b'-q')
+ ('-q', False, '', False)
+ >>> opt(b'-qfoo') # invalid but parsable
+ ('-q', True, 'foo', False)
+
+ unknown or invalid:
+
+ >>> opt(b'--unknown')
+ ('', False, '', False)
+ >>> opt(b'-u')
+ ('', False, '', False)
+ >>> opt(b'-ufoo')
+ ('', False, '', False)
+ >>> opt(b'--')
+ ('', False, '', False)
+ >>> opt(b'-')
+ ('', False, '', False)
+ >>> opt(b'-:')
+ ('', False, '', False)
+ >>> opt(b'-:foo')
+ ('', False, '', False)
+ """
+ if arg.startswith('--'):
+ flag, eq, val = arg.partition('=')
+ if flag[2:] in namelist:
+ return flag, bool(eq), val, False
+ if flag[2:] + '=' in namelist:
+ return flag, bool(eq), val, True
+ elif arg.startswith('-') and arg != '-' and not arg.startswith('-:'):
+ flag, val = arg[:2], arg[2:]
+ i = shortlist.find(flag[1:])
+ if i >= 0:
+ return flag, bool(val), val, shortlist.startswith(':', i + 1)
+ return '', False, '', False
+
+def earlygetopt(args, shortlist, namelist, gnu=False, keepsep=False):
+ """Parse options like getopt, but ignores unknown options and abbreviated
+ forms
+
+ If gnu=False, this stops processing options as soon as a non/unknown-option
+ argument is encountered. Otherwise, option and non-option arguments may be
+ intermixed, and unknown-option arguments are taken as non-option.
+
+ If keepsep=True, '--' won't be removed from the list of arguments left.
+ This is useful for stripping early options from a full command arguments.
+
+ >>> def get(args, gnu=False, keepsep=False):
+ ... return earlygetopt(args, b'R:q', [b'cwd=', b'debugger'],
+ ... gnu=gnu, keepsep=keepsep)
+
+ default parsing rules for early options:
+
+ >>> get([b'x', b'--cwd', b'foo', b'-Rbar', b'-q', b'y'], gnu=True)
+ ([('--cwd', 'foo'), ('-R', 'bar'), ('-q', '')], ['x', 'y'])
+ >>> get([b'x', b'--cwd=foo', b'y', b'-R', b'bar', b'--debugger'], gnu=True)
+ ([('--cwd', 'foo'), ('-R', 'bar'), ('--debugger', '')], ['x', 'y'])
+ >>> get([b'--unknown', b'--cwd=foo', b'--', '--debugger'], gnu=True)
+ ([('--cwd', 'foo')], ['--unknown', '--debugger'])
+
+ restricted parsing rules (early options must come first):
+
+ >>> get([b'--cwd', b'foo', b'-Rbar', b'x', b'-q', b'y'], gnu=False)
+ ([('--cwd', 'foo'), ('-R', 'bar')], ['x', '-q', 'y'])
+ >>> get([b'--cwd=foo', b'x', b'y', b'-R', b'bar', b'--debugger'], gnu=False)
+ ([('--cwd', 'foo')], ['x', 'y', '-R', 'bar', '--debugger'])
+ >>> get([b'--unknown', b'--cwd=foo', b'--', '--debugger'], gnu=False)
+ ([], ['--unknown', '--cwd=foo', '--debugger'])
+
+ stripping early options (without loosing '--'):
+
+ >>> get([b'x', b'-Rbar', b'--', '--debugger'], gnu=True, keepsep=True)[1]
+ ['x', '--', '--debugger']
+
+ last argument:
+
+ >>> get([b'--cwd'])
+ ([], ['--cwd'])
+ >>> get([b'--cwd=foo'])
+ ([('--cwd', 'foo')], [])
+ >>> get([b'-R'])
+ ([], ['-R'])
+ >>> get([b'-Rbar'])
+ ([('-R', 'bar')], [])
+ >>> get([b'-q'])
+ ([('-q', '')], [])
+ >>> get([b'-q', b'--'])
+ ([('-q', '')], [])
+
+ value passed to bool options:
+
+ >>> get([b'--debugger=foo', b'x'])
+ ([], ['--debugger=foo', 'x'])
+ >>> get([b'-qfoo', b'x'])
+ ([], ['-qfoo', 'x'])
+
+ short option isn't separated with '=':
+
+ >>> get([b'-R=bar'])
+ ([('-R', '=bar')], [])
+
+ ':' may be in shortlist, but shouldn't be taken as an option letter:
+
+ >>> get([b'-:', b'y'])
+ ([], ['-:', 'y'])
+
+ '-' is a valid non-option argument:
+
+ >>> get([b'-', b'y'])
+ ([], ['-', 'y'])
+ """
+ # ignoring everything just after '--' isn't correct as '--' may be an
+ # option value (e.g. ['-R', '--']), but we do that consistently.
+ try:
+ argcount = args.index('--')
+ except ValueError:
+ argcount = len(args)
+
+ parsedopts = []
+ parsedargs = []
+ pos = 0
+ while pos < argcount:
+ arg = args[pos]
+ flag, hasval, val, takeval = _earlyoptarg(arg, shortlist, namelist)
+ if not hasval and takeval and pos + 1 >= argcount:
+ # missing last argument
+ break
+ if not flag or hasval and not takeval:
+ # non-option argument or -b/--bool=INVALID_VALUE
+ if gnu:
+ parsedargs.append(arg)
+ pos += 1
+ else:
+ break
+ elif hasval == takeval:
+ # -b/--bool or -s/--str=VALUE
+ parsedopts.append((flag, val))
+ pos += 1
+ else:
+ # -s/--str VALUE
+ parsedopts.append((flag, args[pos + 1]))
+ pos += 2
+
+ parsedargs.extend(args[pos:argcount])
+ parsedargs.extend(args[argcount + (not keepsep):])
+ return parsedopts, parsedargs
+
def gnugetopt(args, options, longoptions):
"""Parse options mostly like getopt.gnu_getopt.
@@ -51,7 +226,7 @@
return opts, args
-def fancyopts(args, options, state, gnu=False):
+def fancyopts(args, options, state, gnu=False, early=False):
"""
read args, parse options, and store options in state
@@ -124,7 +299,9 @@
namelist.append(oname)
# parse arguments
- if gnu:
+ if early:
+ parse = functools.partial(earlygetopt, gnu=gnu)
+ elif gnu:
parse = gnugetopt
else:
parse = pycompat.getoptb
--- a/mercurial/help/environment.txt Thu Nov 16 03:52:42 2017 +0100
+++ b/mercurial/help/environment.txt Fri Dec 01 15:21:05 2017 -0600
@@ -56,9 +56,17 @@
localization. This can be useful when scripting against Mercurial
in the face of existing user configuration.
+ In addition to the features disabled by ``HGPLAIN=``, the following
+ values can be specified to adjust behavior:
+
+ ``+strictflags``
+ Restrict parsing of command line flags.
+
Equivalent options set via command line flags or environment
variables are not overridden.
+ See :hg:`help scripting` for details.
+
HGPLAINEXCEPT
This is a comma-separated list of features to preserve when
HGPLAIN is enabled. Currently the following values are supported:
--- a/mercurial/help/scripting.txt Thu Nov 16 03:52:42 2017 +0100
+++ b/mercurial/help/scripting.txt Fri Dec 01 15:21:05 2017 -0600
@@ -74,6 +74,32 @@
like the username and extensions that may be required to interface
with a repository.
+Command-line Flags
+==================
+
+Mercurial's default command-line parser is designed for humans, and is not
+robust against malicious input. For instance, you can start a debugger by
+passing ``--debugger`` as an option value::
+
+ $ REV=--debugger sh -c 'hg log -r "$REV"'
+
+This happens because several command-line flags need to be scanned without
+using a concrete command table, which may be modified while loading repository
+settings and extensions.
+
+Since Mercurial 4.4.2, the parsing of such flags may be restricted by setting
+``HGPLAIN=+strictflags``. When this feature is enabled, all early options
+(e.g. ``-R/--repository``, ``--cwd``, ``--config``) must be specified first
+amongst the other global options, and cannot be injected to an arbitrary
+location::
+
+ $ HGPLAIN=+strictflags hg -R "$REPO" log -r "$REV"
+
+In earlier Mercurial versions where ``+strictflags`` isn't available, you
+can mitigate the issue by concatenating an option value with its flag::
+
+ $ hg log -r"$REV" --keyword="$KEYWORD"
+
Consuming Command Output
========================
--- a/mercurial/help/subrepos.txt Thu Nov 16 03:52:42 2017 +0100
+++ b/mercurial/help/subrepos.txt Fri Dec 01 15:21:05 2017 -0600
@@ -90,7 +90,7 @@
:archive: archive does not recurse in subrepositories unless
-S/--subrepos is specified.
-:cat: cat currently only handles exact file matches in subrepos.
+:cat: Git subrepositories only support exact file matches.
Subversion subrepositories are currently ignored.
:commit: commit creates a consistent snapshot of the state of the
--- a/mercurial/merge.py Thu Nov 16 03:52:42 2017 +0100
+++ b/mercurial/merge.py Fri Dec 01 15:21:05 2017 -0600
@@ -653,7 +653,7 @@
and repo.dirstate.normalize(f) not in repo.dirstate
and mctx[f2].cmp(wctx[f]))
-def _checkunknowndirs(repo, f):
+class _unknowndirschecker(object):
"""
Look for any unknown files or directories that may have a path conflict
with a file. If any path prefix of the file exists as a file or link,
@@ -663,23 +663,42 @@
Returns the shortest path at which a conflict occurs, or None if there is
no conflict.
"""
+ def __init__(self):
+ # A set of paths known to be good. This prevents repeated checking of
+ # dirs. It will be updated with any new dirs that are checked and found
+ # to be safe.
+ self._unknowndircache = set()
- # Check for path prefixes that exist as unknown files.
- for p in reversed(list(util.finddirs(f))):
- if (repo.wvfs.audit.check(p)
- and repo.wvfs.isfileorlink(p)
- and repo.dirstate.normalize(p) not in repo.dirstate):
- return p
+ # A set of paths that are known to be absent. This prevents repeated
+ # checking of subdirectories that are known not to exist. It will be
+ # updated with any new dirs that are checked and found to be absent.
+ self._missingdircache = set()
- # Check if the file conflicts with a directory containing unknown files.
- if repo.wvfs.audit.check(f) and repo.wvfs.isdir(f):
- # Does the directory contain any files that are not in the dirstate?
- for p, dirs, files in repo.wvfs.walk(f):
- for fn in files:
- relf = repo.dirstate.normalize(repo.wvfs.reljoin(p, fn))
- if relf not in repo.dirstate:
- return f
- return None
+ def __call__(self, repo, f):
+ # Check for path prefixes that exist as unknown files.
+ for p in reversed(list(util.finddirs(f))):
+ if p in self._missingdircache:
+ return
+ if p in self._unknowndircache:
+ continue
+ if repo.wvfs.audit.check(p):
+ if (repo.wvfs.isfileorlink(p)
+ and repo.dirstate.normalize(p) not in repo.dirstate):
+ return p
+ if not repo.wvfs.lexists(p):
+ self._missingdircache.add(p)
+ return
+ self._unknowndircache.add(p)
+
+ # Check if the file conflicts with a directory containing unknown files.
+ if repo.wvfs.audit.check(f) and repo.wvfs.isdir(f):
+ # Does the directory contain any files that are not in the dirstate?
+ for p, dirs, files in repo.wvfs.walk(f):
+ for fn in files:
+ relf = repo.dirstate.normalize(repo.wvfs.reljoin(p, fn))
+ if relf not in repo.dirstate:
+ return f
+ return None
def _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce):
"""
@@ -701,12 +720,13 @@
elif config == 'warn':
warnconflicts.update(conflicts)
+ checkunknowndirs = _unknowndirschecker()
for f, (m, args, msg) in actions.iteritems():
if m in ('c', 'dc'):
if _checkunknownfile(repo, wctx, mctx, f):
fileconflicts.add(f)
elif pathconfig and f not in wctx:
- path = _checkunknowndirs(repo, f)
+ path = checkunknowndirs(repo, f)
if path is not None:
pathconflicts.add(path)
elif m == 'dg':
@@ -895,34 +915,21 @@
# can't be updated to cleanly.
invalidconflicts = set()
+ # The set of directories that contain files that are being created.
+ createdfiledirs = set()
+
# The set of files deleted by all the actions.
deletedfiles = set()
for f, (m, args, msg) in actions.items():
if m in ('c', 'dc', 'm', 'cm'):
# This action may create a new local file.
+ createdfiledirs.update(util.finddirs(f))
if mf.hasdir(f):
# The file aliases a local directory. This might be ok if all
# the files in the local directory are being deleted. This
# will be checked once we know what all the deleted files are.
remoteconflicts.add(f)
- for p in util.finddirs(f):
- if p in mf:
- if p in mctx:
- # The file is in a directory which aliases both a local
- # and a remote file. This is an internal inconsistency
- # within the remote manifest.
- invalidconflicts.add(p)
- else:
- # The file is in a directory which aliases a local file.
- # We will need to rename the local file.
- localconflicts.add(p)
- if p in actions and actions[p][0] in ('c', 'dc', 'm', 'cm'):
- # The file is in a directory which aliases a remote file.
- # This is an internal inconsistency within the remote
- # manifest.
- invalidconflicts.add(p)
-
# Track the names of all deleted files.
if m == 'r':
deletedfiles.add(f)
@@ -934,6 +941,24 @@
f2, flags = args
deletedfiles.add(f2)
+ # Check all directories that contain created files for path conflicts.
+ for p in createdfiledirs:
+ if p in mf:
+ if p in mctx:
+ # A file is in a directory which aliases both a local
+ # and a remote file. This is an internal inconsistency
+ # within the remote manifest.
+ invalidconflicts.add(p)
+ else:
+ # A file is in a directory which aliases a local file.
+ # We will need to rename the local file.
+ localconflicts.add(p)
+ if p in actions and actions[p][0] in ('c', 'dc', 'm', 'cm'):
+ # The file is in a directory which aliases a remote file.
+ # This is an internal inconsistency within the remote
+ # manifest.
+ invalidconflicts.add(p)
+
# Rename all local conflicting files that have not been deleted.
for p in localconflicts:
if p not in deletedfiles:
--- a/mercurial/ui.py Thu Nov 16 03:52:42 2017 +0100
+++ b/mercurial/ui.py Fri Dec 01 15:21:05 2017 -0600
@@ -766,6 +766,7 @@
The return value can either be
- False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
+ - False if feature is disabled by default and not included in HGPLAIN
- True otherwise
'''
if ('HGPLAIN' not in encoding.environ and
@@ -773,6 +774,9 @@
return False
exceptions = encoding.environ.get('HGPLAINEXCEPT',
'').strip().split(',')
+ # TODO: add support for HGPLAIN=+feature,-feature syntax
+ if '+strictflags' not in encoding.environ.get('HGPLAIN', '').split(','):
+ exceptions.append('strictflags')
if feature and exceptions:
return feature not in exceptions
return True
--- a/tests/test-amend-subrepo.t Thu Nov 16 03:52:42 2017 +0100
+++ b/tests/test-amend-subrepo.t Fri Dec 01 15:21:05 2017 -0600
@@ -58,7 +58,7 @@
$ echo a >> s/a
$ hg add -R s
- adding s/a
+ adding s/a (glob)
$ hg amend
abort: uncommitted changes in subrepository "s"
(use --subrepos for recursive commit)
--- a/tests/test-audit-subrepo.t Thu Nov 16 03:52:42 2017 +0100
+++ b/tests/test-audit-subrepo.t Fri Dec 01 15:21:05 2017 -0600
@@ -9,7 +9,7 @@
$ hg init sub/.hg
$ echo 'sub/.hg = sub/.hg' >> .hgsub
$ hg ci -qAm 'add subrepo "sub/.hg"'
- abort: path 'sub/.hg' is inside nested repo 'sub'
+ abort: path 'sub/.hg' is inside nested repo 'sub' (glob)
[255]
prepare tampered repo (including the commit above):
@@ -33,7 +33,7 @@
on clone (and update):
$ hg clone -q hgname hgname2
- abort: path 'sub/.hg' is inside nested repo 'sub'
+ abort: path 'sub/.hg' is inside nested repo 'sub' (glob)
[255]
Test direct symlink traversal
--- a/tests/test-commandserver.t Thu Nov 16 03:52:42 2017 +0100
+++ b/tests/test-commandserver.t Fri Dec 01 15:21:05 2017 -0600
@@ -137,6 +137,20 @@
summary: 1
+check strict parsing of early options:
+
+ >>> import os
+ >>> from hgclient import check, readchannel, runcommand
+ >>> os.environ['HGPLAIN'] = '+strictflags'
+ >>> @check
+ ... def cwd(server):
+ ... readchannel(server)
+ ... runcommand(server, ['log', '-b', '--config=alias.log=!echo pwned',
+ ... 'default'])
+ *** runcommand log -b --config=alias.log=!echo pwned default
+ abort: unknown revision '--config=alias.log=!echo pwned'!
+ [255]
+
check that "histedit --commands=-" can read rules from the input channel:
>>> import cStringIO
--- a/tests/test-dispatch.t Thu Nov 16 03:52:42 2017 +0100
+++ b/tests/test-dispatch.t Fri Dec 01 15:21:05 2017 -0600
@@ -113,6 +113,51 @@
$ hg log -b '--config=alias.log=!echo howdy'
howdy
+Early options must come first if HGPLAIN=+strictflags is specified:
+(BUG: chg cherry-picks early options to pass them as a server command)
+
+#if no-chg
+ $ HGPLAIN=+strictflags hg log -b --config='hooks.pre-log=false' default
+ abort: unknown revision '--config=hooks.pre-log=false'!
+ [255]
+ $ HGPLAIN=+strictflags hg log -b -R. default
+ abort: unknown revision '-R.'!
+ [255]
+ $ HGPLAIN=+strictflags hg log -b --cwd=. default
+ abort: unknown revision '--cwd=.'!
+ [255]
+#endif
+ $ HGPLAIN=+strictflags hg log -b --debugger default
+ abort: unknown revision '--debugger'!
+ [255]
+ $ HGPLAIN=+strictflags hg log -b --config='alias.log=!echo pwned' default
+ abort: unknown revision '--config=alias.log=!echo pwned'!
+ [255]
+
+ $ HGPLAIN=+strictflags hg log --config='hooks.pre-log=false' -b default
+ abort: option --config may not be abbreviated!
+ [255]
+ $ HGPLAIN=+strictflags hg log -q --cwd=.. -b default
+ abort: option --cwd may not be abbreviated!
+ [255]
+ $ HGPLAIN=+strictflags hg log -q -R . -b default
+ abort: option -R has to be separated from other options (e.g. not -qR) and --repository may only be abbreviated as --repo!
+ [255]
+
+ $ HGPLAIN=+strictflags hg --config='hooks.pre-log=false' log -b default
+ abort: pre-log hook exited with status 1
+ [255]
+ $ HGPLAIN=+strictflags hg --cwd .. -q -Ra log -b default
+ 0:cb9a9f314b8b
+
+For compatibility reasons, HGPLAIN=+strictflags is not enabled by plain HGPLAIN:
+
+ $ HGPLAIN= hg log --config='hooks.pre-log=false' -b default
+ abort: pre-log hook exited with status 1
+ [255]
+ $ HGPLAINEXCEPT= hg log --cwd .. -q -Ra -b default
+ 0:cb9a9f314b8b
+
[defaults]
$ hg cat a
--- a/tests/test-doctest.py Thu Nov 16 03:52:42 2017 +0100
+++ b/tests/test-doctest.py Fri Dec 01 15:21:05 2017 -0600
@@ -48,6 +48,7 @@
testmod('mercurial.dagparser', optionflags=doctest.NORMALIZE_WHITESPACE)
testmod('mercurial.dispatch')
testmod('mercurial.encoding')
+testmod('mercurial.fancyopts')
testmod('mercurial.formatter')
testmod('mercurial.hg')
testmod('mercurial.hgweb.hgwebdir_mod')
--- a/tests/test-import.t Thu Nov 16 03:52:42 2017 +0100
+++ b/tests/test-import.t Fri Dec 01 15:21:05 2017 -0600
@@ -972,6 +972,7 @@
adding b
recording removal of a as rename to b (88% similar)
applied to working directory
+ $ echo 'mod b' > b
$ hg st -C
A b
a
@@ -979,6 +980,8 @@
$ hg revert -a
undeleting a
forgetting b
+ $ cat b
+ mod b
$ rm b
$ hg import --no-commit -v -s 100 ../rename.diff -p2
applying ../rename.diff
--- a/tests/test-largefiles-misc.t Thu Nov 16 03:52:42 2017 +0100
+++ b/tests/test-largefiles-misc.t Fri Dec 01 15:21:05 2017 -0600
@@ -390,8 +390,12 @@
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ hg status -S
+Forget doesn't change the content of the file
+ $ echo 'pre-forget content' > subrepo/large.txt
$ hg forget -v subrepo/large.txt
removing subrepo/large.txt (glob)
+ $ cat subrepo/large.txt
+ pre-forget content
Test reverting a forgotten file
$ hg revert -R subrepo subrepo/large.txt
@@ -1060,7 +1064,9 @@
> largefiles=
> EOF
$ echo large > subrepo-root/large
- $ hg -R subrepo-root add --large subrepo-root/large
+ $ mkdir -p subrepo-root/dir/subdir
+ $ echo large2 > subrepo-root/dir/subdir/large.bin
+ $ hg -R subrepo-root add --large subrepo-root/large subrepo-root/dir/subdir/large.bin
$ hg clone -q no-largefiles subrepo-root/no-largefiles
$ cat > subrepo-root/.hgsub <<EOF
> no-largefiles = no-largefiles
@@ -1069,6 +1075,7 @@
$ hg -R subrepo-root commit -m '#0'
Invoking status precommit hook
A .hgsub
+ A dir/subdir/large.bin
A large
? .hgsubstate
$ echo dirty >> subrepo-root/large
@@ -1085,8 +1092,155 @@
reverting subrepo no-largefiles
reverting subrepo-root/no-largefiles/normal1 (glob)
- $ cd ..
+Move (and then undo) a directory move with only largefiles.
+
+ $ listtree() {
+ > find $@ \( -type d -printf "%p/\n" -o -type f -printf "%p\n" \) \
+ > -a -name .hg -prune | sort
+ > }
+
+ $ cd subrepo-root
+ $ listtree .hglf dir* large*
+ .hglf/
+ .hglf/dir/
+ .hglf/dir/subdir/
+ .hglf/dir/subdir/large.bin
+ .hglf/large
+ dir/
+ dir/subdir/
+ dir/subdir/large.bin
+ large
+ large.orig
+
+ $ hg mv dir/subdir dir/subdir2
+ moving .hglf/dir/subdir/large.bin to .hglf/dir/subdir2/large.bin (glob)
+
+ $ listtree .hglf dir* large*
+ .hglf/
+ .hglf/dir/
+ .hglf/dir/subdir2/
+ .hglf/dir/subdir2/large.bin
+ .hglf/large
+ dir/
+ dir/subdir2/
+ dir/subdir2/large.bin
+ large
+ large.orig
+ $ hg status -C
+ A dir/subdir2/large.bin
+ dir/subdir/large.bin
+ R dir/subdir/large.bin
+ ? large.orig
+
+ $ echo 'modified' > dir/subdir2/large.bin
+ $ hg status -C
+ A dir/subdir2/large.bin
+ dir/subdir/large.bin
+ R dir/subdir/large.bin
+ ? large.orig
+
+ $ hg revert --all
+ undeleting .hglf/dir/subdir/large.bin (glob)
+ forgetting .hglf/dir/subdir2/large.bin (glob)
+ reverting subrepo no-largefiles
+
+ $ hg status -C
+ ? dir/subdir2/large.bin
+ ? large.orig
+
+The content of the forgotten file shouldn't be clobbered
+
+ $ cat dir/subdir2/large.bin
+ modified
+
+The standin for subdir2 should be deleted, not just dropped
+ $ listtree .hglf dir* large*
+ .hglf/
+ .hglf/dir/
+ .hglf/dir/subdir/
+ .hglf/dir/subdir/large.bin
+ .hglf/large
+ dir/
+ dir/subdir/
+ dir/subdir/large.bin
+ dir/subdir2/
+ dir/subdir2/large.bin
+ large
+ large.orig
+
+ $ rm -r dir/subdir2
+
+'subdir' should not be in the destination. It would be if the subdir2 directory
+existed under .hglf/.
+ $ hg mv dir/subdir dir/subdir2
+ moving .hglf/dir/subdir/large.bin to .hglf/dir/subdir2/large.bin (glob)
+
+ $ hg status -C
+ A dir/subdir2/large.bin
+ dir/subdir/large.bin
+ R dir/subdir/large.bin
+ ? large.orig
+
+ $ listtree .hglf dir* large*
+ .hglf/
+ .hglf/dir/
+ .hglf/dir/subdir2/
+ .hglf/dir/subdir2/large.bin
+ .hglf/large
+ dir/
+ dir/subdir2/
+ dir/subdir2/large.bin
+ large
+ large.orig
+
+Start from scratch, and rename something other than the final path component.
+
+ $ hg up -qC .
+ $ hg --config extensions.purge= purge
+
+ $ hg mv dir/subdir dir2/subdir
+ moving .hglf/dir/subdir/large.bin to .hglf/dir2/subdir/large.bin (glob)
+
+ $ hg status -C
+ A dir2/subdir/large.bin
+ dir/subdir/large.bin
+ R dir/subdir/large.bin
+
+ $ listtree .hglf dir* large*
+ .hglf/
+ .hglf/dir2/
+ .hglf/dir2/subdir/
+ .hglf/dir2/subdir/large.bin
+ .hglf/large
+ dir2/
+ dir2/subdir/
+ dir2/subdir/large.bin
+ large
+
+ $ hg revert --all
+ undeleting .hglf/dir/subdir/large.bin (glob)
+ forgetting .hglf/dir2/subdir/large.bin (glob)
+ reverting subrepo no-largefiles
+
+ $ hg status -C
+ ? dir2/subdir/large.bin
+
+ $ listtree .hglf dir* large*
+ .hglf/
+ .hglf/dir/
+ .hglf/dir/subdir/
+ .hglf/dir/subdir/large.bin
+ .hglf/large
+ dir/
+ dir/subdir/
+ dir/subdir/large.bin
+ dir2/
+ dir2/subdir/
+ dir2/subdir/large.bin
+ large
+
+ $ cd ../..
Test "pull --rebase" when rebase is enabled before largefiles (issue3861)
=========================================================================
--- a/tests/test-subrepo.t Thu Nov 16 03:52:42 2017 +0100
+++ b/tests/test-subrepo.t Fri Dec 01 15:21:05 2017 -0600
@@ -1071,6 +1071,18 @@
"path": "sub/repo/foo"
}
]
+
+ non-exact match:
+
+ $ hg cat -T '{path}\n' 'glob:**'
+ .hgsub
+ .hgsubstate
+ sub/repo/foo (glob)
+ $ hg cat -T '{path}\n' 're:^sub'
+ sub/repo/foo (glob)
+
+ missing subrepos in working directory:
+
$ mkdir -p tmp/sub/repo
$ hg cat -r 0 --output tmp/%p_p sub/repo/foo
$ cat tmp/sub/repo/foo_p