--- a/contrib/win32/mercurial.iss Fri Jun 27 21:45:16 2008 -0500
+++ b/contrib/win32/mercurial.iss Wed Jul 02 09:31:13 2008 +0200
@@ -2,7 +2,7 @@
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
[Setup]
-AppCopyright=Copyright 2005-2007 Matt Mackall and others
+AppCopyright=Copyright 2005-2008 Matt Mackall and others
AppName=Mercurial
AppVerName=Mercurial snapshot
InfoAfterFile=contrib/win32/postinstall.txt
@@ -18,7 +18,7 @@
DefaultDirName={pf}\Mercurial
SourceDir=..\..
VersionInfoDescription=Mercurial distributed SCM
-VersionInfoCopyright=Copyright 2005-2007 Matt Mackall and others
+VersionInfoCopyright=Copyright 2005-2008 Matt Mackall and others
VersionInfoCompany=Matt Mackall and others
InternalCompressLevel=max
SolidCompression=true
--- a/mercurial/hgweb/hgweb_mod.py Fri Jun 27 21:45:16 2008 -0500
+++ b/mercurial/hgweb/hgweb_mod.py Wed Jul 02 09:31:13 2008 +0200
@@ -10,25 +10,17 @@
from mercurial.node import hex, nullid
from mercurial.repo import RepoError
from mercurial import mdiff, ui, hg, util, patch, hook
-from mercurial import revlog, templater, templatefilters, changegroup
+from mercurial import revlog, templater, templatefilters
from common import get_mtime, style_map, paritygen, countgen, ErrorResponse
from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
from request import wsgirequest
import webcommands, protocol, webutil
-shortcuts = {
- 'cl': [('cmd', ['changelog']), ('rev', None)],
- 'sl': [('cmd', ['shortlog']), ('rev', None)],
- 'cs': [('cmd', ['changeset']), ('node', None)],
- 'f': [('cmd', ['file']), ('filenode', None)],
- 'fl': [('cmd', ['filelog']), ('filenode', None)],
- 'fd': [('cmd', ['filediff']), ('node', None)],
- 'fa': [('cmd', ['annotate']), ('filenode', None)],
- 'mf': [('cmd', ['manifest']), ('manifest', None)],
- 'ca': [('cmd', ['archive']), ('node', None)],
- 'tags': [('cmd', ['tags'])],
- 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
- 'static': [('cmd', ['static']), ('file', None)]
+perms = {
+ 'changegroup': 'pull',
+ 'changegroupsubset': 'pull',
+ 'unbundle': 'push',
+ 'stream_out': 'pull',
}
class hgweb(object):
@@ -44,7 +36,6 @@
self.reponame = name
self.archives = 'zip', 'gz', 'bz2'
self.stripecount = 1
- self._capabilities = None
# a repo owner may set web.templates in .hg/hgrc to get any file
# readable by the user running the CGI script
self.templatepath = self.config("web", "templates",
@@ -76,18 +67,6 @@
self.maxfiles = int(self.config("web", "maxfiles", 10))
self.allowpull = self.configbool("web", "allowpull", True)
self.encoding = self.config("web", "encoding", util._encoding)
- self._capabilities = None
-
- def capabilities(self):
- if self._capabilities is not None:
- return self._capabilities
- caps = ['lookup', 'changegroupsubset']
- if self.configbool('server', 'uncompressed'):
- caps.append('stream=%d' % self.repo.changelog.version)
- if changegroup.bundlepriority:
- caps.append('unbundle=%s' % ','.join(changegroup.bundlepriority))
- self._capabilities = caps
- return caps
def run(self):
if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
@@ -97,22 +76,22 @@
def __call__(self, env, respond):
req = wsgirequest(env, respond)
- self.run_wsgi(req)
- return req
+ return self.run_wsgi(req)
def run_wsgi(self, req):
self.refresh()
- # expand form shortcuts
+ # process this if it's a protocol request
+ # protocol bits don't need to create any URLs
+ # and the clients always use the old URL structure
- for k in shortcuts.iterkeys():
- if k in req.form:
- for name, value in shortcuts[k]:
- if value is None:
- value = req.form[k]
- req.form[name] = value
- del req.form[k]
+ cmd = req.form.get('cmd', [''])[0]
+ if cmd and cmd in protocol.__all__:
+ if cmd in perms and not self.check_perm(req, perms[cmd]):
+ return []
+ method = getattr(protocol, cmd)
+ return method(self.repo, req)
# work with CGI variables to create coherent structure
# use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
@@ -145,8 +124,10 @@
cmd = cmd[style+1:]
# avoid accepting e.g. style parameter as command
- if hasattr(webcommands, cmd) or hasattr(protocol, cmd):
+ if hasattr(webcommands, cmd):
req.form['cmd'] = [cmd]
+ else:
+ cmd = ''
if args and args[0]:
node = args.pop(0)
@@ -164,14 +145,6 @@
req.form['node'] = [fn[:-len(ext)]]
req.form['type'] = [type_]
- # process this if it's a protocol request
-
- cmd = req.form.get('cmd', [''])[0]
- if cmd in protocol.__all__:
- method = getattr(protocol, cmd)
- method(self, req)
- return
-
# process the web interface request
try:
@@ -196,19 +169,20 @@
req.write(content)
del tmpl
+ return ''.join(content),
except revlog.LookupError, err:
req.respond(HTTP_NOT_FOUND, ctype)
msg = str(err)
if 'manifest' not in msg:
msg = 'revision not found: %s' % err.name
- req.write(tmpl('error', error=msg))
+ return ''.join(tmpl('error', error=msg)),
except (RepoError, revlog.RevlogError), inst:
req.respond(HTTP_SERVER_ERROR, ctype)
- req.write(tmpl('error', error=str(inst)))
+ return ''.join(tmpl('error', error=str(inst))),
except ErrorResponse, inst:
req.respond(inst.code, ctype)
- req.write(tmpl('error', error=inst.message))
+ return ''.join(tmpl('error', error=inst.message)),
def templater(self, req):
@@ -364,16 +338,39 @@
'zip': ('application/zip', 'zip', '.zip', None),
}
- def check_perm(self, req, op, default):
- '''check permission for operation based on user auth.
- return true if op allowed, else false.
- default is policy to use if no config given.'''
+ def check_perm(self, req, op):
+ '''Check permission for operation based on request data (including
+ authentication info. Return true if op allowed, else false.'''
+
+ def error(status, message):
+ req.respond(status, protocol.HGTYPE)
+ req.write('0\n%s\n' % message)
+
+ if op == 'pull':
+ return self.allowpull
+
+ # enforce that you can only push using POST requests
+ if req.env['REQUEST_METHOD'] != 'POST':
+ error('405 Method Not Allowed', 'push requires POST request')
+ return False
+
+ # require ssl by default for pushing, auth info cannot be sniffed
+ # and replayed
+ scheme = req.env.get('wsgi.url_scheme')
+ if self.configbool('web', 'push_ssl', True) and scheme != 'https':
+ error(HTTP_OK, 'ssl required')
+ return False
user = req.env.get('REMOTE_USER')
- deny = self.configlist('web', 'deny_' + op)
+ deny = self.configlist('web', 'deny_push')
if deny and (not user or deny == ['*'] or user in deny):
+ error('401 Unauthorized', 'push not authorized')
return False
- allow = self.configlist('web', 'allow_' + op)
- return (allow and (allow == ['*'] or user in allow)) or default
+ allow = self.configlist('web', 'allow_push')
+ result = allow and (allow == ['*'] or user in allow)
+ if not result:
+ error('401 Unauthorized', 'push not authorized')
+
+ return result
--- a/mercurial/hgweb/hgwebdir_mod.py Fri Jun 27 21:45:16 2008 -0500
+++ b/mercurial/hgweb/hgwebdir_mod.py Wed Jul 02 09:31:13 2008 +0200
@@ -70,8 +70,7 @@
def __call__(self, env, respond):
req = wsgirequest(env, respond)
- self.run_wsgi(req)
- return req
+ return self.run_wsgi(req)
def run_wsgi(self, req):
@@ -90,14 +89,12 @@
fname = virtual[7:]
else:
fname = req.form['static'][0]
- req.write(staticfile(static, fname, req))
- return
+ return staticfile(static, fname, req),
# top-level index
elif not virtual:
req.respond(HTTP_OK, ctype)
- req.write(self.makeindex(req, tmpl))
- return
+ return ''.join(self.makeindex(req, tmpl)),
# nested indexes and hgwebs
@@ -108,8 +105,7 @@
req.env['REPO_NAME'] = virtual
try:
repo = hg.repository(self.parentui, real)
- hgweb(repo).run_wsgi(req)
- return
+ return hgweb(repo).run_wsgi(req)
except IOError, inst:
msg = inst.strerror
raise ErrorResponse(HTTP_SERVER_ERROR, msg)
@@ -120,8 +116,7 @@
subdir = virtual + '/'
if [r for r in repos if r.startswith(subdir)]:
req.respond(HTTP_OK, ctype)
- req.write(self.makeindex(req, tmpl, subdir))
- return
+ return ''.join(self.makeindex(req, tmpl, subdir)),
up = virtual.rfind('/')
if up < 0:
@@ -130,11 +125,11 @@
# prefixes not found
req.respond(HTTP_NOT_FOUND, ctype)
- req.write(tmpl("notfound", repo=virtual))
+ return ''.join(tmpl("notfound", repo=virtual)),
except ErrorResponse, err:
req.respond(err.code, ctype)
- req.write(tmpl('error', error=err.message or ''))
+ return ''.join(tmpl('error', error=err.message or '')),
finally:
tmpl = None
--- a/mercurial/hgweb/protocol.py Fri Jun 27 21:45:16 2008 -0500
+++ b/mercurial/hgweb/protocol.py Wed Jul 02 09:31:13 2008 +0200
@@ -21,69 +21,65 @@
HGTYPE = 'application/mercurial-0.1'
-def lookup(web, req):
+def lookup(repo, req):
try:
- r = hex(web.repo.lookup(req.form['key'][0]))
+ r = hex(repo.lookup(req.form['key'][0]))
success = 1
except Exception,inst:
r = str(inst)
success = 0
resp = "%s %s\n" % (success, r)
req.respond(HTTP_OK, HGTYPE, length=len(resp))
- req.write(resp)
+ yield resp
-def heads(web, req):
- resp = " ".join(map(hex, web.repo.heads())) + "\n"
+def heads(repo, req):
+ resp = " ".join(map(hex, repo.heads())) + "\n"
req.respond(HTTP_OK, HGTYPE, length=len(resp))
- req.write(resp)
+ yield resp
-def branches(web, req):
+def branches(repo, req):
nodes = []
if 'nodes' in req.form:
nodes = map(bin, req.form['nodes'][0].split(" "))
resp = cStringIO.StringIO()
- for b in web.repo.branches(nodes):
+ for b in repo.branches(nodes):
resp.write(" ".join(map(hex, b)) + "\n")
resp = resp.getvalue()
req.respond(HTTP_OK, HGTYPE, length=len(resp))
- req.write(resp)
+ yield resp
-def between(web, req):
+def between(repo, req):
if 'pairs' in req.form:
pairs = [map(bin, p.split("-"))
for p in req.form['pairs'][0].split(" ")]
resp = cStringIO.StringIO()
- for b in web.repo.between(pairs):
+ for b in repo.between(pairs):
resp.write(" ".join(map(hex, b)) + "\n")
resp = resp.getvalue()
req.respond(HTTP_OK, HGTYPE, length=len(resp))
- req.write(resp)
+ yield resp
-def changegroup(web, req):
+def changegroup(repo, req):
req.respond(HTTP_OK, HGTYPE)
nodes = []
- if not web.allowpull:
- return
if 'roots' in req.form:
nodes = map(bin, req.form['roots'][0].split(" "))
z = zlib.compressobj()
- f = web.repo.changegroup(nodes, 'serve')
+ f = repo.changegroup(nodes, 'serve')
while 1:
chunk = f.read(4096)
if not chunk:
break
- req.write(z.compress(chunk))
+ yield z.compress(chunk)
- req.write(z.flush())
+ yield z.flush()
-def changegroupsubset(web, req):
+def changegroupsubset(repo, req):
req.respond(HTTP_OK, HGTYPE)
bases = []
heads = []
- if not web.allowpull:
- return
if 'bases' in req.form:
bases = [bin(x) for x in req.form['bases'][0].split(' ')]
@@ -91,67 +87,44 @@
heads = [bin(x) for x in req.form['heads'][0].split(' ')]
z = zlib.compressobj()
- f = web.repo.changegroupsubset(bases, heads, 'serve')
+ f = repo.changegroupsubset(bases, heads, 'serve')
while 1:
chunk = f.read(4096)
if not chunk:
break
- req.write(z.compress(chunk))
+ yield z.compress(chunk)
- req.write(z.flush())
+ yield z.flush()
-def capabilities(web, req):
- resp = ' '.join(web.capabilities())
- req.respond(HTTP_OK, HGTYPE, length=len(resp))
- req.write(resp)
+def capabilities(repo, req):
+ caps = ['lookup', 'changegroupsubset']
+ if repo.ui.configbool('server', 'uncompressed', untrusted=True):
+ caps.append('stream=%d' % repo.changelog.version)
+ if changegroupmod.bundlepriority:
+ caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority))
+ rsp = ' '.join(caps)
+ req.respond(HTTP_OK, HGTYPE, length=len(rsp))
+ yield rsp
-def unbundle(web, req):
+def unbundle(repo, req):
+
+ errorfmt = '0\n%s\n'
+ proto = req.env.get('wsgi.url_scheme') or 'http'
+ their_heads = req.form['heads'][0].split(' ')
- def bail(response, headers={}):
+ def check_heads():
+ heads = map(hex, repo.heads())
+ return their_heads == [hex('force')] or their_heads == heads
+
+ # fail early if possible
+ if not check_heads():
length = int(req.env.get('CONTENT_LENGTH', 0))
for s in util.filechunkiter(req, limit=length):
# drain incoming bundle, else client will not see
# response when run outside cgi script
pass
-
- status = headers.pop('status', HTTP_OK)
- req.header(headers.items())
- req.respond(status, HGTYPE)
- req.write('0\n')
- req.write(response)
-
- # enforce that you can only unbundle with POST requests
- if req.env['REQUEST_METHOD'] != 'POST':
- headers = {'status': '405 Method Not Allowed'}
- bail('unbundle requires POST request\n', headers)
- return
-
- # require ssl by default, auth info cannot be sniffed and
- # replayed
- ssl_req = web.configbool('web', 'push_ssl', True)
- if ssl_req:
- if req.env.get('wsgi.url_scheme') != 'https':
- bail('ssl required\n')
- return
- proto = 'https'
- else:
- proto = 'http'
-
- # do not allow push unless explicitly allowed
- if not web.check_perm(req, 'push', False):
- bail('push not authorized\n', headers={'status': '401 Unauthorized'})
- return
-
- their_heads = req.form['heads'][0].split(' ')
-
- def check_heads():
- heads = map(hex, web.repo.heads())
- return their_heads == [hex('force')] or their_heads == heads
-
- # fail early if possible
- if not check_heads():
- bail('unsynced changes\n')
- return
+ req.respond(HTTP_OK, HGTYPE)
+ return errorfmt % 'unsynced changes',
req.respond(HTTP_OK, HGTYPE)
@@ -166,12 +139,10 @@
fp.write(s)
try:
- lock = web.repo.lock()
+ lock = repo.lock()
try:
if not check_heads():
- req.write('0\n')
- req.write('unsynced changes\n')
- return
+ return errorfmt % 'unsynced changes',
fp.seek(0)
header = fp.read(6)
@@ -190,26 +161,23 @@
url = 'remote:%s:%s' % (proto,
req.env.get('REMOTE_HOST', ''))
try:
- ret = web.repo.addchangegroup(gen, 'serve', url)
+ ret = repo.addchangegroup(gen, 'serve', url)
except util.Abort, inst:
sys.stdout.write("abort: %s\n" % inst)
ret = 0
finally:
val = sys.stdout.getvalue()
sys.stdout, sys.stderr = oldio
- req.write('%d\n' % ret)
- req.write(val)
+ return '%d\n%s' % (ret, val),
finally:
del lock
except ValueError, inst:
- req.write('0\n')
- req.write(str(inst) + '\n')
+ return errorfmt % inst,
except (OSError, IOError), inst:
- req.write('0\n')
filename = getattr(inst, 'filename', '')
# Don't send our filesystem layout to the client
- if filename.startswith(web.repo.root):
- filename = filename[len(web.repo.root)+1:]
+ if filename.startswith(repo.root):
+ filename = filename[len(repo.root)+1:]
else:
filename = ''
error = getattr(inst, 'strerror', 'Unknown error')
@@ -218,13 +186,11 @@
else:
code = HTTP_SERVER_ERROR
req.respond(code)
- req.write('%s: %s\n' % (error, filename))
+ return '0\n%s: %s\n' % (error, filename),
finally:
fp.close()
os.unlink(tempname)
-def stream_out(web, req):
- if not web.allowpull:
- return
+def stream_out(repo, req):
req.respond(HTTP_OK, HGTYPE)
- streamclone.stream_out(web.repo, req, untrusted=True)
+ return streamclone.stream_out(repo, untrusted=True)
--- a/mercurial/hgweb/request.py Fri Jun 27 21:45:16 2008 -0500
+++ b/mercurial/hgweb/request.py Wed Jul 02 09:31:13 2008 +0200
@@ -9,6 +9,31 @@
import socket, cgi, errno
from common import ErrorResponse, statusmessage
+shortcuts = {
+ 'cl': [('cmd', ['changelog']), ('rev', None)],
+ 'sl': [('cmd', ['shortlog']), ('rev', None)],
+ 'cs': [('cmd', ['changeset']), ('node', None)],
+ 'f': [('cmd', ['file']), ('filenode', None)],
+ 'fl': [('cmd', ['filelog']), ('filenode', None)],
+ 'fd': [('cmd', ['filediff']), ('node', None)],
+ 'fa': [('cmd', ['annotate']), ('filenode', None)],
+ 'mf': [('cmd', ['manifest']), ('manifest', None)],
+ 'ca': [('cmd', ['archive']), ('node', None)],
+ 'tags': [('cmd', ['tags'])],
+ 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
+ 'static': [('cmd', ['static']), ('file', None)]
+}
+
+def expand(form):
+ for k in shortcuts.iterkeys():
+ if k in form:
+ for name, value in shortcuts[k]:
+ if value is None:
+ value = form[k]
+ form[name] = value
+ del form[k]
+ return form
+
class wsgirequest(object):
def __init__(self, wsgienv, start_response):
version = wsgienv['wsgi.version']
@@ -21,7 +46,7 @@
self.multiprocess = wsgienv['wsgi.multiprocess']
self.run_once = wsgienv['wsgi.run_once']
self.env = wsgienv
- self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
+ self.form = expand(cgi.parse(self.inp, self.env, keep_blank_values=1))
self._start_response = start_response
self.server_write = None
self.headers = []
--- a/mercurial/hgweb/server.py Fri Jun 27 21:45:16 2008 -0500
+++ b/mercurial/hgweb/server.py Wed Jul 02 09:31:13 2008 +0200
@@ -122,7 +122,8 @@
self.saved_headers = []
self.sent_headers = False
self.length = None
- self.server.application(env, self._start_response)
+ for chunk in self.server.application(env, self._start_response):
+ self._write(chunk)
def send_headers(self):
if not self.saved_status:
--- a/mercurial/httprepo.py Fri Jun 27 21:45:16 2008 -0500
+++ b/mercurial/httprepo.py Wed Jul 02 09:31:13 2008 +0200
@@ -268,6 +268,7 @@
# 1.0 here is the _protocol_ version
opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
+ opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
urllib2.install_opener(opener)
def url(self):
--- a/mercurial/sshserver.py Fri Jun 27 21:45:16 2008 -0500
+++ b/mercurial/sshserver.py Wed Jul 02 09:31:13 2008 +0200
@@ -204,4 +204,6 @@
os.unlink(tempname)
def do_stream_out(self):
- streamclone.stream_out(self.repo, self.fout)
+ for chunk in streamclone.stream_out(self.repo):
+ self.fout.write(chunk)
+ self.fout.flush()
--- a/mercurial/streamclone.py Fri Jun 27 21:45:16 2008 -0500
+++ b/mercurial/streamclone.py Wed Jul 02 09:31:13 2008 +0200
@@ -51,12 +51,12 @@
#
# server writes out raw file data.
-def stream_out(repo, fileobj, untrusted=False):
+def stream_out(repo, untrusted=False):
'''stream out all metadata files in repository.
writes to file-like object, must support write() and optional flush().'''
if not repo.ui.configbool('server', 'uncompressed', untrusted=untrusted):
- fileobj.write('1\n')
+ yield '1\n'
return
# get consistent snapshot of repo. lock during scan so lock not
@@ -67,10 +67,10 @@
repolock = repo.lock()
except (lock.LockHeld, lock.LockUnavailable), inst:
repo.ui.warn('locking the repository failed: %s\n' % (inst,))
- fileobj.write('2\n')
+ yield '2\n'
return
- fileobj.write('0\n')
+ yield '0\n'
repo.ui.debug('scanning\n')
entries = []
total_bytes = 0
@@ -83,11 +83,9 @@
repo.ui.debug('%d files, %d bytes to transfer\n' %
(len(entries), total_bytes))
- fileobj.write('%d %d\n' % (len(entries), total_bytes))
+ yield '%d %d\n' % (len(entries), total_bytes)
for name, size in entries:
repo.ui.debug('sending %s (%d bytes)\n' % (name, size))
- fileobj.write('%s\0%d\n' % (name, size))
+ yield '%s\0%d\n' % (name, size)
for chunk in util.filechunkiter(repo.sopener(name), limit=size):
- fileobj.write(chunk)
- flush = getattr(fileobj, 'flush', None)
- if flush: flush()
+ yield chunk
--- a/mercurial/templater.py Fri Jun 27 21:45:16 2008 -0500
+++ b/mercurial/templater.py Wed Jul 02 09:31:13 2008 +0200
@@ -81,18 +81,18 @@
def __contains__(self, key):
return key in self.cache or key in self.map
- def __call__(self, t, **map):
- '''perform expansion.
- t is name of map element to expand.
- map is added elements to use during expansion.'''
+ def _template(self, t):
+ '''Get the template for the given template name. Use a local cache.'''
if not t in self.cache:
try:
self.cache[t] = file(self.map[t]).read()
except IOError, inst:
raise IOError(inst.args[0], _('template file %s: %s') %
(self.map[t], inst.args[1]))
- tmpl = self.cache[t]
+ return self.cache[t]
+ def _process(self, tmpl, map):
+ '''Render a template. Returns a generator.'''
while tmpl:
m = self.template_re.search(tmpl)
if not m:
@@ -119,13 +119,34 @@
lm = map.copy()
for i in v:
lm.update(i)
- yield self(format, **lm)
+ t = self._template(format)
+ yield self._process(t, lm)
else:
if fl:
for f in fl.split("|")[1:]:
v = self.filters[f](v)
yield v
+ def __call__(self, t, **map):
+ '''Perform expansion. t is name of map element to expand. map contains
+ added elements for use during expansion. Is a generator.'''
+ tmpl = self._template(t)
+ iters = [self._process(tmpl, map)]
+ while iters:
+ try:
+ item = iters[0].next()
+ except StopIteration:
+ iters.pop(0)
+ continue
+ if isinstance(item, str):
+ yield item
+ elif item is None:
+ yield ''
+ elif hasattr(item, '__iter__'):
+ iters.insert(0, iter(item))
+ else:
+ yield str(item)
+
def templatepath(name=None):
'''return location of template file or directory (if no name).
returns None if not found.'''
--- a/setup.py Fri Jun 27 21:45:16 2008 -0500
+++ b/setup.py Wed Jul 02 09:31:13 2008 +0200
@@ -129,7 +129,7 @@
[os.path.join(root, file_) for file_ in files])
for root, dirs, files in os.walk('templates')],
cmdclass=cmdclass,
- options=dict(py2exe=dict(packages=['hgext']),
+ options=dict(py2exe=dict(packages=['hgext', 'email']),
bdist_mpkg=dict(zipdist=True,
license='COPYING',
readme='contrib/macosx/Readme.html',
--- a/templates/coal/changeset.tmpl Fri Jun 27 21:45:16 2008 -0500
+++ b/templates/coal/changeset.tmpl Wed Jul 02 09:31:13 2008 +0200
@@ -9,8 +9,8 @@
<img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
</div>
<ul>
- <li><a href="{url}shortlog{sessionvars%urlparameter}">log</a></li>
- <li><a href="{url}graph{sessionvars%urlparameter}">graph</a></li>
+ <li><a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">log</a></li>
+ <li><a href="{url}graph/{node|short}{sessionvars%urlparameter}">graph</a></li>
<li><a href="{url}tags{sessionvars%urlparameter}">tags</a></li>
</ul>
<ul>
--- a/templates/coal/fileannotate.tmpl Fri Jun 27 21:45:16 2008 -0500
+++ b/templates/coal/fileannotate.tmpl Wed Jul 02 09:31:13 2008 +0200
@@ -10,8 +10,8 @@
<img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
</div>
<ul>
-<li><a href="{url}shortlog/{sessionvars%urlparameter}">log</a></li>
-<li><a href="{url}graph/{sessionvars%urlparameter}">graph</a></li>
+<li><a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">log</a></li>
+<li><a href="{url}graph/{node|short}{sessionvars%urlparameter}">graph</a></li>
<li><a href="{url}tags{sessionvars%urlparameter}">tags</a></li>
</ul>
--- a/templates/coal/filediff.tmpl Fri Jun 27 21:45:16 2008 -0500
+++ b/templates/coal/filediff.tmpl Wed Jul 02 09:31:13 2008 +0200
@@ -10,8 +10,8 @@
<img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
</div>
<ul>
-<li><a href="{url}shortlog/{sessionvars%urlparameter}">log</a></li>
-<li><a href="{url}graph/{sessionvars%urlparameter}">graph</a></li>
+<li><a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">log</a></li>
+<li><a href="{url}graph/{node|short}{sessionvars%urlparameter}">graph</a></li>
<li><a href="{url}tags{sessionvars%urlparameter}">tags</a></li>
</ul>
<ul>
--- a/templates/coal/filelog.tmpl Fri Jun 27 21:45:16 2008 -0500
+++ b/templates/coal/filelog.tmpl Wed Jul 02 09:31:13 2008 +0200
@@ -15,8 +15,8 @@
<img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
</div>
<ul>
-<li><a href="{url}shortlog/{sessionvars%urlparameter}">log</a></li>
-<li><a href="{url}graph/{sessionvars%urlparameter}">graph</a></li>
+<li><a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">log</a></li>
+<li><a href="{url}graph/{node|short}{sessionvars%urlparameter}">graph</a></li>
<li><a href="{url}tags{sessionvars%urlparameter}">tags</a></li>
</ul>
<ul>
--- a/templates/coal/filerevision.tmpl Fri Jun 27 21:45:16 2008 -0500
+++ b/templates/coal/filerevision.tmpl Wed Jul 02 09:31:13 2008 +0200
@@ -10,8 +10,8 @@
<img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
</div>
<ul>
-<li><a href="{url}shortlog/{sessionvars%urlparameter}">log</a></li>
-<li><a href="{url}graph/{sessionvars%urlparameter}">graph</a></li>
+<li><a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">log</a></li>
+<li><a href="{url}graph/{node|short}{sessionvars%urlparameter}">graph</a></li>
<li><a href="{url}tags{sessionvars%urlparameter}">tags</a></li>
</ul>
<ul>
--- a/templates/coal/graph.tmpl Fri Jun 27 21:45:16 2008 -0500
+++ b/templates/coal/graph.tmpl Wed Jul 02 09:31:13 2008 +0200
@@ -15,7 +15,7 @@
<img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
</div>
<ul>
-<li><a href="{url}shortlog{sessionvars%urlparameter}">log</a></li>
+<li><a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">log</a></li>
<li class="active">graph</li>
<li><a href="{url}tags{sessionvars%urlparameter}">tags</a></li>
</ul>
--- a/templates/coal/manifest.tmpl Fri Jun 27 21:45:16 2008 -0500
+++ b/templates/coal/manifest.tmpl Wed Jul 02 09:31:13 2008 +0200
@@ -10,8 +10,8 @@
<img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
</div>
<ul>
-<li><a href="{url}shortlog/{sessionvars%urlparameter}">log</a></li>
-<li><a href="{url}graph/{sessionvars%urlparameter}">graph</a></li>
+<li><a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">log</a></li>
+<li><a href="{url}graph/{node|short}{sessionvars%urlparameter}">graph</a></li>
<li><a href="{url}tags{sessionvars%urlparameter}">tags</a></li>
</ul>
<ul>
--- a/templates/coal/shortlog.tmpl Fri Jun 27 21:45:16 2008 -0500
+++ b/templates/coal/shortlog.tmpl Wed Jul 02 09:31:13 2008 +0200
@@ -15,7 +15,7 @@
</div>
<ul>
<li class="active">log</li>
-<li><a href="{url}graph{sessionvars%urlparameter}">graph</a></li>
+<li><a href="{url}graph/{node|short}{sessionvars%urlparameter}">graph</a></li>
<li><a href="{url}tags{sessionvars%urlparameter}">tags</a></li>
</ul>
<ul>
--- a/tests/test-hgweb-commands Fri Jun 27 21:45:16 2008 -0500
+++ b/tests/test-hgweb-commands Wed Jul 02 09:31:13 2008 +0200
@@ -34,21 +34,22 @@
echo % Overviews
"$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/tags/?style=atom' | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//"
"$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/summary/?style=gitweb' | sed "s/[0-9]* years ago/long ago/g"
+"$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/graph/?style=gitweb' | sed "s/[0-9]* years/long/g"
echo % capabilities
-"$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/capabilities'
+"$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=capabilities'
echo % heads
-"$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/heads'
+"$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=heads'
echo % lookup
-"$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/lookup/1'
+"$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=lookup&node=1'
echo % branches
-"$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/branches'
+"$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=branches'
echo % changegroup
-"$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/changegroup'
+"$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=changegroup'
echo % stream_out
-"$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/stream_out'
+"$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=stream_out'
echo % failing unbundle, requires POST request
-"$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/unbundle'
+"$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=unbundle'
echo % Static files
"$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/static/style.css'
Binary file tests/test-hgweb-commands.out has changed
--- a/tests/test-hgweb-no-path-info Fri Jun 27 21:45:16 2008 -0500
+++ b/tests/test-hgweb-no-path-info Wed Jul 02 09:31:13 2008 +0200
@@ -43,15 +43,17 @@
output = StringIO()
env['QUERY_STRING'] = 'style=atom'
-hgweb('.', name = 'repo')(env, startrsp)
-print output.getvalue()
+content = hgweb('.', name = 'repo')(env, startrsp)
+sys.stdout.write(output.getvalue())
+sys.stdout.write(''.join(content))
print '---- ERRORS'
print errors.getvalue()
output = StringIO()
env['QUERY_STRING'] = 'style=raw'
-hgwebdir({'repo': '.'})(env, startrsp)
-print output.getvalue()
+content = hgwebdir({'repo': '.'})(env, startrsp)
+sys.stdout.write(output.getvalue())
+sys.stdout.write(''.join(content))
print '---- ERRORS'
print errors.getvalue()
EOF
--- a/tests/test-hgweb-no-path-info.out Fri Jun 27 21:45:16 2008 -0500
+++ b/tests/test-hgweb-no-path-info.out Wed Jul 02 09:31:13 2008 +0200
@@ -35,7 +35,6 @@
</entry>
</feed>
-
---- ERRORS
---- HEADERS
@@ -45,6 +44,5 @@
repo/
-
---- ERRORS
--- a/tests/test-hgweb-no-request-uri Fri Jun 27 21:45:16 2008 -0500
+++ b/tests/test-hgweb-no-request-uri Wed Jul 02 09:31:13 2008 +0200
@@ -44,32 +44,36 @@
output = StringIO()
env['PATH_INFO'] = '/'
env['QUERY_STRING'] = 'style=atom'
-hgweb('.', name = 'repo')(env, startrsp)
-print output.getvalue()
+content = hgweb('.', name = 'repo')(env, startrsp)
+sys.stdout.write(output.getvalue())
+sys.stdout.write(''.join(content))
print '---- ERRORS'
print errors.getvalue()
output = StringIO()
env['PATH_INFO'] = '/file/tip/'
env['QUERY_STRING'] = 'style=raw'
-hgweb('.', name = 'repo')(env, startrsp)
-print output.getvalue()
+content = hgweb('.', name = 'repo')(env, startrsp)
+sys.stdout.write(output.getvalue())
+sys.stdout.write(''.join(content))
print '---- ERRORS'
print errors.getvalue()
output = StringIO()
env['PATH_INFO'] = '/'
env['QUERY_STRING'] = 'style=raw'
-hgwebdir({'repo': '.'})(env, startrsp)
-print output.getvalue()
+content = hgwebdir({'repo': '.'})(env, startrsp)
+sys.stdout.write(output.getvalue())
+sys.stdout.write(''.join(content))
print '---- ERRORS'
print errors.getvalue()
output = StringIO()
env['PATH_INFO'] = '/repo/file/tip/'
env['QUERY_STRING'] = 'style=raw'
-hgwebdir({'repo': '.'})(env, startrsp)
-print output.getvalue()
+content = hgwebdir({'repo': '.'})(env, startrsp)
+sys.stdout.write(output.getvalue())
+sys.stdout.write(''.join(content))
print '---- ERRORS'
print errors.getvalue()
EOF
--- a/tests/test-hgweb-no-request-uri.out Fri Jun 27 21:45:16 2008 -0500
+++ b/tests/test-hgweb-no-request-uri.out Wed Jul 02 09:31:13 2008 +0200
@@ -35,6 +35,24 @@
</entry>
</feed>
+---- ERRORS
+
+---- HEADERS
+200 Script output follows
+---- DATA
+[('Content-Type', 'text/plain; charset=ascii')]
+
+-rw-r--r-- 4 bar
+
+
+---- ERRORS
+
+---- HEADERS
+200 Script output follows
+---- DATA
+[('Content-Type', 'text/plain; charset=ascii')]
+
+/repo/
---- ERRORS
@@ -46,27 +64,5 @@
-rw-r--r-- 4 bar
-
---- ERRORS
----- HEADERS
-200 Script output follows
----- DATA
-[('Content-Type', 'text/plain; charset=ascii')]
-
-/repo/
-
-
----- ERRORS
-
----- HEADERS
-200 Script output follows
----- DATA
-[('Content-Type', 'text/plain; charset=ascii')]
-
--rw-r--r-- 4 bar
-
-
-
----- ERRORS
-
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-pull-http Wed Jul 02 09:31:13 2008 +0200
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+cp "$TESTDIR"/printenv.py .
+
+hg init test
+cd test
+echo a > a
+hg ci -Ama -d '0 0'
+
+cd ..
+hg clone test test2
+cd test2
+echo a >> a
+hg ci -mb -d '0 0'
+
+echo % expect error, cloning not allowed
+echo '[web]' > .hg/hgrc
+echo 'allowpull = false' >> .hg/hgrc
+hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log
+cat hg.pid >> $DAEMON_PIDS
+hg clone http://localhost:$HGPORT/ test3 | sed -e 's,:[0-9][0-9]*/,/,'
+kill `cat hg.pid`
+echo % serve errors
+cat errors.log
+
+req() {
+ hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log
+ cat hg.pid >> $DAEMON_PIDS
+ hg --cwd ../test pull http://localhost:$HGPORT/ | sed -e 's,:[0-9][0-9]*/,/,'
+ kill `cat hg.pid`
+ echo % serve errors
+ cat errors.log
+}
+
+echo % expect error, pulling not allowed
+req
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-pull-http.out Wed Jul 02 09:31:13 2008 +0200
@@ -0,0 +1,12 @@
+adding a
+updating working directory
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% expect error, cloning not allowed
+abort: error:
+requesting all changes
+% serve errors
+% expect error, pulling not allowed
+abort: error:
+pulling from http://localhost/
+searching for changes
+% serve errors