diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py --- a/mercurial/cmdutil.py +++ b/mercurial/cmdutil.py @@ -1594,7 +1594,7 @@ def showmarker(fm, marker, index=None): fm.write('date', '(%s) ', fm.formatdate(marker.date())) meta = marker.metadata().copy() meta.pop('date', None) - smeta = {_maybebytestr(k): _maybebytestr(v) for k, v in meta.iteritems()} + smeta = util.rapply(_maybebytestr, meta) fm.write('metadata', '{%s}', fm.formatdict(smeta, fmt='%r: %r', sep=', ')) fm.plain('\n') diff --git a/mercurial/ui.py b/mercurial/ui.py --- a/mercurial/ui.py +++ b/mercurial/ui.py @@ -148,14 +148,10 @@ b"""# example system-wide hg config (see } def _maybestrurl(maybebytes): - if maybebytes is None: - return None - return pycompat.strurl(maybebytes) + return util.rapply(pycompat.strurl, maybebytes) def _maybebytesurl(maybestr): - if maybestr is None: - return None - return pycompat.bytesurl(maybestr) + return util.rapply(pycompat.bytesurl, maybestr) class httppasswordmgrdbproxy(object): """Delays loading urllib2 until it's needed.""" @@ -168,18 +164,14 @@ class httppasswordmgrdbproxy(object): return self._mgr def add_password(self, realm, uris, user, passwd): - if isinstance(uris, tuple): - uris = tuple(_maybestrurl(u) for u in uris) - else: - uris = _maybestrurl(uris) return self._get_mgr().add_password( - _maybestrurl(realm), uris, + _maybestrurl(realm), _maybestrurl(uris), _maybestrurl(user), _maybestrurl(passwd)) def find_user_password(self, realm, uri): - return tuple(_maybebytesurl(v) for v in - self._get_mgr().find_user_password(_maybestrurl(realm), - _maybestrurl(uri))) + mgr = self._get_mgr() + return _maybebytesurl(mgr.find_user_password(_maybestrurl(realm), + _maybestrurl(uri))) def _catchterm(*args): raise error.SignalInterrupt diff --git a/mercurial/util.py b/mercurial/util.py --- a/mercurial/util.py +++ b/mercurial/util.py @@ -183,6 +183,39 @@ os.stat_float_times(False) def safehasattr(thing, attr): return getattr(thing, attr, _notset) is not _notset +def _rapply(f, xs): + if xs is None: + # assume None means non-value of optional data + return xs + if isinstance(xs, (list, set, tuple)): + return type(xs)(_rapply(f, x) for x in xs) + if isinstance(xs, dict): + return type(xs)((_rapply(f, k), _rapply(f, v)) for k, v in xs.items()) + return f(xs) + +def rapply(f, xs): + """Apply function recursively to every item preserving the data structure + + >>> def f(x): + ... return 'f(%s)' % x + >>> rapply(f, None) is None + True + >>> rapply(f, 'a') + 'f(a)' + >>> rapply(f, {'a'}) == {'f(a)'} + True + >>> rapply(f, ['a', 'b', None, {'c': 'd'}, []]) + ['f(a)', 'f(b)', None, {'f(c)': 'f(d)'}, []] + + >>> xs = [object()] + >>> rapply(pycompat.identity, xs) is xs + True + """ + if f is pycompat.identity: + # fast path mainly for py2 + return xs + return _rapply(f, xs) + def bytesinput(fin, fout, *args, **kwargs): sin, sout = sys.stdin, sys.stdout try: diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -123,10 +123,7 @@ class webproto(abstractserverproto): return [data[k] for k in keys] def _args(self): - args = self._req.form.copy() - if pycompat.ispy3: - args = {k.encode('ascii'): [v.encode('ascii') for v in vs] - for k, vs in args.items()} + args = util.rapply(pycompat.bytesurl, self._req.form.copy()) postlen = int(self._req.env.get(r'HTTP_X_HGARGS_POST', 0)) if postlen: args.update(cgi.parse_qs(