Mercurial > hg
changeset 35186:aef2b98d9352
merge with stable
author | Kevin Bullock <kbullock+mercurial@ringworld.org> |
---|---|
date | Fri, 01 Dec 2017 15:21:05 -0600 |
parents | 66ecde8a704d (current diff) fff9ffa2ea05 (diff) |
children | b4b0aed7bfaf |
files | hgext/largefiles/lfcommands.py mercurial/dispatch.py mercurial/help/environment.txt mercurial/ui.py tests/test-import.t |
diffstat | 19 files changed, 558 insertions(+), 51 deletions(-) [+] |
line wrap: on
line diff
--- 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