mercurial/hgweb/webcommands.py
changeset 5591 08887121a652
child 5595 b95b2525c6e8
equal deleted inserted replaced
5590:05451f6b5f07 5591:08887121a652
       
     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, tempfile, errno, os, sys
       
     9 from mercurial import revlog, util, streamclone
       
    10 from mercurial.i18n import gettext as _
       
    11 from mercurial.node import *
       
    12 from common import staticfile
       
    13 
       
    14 def log(web, req):
       
    15     if req.form.has_key('file') and req.form['file'][0]:
       
    16         filelog(web, req)
       
    17     else:
       
    18         changelog(web, req)
       
    19 
       
    20 def file(web, req):
       
    21     path = web.cleanpath(req.form.get('file', [''])[0])
       
    22     if path:
       
    23         try:
       
    24             req.write(web.filerevision(web.filectx(req)))
       
    25             return
       
    26         except revlog.LookupError:
       
    27             pass
       
    28 
       
    29     req.write(web.manifest(web.changectx(req), path))
       
    30 
       
    31 def changelog(web, req, shortlog = False):
       
    32     if req.form.has_key('node'):
       
    33         ctx = web.changectx(req)
       
    34     else:
       
    35         if req.form.has_key('rev'):
       
    36             hi = req.form['rev'][0]
       
    37         else:
       
    38             hi = web.repo.changelog.count() - 1
       
    39         try:
       
    40             ctx = web.repo.changectx(hi)
       
    41         except hg.RepoError:
       
    42             req.write(web.search(hi)) # XXX redirect to 404 page?
       
    43             return
       
    44 
       
    45     req.write(web.changelog(ctx, shortlog = shortlog))
       
    46 
       
    47 def shortlog(web, req):
       
    48     changelog(web, req, shortlog = True)
       
    49 
       
    50 def changeset(web, req):
       
    51     req.write(web.changeset(web.changectx(req)))
       
    52 
       
    53 rev = changeset
       
    54 
       
    55 def manifest(web, req):
       
    56     req.write(web.manifest(web.changectx(req),
       
    57                            web.cleanpath(req.form['path'][0])))
       
    58 
       
    59 def tags(web, req):
       
    60     req.write(web.tags())
       
    61 
       
    62 def summary(web, req):
       
    63     req.write(web.summary())
       
    64 
       
    65 def filediff(web, req):
       
    66     req.write(web.filediff(web.filectx(req)))
       
    67 
       
    68 diff = filediff
       
    69 
       
    70 def annotate(web, req):
       
    71     req.write(web.fileannotate(web.filectx(req)))
       
    72 
       
    73 def filelog(web, req):
       
    74     req.write(web.filelog(web.filectx(req)))
       
    75 
       
    76 def lookup(web, req):
       
    77     try:
       
    78         r = hex(web.repo.lookup(req.form['key'][0]))
       
    79         success = 1
       
    80     except Exception,inst:
       
    81         r = str(inst)
       
    82         success = 0
       
    83     resp = "%s %s\n" % (success, r)
       
    84     req.httphdr("application/mercurial-0.1", length=len(resp))
       
    85     req.write(resp)
       
    86 
       
    87 def heads(web, req):
       
    88     resp = " ".join(map(hex, web.repo.heads())) + "\n"
       
    89     req.httphdr("application/mercurial-0.1", length=len(resp))
       
    90     req.write(resp)
       
    91 
       
    92 def branches(web, req):
       
    93     nodes = []
       
    94     if req.form.has_key('nodes'):
       
    95         nodes = map(bin, req.form['nodes'][0].split(" "))
       
    96     resp = cStringIO.StringIO()
       
    97     for b in web.repo.branches(nodes):
       
    98         resp.write(" ".join(map(hex, b)) + "\n")
       
    99     resp = resp.getvalue()
       
   100     req.httphdr("application/mercurial-0.1", length=len(resp))
       
   101     req.write(resp)
       
   102 
       
   103 def between(web, req):
       
   104     if req.form.has_key('pairs'):
       
   105         pairs = [map(bin, p.split("-"))
       
   106                  for p in req.form['pairs'][0].split(" ")]
       
   107     resp = cStringIO.StringIO()
       
   108     for b in web.repo.between(pairs):
       
   109         resp.write(" ".join(map(hex, b)) + "\n")
       
   110     resp = resp.getvalue()
       
   111     req.httphdr("application/mercurial-0.1", length=len(resp))
       
   112     req.write(resp)
       
   113 
       
   114 def changegroup(web, req):
       
   115     req.httphdr("application/mercurial-0.1")
       
   116     nodes = []
       
   117     if not web.allowpull:
       
   118         return
       
   119 
       
   120     if req.form.has_key('roots'):
       
   121         nodes = map(bin, req.form['roots'][0].split(" "))
       
   122 
       
   123     z = zlib.compressobj()
       
   124     f = web.repo.changegroup(nodes, 'serve')
       
   125     while 1:
       
   126         chunk = f.read(4096)
       
   127         if not chunk:
       
   128             break
       
   129         req.write(z.compress(chunk))
       
   130 
       
   131     req.write(z.flush())
       
   132 
       
   133 def changegroupsubset(web, req):
       
   134     req.httphdr("application/mercurial-0.1")
       
   135     bases = []
       
   136     heads = []
       
   137     if not web.allowpull:
       
   138         return
       
   139 
       
   140     if req.form.has_key('bases'):
       
   141         bases = [bin(x) for x in req.form['bases'][0].split(' ')]
       
   142     if req.form.has_key('heads'):
       
   143         heads = [bin(x) for x in req.form['heads'][0].split(' ')]
       
   144 
       
   145     z = zlib.compressobj()
       
   146     f = web.repo.changegroupsubset(bases, heads, 'serve')
       
   147     while 1:
       
   148         chunk = f.read(4096)
       
   149         if not chunk:
       
   150             break
       
   151         req.write(z.compress(chunk))
       
   152 
       
   153     req.write(z.flush())
       
   154 
       
   155 def archive(web, req):
       
   156     type_ = req.form['type'][0]
       
   157     allowed = web.configlist("web", "allow_archive")
       
   158     if (type_ in web.archives and (type_ in allowed or
       
   159         web.configbool("web", "allow" + type_, False))):
       
   160         web.archive(req, req.form['node'][0], type_)
       
   161         return
       
   162 
       
   163     req.respond(400, web.t('error',
       
   164                            error='Unsupported archive type: %s' % type_))
       
   165 
       
   166 def static(web, req):
       
   167     fname = req.form['file'][0]
       
   168     # a repo owner may set web.static in .hg/hgrc to get any file
       
   169     # readable by the user running the CGI script
       
   170     static = web.config("web", "static",
       
   171                         os.path.join(web.templatepath, "static"),
       
   172                         untrusted=False)
       
   173     req.write(staticfile(static, fname, req))
       
   174 
       
   175 def capabilities(web, req):
       
   176     caps = ['lookup', 'changegroupsubset']
       
   177     if web.configbool('server', 'uncompressed'):
       
   178         caps.append('stream=%d' % web.repo.changelog.version)
       
   179     # XXX: make configurable and/or share code with do_unbundle:
       
   180     unbundleversions = ['HG10GZ', 'HG10BZ', 'HG10UN']
       
   181     if unbundleversions:
       
   182         caps.append('unbundle=%s' % ','.join(unbundleversions))
       
   183     resp = ' '.join(caps)
       
   184     req.httphdr("application/mercurial-0.1", length=len(resp))
       
   185     req.write(resp)
       
   186 
       
   187 def unbundle(web, req):
       
   188     def bail(response, headers={}):
       
   189         length = int(req.env['CONTENT_LENGTH'])
       
   190         for s in util.filechunkiter(req, limit=length):
       
   191             # drain incoming bundle, else client will not see
       
   192             # response when run outside cgi script
       
   193             pass
       
   194         req.httphdr("application/mercurial-0.1", headers=headers)
       
   195         req.write('0\n')
       
   196         req.write(response)
       
   197 
       
   198     # require ssl by default, auth info cannot be sniffed and
       
   199     # replayed
       
   200     ssl_req = web.configbool('web', 'push_ssl', True)
       
   201     if ssl_req:
       
   202         if req.env.get('wsgi.url_scheme') != 'https':
       
   203             bail(_('ssl required\n'))
       
   204             return
       
   205         proto = 'https'
       
   206     else:
       
   207         proto = 'http'
       
   208 
       
   209     # do not allow push unless explicitly allowed
       
   210     if not web.check_perm(req, 'push', False):
       
   211         bail(_('push not authorized\n'),
       
   212              headers={'status': '401 Unauthorized'})
       
   213         return
       
   214 
       
   215     their_heads = req.form['heads'][0].split(' ')
       
   216 
       
   217     def check_heads():
       
   218         heads = map(hex, web.repo.heads())
       
   219         return their_heads == [hex('force')] or their_heads == heads
       
   220 
       
   221     # fail early if possible
       
   222     if not check_heads():
       
   223         bail(_('unsynced changes\n'))
       
   224         return
       
   225 
       
   226     req.httphdr("application/mercurial-0.1")
       
   227 
       
   228     # do not lock repo until all changegroup data is
       
   229     # streamed. save to temporary file.
       
   230 
       
   231     fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
       
   232     fp = os.fdopen(fd, 'wb+')
       
   233     try:
       
   234         length = int(req.env['CONTENT_LENGTH'])
       
   235         for s in util.filechunkiter(req, limit=length):
       
   236             fp.write(s)
       
   237 
       
   238         try:
       
   239             lock = web.repo.lock()
       
   240             try:
       
   241                 if not check_heads():
       
   242                     req.write('0\n')
       
   243                     req.write(_('unsynced changes\n'))
       
   244                     return
       
   245 
       
   246                 fp.seek(0)
       
   247                 header = fp.read(6)
       
   248                 if not header.startswith("HG"):
       
   249                     # old client with uncompressed bundle
       
   250                     def generator(f):
       
   251                         yield header
       
   252                         for chunk in f:
       
   253                             yield chunk
       
   254                 elif not header.startswith("HG10"):
       
   255                     req.write("0\n")
       
   256                     req.write(_("unknown bundle version\n"))
       
   257                     return
       
   258                 elif header == "HG10GZ":
       
   259                     def generator(f):
       
   260                         zd = zlib.decompressobj()
       
   261                         for chunk in f:
       
   262                             yield zd.decompress(chunk)
       
   263                 elif header == "HG10BZ":
       
   264                     def generator(f):
       
   265                         zd = bz2.BZ2Decompressor()
       
   266                         zd.decompress("BZ")
       
   267                         for chunk in f:
       
   268                             yield zd.decompress(chunk)
       
   269                 elif header == "HG10UN":
       
   270                     def generator(f):
       
   271                         for chunk in f:
       
   272                             yield chunk
       
   273                 else:
       
   274                     req.write("0\n")
       
   275                     req.write(_("unknown bundle compression type\n"))
       
   276                     return
       
   277                 gen = generator(util.filechunkiter(fp, 4096))
       
   278 
       
   279                 # send addchangegroup output to client
       
   280 
       
   281                 old_stdout = sys.stdout
       
   282                 sys.stdout = cStringIO.StringIO()
       
   283 
       
   284                 try:
       
   285                     url = 'remote:%s:%s' % (proto,
       
   286                                             req.env.get('REMOTE_HOST', ''))
       
   287                     try:
       
   288                         ret = web.repo.addchangegroup(
       
   289                                     util.chunkbuffer(gen), 'serve', url)
       
   290                     except util.Abort, inst:
       
   291                         sys.stdout.write("abort: %s\n" % inst)
       
   292                         ret = 0
       
   293                 finally:
       
   294                     val = sys.stdout.getvalue()
       
   295                     sys.stdout = old_stdout
       
   296                 req.write('%d\n' % ret)
       
   297                 req.write(val)
       
   298             finally:
       
   299                 del lock
       
   300         except (OSError, IOError), inst:
       
   301             req.write('0\n')
       
   302             filename = getattr(inst, 'filename', '')
       
   303             # Don't send our filesystem layout to the client
       
   304             if filename.startswith(web.repo.root):
       
   305                 filename = filename[len(web.repo.root)+1:]
       
   306             else:
       
   307                 filename = ''
       
   308             error = getattr(inst, 'strerror', 'Unknown error')
       
   309             if inst.errno == errno.ENOENT:
       
   310                 code = 404
       
   311             else:
       
   312                 code = 500
       
   313             req.respond(code, '%s: %s\n' % (error, filename))
       
   314     finally:
       
   315         fp.close()
       
   316         os.unlink(tempname)
       
   317 
       
   318 def stream_out(web, req):
       
   319     req.httphdr("application/mercurial-0.1")
       
   320     streamclone.stream_out(web.repo, req, untrusted=True)