hgext/schemes.py
author FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
Sat, 24 Oct 2015 18:58:57 +0900
branchstable
changeset 26831 0a7610758c42
parent 26587 56b2bcea2529
child 27981 d630eac3a5db
permissions -rw-r--r--
localrepo: discard objects in _filecache at transaction failure (issue4876) 'repo.invalidate()' deletes 'filecache'-ed properties by 'filecache.__delete__()' below via 'delattr(unfiltered, k)'. But cached objects are still kept in 'repo._filecache'. def __delete__(self, obj): try: del obj.__dict__[self.name] except KeyError: raise AttributeError(self.name) If 'repo' object is reused even after failure of command execution, referring 'filecache'-ed property may reuse one kept in 'repo._filecache', even if reloading from a file is expected. Executing command sequence on command server is a typical case of this situation (5c0f5db65c6b also tried to fix this issue). For example: 1. start a command execution 2. 'changelog.delayupdate()' is invoked in a transaction scope This replaces own 'opener' by '_divertopener()' for additional accessing to '00changelog.i.a' (aka "pending file"). 3. transaction is aborted, and command (1) execution is ended After 'repo.invalidate()' at releasing store lock, changelog object above (= 'opener' of it is still replaced) is deleted from 'repo.__dict__', but still kept in 'repo._filecache'. 4. start next command execution with same 'repo' 5. referring 'repo.changelog' may reuse changelog object kept in 'repo._filecache' according to timestamp of '00changelog.i' '00changelog.i' is truncated at transaction failure (even though this truncation is unintentional one, as described later), and 'st_mtime' of it is changed. But 'st_mtime' doesn't have enough resolution to always detect this truncation, and invalid changelog object kept in 'repo._filecache' is reused occasionally. Then, "No such file or directory" error occurs for '00changelog.i.a', which is already removed at (3). This patch discards objects in '_filecache' other than dirstate at transaction failure. Changes in 'invalidate()' can't be simplified by 'self._filecache = {}', because 'invalidate()' should keep dirstate in 'self._filecache' 'repo.invalidate()' at "hg qpush" failure is removed in this patch, because now it is redundant. This patch doesn't make 'repo.invalidate()' always discard objects in '_filecache', because 'repo.invalidate()' is invoked also at unlocking store lock. - "always discard objects in filecache at unlocking" may cause serious performance problem for subsequent procedures at normal execution - but it is impossible to "discard objects in filecache at unlocking only at failure", because 'releasefn' of lock can't know whether a lock scope is terminated normally or not BTW, using "with" statement described in PEP343 for lock may resolve this ? After this patch, truncation of '00changelog.i' still occurs at transaction failure, even though newly added revisions exist only in '00changelog.i.a' and size of '00changelog.i' isn't changed by this truncation. Updating 'st_mtime' of '00changelog.i' implied by this redundant truncation also affects cache behavior as described above. This will be fixed by dropping '00changelog.i' at aborting from the list of files to be truncated in transaction.

# Copyright 2009, Alexander Solovyov <piranha@piranha.org.ua>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.

"""extend schemes with shortcuts to repository swarms

This extension allows you to specify shortcuts for parent URLs with a
lot of repositories to act like a scheme, for example::

  [schemes]
  py = http://code.python.org/hg/

After that you can use it like::

  hg clone py://trunk/

Additionally there is support for some more complex schemas, for
example used by Google Code::

  [schemes]
  gcode = http://{1}.googlecode.com/hg/

The syntax is taken from Mercurial templates, and you have unlimited
number of variables, starting with ``{1}`` and continuing with
``{2}``, ``{3}`` and so on. This variables will receive parts of URL
supplied, split by ``/``. Anything not specified as ``{part}`` will be
just appended to an URL.

For convenience, the extension adds these schemes by default::

  [schemes]
  py = http://hg.python.org/
  bb = https://bitbucket.org/
  bb+ssh = ssh://hg@bitbucket.org/
  gcode = https://{1}.googlecode.com/hg/
  kiln = https://{1}.kilnhg.com/Repo/

You can override a predefined scheme by defining a new scheme with the
same name.
"""

import os, re
from mercurial import extensions, hg, templater, util, error
from mercurial.i18n import _

# Note for extension authors: ONLY specify testedwith = 'internal' for
# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
# be specifying the version(s) of Mercurial they are tested with, or
# leave the attribute unspecified.
testedwith = 'internal'


class ShortRepository(object):
    def __init__(self, url, scheme, templater):
        self.scheme = scheme
        self.templater = templater
        self.url = url
        try:
            self.parts = max(map(int, re.findall(r'\{(\d+)\}', self.url)))
        except ValueError:
            self.parts = 0

    def __repr__(self):
        return '<ShortRepository: %s>' % self.scheme

    def instance(self, ui, url, create):
        # Should this use the util.url class, or is manual parsing better?
        try:
            url = url.split('://', 1)[1]
        except IndexError:
            raise error.Abort(_("no '://' in scheme url '%s'") % url)
        parts = url.split('/', self.parts)
        if len(parts) > self.parts:
            tail = parts[-1]
            parts = parts[:-1]
        else:
            tail = ''
        context = dict((str(i + 1), v) for i, v in enumerate(parts))
        url = ''.join(self.templater.process(self.url, context)) + tail
        return hg._peerlookup(url).instance(ui, url, create)

def hasdriveletter(orig, path):
    if path:
        for scheme in schemes:
            if path.startswith(scheme + ':'):
                return False
    return orig(path)

schemes = {
    'py': 'http://hg.python.org/',
    'bb': 'https://bitbucket.org/',
    'bb+ssh': 'ssh://hg@bitbucket.org/',
    'gcode': 'https://{1}.googlecode.com/hg/',
    'kiln': 'https://{1}.kilnhg.com/Repo/'
    }

def extsetup(ui):
    schemes.update(dict(ui.configitems('schemes')))
    t = templater.engine(lambda x: x)
    for scheme, url in schemes.items():
        if (os.name == 'nt' and len(scheme) == 1 and scheme.isalpha()
            and os.path.exists('%s:\\' % scheme)):
            raise error.Abort(_('custom scheme %s:// conflicts with drive '
                               'letter %s:\\\n') % (scheme, scheme.upper()))
        hg.schemes[scheme] = ShortRepository(url, scheme, t)

    extensions.wrapfunction(util, 'hasdriveletter', hasdriveletter)