changeset 9912:311eeb2f6951

Merge with crew-stable
author Patrick Mezard <pmezard@gmail.com>
date Mon, 23 Nov 2009 22:46:48 +0100
parents 6f92997dbdca (diff) 58bdcab55bdc (current diff)
children f139814d20b3
files
diffstat 16 files changed, 132 insertions(+), 77 deletions(-) [+]
line wrap: on
line diff
--- a/hgext/convert/filemap.py	Mon Nov 23 22:08:59 2009 +0100
+++ b/hgext/convert/filemap.py	Mon Nov 23 22:46:48 2009 +0100
@@ -10,11 +10,11 @@
 from common import SKIPREV, converter_source
 
 def rpairs(name):
-    yield '.', name
     e = len(name)
     while e != -1:
         yield name[:e], name[e+1:]
         e = name.rfind('/', 0, e)
+    yield '.', name
 
 class filemapper(object):
     '''Map and filter filenames when importing.
@@ -82,7 +82,7 @@
             exc = self.lookup(name, self.exclude)[0]
         else:
             exc = ''
-        if not inc or exc:
+        if (not self.include and exc) or (len(inc) <= len(exc)):
             return None
         newpre, pre, suf = self.lookup(name, self.rename)
         if newpre:
--- a/hgext/relink.py	Mon Nov 23 22:08:59 2009 +0100
+++ b/hgext/relink.py	Mon Nov 23 22:46:48 2009 +0100
@@ -14,25 +14,27 @@
 def relink(ui, repo, origin=None, **opts):
     """recreate hardlinks between two repositories
 
-    When repositories are cloned locally, their data files will be hardlinked
-    so that they only use the space of a single repository.
+    When repositories are cloned locally, their data files will be
+    hardlinked so that they only use the space of a single repository.
 
-    Unfortunately, subsequent pulls into either repository will break hardlinks
-    for any files touched by the new changesets, even if both repositories end
-    up pulling the same changes.
+    Unfortunately, subsequent pulls into either repository will break
+    hardlinks for any files touched by the new changesets, even if
+    both repositories end up pulling the same changes.
 
-    Similarly, passing --rev to "hg clone" will fail to use
-    any hardlinks, falling back to a complete copy of the source repository.
+    Similarly, passing --rev to "hg clone" will fail to use any
+    hardlinks, falling back to a complete copy of the source
+    repository.
 
-    This command lets you recreate those hardlinks and reclaim that wasted
-    space.
+    This command lets you recreate those hardlinks and reclaim that
+    wasted space.
 
-    This repository will be relinked to share space with ORIGIN, which must be
-    on the same local disk. If ORIGIN is omitted, looks for "default-relink",
-    then "default", in [paths].
+    This repository will be relinked to share space with ORIGIN, which
+    must be on the same local disk. If ORIGIN is omitted, looks for
+    "default-relink", then "default", in [paths].
 
-    Do not attempt any read operations on this repository while the command is
-    running. (Both repositories will be locked against writes.)
+    Do not attempt any read operations on this repository while the
+    command is running. (Both repositories will be locked against
+    writes.)
     """
     src = hg.repository(
         cmdutil.remoteui(repo, opts),
--- a/mercurial/commands.py	Mon Nov 23 22:08:59 2009 +0100
+++ b/mercurial/commands.py	Mon Nov 23 22:46:48 2009 +0100
@@ -1476,7 +1476,7 @@
             ui.write('\n')
 
         try:
-            aliases, i = cmdutil.findcmd(name, table, False)
+            aliases, entry = cmdutil.findcmd(name, table, False)
         except error.AmbiguousCommand, inst:
             # py3k fix: except vars can't be used outside the scope of the
             # except block, nor can be used inside a lambda. python issue4617
@@ -1486,11 +1486,11 @@
             return
 
         # synopsis
-        if len(i) > 2:
-            if i[2].startswith('hg'):
-                ui.write("%s\n" % i[2])
+        if len(entry) > 2:
+            if entry[2].startswith('hg'):
+                ui.write("%s\n" % entry[2])
             else:
-                ui.write('hg %s %s\n' % (aliases[0], i[2]))
+                ui.write('hg %s %s\n' % (aliases[0], entry[2]))
         else:
             ui.write('hg %s\n' % aliases[0])
 
@@ -1499,7 +1499,7 @@
             ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
 
         # description
-        doc = gettext(i[0].__doc__)
+        doc = gettext(entry[0].__doc__)
         if not doc:
             doc = _("(no help text available)")
         if ui.quiet:
@@ -1508,8 +1508,8 @@
 
         if not ui.quiet:
             # options
-            if i[1]:
-                option_lists.append((_("options:\n"), i[1]))
+            if entry[1]:
+                option_lists.append((_("options:\n"), entry[1]))
 
             addglobalopts(False)
 
--- a/mercurial/dispatch.py	Mon Nov 23 22:08:59 2009 +0100
+++ b/mercurial/dispatch.py	Mon Nov 23 22:46:48 2009 +0100
@@ -200,6 +200,12 @@
             self.args = aliasargs(self.fn) + args
             if cmd not in commands.norepo.split(' '):
                 self.norepo = False
+            if self.help.startswith("hg " + cmd):
+                # drop prefix in old-style help lines so hg shows the alias
+                self.help = self.help[4 + len(cmd):]
+            self.__doc__ = _("alias for: hg %s\n\n%s") \
+                               % (definition, self.fn.__doc__)
+
         except error.UnknownCommand:
             def fn(ui, *args):
                 ui.warn(_("alias '%s' resolves to unknown command '%s'\n") \
@@ -240,14 +246,14 @@
 
     if args:
         cmd, args = args[0], args[1:]
-        aliases, i = cmdutil.findcmd(cmd, commands.table,
+        aliases, entry = cmdutil.findcmd(cmd, commands.table,
                                      ui.config("ui", "strict"))
         cmd = aliases[0]
-        args = aliasargs(i[0]) + args
+        args = aliasargs(entry[0]) + args
         defaults = ui.config("defaults", cmd)
         if defaults:
             args = map(util.expandpath, shlex.split(defaults)) + args
-        c = list(i[1])
+        c = list(entry[1])
     else:
         cmd = None
         c = []
@@ -267,7 +273,7 @@
         options[n] = cmdoptions[n]
         del cmdoptions[n]
 
-    return (cmd, cmd and i[0] or None, args, options, cmdoptions)
+    return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
 
 def _parseconfig(ui, config):
     """parse the --config options from the command line"""
--- a/mercurial/hgweb/common.py	Mon Nov 23 22:08:59 2009 +0100
+++ b/mercurial/hgweb/common.py	Mon Nov 23 22:46:48 2009 +0100
@@ -16,6 +16,58 @@
 HTTP_METHOD_NOT_ALLOWED = 405
 HTTP_SERVER_ERROR = 500
 
+# Hooks for hgweb permission checks; extensions can add hooks here. Each hook
+# is invoked like this: hook(hgweb, request, operation), where operation is
+# either read, pull or push. Hooks should either raise an ErrorResponse
+# exception, or just return.
+# It is possible to do both authentication and authorization through this.
+permhooks = []
+
+def checkauthz(hgweb, req, op):
+    '''Check permission for operation based on request data (including
+    authentication info). Return if op allowed, else raise an ErrorResponse
+    exception.'''
+
+    user = req.env.get('REMOTE_USER')
+
+    deny_read = hgweb.configlist('web', 'deny_read')
+    if deny_read and (not user or deny_read == ['*'] or user in deny_read):
+        raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
+
+    allow_read = hgweb.configlist('web', 'allow_read')
+    result = (not allow_read) or (allow_read == ['*'])
+    if not (result or user in allow_read):
+        raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
+
+    if op == 'pull' and not hgweb.allowpull:
+        raise ErrorResponse(HTTP_UNAUTHORIZED, 'pull not authorized')
+    elif op == 'pull' or op is None: # op is None for interface requests
+        return
+
+    # enforce that you can only push using POST requests
+    if req.env['REQUEST_METHOD'] != 'POST':
+        msg = 'push requires POST request'
+        raise ErrorResponse(HTTP_METHOD_NOT_ALLOWED, msg)
+
+    # require ssl by default for pushing, auth info cannot be sniffed
+    # and replayed
+    scheme = req.env.get('wsgi.url_scheme')
+    if hgweb.configbool('web', 'push_ssl', True) and scheme != 'https':
+        raise ErrorResponse(HTTP_OK, 'ssl required')
+
+    deny = hgweb.configlist('web', 'deny_push')
+    if deny and (not user or deny == ['*'] or user in deny):
+        raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
+
+    allow = hgweb.configlist('web', 'allow_push')
+    result = allow and (allow == ['*'] or user in allow)
+    if not result:
+        raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
+
+# Add the default permhook, which provides simple authorization.
+permhooks.append(checkauthz)
+
+
 class ErrorResponse(Exception):
     def __init__(self, code, message=None, headers=[]):
         Exception.__init__(self)
--- a/mercurial/hgweb/hgweb_mod.py	Mon Nov 23 22:08:59 2009 +0100
+++ b/mercurial/hgweb/hgweb_mod.py	Mon Nov 23 22:46:48 2009 +0100
@@ -8,7 +8,7 @@
 
 import os
 from mercurial import ui, hg, hook, error, encoding, templater
-from common import get_mtime, ErrorResponse
+from common import get_mtime, ErrorResponse, permhooks
 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
 from common import HTTP_UNAUTHORIZED, HTTP_METHOD_NOT_ALLOWED
 from request import wsgirequest
@@ -54,7 +54,9 @@
         return self.repo.ui.configlist(section, name, default,
                                        untrusted=untrusted)
 
-    def refresh(self):
+    def refresh(self, request=None):
+        if request:
+            self.repo.ui.environ = request.env
         mtime = get_mtime(self.repo.root)
         if mtime != self.mtime:
             self.mtime = mtime
@@ -80,7 +82,7 @@
 
     def run_wsgi(self, req):
 
-        self.refresh()
+        self.refresh(req)
 
         # work with CGI variables to create coherent structure
         # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
@@ -281,42 +283,5 @@
         }
 
     def check_perm(self, req, op):
-        '''Check permission for operation based on request data (including
-        authentication info). Return if op allowed, else raise an ErrorResponse
-        exception.'''
-
-        user = req.env.get('REMOTE_USER')
-
-        deny_read = self.configlist('web', 'deny_read')
-        if deny_read and (not user or deny_read == ['*'] or user in deny_read):
-            raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
-
-        allow_read = self.configlist('web', 'allow_read')
-        result = (not allow_read) or (allow_read == ['*'])
-        if not (result or user in allow_read):
-            raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
-
-        if op == 'pull' and not self.allowpull:
-            raise ErrorResponse(HTTP_UNAUTHORIZED, 'pull not authorized')
-        elif op == 'pull' or op is None: # op is None for interface requests
-            return
-
-        # enforce that you can only push using POST requests
-        if req.env['REQUEST_METHOD'] != 'POST':
-            msg = 'push requires POST request'
-            raise ErrorResponse(HTTP_METHOD_NOT_ALLOWED, msg)
-
-        # 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':
-            raise ErrorResponse(HTTP_OK, 'ssl required')
-
-        deny = self.configlist('web', 'deny_push')
-        if deny and (not user or deny == ['*'] or user in deny):
-            raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
-
-        allow = self.configlist('web', 'allow_push')
-        result = allow and (allow == ['*'] or user in allow)
-        if not result:
-            raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
+        for hook in permhooks:
+            hook(self, req, op)
--- a/mercurial/httprepo.py	Mon Nov 23 22:08:59 2009 +0100
+++ b/mercurial/httprepo.py	Mon Nov 23 22:46:48 2009 +0100
@@ -93,7 +93,7 @@
         resp_url = resp.geturl()
         if resp_url.endswith(qs):
             resp_url = resp_url[:-len(qs)]
-        if self._url != resp_url:
+        if self._url.rstrip('/') != resp_url.rstrip('/'):
             self.ui.status(_('real URL is %s\n') % resp_url)
             self._url = resp_url
         try:
--- a/mercurial/ui.py	Mon Nov 23 22:08:59 2009 +0100
+++ b/mercurial/ui.py	Mon Nov 23 22:46:48 2009 +0100
@@ -29,8 +29,11 @@
             self._ocfg = src._ocfg.copy()
             self._trustusers = src._trustusers.copy()
             self._trustgroups = src._trustgroups.copy()
+            self.environ = src.environ
             self.fixconfig()
         else:
+            # shared read-only environment
+            self.environ = os.environ
             # we always trust global config files
             for f in util.rcpath():
                 self.readconfig(f, trust=True)
--- a/templates/gitweb/fileannotate.tmpl	Mon Nov 23 22:08:59 2009 +0100
+++ b/templates/gitweb/fileannotate.tmpl	Mon Nov 23 22:46:48 2009 +0100
@@ -21,6 +21,7 @@
 <a href="{url}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">files</a> |
 <a href="{url}rev/{node|short}{sessionvars%urlparameter}">changeset</a> |
 <a href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file</a> |
+<a href="{url}file/tip/{file|urlescape}{sessionvars%urlparameter}">latest</a> |
 <a href="{url}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">revisions</a> |
 annotate |
 <a href="{url}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">diff</a> |
--- a/templates/gitweb/filediff.tmpl	Mon Nov 23 22:08:59 2009 +0100
+++ b/templates/gitweb/filediff.tmpl	Mon Nov 23 22:46:48 2009 +0100
@@ -21,6 +21,7 @@
 <a href="{url}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">files</a> |
 <a href="{url}rev/{node|short}{sessionvars%urlparameter}">changeset</a> |
 <a href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file</a> |
+<a href="{url}file/tip/{file|urlescape}{sessionvars%urlparameter}">latest</a> |
 <a href="{url}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">revisions</a> |
 <a href="{url}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">annotate</a> |
 diff |
--- a/templates/gitweb/filerevision.tmpl	Mon Nov 23 22:08:59 2009 +0100
+++ b/templates/gitweb/filerevision.tmpl	Mon Nov 23 22:46:48 2009 +0100
@@ -21,6 +21,7 @@
 <a href="{url}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">files</a> |
 <a href="{url}rev/{node|short}{sessionvars%urlparameter}">changeset</a> |
 file |
+<a href="{url}file/tip/{file|urlescape}{sessionvars%urlparameter}">latest</a> |
 <a href="{url}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">revisions</a> |
 <a href="{url}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">annotate</a> |
 <a href="{url}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">diff</a> |
--- a/tests/run-tests.py	Mon Nov 23 22:08:59 2009 +0100
+++ b/tests/run-tests.py	Mon Nov 23 22:46:48 2009 +0100
@@ -293,10 +293,18 @@
     script = os.path.realpath(sys.argv[0])
     hgroot = os.path.dirname(os.path.dirname(script))
     os.chdir(hgroot)
+    nohome = '--home=""'
+    if os.name == 'nt':
+        # The --home="" trick works only on OS where os.sep == '/'
+        # because of a distutils convert_path() fast-path. Avoid it at
+        # least on Windows for now, deal with .pydistutils.cfg bugs
+        # when they happen.
+        nohome = ''
     cmd = ('%s setup.py %s clean --all'
            ' install --force --prefix="%s" --install-lib="%s"'
-           ' --install-scripts="%s" >%s 2>&1'
-           % (sys.executable, pure, INST, PYTHONDIR, BINDIR, installerrs))
+           ' --install-scripts="%s" %s >%s 2>&1'
+           % (sys.executable, pure, INST, PYTHONDIR, BINDIR, nohome,
+              installerrs))
     vlog("# Running", cmd)
     if os.system(cmd) == 0:
         if not options.verbose:
--- a/tests/test-convert-filemap	Mon Nov 23 22:08:59 2009 +0100
+++ b/tests/test-convert-filemap	Mon Nov 23 22:46:48 2009 +0100
@@ -16,9 +16,11 @@
 
 echo foo > foo
 echo baz > baz
-mkdir dir
+mkdir -p dir/subdir
 echo dir/file >> dir/file
 echo dir/file2 >> dir/file2
+echo dir/subdir/file3 >> dir/subdir/file3
+echo dir/subdir/file4 >> dir/subdir/file4
 hg ci -d '0 0' -qAm '0: add foo baz dir/'
 
 echo bar > bar
@@ -114,6 +116,8 @@
 include copied
 rename foo foo2
 rename copied copied2
+exclude dir/subdir
+include dir/subdir/file3
 EOF
 hg -q convert --filemap renames.fmap --datesort source renames.repo
 hg up -q -R renames.repo
--- a/tests/test-convert-filemap.out	Mon Nov 23 22:08:59 2009 +0100
+++ b/tests/test-convert-filemap.out	Mon Nov 23 22:46:48 2009 +0100
@@ -16,7 +16,7 @@
 |/
 o  1 "1: add bar quux; copy foo to copied" files: bar copied quux
 |
-o  0 "0: add foo baz dir/" files: baz dir/file dir/file2 foo
+o  0 "0: add foo baz dir/" files: baz dir/file dir/file2 dir/subdir/file3 dir/subdir/file4 foo
 
 % final file versions in this repo:
 9463f52fe115e377cf2878d4fc548117211063f2 644   bar
@@ -24,6 +24,8 @@
 6ca237634e1f6bee1b6db94292fb44f092a25842 644   copied
 3e20847584beff41d7cd16136b7331ab3d754be0 644   dir/file
 75e6d3f8328f5f6ace6bf10b98df793416a09dca 644   dir/file2
+5fe139720576e18e34bcc9f79174db8897c8afe9 644   dir/subdir/file3
+57a1c1511590f3de52874adfa04effe8a77d64af 644   dir/subdir/file4
 9a7b52012991e4873687192c3e17e61ba3e837a3 644   foo
 bc3eca3f47023a3e70ca0d8cc95a22a6827db19d 644   quux
 copied renamed from foo:2ed2a3912a0b24502043eae84ee4b279c18b90dd
@@ -144,10 +146,11 @@
 |
 o  1 "1: add bar quux; copy foo to copied" files: copied2
 |
-o  0 "0: add foo baz dir/" files: dir2/file foo2
+o  0 "0: add foo baz dir/" files: dir2/file dir2/subdir/file3 foo2
 
 e5e3d520be9be45937d0b06b004fadcd6c221fa2 644   copied2
 3e20847584beff41d7cd16136b7331ab3d754be0 644   dir2/file
+5fe139720576e18e34bcc9f79174db8897c8afe9 644   dir2/subdir/file3
 9a7b52012991e4873687192c3e17e61ba3e837a3 644   foo2
 copied2 renamed from foo2:2ed2a3912a0b24502043eae84ee4b279c18b90dd
 copied:
--- a/tests/test-non-interactive-wsgi	Mon Nov 23 22:08:59 2009 +0100
+++ b/tests/test-non-interactive-wsgi	Mon Nov 23 22:46:48 2009 +0100
@@ -60,9 +60,14 @@
 	'SERVER_PROTOCOL': 'HTTP/1.0'
 }
 
-hgweb('.')(env, startrsp)
+i = hgweb('.')
+i(env, startrsp)
 print '---- ERRORS'
 print errors.getvalue()
+print '---- OS.ENVIRON wsgi variables'
+print sorted([x for x in os.environ if x.startswith('wsgi')])
+print '---- request.ENVIRON wsgi variables'
+print sorted([x for x in i.repo.ui.environ if x.startswith('wsgi')])
 EOF
 
 python request.py
--- a/tests/test-non-interactive-wsgi.out	Mon Nov 23 22:08:59 2009 +0100
+++ b/tests/test-non-interactive-wsgi.out	Mon Nov 23 22:46:48 2009 +0100
@@ -10,3 +10,7 @@
 [('Content-Type', 'text/html; charset=ascii')]
 ---- ERRORS
 
+---- OS.ENVIRON wsgi variables
+[]
+---- request.ENVIRON wsgi variables
+['wsgi.errors', 'wsgi.input', 'wsgi.multiprocess', 'wsgi.multithread', 'wsgi.run_once', 'wsgi.url_scheme', 'wsgi.version']