mercurial/hgweb/protocol.py
changeset 5598 d534ba1c4eb4
child 5915 d0576d065993
equal deleted inserted replaced
5597:e7f99a3ed008 5598:d534ba1c4eb4
       
     1 #
       
     2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
       
     3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
       
     4 #
       
     5 # This software may be used and distributed according to the terms
       
     6 # of the GNU General Public License, incorporated herein by reference.
       
     7 
       
     8 import cStringIO, zlib, bz2, tempfile, errno, os, sys
       
     9 from mercurial import util, streamclone
       
    10 from mercurial.i18n import gettext as _
       
    11 from mercurial.node import *
       
    12 
       
    13 def lookup(web, req):
       
    14     try:
       
    15         r = hex(web.repo.lookup(req.form['key'][0]))
       
    16         success = 1
       
    17     except Exception,inst:
       
    18         r = str(inst)
       
    19         success = 0
       
    20     resp = "%s %s\n" % (success, r)
       
    21     req.httphdr("application/mercurial-0.1", length=len(resp))
       
    22     req.write(resp)
       
    23 
       
    24 def heads(web, req):
       
    25     resp = " ".join(map(hex, web.repo.heads())) + "\n"
       
    26     req.httphdr("application/mercurial-0.1", length=len(resp))
       
    27     req.write(resp)
       
    28 
       
    29 def branches(web, req):
       
    30     nodes = []
       
    31     if req.form.has_key('nodes'):
       
    32         nodes = map(bin, req.form['nodes'][0].split(" "))
       
    33     resp = cStringIO.StringIO()
       
    34     for b in web.repo.branches(nodes):
       
    35         resp.write(" ".join(map(hex, b)) + "\n")
       
    36     resp = resp.getvalue()
       
    37     req.httphdr("application/mercurial-0.1", length=len(resp))
       
    38     req.write(resp)
       
    39 
       
    40 def between(web, req):
       
    41     if req.form.has_key('pairs'):
       
    42         pairs = [map(bin, p.split("-"))
       
    43                  for p in req.form['pairs'][0].split(" ")]
       
    44     resp = cStringIO.StringIO()
       
    45     for b in web.repo.between(pairs):
       
    46         resp.write(" ".join(map(hex, b)) + "\n")
       
    47     resp = resp.getvalue()
       
    48     req.httphdr("application/mercurial-0.1", length=len(resp))
       
    49     req.write(resp)
       
    50 
       
    51 def changegroup(web, req):
       
    52     req.httphdr("application/mercurial-0.1")
       
    53     nodes = []
       
    54     if not web.allowpull:
       
    55         return
       
    56 
       
    57     if req.form.has_key('roots'):
       
    58         nodes = map(bin, req.form['roots'][0].split(" "))
       
    59 
       
    60     z = zlib.compressobj()
       
    61     f = web.repo.changegroup(nodes, 'serve')
       
    62     while 1:
       
    63         chunk = f.read(4096)
       
    64         if not chunk:
       
    65             break
       
    66         req.write(z.compress(chunk))
       
    67 
       
    68     req.write(z.flush())
       
    69 
       
    70 def changegroupsubset(web, req):
       
    71     req.httphdr("application/mercurial-0.1")
       
    72     bases = []
       
    73     heads = []
       
    74     if not web.allowpull:
       
    75         return
       
    76 
       
    77     if req.form.has_key('bases'):
       
    78         bases = [bin(x) for x in req.form['bases'][0].split(' ')]
       
    79     if req.form.has_key('heads'):
       
    80         heads = [bin(x) for x in req.form['heads'][0].split(' ')]
       
    81 
       
    82     z = zlib.compressobj()
       
    83     f = web.repo.changegroupsubset(bases, heads, 'serve')
       
    84     while 1:
       
    85         chunk = f.read(4096)
       
    86         if not chunk:
       
    87             break
       
    88         req.write(z.compress(chunk))
       
    89 
       
    90     req.write(z.flush())
       
    91 
       
    92 def capabilities(web, req):
       
    93     caps = ['lookup', 'changegroupsubset']
       
    94     if web.configbool('server', 'uncompressed'):
       
    95         caps.append('stream=%d' % web.repo.changelog.version)
       
    96     # XXX: make configurable and/or share code with do_unbundle:
       
    97     unbundleversions = ['HG10GZ', 'HG10BZ', 'HG10UN']
       
    98     if unbundleversions:
       
    99         caps.append('unbundle=%s' % ','.join(unbundleversions))
       
   100     resp = ' '.join(caps)
       
   101     req.httphdr("application/mercurial-0.1", length=len(resp))
       
   102     req.write(resp)
       
   103 
       
   104 def unbundle(web, req):
       
   105     def bail(response, headers={}):
       
   106         length = int(req.env['CONTENT_LENGTH'])
       
   107         for s in util.filechunkiter(req, limit=length):
       
   108             # drain incoming bundle, else client will not see
       
   109             # response when run outside cgi script
       
   110             pass
       
   111         req.httphdr("application/mercurial-0.1", headers=headers)
       
   112         req.write('0\n')
       
   113         req.write(response)
       
   114 
       
   115     # require ssl by default, auth info cannot be sniffed and
       
   116     # replayed
       
   117     ssl_req = web.configbool('web', 'push_ssl', True)
       
   118     if ssl_req:
       
   119         if req.env.get('wsgi.url_scheme') != 'https':
       
   120             bail(_('ssl required\n'))
       
   121             return
       
   122         proto = 'https'
       
   123     else:
       
   124         proto = 'http'
       
   125 
       
   126     # do not allow push unless explicitly allowed
       
   127     if not web.check_perm(req, 'push', False):
       
   128         bail(_('push not authorized\n'),
       
   129              headers={'status': '401 Unauthorized'})
       
   130         return
       
   131 
       
   132     their_heads = req.form['heads'][0].split(' ')
       
   133 
       
   134     def check_heads():
       
   135         heads = map(hex, web.repo.heads())
       
   136         return their_heads == [hex('force')] or their_heads == heads
       
   137 
       
   138     # fail early if possible
       
   139     if not check_heads():
       
   140         bail(_('unsynced changes\n'))
       
   141         return
       
   142 
       
   143     req.httphdr("application/mercurial-0.1")
       
   144 
       
   145     # do not lock repo until all changegroup data is
       
   146     # streamed. save to temporary file.
       
   147 
       
   148     fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
       
   149     fp = os.fdopen(fd, 'wb+')
       
   150     try:
       
   151         length = int(req.env['CONTENT_LENGTH'])
       
   152         for s in util.filechunkiter(req, limit=length):
       
   153             fp.write(s)
       
   154 
       
   155         try:
       
   156             lock = web.repo.lock()
       
   157             try:
       
   158                 if not check_heads():
       
   159                     req.write('0\n')
       
   160                     req.write(_('unsynced changes\n'))
       
   161                     return
       
   162 
       
   163                 fp.seek(0)
       
   164                 header = fp.read(6)
       
   165                 if not header.startswith("HG"):
       
   166                     # old client with uncompressed bundle
       
   167                     def generator(f):
       
   168                         yield header
       
   169                         for chunk in f:
       
   170                             yield chunk
       
   171                 elif not header.startswith("HG10"):
       
   172                     req.write("0\n")
       
   173                     req.write(_("unknown bundle version\n"))
       
   174                     return
       
   175                 elif header == "HG10GZ":
       
   176                     def generator(f):
       
   177                         zd = zlib.decompressobj()
       
   178                         for chunk in f:
       
   179                             yield zd.decompress(chunk)
       
   180                 elif header == "HG10BZ":
       
   181                     def generator(f):
       
   182                         zd = bz2.BZ2Decompressor()
       
   183                         zd.decompress("BZ")
       
   184                         for chunk in f:
       
   185                             yield zd.decompress(chunk)
       
   186                 elif header == "HG10UN":
       
   187                     def generator(f):
       
   188                         for chunk in f:
       
   189                             yield chunk
       
   190                 else:
       
   191                     req.write("0\n")
       
   192                     req.write(_("unknown bundle compression type\n"))
       
   193                     return
       
   194                 gen = generator(util.filechunkiter(fp, 4096))
       
   195 
       
   196                 # send addchangegroup output to client
       
   197 
       
   198                 old_stdout = sys.stdout
       
   199                 sys.stdout = cStringIO.StringIO()
       
   200 
       
   201                 try:
       
   202                     url = 'remote:%s:%s' % (proto,
       
   203                                             req.env.get('REMOTE_HOST', ''))
       
   204                     try:
       
   205                         ret = web.repo.addchangegroup(
       
   206                                     util.chunkbuffer(gen), 'serve', url)
       
   207                     except util.Abort, inst:
       
   208                         sys.stdout.write("abort: %s\n" % inst)
       
   209                         ret = 0
       
   210                 finally:
       
   211                     val = sys.stdout.getvalue()
       
   212                     sys.stdout = old_stdout
       
   213                 req.write('%d\n' % ret)
       
   214                 req.write(val)
       
   215             finally:
       
   216                 del lock
       
   217         except (OSError, IOError), inst:
       
   218             req.write('0\n')
       
   219             filename = getattr(inst, 'filename', '')
       
   220             # Don't send our filesystem layout to the client
       
   221             if filename.startswith(web.repo.root):
       
   222                 filename = filename[len(web.repo.root)+1:]
       
   223             else:
       
   224                 filename = ''
       
   225             error = getattr(inst, 'strerror', 'Unknown error')
       
   226             if inst.errno == errno.ENOENT:
       
   227                 code = 404
       
   228             else:
       
   229                 code = 500
       
   230             req.respond(code, '%s: %s\n' % (error, filename))
       
   231     finally:
       
   232         fp.close()
       
   233         os.unlink(tempname)
       
   234 
       
   235 def stream_out(web, req):
       
   236     req.httphdr("application/mercurial-0.1")
       
   237     streamclone.stream_out(web.repo, req, untrusted=True)