diff options
Diffstat (limited to 'cvs2svn_lib/repository_mirror.py')
-rw-r--r-- | cvs2svn_lib/repository_mirror.py | 897 |
1 files changed, 0 insertions, 897 deletions
diff --git a/cvs2svn_lib/repository_mirror.py b/cvs2svn_lib/repository_mirror.py deleted file mode 100644 index 72e2ba1..0000000 --- a/cvs2svn_lib/repository_mirror.py +++ /dev/null @@ -1,897 +0,0 @@ -# (Be in -*- python -*- mode.) -# -# ==================================================================== -# Copyright (c) 2000-2009 CollabNet. All rights reserved. -# -# This software is licensed as described in the file COPYING, which -# you should have received as part of this distribution. The terms -# are also available at http://subversion.tigris.org/license-1.html. -# If newer versions of this license are posted there, you may use a -# newer version instead, at your option. -# -# This software consists of voluntary contributions made by many -# individuals. For exact contribution history, see the revision -# history and logs, available at http://cvs2svn.tigris.org/. -# ==================================================================== - -"""This module contains the RepositoryMirror class and supporting classes. - -RepositoryMirror represents the skeleton of a versioned file tree with -multiple lines of development ('LODs'). It records the presence or -absence of files and directories, but not their contents. Given three -values (revnum, lod, cvs_path), it can tell you whether the specified -CVSPath existed on the specified LOD in the given revision number. -The file trees corresponding to the most recent revision can be -modified. - -The individual file trees are stored using immutable tree structures. -Each directory node is represented as a MirrorDirectory instance, -which is basically a map {cvs_path : node_id}, where cvs_path is a -CVSPath within the directory, and node_id is an integer ID that -uniquely identifies another directory node if that node is a -CVSDirectory, or None if that node is a CVSFile. If a directory node -is to be modified, then first a new node is created with a copy of the -original node's contents, then the copy is modified. A reference to -the copy also has to be stored in the parent node, meaning that the -parent node needs to be modified, and so on recursively to the root -node of the file tree. This data structure allows cheap deep copies, -which is useful for tagging and branching. - -The class must also be able to find the root directory node -corresponding to a particular (revnum, lod). This is done by keeping -an LODHistory instance for each LOD, which can determine the root -directory node ID for that LOD for any revnum. It does so by -recording changes to the root directory node ID only for revisions in -which it changed. Thus it stores two arrays, revnums (a list of the -revision numbers when the ID changed), and ids (a list of the -corresponding IDs). To find the ID for a particular revnum, first a -binary search is done in the revnums array to find the index of the -last change preceding revnum, then the corresponding ID is read from -the ids array. Since most revisions change only one LOD, this allows -storage of the history of potentially tens of thousands of LODs over -hundreds of thousands of revisions in an amount of space that scales -as O(numberOfLODs + numberOfRevisions), rather than O(numberOfLODs * -numberOfRevisions) as would be needed if the information were stored -in the equivalent of a 2D array. - -The internal operation of these classes is somewhat intricate, but the -interface attempts to hide the complexity, enforce the usage rules, -and allow efficient access. The most important facts to remember are -(1) that a directory node can be used for multiple purposes (for -multiple branches and for multiple revisions on a single branch), (2) -that only a node that has been created within the current revision is -allowed to be mutated, and (3) that the current revision can include -nodes carried over from prior revisions, which are immutable. - -This leads to a bewildering variety of MirrorDirectory classes. The -most important distinction is between OldMirrorDirectories and -CurrentMirrorDirectories. A single node can be represented multiple -ways in memory at the same time, depending on whether it was looked up -as part of the current revision or part of an old revision: - - MirrorDirectory -- the base class for all MirrorDirectory nodes. - This class allows lookup of subnodes and iteration over - subnodes. - - OldMirrorDirectory -- a MirrorDirectory that was looked up for an - old revision. These instances are immutable, as only the - current revision is allowed to be modified. - - CurrentMirrorDirectory -- a MirrorDirectory that was looked up for - the current revision. Such an instance is always logically - mutable, though mutating it might require the node to be - copied first. Such an instance might represent a node that - has already been copied during this revision and can therefore - be modified freely (such nodes implement - _WritableMirrorDirectoryMixin), or it might represent a node - that was carried over from an old revision and hasn't been - copied yet (such nodes implement - _ReadOnlyMirrorDirectoryMixin). If the latter, then the node - copies itself (and bubbles up the change) before allowing - itself to be modified. But the distinction is managed - internally; client classes should not have to worry about it. - - CurrentMirrorLODDirectory -- A CurrentMirrorDirectory representing - the root directory of a line of development in the current - revision. This class has two concrete subclasses, - _CurrentMirrorReadOnlyLODDirectory and - _CurrentMirrorWritableLODDirectory, depending on whether the - node has already been copied during this revision. - - - CurrentMirrorSubdirectory -- A CurrentMirrorDirectory representing - a subdirectory within a line of development's directory tree - in the current revision. This class has two concrete - subclasses, _CurrentMirrorReadOnlySubdirectory and - _CurrentMirrorWritableSubdirectory, depending on whether the - node has already been copied during this revision. - - DeletedCurrentMirrorDirectory -- a MirrorDirectory that has been - deleted. Such an instance is disabled so that it cannot - accidentally be used. - -While a revision is being processed, RepositoryMirror._new_nodes holds -every writable CurrentMirrorDirectory instance (i.e., every node that -has been created in the revision). Since these nodes are mutable, it -is important that there be exactly one instance associated with each -node; otherwise there would be problems keeping the instances -synchronized. These are written to the database by -RepositoryMirror.end_commit(). - -OldMirrorDirectory and read-only CurrentMirrorDirectory instances are -*not* cached; they are recreated whenever they are referenced. There -might be multiple instances referring to the same node. A read-only -CurrentMirrorDirectory instance is mutated in place into a writable -CurrentMirrorDirectory instance if it needs to be modified. - -FIXME: The rules for when a MirrorDirectory instance can continue to -be used vs. when it has to be read again (because it has been modified -indirectly and therefore copied) are confusing and error-prone. -Probably the semantics should be changed. - -""" - - -import bisect - -from cvs2svn_lib import config -from cvs2svn_lib.common import DB_OPEN_NEW -from cvs2svn_lib.common import InternalError -from cvs2svn_lib.log import Log -from cvs2svn_lib.context import Ctx -from cvs2svn_lib.cvs_file import CVSFile -from cvs2svn_lib.cvs_file import CVSDirectory -from cvs2svn_lib.key_generator import KeyGenerator -from cvs2svn_lib.artifact_manager import artifact_manager -from cvs2svn_lib.serializer import MarshalSerializer -from cvs2svn_lib.database import IndexedDatabase - - -class RepositoryMirrorError(Exception): - """An error related to the RepositoryMirror.""" - - pass - - -class LODExistsError(RepositoryMirrorError): - """The LOD already exists in the repository. - - Exception raised if an attempt is made to add an LOD to the - repository mirror and that LOD already exists in the youngest - revision of the repository.""" - - pass - - -class PathExistsError(RepositoryMirrorError): - """The path already exists in the repository. - - Exception raised if an attempt is made to add a path to the - repository mirror and that path already exists in the youngest - revision of the repository.""" - - pass - - -class DeletedNodeReusedError(RepositoryMirrorError): - """The MirrorDirectory has already been deleted and shouldn't be reused.""" - - pass - - -class CopyFromCurrentNodeError(RepositoryMirrorError): - """A CurrentMirrorDirectory cannot be copied to the current revision.""" - - pass - - -class MirrorDirectory(object): - """Represent a node within the RepositoryMirror. - - Instances of this class act like a map {CVSPath : MirrorDirectory}, - where CVSPath is an item within this directory (i.e., a file or - subdirectory within this directory). The value is either another - MirrorDirectory instance (for directories) or None (for files).""" - - def __init__(self, repo, id, entries): - # The RepositoryMirror containing this directory: - self.repo = repo - - # The id of this node: - self.id = id - - # The entries within this directory, stored as a map {CVSPath : - # node_id}. The node_ids are integers for CVSDirectories, None - # for CVSFiles: - self._entries = entries - - def __getitem__(self, cvs_path): - """Return the MirrorDirectory associated with the specified subnode. - - Return a MirrorDirectory instance if the subnode is a - CVSDirectory; None if it is a CVSFile. Raise KeyError if the - specified subnode does not exist.""" - - raise NotImplementedError() - - def __len__(self): - """Return the number of CVSPaths within this node.""" - - return len(self._entries) - - def __contains__(self, cvs_path): - """Return True iff CVS_PATH is contained in this node.""" - - return cvs_path in self._entries - - def __iter__(self): - """Iterate over the CVSPaths within this node.""" - - return self._entries.__iter__() - - def _format_entries(self): - """Format the entries map for output in subclasses' __repr__() methods.""" - - def format_item(key, value): - if value is None: - return str(key) - else: - return '%s -> %x' % (key, value,) - - items = self._entries.items() - items.sort() - return '{%s}' % (', '.join([format_item(*item) for item in items]),) - - def __str__(self): - """For convenience only. The format is subject to change at any time.""" - - return '%s<%x>' % (self.__class__.__name__, self.id,) - - -class OldMirrorDirectory(MirrorDirectory): - """Represent a historical directory within the RepositoryMirror.""" - - def __getitem__(self, cvs_path): - id = self._entries[cvs_path] - if id is None: - # This represents a leaf node. - return None - else: - return OldMirrorDirectory(self.repo, id, self.repo._node_db[id]) - - def __repr__(self): - """For convenience only. The format is subject to change at any time.""" - - return '%s(%s)' % (self, self._format_entries(),) - - -class CurrentMirrorDirectory(MirrorDirectory): - """Represent a directory that currently exists in the RepositoryMirror.""" - - def __init__(self, repo, id, lod, cvs_path, entries): - MirrorDirectory.__init__(self, repo, id, entries) - self.lod = lod - self.cvs_path = cvs_path - - def __getitem__(self, cvs_path): - id = self._entries[cvs_path] - if id is None: - # This represents a leaf node. - return None - else: - try: - return self.repo._new_nodes[id] - except KeyError: - return _CurrentMirrorReadOnlySubdirectory( - self.repo, id, self.lod, cvs_path, self, - self.repo._node_db[id] - ) - - def __setitem__(self, cvs_path, node): - """Create or overwrite a subnode of this node. - - CVS_PATH is the path of the subnode. NODE will be the new value - of the node; for CVSDirectories it should be a MirrorDirectory - instance; for CVSFiles it should be None.""" - - if isinstance(node, DeletedCurrentMirrorDirectory): - raise DeletedNodeReusedError( - '%r has already been deleted and should not be reused' % (node,) - ) - elif isinstance(node, CurrentMirrorDirectory): - raise CopyFromCurrentNodeError( - '%r was created in the current node and cannot be copied' % (node,) - ) - else: - self._set_entry(cvs_path, node) - - def __delitem__(self, cvs_path): - """Remove the subnode of this node at CVS_PATH. - - If the node does not exist, then raise a KeyError.""" - - node = self[cvs_path] - self._del_entry(cvs_path) - if isinstance(node, _WritableMirrorDirectoryMixin): - node._mark_deleted() - - def mkdir(self, cvs_directory): - """Create an empty subdirectory of this node at CVS_PATH. - - Return the CurrentDirectory that was created.""" - - assert isinstance(cvs_directory, CVSDirectory) - if cvs_directory in self: - raise PathExistsError( - 'Attempt to create directory \'%s\' in %s in repository mirror ' - 'when it already exists.' - % (cvs_directory, self.lod,) - ) - - new_node = _CurrentMirrorWritableSubdirectory( - self.repo, self.repo._key_generator.gen_id(), self.lod, cvs_directory, - self, {} - ) - self._set_entry(cvs_directory, new_node) - self.repo._new_nodes[new_node.id] = new_node - return new_node - - def add_file(self, cvs_file): - """Create a file within this node at CVS_FILE.""" - - assert isinstance(cvs_file, CVSFile) - if cvs_file in self: - raise PathExistsError( - 'Attempt to create file \'%s\' in %s in repository mirror ' - 'when it already exists.' - % (cvs_file, self.lod,) - ) - - self._set_entry(cvs_file, None) - - def __repr__(self): - """For convenience only. The format is subject to change at any time.""" - - return '%s(%r, %r, %s)' % ( - self, self.lod, self.cvs_path, self._format_entries(), - ) - - -class DeletedCurrentMirrorDirectory(object): - """A MirrorDirectory that has been deleted. - - A MirrorDirectory that used to be a _WritableMirrorDirectoryMixin - but then was deleted. Such instances are turned into this class so - that nobody can accidentally mutate them again.""" - - pass - - -class _WritableMirrorDirectoryMixin: - """Mixin for MirrorDirectories that are already writable. - - A MirrorDirectory is writable if it has already been recreated - during the current revision.""" - - def _set_entry(self, cvs_path, node): - """Create or overwrite a subnode of this node, with no checks.""" - - if node is None: - self._entries[cvs_path] = None - else: - self._entries[cvs_path] = node.id - - def _del_entry(self, cvs_path): - """Remove the subnode of this node at CVS_PATH, with no checks.""" - - del self._entries[cvs_path] - - def _mark_deleted(self): - """Mark this object and any writable descendants as being deleted.""" - - self.__class__ = DeletedCurrentMirrorDirectory - - for (cvs_path, id) in self._entries.iteritems(): - if id in self.repo._new_nodes: - node = self[cvs_path] - if isinstance(node, _WritableMirrorDirectoryMixin): - # Mark deleted and recurse: - node._mark_deleted() - - -class _ReadOnlyMirrorDirectoryMixin: - """Mixin for a CurrentMirrorDirectory that hasn't yet been made writable.""" - - def _make_writable(self): - raise NotImplementedError() - - def _set_entry(self, cvs_path, node): - """Create or overwrite a subnode of this node, with no checks.""" - - self._make_writable() - self._set_entry(cvs_path, node) - - def _del_entry(self, cvs_path): - """Remove the subnode of this node at CVS_PATH, with no checks.""" - - self._make_writable() - self._del_entry(cvs_path) - - -class CurrentMirrorLODDirectory(CurrentMirrorDirectory): - """Represent an LOD's main directory in the mirror's current version.""" - - def __init__(self, repo, id, lod, entries): - CurrentMirrorDirectory.__init__( - self, repo, id, lod, lod.project.get_root_cvs_directory(), entries - ) - - def delete(self): - """Remove the directory represented by this object.""" - - lod_history = self.repo._get_lod_history(self.lod) - assert lod_history.exists() - lod_history.update(self.repo._youngest, None) - self._mark_deleted() - - -class _CurrentMirrorReadOnlyLODDirectory( - CurrentMirrorLODDirectory, _ReadOnlyMirrorDirectoryMixin - ): - """Represent an LOD's main directory in the mirror's current version.""" - - def _make_writable(self): - self.__class__ = _CurrentMirrorWritableLODDirectory - # Create a new ID: - self.id = self.repo._key_generator.gen_id() - self.repo._new_nodes[self.id] = self - self.repo._get_lod_history(self.lod).update(self.repo._youngest, self.id) - self._entries = self._entries.copy() - - -class _CurrentMirrorWritableLODDirectory( - CurrentMirrorLODDirectory, _WritableMirrorDirectoryMixin - ): - pass - - -class CurrentMirrorSubdirectory(CurrentMirrorDirectory): - """Represent a subdirectory in the mirror's current version.""" - - def __init__(self, repo, id, lod, cvs_path, parent_mirror_dir, entries): - CurrentMirrorDirectory.__init__(self, repo, id, lod, cvs_path, entries) - self.parent_mirror_dir = parent_mirror_dir - - def delete(self): - """Remove the directory represented by this object.""" - - del self.parent_mirror_dir[self.cvs_path] - - -class _CurrentMirrorReadOnlySubdirectory( - CurrentMirrorSubdirectory, _ReadOnlyMirrorDirectoryMixin - ): - """Represent a subdirectory in the mirror's current version.""" - - def _make_writable(self): - self.__class__ = _CurrentMirrorWritableSubdirectory - # Create a new ID: - self.id = self.repo._key_generator.gen_id() - self.repo._new_nodes[self.id] = self - self.parent_mirror_dir._set_entry(self.cvs_path, self) - self._entries = self._entries.copy() - - -class _CurrentMirrorWritableSubdirectory( - CurrentMirrorSubdirectory, _WritableMirrorDirectoryMixin - ): - pass - - -class LODHistory(object): - """The history of root nodes for a line of development. - - Members: - - _mirror -- (RepositoryMirror) the RepositoryMirror that manages - this LODHistory. - - lod -- (LineOfDevelopment) the LOD described by this LODHistory. - - revnums -- (list of int) the revision numbers in which the id - changed, in numerical order. - - ids -- (list of (int or None)) the ID of the node describing the - root of this LOD starting at the corresponding revision - number, or None if the LOD did not exist in that revision. - - To find the root id for a given revision number, a binary search is - done within REVNUMS to find the index of the most recent revision at - the time of REVNUM, then that index is used to read the id out of - IDS. - - A sentry is written at the zeroth index of both arrays to describe - the initial situation, namely, that the LOD doesn't exist in - revision r0.""" - - __slots__ = ['_mirror', 'lod', 'revnums', 'ids'] - - def __init__(self, mirror, lod): - self._mirror = mirror - self.lod = lod - self.revnums = [0] - self.ids = [None] - - def get_id(self, revnum): - """Get the ID of the root path for this LOD in REVNUM. - - Raise KeyError if this LOD didn't exist in REVNUM.""" - - index = bisect.bisect_right(self.revnums, revnum) - 1 - id = self.ids[index] - - if id is None: - raise KeyError() - - return id - - def get_current_id(self): - """Get the ID of the root path for this LOD in the current revision. - - Raise KeyError if this LOD doesn't currently exist.""" - - id = self.ids[-1] - - if id is None: - raise KeyError() - - return id - - def exists(self): - """Return True iff LOD exists in the current revision.""" - - return self.ids[-1] is not None - - def update(self, revnum, id): - """Indicate that the root node of this LOD changed to ID at REVNUM. - - REVNUM is a revision number that must be the same as that of the - previous recorded change (in which case the previous change is - overwritten) or later (in which the new change is appended). - - ID can be a node ID, or it can be None to indicate that this LOD - ceased to exist in REVNUM.""" - - if revnum < self.revnums[-1]: - raise KeyError() - elif revnum == self.revnums[-1]: - # This is an attempt to overwrite an entry that was already - # updated during this revision. Don't allow the replacement - # None -> None or allow one new id to be replaced with another: - old_id = self.ids[-1] - if old_id is None and id is None: - raise InternalError( - 'ID changed from None -> None for %s, r%d' % (self.lod, revnum,) - ) - elif (old_id is not None and id is not None - and old_id in self._mirror._new_nodes): - raise InternalError( - 'ID changed from %x -> %x for %s, r%d' - % (old_id, id, self.lod, revnum,) - ) - self.ids[-1] = id - else: - self.revnums.append(revnum) - self.ids.append(id) - - -class _NodeDatabase(object): - """A database storing all of the directory nodes. - - The nodes are written in groups every time write_new_nodes() is - called. To the database is written a dictionary {node_id : - [(cvs_path.id, node_id),...]}, where the keys are the node_ids of - the new nodes. When a node is read, its whole group is read and - cached under the assumption that the other nodes in the group are - likely to be needed soon. The cache is retained across revisions - and cleared when _cache_max_size is exceeded. - - The dictionaries for nodes that have been read from the database - during the current revision are cached by node_id in the _cache - member variable. The corresponding dictionaries are *not* copied - when read. To avoid cross-talk between distinct MirrorDirectory - instances that have the same node_id, users of these dictionaries - have to copy them before modification.""" - - # How many entries should be allowed in the cache for each - # CVSDirectory in the repository. (This number is very roughly the - # number of complete lines of development that can be stored in the - # cache at one time.) - CACHE_SIZE_MULTIPLIER = 5 - - # But the cache will never be limited to less than this number: - MIN_CACHE_LIMIT = 5000 - - def __init__(self): - self.cvs_file_db = Ctx()._cvs_file_db - self.db = IndexedDatabase( - artifact_manager.get_temp_file(config.MIRROR_NODES_STORE), - artifact_manager.get_temp_file(config.MIRROR_NODES_INDEX_TABLE), - DB_OPEN_NEW, serializer=MarshalSerializer(), - ) - - # A list of the maximum node_id stored by each call to - # write_new_nodes(): - self._max_node_ids = [0] - - # A map {node_id : {cvs_path : node_id}}: - self._cache = {} - - # The number of directories in the repository: - num_dirs = len([ - cvs_path - for cvs_path in self.cvs_file_db.itervalues() - if isinstance(cvs_path, CVSDirectory) - ]) - - self._cache_max_size = max( - int(self.CACHE_SIZE_MULTIPLIER * num_dirs), - self.MIN_CACHE_LIMIT, - ) - - def _load(self, items): - retval = {} - for (id, value) in items: - retval[self.cvs_file_db.get_file(id)] = value - return retval - - def _dump(self, node): - return [ - (cvs_path.id, value) - for (cvs_path, value) in node.iteritems() - ] - - def _determine_index(self, id): - """Return the index of the record holding the node with ID.""" - - return bisect.bisect_left(self._max_node_ids, id) - - def __getitem__(self, id): - try: - items = self._cache[id] - except KeyError: - index = self._determine_index(id) - for (node_id, items) in self.db[index].items(): - self._cache[node_id] = self._load(items) - items = self._cache[id] - - return items - - def write_new_nodes(self, nodes): - """Write NODES to the database. - - NODES is an iterable of writable CurrentMirrorDirectory instances.""" - - if len(self._cache) > self._cache_max_size: - # The size of the cache has exceeded the threshold. Discard the - # old cache values (but still store the new nodes into the - # cache): - Log().debug('Clearing node cache') - self._cache.clear() - - data = {} - max_node_id = 0 - for node in nodes: - max_node_id = max(max_node_id, node.id) - data[node.id] = self._dump(node._entries) - self._cache[node.id] = node._entries - - self.db[len(self._max_node_ids)] = data - - if max_node_id == 0: - # Rewrite last value: - self._max_node_ids.append(self._max_node_ids[-1]) - else: - self._max_node_ids.append(max_node_id) - - def close(self): - self._cache.clear() - self.db.close() - self.db = None - - -class RepositoryMirror: - """Mirror a repository and its history. - - Mirror a repository as it is constructed, one revision at a time. - For each LineOfDevelopment we store a skeleton of the directory - structure within that LOD for each revnum in which it changed. - - For each LOD that has been seen so far, an LODHistory instance is - stored in self._lod_histories. An LODHistory keeps track of each - revnum in which files were added to or deleted from that LOD, as - well as the node id of the root of the node tree describing the LOD - contents at that revision. - - The LOD trees themselves are stored in the _node_db database, which - maps node ids to nodes. A node is a map from CVSPath to ids of the - corresponding subnodes. The _node_db is stored on disk and each - access is expensive. - - The _node_db database only holds the nodes for old revisions. The - revision that is being constructed is kept in memory in the - _new_nodes map, which is cheap to access. - - You must invoke start_commit() before each commit and end_commit() - afterwards.""" - - def register_artifacts(self, which_pass): - """Register the artifacts that will be needed for this object.""" - - artifact_manager.register_temp_file( - config.MIRROR_NODES_INDEX_TABLE, which_pass - ) - artifact_manager.register_temp_file( - config.MIRROR_NODES_STORE, which_pass - ) - - def open(self): - """Set up the RepositoryMirror and prepare it for commits.""" - - self._key_generator = KeyGenerator() - - # A map from LOD to LODHistory instance for all LODs that have - # been referenced so far: - self._lod_histories = {} - - # This corresponds to the 'nodes' table in a Subversion fs. (We - # don't need a 'representations' or 'strings' table because we - # only track file existence, not file contents.) - self._node_db = _NodeDatabase() - - # Start at revision 0 without a root node. - self._youngest = 0 - - def start_commit(self, revnum): - """Start a new commit.""" - - assert revnum > self._youngest - self._youngest = revnum - - # A map {node_id : _WritableMirrorDirectoryMixin}. - self._new_nodes = {} - - def end_commit(self): - """Called at the end of each commit. - - This method copies the newly created nodes to the on-disk nodes - db.""" - - # Copy the new nodes to the _node_db - self._node_db.write_new_nodes([ - node - for node in self._new_nodes.values() - if not isinstance(node, DeletedCurrentMirrorDirectory) - ]) - - del self._new_nodes - - def _get_lod_history(self, lod): - """Return the LODHistory instance describing LOD. - - Create a new (empty) LODHistory if it doesn't yet exist.""" - - try: - return self._lod_histories[lod] - except KeyError: - lod_history = LODHistory(self, lod) - self._lod_histories[lod] = lod_history - return lod_history - - def get_old_lod_directory(self, lod, revnum): - """Return the directory for the root path of LOD at revision REVNUM. - - Return an instance of MirrorDirectory if the path exists; - otherwise, raise KeyError.""" - - lod_history = self._get_lod_history(lod) - id = lod_history.get_id(revnum) - return OldMirrorDirectory(self, id, self._node_db[id]) - - def get_old_path(self, cvs_path, lod, revnum): - """Return the node for CVS_PATH from LOD at REVNUM. - - If CVS_PATH is a CVSDirectory, then return an instance of - OldMirrorDirectory. If CVS_PATH is a CVSFile, return None. - - If CVS_PATH does not exist in the specified LOD and REVNUM, raise - KeyError.""" - - node = self.get_old_lod_directory(lod, revnum) - - for sub_path in cvs_path.get_ancestry()[1:]: - node = node[sub_path] - - return node - - def get_current_lod_directory(self, lod): - """Return the directory for the root path of LOD in the current revision. - - Return an instance of CurrentMirrorDirectory. Raise KeyError if - the path doesn't already exist.""" - - lod_history = self._get_lod_history(lod) - id = lod_history.get_current_id() - try: - return self._new_nodes[id] - except KeyError: - return _CurrentMirrorReadOnlyLODDirectory( - self, id, lod, self._node_db[id] - ) - - def get_current_path(self, cvs_path, lod): - """Return the node for CVS_PATH from LOD in the current revision. - - If CVS_PATH is a CVSDirectory, then return an instance of - CurrentMirrorDirectory. If CVS_PATH is a CVSFile, return None. - - If CVS_PATH does not exist in the current revision of the - specified LOD, raise KeyError.""" - - node = self.get_current_lod_directory(lod) - - for sub_path in cvs_path.get_ancestry()[1:]: - node = node[sub_path] - - return node - - def add_lod(self, lod): - """Create a new LOD in this repository. - - Return the CurrentMirrorDirectory that was created. If the LOD - already exists, raise LODExistsError.""" - - lod_history = self._get_lod_history(lod) - if lod_history.exists(): - raise LODExistsError( - 'Attempt to create %s in repository mirror when it already exists.' - % (lod,) - ) - new_node = _CurrentMirrorWritableLODDirectory( - self, self._key_generator.gen_id(), lod, {} - ) - lod_history.update(self._youngest, new_node.id) - self._new_nodes[new_node.id] = new_node - return new_node - - def copy_lod(self, src_lod, dest_lod, src_revnum): - """Copy all of SRC_LOD at SRC_REVNUM to DST_LOD. - - In the youngest revision of the repository, the destination LOD - *must not* already exist. - - Return the new node at DEST_LOD, as a CurrentMirrorDirectory.""" - - # Get the node of our src_path - src_node = self.get_old_lod_directory(src_lod, src_revnum) - - dest_lod_history = self._get_lod_history(dest_lod) - if dest_lod_history.exists(): - raise LODExistsError( - 'Attempt to copy to %s in repository mirror when it already exists.' - % (dest_lod,) - ) - - dest_lod_history.update(self._youngest, src_node.id) - - # Return src_node, except packaged up as a CurrentMirrorDirectory: - return self.get_current_lod_directory(dest_lod) - - def close(self): - """Free resources and close databases.""" - - self._lod_histories = None - self._node_db.close() - self._node_db = None - - |