comparison mercurial/wireproto.py @ 36067:caca3ac2ac04

wireproto: use maybecapturestdio() for push responses (API) The "pushres" and "pusherr" response types currently call proto.restore() in the HTTP protocol. This completes the pairing with proto.redirect() that occurs in the @wireprotocommand functions. (But since the SSH protocol has a no-op redirect(), it doesn't bother calling restore() because it would also be a no-op.) Having the disconnect between these paired calls is very confusing. Knowing that you must use proto.redirect() if returning a "pushres" or a "pusherr" is even wonkier. We replace this confusing code with our new context manager for [maybe] capturing output. The "pushres" and "pusherr" types have gained an "output" argument to their constructor and an attribute to hold captured data. The HTTP protocol now retrieves output from these objects. .. api:: ``wireproto.pushres`` and ``wireproto.pusherr`` now explicitly track stdio output. Differential Revision: https://phab.mercurial-scm.org/D2082
author Gregory Szorc <gregory.szorc@gmail.com>
date Wed, 07 Feb 2018 20:19:06 -0800
parents 2ad145fbde54
children 957e773614d0
comparison
equal deleted inserted replaced
36066:2ad145fbde54 36067:caca3ac2ac04
508 class pushres(object): 508 class pushres(object):
509 """wireproto reply: success with simple integer return 509 """wireproto reply: success with simple integer return
510 510
511 The call was successful and returned an integer contained in `self.res`. 511 The call was successful and returned an integer contained in `self.res`.
512 """ 512 """
513 def __init__(self, res): 513 def __init__(self, res, output):
514 self.res = res 514 self.res = res
515 self.output = output
515 516
516 class pusherr(object): 517 class pusherr(object):
517 """wireproto reply: failure 518 """wireproto reply: failure
518 519
519 The call failed. The `self.res` attribute contains the error message. 520 The call failed. The `self.res` attribute contains the error message.
520 """ 521 """
521 def __init__(self, res): 522 def __init__(self, res, output):
522 self.res = res 523 self.res = res
524 self.output = output
523 525
524 class ooberror(object): 526 class ooberror(object):
525 """wireproto reply: failure of a batch of operation 527 """wireproto reply: failure of a batch of operation
526 528
527 Something failed during a batch call. The error message is stored in 529 Something failed during a batch call. The error message is stored in
995 997
996 @wireprotocommand('unbundle', 'heads') 998 @wireprotocommand('unbundle', 'heads')
997 def unbundle(repo, proto, heads): 999 def unbundle(repo, proto, heads):
998 their_heads = decodelist(heads) 1000 their_heads = decodelist(heads)
999 1001
1000 try: 1002 with proto.mayberedirectstdio() as output:
1001 proto.redirect()
1002
1003 exchange.check_heads(repo, their_heads, 'preparing changes')
1004
1005 # write bundle data to temporary file because it can be big
1006 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1007 fp = os.fdopen(fd, pycompat.sysstr('wb+'))
1008 r = 0
1009 try: 1003 try:
1010 proto.getfile(fp) 1004 exchange.check_heads(repo, their_heads, 'preparing changes')
1011 fp.seek(0) 1005
1012 gen = exchange.readbundle(repo.ui, fp, None) 1006 # write bundle data to temporary file because it can be big
1013 if (isinstance(gen, changegroupmod.cg1unpacker) 1007 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1014 and not bundle1allowed(repo, 'push')): 1008 fp = os.fdopen(fd, pycompat.sysstr('wb+'))
1015 if proto.name == 'http': 1009 r = 0
1016 # need to special case http because stderr do not get to
1017 # the http client on failed push so we need to abuse some
1018 # other error type to make sure the message get to the
1019 # user.
1020 return ooberror(bundle2required)
1021 raise error.Abort(bundle2requiredmain,
1022 hint=bundle2requiredhint)
1023
1024 r = exchange.unbundle(repo, gen, their_heads, 'serve',
1025 proto._client())
1026 if util.safehasattr(r, 'addpart'):
1027 # The return looks streamable, we are in the bundle2 case and
1028 # should return a stream.
1029 return streamres_legacy(gen=r.getchunks())
1030 return pushres(r)
1031
1032 finally:
1033 fp.close()
1034 os.unlink(tempname)
1035
1036 except (error.BundleValueError, error.Abort, error.PushRaced) as exc:
1037 # handle non-bundle2 case first
1038 if not getattr(exc, 'duringunbundle2', False):
1039 try: 1010 try:
1040 raise 1011 proto.getfile(fp)
1041 except error.Abort: 1012 fp.seek(0)
1042 # The old code we moved used util.stderr directly. 1013 gen = exchange.readbundle(repo.ui, fp, None)
1043 # We did not change it to minimise code change. 1014 if (isinstance(gen, changegroupmod.cg1unpacker)
1044 # This need to be moved to something proper. 1015 and not bundle1allowed(repo, 'push')):
1045 # Feel free to do it. 1016 if proto.name == 'http':
1046 util.stderr.write("abort: %s\n" % exc) 1017 # need to special case http because stderr do not get to
1018 # the http client on failed push so we need to abuse
1019 # some other error type to make sure the message get to
1020 # the user.
1021 return ooberror(bundle2required)
1022 raise error.Abort(bundle2requiredmain,
1023 hint=bundle2requiredhint)
1024
1025 r = exchange.unbundle(repo, gen, their_heads, 'serve',
1026 proto._client())
1027 if util.safehasattr(r, 'addpart'):
1028 # The return looks streamable, we are in the bundle2 case
1029 # and should return a stream.
1030 return streamres_legacy(gen=r.getchunks())
1031 return pushres(r, output.getvalue() if output else '')
1032
1033 finally:
1034 fp.close()
1035 os.unlink(tempname)
1036
1037 except (error.BundleValueError, error.Abort, error.PushRaced) as exc:
1038 # handle non-bundle2 case first
1039 if not getattr(exc, 'duringunbundle2', False):
1040 try:
1041 raise
1042 except error.Abort:
1043 # The old code we moved used util.stderr directly.
1044 # We did not change it to minimise code change.
1045 # This need to be moved to something proper.
1046 # Feel free to do it.
1047 util.stderr.write("abort: %s\n" % exc)
1048 if exc.hint is not None:
1049 util.stderr.write("(%s)\n" % exc.hint)
1050 return pushres(0, output.getvalue() if output else '')
1051 except error.PushRaced:
1052 return pusherr(str(exc),
1053 output.getvalue() if output else '')
1054
1055 bundler = bundle2.bundle20(repo.ui)
1056 for out in getattr(exc, '_bundle2salvagedoutput', ()):
1057 bundler.addpart(out)
1058 try:
1059 try:
1060 raise
1061 except error.PushkeyFailed as exc:
1062 # check client caps
1063 remotecaps = getattr(exc, '_replycaps', None)
1064 if (remotecaps is not None
1065 and 'pushkey' not in remotecaps.get('error', ())):
1066 # no support remote side, fallback to Abort handler.
1067 raise
1068 part = bundler.newpart('error:pushkey')
1069 part.addparam('in-reply-to', exc.partid)
1070 if exc.namespace is not None:
1071 part.addparam('namespace', exc.namespace,
1072 mandatory=False)
1073 if exc.key is not None:
1074 part.addparam('key', exc.key, mandatory=False)
1075 if exc.new is not None:
1076 part.addparam('new', exc.new, mandatory=False)
1077 if exc.old is not None:
1078 part.addparam('old', exc.old, mandatory=False)
1079 if exc.ret is not None:
1080 part.addparam('ret', exc.ret, mandatory=False)
1081 except error.BundleValueError as exc:
1082 errpart = bundler.newpart('error:unsupportedcontent')
1083 if exc.parttype is not None:
1084 errpart.addparam('parttype', exc.parttype)
1085 if exc.params:
1086 errpart.addparam('params', '\0'.join(exc.params))
1087 except error.Abort as exc:
1088 manargs = [('message', str(exc))]
1089 advargs = []
1047 if exc.hint is not None: 1090 if exc.hint is not None:
1048 util.stderr.write("(%s)\n" % exc.hint) 1091 advargs.append(('hint', exc.hint))
1049 return pushres(0) 1092 bundler.addpart(bundle2.bundlepart('error:abort',
1050 except error.PushRaced: 1093 manargs, advargs))
1051 return pusherr(str(exc)) 1094 except error.PushRaced as exc:
1052 1095 bundler.newpart('error:pushraced', [('message', str(exc))])
1053 bundler = bundle2.bundle20(repo.ui) 1096 return streamres_legacy(gen=bundler.getchunks())
1054 for out in getattr(exc, '_bundle2salvagedoutput', ()):
1055 bundler.addpart(out)
1056 try:
1057 try:
1058 raise
1059 except error.PushkeyFailed as exc:
1060 # check client caps
1061 remotecaps = getattr(exc, '_replycaps', None)
1062 if (remotecaps is not None
1063 and 'pushkey' not in remotecaps.get('error', ())):
1064 # no support remote side, fallback to Abort handler.
1065 raise
1066 part = bundler.newpart('error:pushkey')
1067 part.addparam('in-reply-to', exc.partid)
1068 if exc.namespace is not None:
1069 part.addparam('namespace', exc.namespace, mandatory=False)
1070 if exc.key is not None:
1071 part.addparam('key', exc.key, mandatory=False)
1072 if exc.new is not None:
1073 part.addparam('new', exc.new, mandatory=False)
1074 if exc.old is not None:
1075 part.addparam('old', exc.old, mandatory=False)
1076 if exc.ret is not None:
1077 part.addparam('ret', exc.ret, mandatory=False)
1078 except error.BundleValueError as exc:
1079 errpart = bundler.newpart('error:unsupportedcontent')
1080 if exc.parttype is not None:
1081 errpart.addparam('parttype', exc.parttype)
1082 if exc.params:
1083 errpart.addparam('params', '\0'.join(exc.params))
1084 except error.Abort as exc:
1085 manargs = [('message', str(exc))]
1086 advargs = []
1087 if exc.hint is not None:
1088 advargs.append(('hint', exc.hint))
1089 bundler.addpart(bundle2.bundlepart('error:abort',
1090 manargs, advargs))
1091 except error.PushRaced as exc:
1092 bundler.newpart('error:pushraced', [('message', str(exc))])
1093 return streamres_legacy(gen=bundler.getchunks())