##// END OF EJS Templates
httppeer: remove support for connecting to <0.9.1 servers (BC)...
httppeer: remove support for connecting to <0.9.1 servers (BC) Previously, HTTP wire protocol clients would attempt a "capabilities" wire protocol command. If that failed, they would fall back to issuing a "between" command. The "capabilities" command was added in Mercurial 0.9.1 (released July 2006). The "between" command has been present for as long as the wire protocol has existed. So if the "between" command failed, it was safe to assume that the remote could not speak any version of the Mercurial wire protocol. The "between" fallback was added in 395a84f78736 in 2011. Before that changeset, Mercurial would *always* issue the "between" command and would issue "capabilities" if capabilities were requested. At that time, many connections would issue "capabilities" eventually, so it was decided to issue "capabilities" by default and fall back to "between" if that failed. This saved a round trip when connecting to modern servers while still preserving compatibility with legacy servers. Fast forward ~7 years. Mercurial servers supporting "capabilities" have been around for over a decade. If modern clients are connecting to <0.9.1 servers, they are getting a bad experience. They may even be getting bad data (an old server is vulnerable to numerous security issues and could have been p0wned, leading to a Mercurial repository serving backdoors or other badness). In addition, the fallback can harm experience for modern servers. If a client experiences an intermittent HTTP request failure (due to bad network, etc) and falls back to a "between" that works, it would assume an empty capability set and would attempt to communicate with the repository using a very ancient wire protocol. Auditing HTTP logs for hg.mozilla.org, I did find a handful of requests for the null range of the "between" command. However, requests can be days apart. And when I do see requests, they come in batches. Those batches seem to correlate to spikes of HTTP 500 or other server/network events. So I think these requests are fallbacks from failed "capabilities" requests and not from old clients. If you need even more evidence to discontinue support, apparently we have no test coverage for communicating with servers not supporting "capabilities." I know this because all tests pass with the "between" fallback removed. Finally, server-side support for <0.9.1 pushing (the "addchangegroup" wire protocol command along with locking-related commands) was dropped from the HTTP client in fda0867cfe03 in 2017 and the SSH client in 9f6e0e7ef828 in 2015. I think this all adds up to enough justification for removing client support for communicating with servers not supporting "capabilities." So this commit removes that fallback. Differential Revision: https://phab.mercurial-scm.org/D2001

File last commit:

r35227:98a5aa55 default
r35902:197d10e1 default
Show More
fancyopts.py
321 lines | 9.6 KiB | text/x-python | PythonLexer
Martin Geisler
fancyopts: add copyright and license header
r8230 # fancyopts.py - better command line parsing
#
# Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
#
# This software may be used and distributed according to the terms of the
Matt Mackall
Update license to GPLv2+
r10263 # GNU General Public License version 2 or any later version.
Martin Geisler
fancyopts: add copyright and license header
r8230
Gregory Szorc
fancyopts: use absolute_import
r25947 from __future__ import absolute_import
Yuya Nishihara
fancyopts: add early-options parser compatible with getopt()...
r35179 import functools
Gregory Szorc
fancyopts: use absolute_import
r25947 from .i18n import _
Pulkit Goyal
py3: make a bytes version of getopt.getopt()...
r30578 from . import (
error,
pycompat,
)
mpm@selenic.com
Add back links from file revisions to changeset revisions...
r0
Augie Fackler
flags: allow specifying --no-boolean-flag on the command line (BC)...
r29947 # Set of flags to not apply boolean negation logic on
Martin von Zweigbergk
cleanup: use set literals...
r32291 nevernegate = {
Augie Fackler
flags: allow specifying --no-boolean-flag on the command line (BC)...
r29947 # avoid --no-noninteractive
'noninteractive',
# These two flags are special because they cause hg to do one
# thing and then exit, and so aren't suitable for use in things
# like aliases anyway.
'help',
'version',
Martin von Zweigbergk
cleanup: use set literals...
r32291 }
Augie Fackler
flags: allow specifying --no-boolean-flag on the command line (BC)...
r29947
Yuya Nishihara
fancyopts: add early-options parser compatible with getopt()...
r35179 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)
Yuya Nishihara
fancyopts: fix handling of "--" value in earlygetopt()
r35227 ([], ['--unknown', '--cwd=foo', '--', '--debugger'])
Yuya Nishihara
fancyopts: add early-options parser compatible with getopt()...
r35179
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', '')], [])
Yuya Nishihara
fancyopts: fix handling of "--" value in earlygetopt()
r35227 '--' may be a value:
>>> get([b'-R', b'--', b'x'])
([('-R', '--')], ['x'])
>>> get([b'--cwd', b'--', b'x'])
([('--cwd', '--')], ['x'])
Yuya Nishihara
fancyopts: add early-options parser compatible with getopt()...
r35179 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'])
"""
parsedopts = []
parsedargs = []
pos = 0
Yuya Nishihara
fancyopts: fix handling of "--" value in earlygetopt()
r35227 while pos < len(args):
Yuya Nishihara
fancyopts: add early-options parser compatible with getopt()...
r35179 arg = args[pos]
Yuya Nishihara
fancyopts: fix handling of "--" value in earlygetopt()
r35227 if arg == '--':
pos += not keepsep
break
Yuya Nishihara
fancyopts: add early-options parser compatible with getopt()...
r35179 flag, hasval, val, takeval = _earlyoptarg(arg, shortlist, namelist)
Yuya Nishihara
fancyopts: fix handling of "--" value in earlygetopt()
r35227 if not hasval and takeval and pos + 1 >= len(args):
Yuya Nishihara
fancyopts: add early-options parser compatible with getopt()...
r35179 # 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
Yuya Nishihara
fancyopts: fix handling of "--" value in earlygetopt()
r35227 parsedargs.extend(args[pos:])
Yuya Nishihara
fancyopts: add early-options parser compatible with getopt()...
r35179 return parsedopts, parsedargs
Yuya Nishihara
dispatch: alias --repo to --repository while parsing early options...
r35223 def fancyopts(args, options, state, gnu=False, early=False, optaliases=None):
Matt Mackall
fancyopts: lots of cleanups
r5638 """
read args, parse options, and store options in state
each option is a tuple of:
short option or ''
long option
default value
description
FUJIWARA Katsunori
help: show value requirement and multiple occurrence of options...
r11321 option value label(optional)
Matt Mackall
fancyopts: lots of cleanups
r5638
option types include:
boolean or none - option sets variable in state to true
string - parameter string is stored in state
list - parameter string is added to a list
integer - parameter strings is stored as int
function - call function with parameter
mpm@selenic.com
Add back links from file revisions to changeset revisions...
r0
Yuya Nishihara
dispatch: alias --repo to --repository while parsing early options...
r35223 optaliases is a mapping from a canonical option name to a list of
additional long options. This exists for preserving backward compatibility
of early options. If we want to use it extensively, please consider moving
the functionality to the options table (e.g separate long options by '|'.)
Matt Mackall
fancyopts: lots of cleanups
r5638 non-option args are returned
"""
Yuya Nishihara
dispatch: alias --repo to --repository while parsing early options...
r35223 if optaliases is None:
optaliases = {}
Matt Mackall
fancyopts: lots of cleanups
r5638 namelist = []
shortlist = ''
argmap = {}
defmap = {}
Augie Fackler
flags: allow specifying --no-boolean-flag on the command line (BC)...
r29947 negations = {}
alllong = set(o[1] for o in options)
Matt Mackall
fancyopts: lots of cleanups
r5638
FUJIWARA Katsunori
help: show value requirement and multiple occurrence of options...
r11321 for option in options:
if len(option) == 5:
short, name, default, comment, dummy = option
else:
short, name, default, comment = option
Matt Mackall
fancyopts: lots of cleanups
r5638 # convert opts to getopt format
Yuya Nishihara
dispatch: alias --repo to --repository while parsing early options...
r35223 onames = [name]
onames.extend(optaliases.get(name, []))
Matt Mackall
fancyopts: lots of cleanups
r5638 name = name.replace('-', '_')
Yuya Nishihara
dispatch: alias --repo to --repository while parsing early options...
r35223 argmap['-' + short] = name
for n in onames:
argmap['--' + n] = name
Matt Mackall
fancyopts: lots of cleanups
r5638 defmap[name] = default
# copy defaults to state
if isinstance(default, list):
state[name] = default[:]
Augie Fackler
fancyopts: restore use of callable() since it was readded in Python 3.2
r21794 elif callable(default):
Matt Mackall
fancyopts: lots of cleanups
r5638 state[name] = None
Thomas Arendsen Hein
fancyopts: Copy list arguments in command table before modifying....
r5093 else:
Matt Mackall
fancyopts: lots of cleanups
r5638 state[name] = default
mpm@selenic.com
Add back links from file revisions to changeset revisions...
r0
Matt Mackall
fancyopts: lots of cleanups
r5638 # does it take a parameter?
if not (default is None or default is True or default is False):
Matt Mackall
many, many trivial check-code fixups
r10282 if short:
short += ':'
Yuya Nishihara
dispatch: alias --repo to --repository while parsing early options...
r35223 onames = [n + '=' for n in onames]
elif name not in nevernegate:
for n in onames:
if n.startswith('no-'):
insert = n[3:]
else:
insert = 'no-' + n
# backout (as a practical example) has both --commit and
# --no-commit options, so we don't want to allow the
# negations of those flags.
if insert not in alllong:
assert ('--' + n) not in negations
negations['--' + insert] = '--' + n
namelist.append(insert)
Matt Mackall
fancyopts: lots of cleanups
r5638 if short:
shortlist += short
if name:
Yuya Nishihara
dispatch: alias --repo to --repository while parsing early options...
r35223 namelist.extend(onames)
Matt Mackall
fancyopts: lots of cleanups
r5638
# parse arguments
Yuya Nishihara
fancyopts: add early-options parser compatible with getopt()...
r35179 if early:
parse = functools.partial(earlygetopt, gnu=gnu)
elif gnu:
Yuya Nishihara
fancyopts: use getopt.gnu_getopt()...
r35226 parse = pycompat.gnugetoptb
Augie Fackler
fancyopts: Parse options that occur after arguments....
r7772 else:
Pulkit Goyal
py3: make a bytes version of getopt.getopt()...
r30578 parse = pycompat.getoptb
Augie Fackler
fancyopts: Parse options that occur after arguments....
r7772 opts, args = parse(args, shortlist, namelist)
mpm@selenic.com
Add back links from file revisions to changeset revisions...
r0
Matt Mackall
fancyopts: lots of cleanups
r5638 # transfer result to state
for opt, val in opts:
Augie Fackler
flags: allow specifying --no-boolean-flag on the command line (BC)...
r29947 boolval = True
negation = negations.get(opt, False)
if negation:
opt = negation
boolval = False
Matt Mackall
fancyopts: lots of cleanups
r5638 name = argmap[opt]
introom
fancyopts: allow all callable as default parameter value...
r25563 obj = defmap[name]
t = type(obj)
if callable(obj):
Matt Mackall
fancyopts: lots of cleanups
r5638 state[name] = defmap[name](val)
elif t is type(1):
Idan Kamara
fancyopts: don't show a traceback on invalid integer values
r17712 try:
state[name] = int(val)
except ValueError:
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(_('invalid value %r for option %s, '
Idan Kamara
fancyopts: don't show a traceback on invalid integer values
r17712 'expected int') % (val, opt))
Matt Mackall
fancyopts: lots of cleanups
r5638 elif t is type(''):
state[name] = val
elif t is type([]):
state[name].append(val)
elif t is type(None) or t is type(False):
Augie Fackler
flags: allow specifying --no-boolean-flag on the command line (BC)...
r29947 state[name] = boolval
mpm@selenic.com
Beginning of new command parsing interface...
r209
Matt Mackall
fancyopts: lots of cleanups
r5638 # return unparsed args
mpm@selenic.com
Add back links from file revisions to changeset revisions...
r0 return args