Mercurial > public > mercurial-scm > hg-stable
comparison mercurial/wireproto.py @ 36105: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
36104:2ad145fbde54 | 36105: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()) |