##// END OF EJS Templates
localrepo: iteratively derive local repository type...
localrepo: iteratively derive local repository type This commit implements the dynamic local repository type derivation that was explained in the recent commit bfeab472e3c0 "localrepo: create new function for instantiating a local repo object." Instead of a static localrepository class/type which must be customized after construction, we now dynamically construct a type by building up base classes/types to represent specific repository interfaces. Conceptually, the end state is similar to what was happening when various extensions would monkeypatch the __class__ of newly-constructed repo instances. However, the approach is inverted. Instead of making the instance then customizing it, we do the customization up front by influencing the behavior of the type then we instantiate that custom type. This approach gives us much more flexibility. For example, we can use completely separate classes for implementing different aspects of the repository. For example, we could have one class representing revlog-based file storage and another representing non-revlog based file storage. When then choose which implementation to use based on the presence of repo requirements. A concern with this approach is that it creates a lot more types and complexity and that complexity adds overhead. Yes, it is true that this approach will result in more types being created. Yes, this is more complicated than traditional "instantiate a static type." However, I believe the alternatives to supporting alternate storage backends are just as complicated. (Before I arrived at this solution, I had patches storing factory functions on local repo instances for e.g. constructing a file storage instance. We ended up having a handful of these. And this was logically identical to assigning custom methods. Since we were logically changing the type of the instance, I figured it would be better to just use specialized types instead of introducing levels of abstraction at run-time.) On the performance front, I don't believe that having N base classes has any significant performance overhead compared to just a single base class. Intuition says that Python will need to iterate the base classes to find an attribute. However, CPython caches method lookups: as long as the __class__ or MRO isn't changing, method attribute lookup should be constant time after first access. And non-method attributes are stored in __dict__, of which there is only 1 per object, so the number of base classes for __dict__ is irrelevant. Anyway, this commit splits up the monolithic completelocalrepository interface into sub-interfaces: 1 for file storage and 1 representing everything else. We've taught ``makelocalrepository()`` to call a series of factory functions which will produce types implementing specific interfaces. It then calls type() to create a new type from the built-up list of base types. This commit should be considered a start and not the end state. I suspect we'll hit a number of problems as we start to implement alternate storage backends: * Passing custom arguments to __init__ and setting custom attributes on __dict__. * Customizing the set of interfaces that are needed. e.g. the "readonly" intent could translate to not requesting an interface providing methods related to writing. * More ergonomic way for extensions to insert themselves so their callbacks aren't unconditionally called. * Wanting to modify vfs instances, other arguments passed to __init__. That being said, this code is usable in its current state and I'm convinced future commits will demonstrate the value in this approach. Differential Revision: https://phab.mercurial-scm.org/D4642

File last commit:

r38484:2c2e8246 @54 default
r39800:e4e88157 default
Show More
darcs.py
224 lines | 8.3 KiB | text/x-python | PythonLexer
Martin Geisler
convert: add copyright and license headers to back-ends
r8250 # darcs.py - darcs support for the convert extension
#
# Copyright 2007-2009 Matt Mackall <mpm@selenic.com> and others
#
# This software may be used and distributed according to the terms of the
Matt Mackall
Update license to GPLv2+
r10263 # GNU General Public License version 2 or any later version.
timeless
convert: darcs use absolute_import
r28368 from __future__ import absolute_import
Bryan O'Sullivan
convert: support darcs as a source repo
r5359
timeless
convert: darcs use absolute_import
r28368 import errno
import os
import re
import shutil
Yuya Nishihara
py3: wrap tempfile.mkdtemp() to use bytes path...
r38183
Bryan O'Sullivan
convert: support darcs as a source repo
r5359 from mercurial.i18n import _
timeless
convert: darcs use absolute_import
r28368 from mercurial import (
error,
Yuya Nishihara
py3: wrap tempfile.mkdtemp() to use bytes path...
r38183 pycompat,
timeless
convert: darcs use absolute_import
r28368 util,
)
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 from mercurial.utils import dateutil
timeless
convert: darcs use absolute_import
r28368 from . import common
NoRepo = common.NoRepo
Bryan O'Sullivan
convert: support darcs as a source repo
r5359
# The naming drift of ElementTree is fun!
Matt Mackall
many, many trivial check-code fixups
r10282 try:
timeless
convert: darcs use absolute_import
r28368 import xml.etree.cElementTree.ElementTree as ElementTree
import xml.etree.cElementTree.XMLParser as XMLParser
Bryan O'Sullivan
convert: support darcs as a source repo
r5359 except ImportError:
Matt Mackall
many, many trivial check-code fixups
r10282 try:
timeless
convert: darcs use absolute_import
r28368 import xml.etree.ElementTree.ElementTree as ElementTree
import xml.etree.ElementTree.XMLParser as XMLParser
Bryan O'Sullivan
convert: support darcs as a source repo
r5359 except ImportError:
Matt Mackall
many, many trivial check-code fixups
r10282 try:
timeless
convert: darcs use absolute_import
r28368 import elementtree.cElementTree.ElementTree as ElementTree
import elementtree.cElementTree.XMLParser as XMLParser
Bryan O'Sullivan
convert: support darcs as a source repo
r5359 except ImportError:
Matt Mackall
many, many trivial check-code fixups
r10282 try:
timeless
convert: darcs use absolute_import
r28368 import elementtree.ElementTree.ElementTree as ElementTree
import elementtree.ElementTree.XMLParser as XMLParser
Matt Mackall
many, many trivial check-code fixups
r10282 except ImportError:
Matt Mackall
check-code: enable camelcase check, fix up problems
r15457 pass
Bryan O'Sullivan
convert: support darcs as a source repo
r5359
timeless
convert: darcs use absolute_import
r28368 class darcs_source(common.converter_source, common.commandline):
Matt Harbison
convert: save an indicator of the repo type for sources and sinks...
r35168 def __init__(self, ui, repotype, path, revs=None):
common.converter_source.__init__(self, ui, repotype, path, revs=revs)
timeless
convert: darcs use absolute_import
r28368 common.commandline.__init__(self, ui, 'darcs')
Bryan O'Sullivan
convert: support darcs as a source repo
r5359
Patrick Mezard
convert/darcs: improve unsupported format detection (issue2172)
r12393 # check for _darcs, ElementTree so that we can easily skip
# test-convert-darcs if ElementTree is not around
Alexis S. L. Carvalho
test-convert-darcs: skip if we can't find the elementtree module...
r5520 if not os.path.exists(os.path.join(path, '_darcs')):
Martin Geisler
convert: mark strings for translation
r10939 raise NoRepo(_("%s does not look like a darcs repository") % path)
Bryan O'Sullivan
convert: support darcs as a source repo
r5359
timeless
convert: darcs use absolute_import
r28368 common.checktool('darcs')
Bryan O'Sullivan
issue1251: bail if darcs version is too old
r9242 version = self.run0('--version').splitlines()[0].strip()
if version < '2.1':
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(_('darcs version 2.1 or newer needed (found %r)')
% version)
Patrick Mezard
convert: fail if an external required tool is not found
r5497
Matt Mackall
check-code: enable camelcase check, fix up problems
r15457 if "ElementTree" not in globals():
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(_("Python ElementTree module is not available"))
Bryan O'Sullivan
convert: support darcs as a source repo
r5359
Matt Mackall
backout dbdb777502dc (issue3077) (issue3071)...
r15381 self.path = os.path.realpath(path)
Bryan O'Sullivan
convert: support darcs as a source repo
r5359
self.lastrev = None
self.changes = {}
self.parents = {}
self.tags = {}
Patrick Mezard
convert/darcs: improve unsupported format detection (issue2172)
r12393 # Check darcs repository format
format = self.format()
if format:
if format in ('darcs-1.0', 'hashed'):
raise NoRepo(_("%s repository format is unsupported, "
"please upgrade") % format)
else:
self.ui.warn(_('failed to detect repository format!'))
Bryan O'Sullivan
convert: support darcs as a source repo
r5359 def before(self):
Yuya Nishihara
py3: wrap tempfile.mkdtemp() to use bytes path...
r38183 self.tmppath = pycompat.mkdtemp(
Bryan O'Sullivan
convert: support darcs as a source repo
r5359 prefix='convert-' + os.path.basename(self.path) + '-')
output, status = self.run('init', repodir=self.tmppath)
self.checkexit(status)
Bryan O'Sullivan
convert: abstract darcs's commandline handling
r5512 tree = self.xml('changes', xml_output=True, summary=True,
repodir=self.path)
Bryan O'Sullivan
convert: support darcs as a source repo
r5359 tagname = None
child = None
for elt in tree.findall('patch'):
node = elt.get('hash')
name = elt.findtext('name', '')
if name.startswith('TAG '):
tagname = name[4:].strip()
elif tagname is not None:
self.tags[tagname] = node
tagname = None
self.changes[node] = elt
self.parents[child] = [node]
child = node
self.parents[child] = []
def after(self):
Martin Geisler
do not attempt to translate ui.debug output
r9467 self.ui.debug('cleaning up %s\n' % self.tmppath)
Bryan O'Sullivan
convert: fix a few residual bugs in darcs importer
r5362 shutil.rmtree(self.tmppath, ignore_errors=True)
Bryan O'Sullivan
convert: support darcs as a source repo
r5359
Brodie Rao
convert/darcs: support changelogs with bytes 0x7F-0xFF (issue2411)...
r12717 def recode(self, s, encoding=None):
Pulkit Goyal
py3: replace `unicode` with pycompat.unicode...
r38332 if isinstance(s, pycompat.unicode):
Brodie Rao
convert/darcs: support changelogs with bytes 0x7F-0xFF (issue2411)...
r12717 # XMLParser returns unicode objects for anything it can't
# encode into ASCII. We convert them back to str to get
# recode's normal conversion behavior.
s = s.encode('latin-1')
return super(darcs_source, self).recode(s, encoding)
Bryan O'Sullivan
convert: abstract darcs's commandline handling
r5512 def xml(self, cmd, **kwargs):
Brodie Rao
convert/darcs: handle non-ASCII metadata in darcs changelog (issue2354)...
r12252 # NOTE: darcs is currently encoding agnostic and will print
# patch metadata byte-for-byte, even in the XML changelog.
Bryan O'Sullivan
convert: support darcs as a source repo
r5359 etree = ElementTree()
Brodie Rao
convert/darcs: support changelogs with bytes 0x7F-0xFF (issue2411)...
r12717 # While we are decoding the XML as latin-1 to be as liberal as
# possible, etree will still raise an exception if any
# non-printable characters are in the XML changelog.
parser = XMLParser(encoding='latin-1')
Patrick Mezard
convert: use subprocess for all commandline calls...
r17413 p = self._run(cmd, **kwargs)
etree.parse(p.stdout, parser=parser)
p.wait()
self.checkexit(p.returncode)
Bryan O'Sullivan
convert: support darcs as a source repo
r5359 return etree.getroot()
Patrick Mezard
convert/darcs: improve unsupported format detection (issue2172)
r12393 def format(self):
Yuya Nishihara
convert: don't pass --no-files to "darcs show repo" command...
r38484 output, status = self.run('show', 'repo', repodir=self.path)
Patrick Mezard
convert/darcs: improve unsupported format detection (issue2172)
r12393 self.checkexit(status)
m = re.search(r'^\s*Format:\s*(.*)$', output, re.MULTILINE)
if not m:
return None
return ','.join(sorted(f.strip() for f in m.group(1).split(',')))
Patrick Mezard
convert/darcs: handle directory renaming
r9527 def manifest(self):
man = []
output, status = self.run('show', 'files', no_directories=True,
repodir=self.tmppath)
self.checkexit(status)
for line in output.split('\n'):
path = line[2:]
if path:
man.append(path)
return man
Bryan O'Sullivan
convert: support darcs as a source repo
r5359 def getheads(self):
return self.parents[None]
def getcommit(self, rev):
elt = self.changes[rev]
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 dateformat = '%a %b %d %H:%M:%S %Z %Y'
date = dateutil.strdate(elt.get('local_date'), dateformat)
Bryan O'Sullivan
convert: support darcs as a source repo
r5359 desc = elt.findtext('name') + '\n' + elt.findtext('comment', '')
Brodie Rao
convert/darcs: handle non-ASCII metadata in darcs changelog (issue2354)...
r12252 # etree can return unicode objects for name, comment, and author,
# so recode() is used to ensure str objects are emitted.
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 newdateformat = '%Y-%m-%d %H:%M:%S %1%2'
timeless
convert: darcs use absolute_import
r28368 return common.commit(author=self.recode(elt.get('author')),
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 date=dateutil.datestr(date, newdateformat),
timeless
convert: darcs use absolute_import
r28368 desc=self.recode(desc).strip(),
parents=self.parents[rev])
Bryan O'Sullivan
convert: support darcs as a source repo
r5359
def pull(self, rev):
Bryan O'Sullivan
convert: abstract darcs's commandline handling
r5512 output, status = self.run('pull', self.path, all=True,
match='hash %s' % rev,
no_test=True, no_posthook=True,
external_merge='/bin/false',
Bryan O'Sullivan
convert: support darcs as a source repo
r5359 repodir=self.tmppath)
if status:
if output.find('We have conflicts in') == -1:
self.checkexit(status, output)
Bryan O'Sullivan
convert: abstract darcs's commandline handling
r5512 output, status = self.run('revert', all=True, repodir=self.tmppath)
Bryan O'Sullivan
convert: support darcs as a source repo
r5359 self.checkexit(status, output)
Mads Kiilerich
convert: introduce --full for converting all files...
r22300 def getchanges(self, rev, full):
if full:
timeless@mozdev.org
grammar: use does instead of do where appropriate
r26779 raise error.Abort(_("convert from darcs does not support --full"))
Bryan O'Sullivan
convert: support darcs as a source repo
r5359 copies = {}
changes = []
Patrick Mezard
convert/darcs: handle directory renaming
r9527 man = None
Bryan O'Sullivan
convert: support darcs as a source repo
r5359 for elt in self.changes[rev].find('summary').getchildren():
if elt.tag in ('add_directory', 'remove_directory'):
continue
if elt.tag == 'move':
Patrick Mezard
convert/darcs: handle directory renaming
r9527 if man is None:
man = self.manifest()
source, dest = elt.get('from'), elt.get('to')
if source in man:
# File move
changes.append((source, rev))
changes.append((dest, rev))
copies[dest] = source
else:
# Directory move, deduce file moves from manifest
source = source + '/'
for f in man:
if not f.startswith(source):
continue
fdest = dest + '/' + f[len(source):]
changes.append((f, rev))
changes.append((fdest, rev))
copies[fdest] = f
Bryan O'Sullivan
convert: support darcs as a source repo
r5359 else:
changes.append((elt.text.strip(), rev))
Patrick Mezard
convert/darcs: handle directory renaming
r9527 self.pull(rev)
Bryan O'Sullivan
convert: support darcs as a source repo
r5359 self.lastrev = rev
Mads Kiilerich
convert: optimize convert of files that are unmodified from p2 in merges...
r24395 return sorted(changes), copies, set()
Bryan O'Sullivan
convert: support darcs as a source repo
r5359
def getfile(self, name, rev):
if rev != self.lastrev:
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(_('internal calling inconsistency'))
Patrick Mezard
convert: merge sources getmode() into getfile()
r11134 path = os.path.join(self.tmppath, name)
Mads Kiilerich
convert: use None value for missing files instead of overloading IOError...
r22296 try:
data = util.readfile(path)
mode = os.lstat(path).st_mode
Gregory Szorc
global: mass rewrite to use modern exception syntax...
r25660 except IOError as inst:
Mads Kiilerich
convert: use None value for missing files instead of overloading IOError...
r22296 if inst.errno == errno.ENOENT:
return None, None
raise
Gregory Szorc
global: mass rewrite to use modern octal syntax...
r25658 mode = (mode & 0o111) and 'x' or ''
Patrick Mezard
convert: merge sources getmode() into getfile()
r11134 return data, mode
Bryan O'Sullivan
convert: support darcs as a source repo
r5359
def gettags(self):
return self.tags