diff -r 387af9e5df70 -r 6169d95dce3b mercurial/httppeer.py --- a/mercurial/httppeer.py Thu May 03 14:43:25 2018 +0900 +++ b/mercurial/httppeer.py Mon Apr 30 15:32:11 2018 -0700 @@ -328,13 +328,24 @@ return res +class RedirectedRepoError(error.RepoError): + def __init__(self, msg, respurl): + super(RedirectedRepoError, self).__init__(msg) + self.respurl = respurl + def parsev1commandresponse(ui, baseurl, requrl, qs, resp, compressible, allowcbor=False): # record the url we got redirected to + redirected = False respurl = pycompat.bytesurl(resp.geturl()) if respurl.endswith(qs): respurl = respurl[:-len(qs)] + qsdropped = False + else: + qsdropped = True + if baseurl.rstrip('/') != respurl.rstrip('/'): + redirected = True if not ui.quiet: ui.warn(_('real URL is %s\n') % respurl) @@ -351,10 +362,16 @@ # application/hg-changegroup. We don't support such old servers. if not proto.startswith('application/mercurial-'): ui.debug("requested URL: '%s'\n" % util.hidepassword(requrl)) - raise error.RepoError( - _("'%s' does not appear to be an hg repository:\n" - "---%%<--- (%s)\n%s\n---%%<---\n") - % (safeurl, proto or 'no content-type', resp.read(1024))) + msg = _("'%s' does not appear to be an hg repository:\n" + "---%%<--- (%s)\n%s\n---%%<---\n") % ( + safeurl, proto or 'no content-type', resp.read(1024)) + + # Some servers may strip the query string from the redirect. We + # raise a special error type so callers can react to this specially. + if redirected and qsdropped: + raise RedirectedRepoError(msg, respurl) + else: + raise error.RepoError(msg) try: subtype = proto.split('-', 1)[1] @@ -434,8 +451,6 @@ # End of ipeercommands interface. - # look up capabilities only when needed - def _callstream(self, cmd, _compressible=False, **args): args = pycompat.byteskwargs(args) @@ -853,12 +868,32 @@ req, requrl, qs = makev1commandrequest(ui, requestbuilder, caps, capable, url, 'capabilities', args) - resp = sendrequest(ui, opener, req) - respurl, ct, resp = parsev1commandresponse(ui, url, requrl, qs, resp, - compressible=False, - allowcbor=advertisev2) + # The server may redirect us to the repo root, stripping the + # ?cmd=capabilities query string from the URL. The server would likely + # return HTML in this case and ``parsev1commandresponse()`` would raise. + # We catch this special case and re-issue the capabilities request against + # the new URL. + # + # We should ideally not do this, as a redirect that drops the query + # string from the URL is arguably a server bug. (Garbage in, garbage out). + # However, Mercurial clients for several years appeared to handle this + # issue without behavior degradation. And according to issue 5860, it may + # be a longstanding bug in some server implementations. So we allow a + # redirect that drops the query string to "just work." + try: + respurl, ct, resp = parsev1commandresponse(ui, url, requrl, qs, resp, + compressible=False, + allowcbor=advertisev2) + except RedirectedRepoError as e: + req, requrl, qs = makev1commandrequest(ui, requestbuilder, caps, + capable, e.respurl, + 'capabilities', args) + resp = sendrequest(ui, opener, req) + respurl, ct, resp = parsev1commandresponse(ui, url, requrl, qs, resp, + compressible=False, + allowcbor=advertisev2) try: rawdata = resp.read()