1 # (Be in -*- python -*- mode.)
3 # ====================================================================
4 # Copyright (c) 2000-2009 CollabNet. All rights reserved.
6 # This software is licensed as described in the file COPYING, which
7 # you should have received as part of this distribution. The terms
8 # are also available at http://subversion.tigris.org/license-1.html.
9 # If newer versions of this license are posted there, you may use a
10 # newer version instead, at your option.
12 # This software consists of voluntary contributions made by many
13 # individuals. For exact contribution history, see the revision
14 # history and logs, available at http://cvs2svn.tigris.org/.
15 # ====================================================================
17 """This module contains the RepositoryMirror class and supporting classes.
19 RepositoryMirror represents the skeleton of a versioned file tree with
20 multiple lines of development ('LODs'). It records the presence or
21 absence of files and directories, but not their contents. Given three
22 values (revnum, lod, cvs_path), it can tell you whether the specified
23 CVSPath existed on the specified LOD in the given revision number.
24 The file trees corresponding to the most recent revision can be
27 The individual file trees are stored using immutable tree structures.
28 Each directory node is represented as a MirrorDirectory instance,
29 which is basically a map {cvs_path : node_id}, where cvs_path is a
30 CVSPath within the directory, and node_id is an integer ID that
31 uniquely identifies another directory node if that node is a
32 CVSDirectory, or None if that node is a CVSFile. If a directory node
33 is to be modified, then first a new node is created with a copy of the
34 original node's contents, then the copy is modified. A reference to
35 the copy also has to be stored in the parent node, meaning that the
36 parent node needs to be modified, and so on recursively to the root
37 node of the file tree. This data structure allows cheap deep copies,
38 which is useful for tagging and branching.
40 The class must also be able to find the root directory node
41 corresponding to a particular (revnum, lod). This is done by keeping
42 an LODHistory instance for each LOD, which can determine the root
43 directory node ID for that LOD for any revnum. It does so by
44 recording changes to the root directory node ID only for revisions in
45 which it changed. Thus it stores two arrays, revnums (a list of the
46 revision numbers when the ID changed), and ids (a list of the
47 corresponding IDs). To find the ID for a particular revnum, first a
48 binary search is done in the revnums array to find the index of the
49 last change preceding revnum, then the corresponding ID is read from
50 the ids array. Since most revisions change only one LOD, this allows
51 storage of the history of potentially tens of thousands of LODs over
52 hundreds of thousands of revisions in an amount of space that scales
53 as O(numberOfLODs + numberOfRevisions), rather than O(numberOfLODs *
54 numberOfRevisions) as would be needed if the information were stored
55 in the equivalent of a 2D array.
57 The internal operation of these classes is somewhat intricate, but the
58 interface attempts to hide the complexity, enforce the usage rules,
59 and allow efficient access. The most important facts to remember are
60 (1) that a directory node can be used for multiple purposes (for
61 multiple branches and for multiple revisions on a single branch), (2)
62 that only a node that has been created within the current revision is
63 allowed to be mutated, and (3) that the current revision can include
64 nodes carried over from prior revisions, which are immutable.
66 This leads to a bewildering variety of MirrorDirectory classes. The
67 most important distinction is between OldMirrorDirectories and
68 CurrentMirrorDirectories. A single node can be represented multiple
69 ways in memory at the same time, depending on whether it was looked up
70 as part of the current revision or part of an old revision:
72 MirrorDirectory -- the base class for all MirrorDirectory nodes.
73 This class allows lookup of subnodes and iteration over
76 OldMirrorDirectory -- a MirrorDirectory that was looked up for an
77 old revision. These instances are immutable, as only the
78 current revision is allowed to be modified.
80 CurrentMirrorDirectory -- a MirrorDirectory that was looked up for
81 the current revision. Such an instance is always logically
82 mutable, though mutating it might require the node to be
83 copied first. Such an instance might represent a node that
84 has already been copied during this revision and can therefore
85 be modified freely (such nodes implement
86 _WritableMirrorDirectoryMixin), or it might represent a node
87 that was carried over from an old revision and hasn't been
88 copied yet (such nodes implement
89 _ReadOnlyMirrorDirectoryMixin). If the latter, then the node
90 copies itself (and bubbles up the change) before allowing
91 itself to be modified. But the distinction is managed
92 internally; client classes should not have to worry about it.
94 CurrentMirrorLODDirectory -- A CurrentMirrorDirectory representing
95 the root directory of a line of development in the current
96 revision. This class has two concrete subclasses,
97 _CurrentMirrorReadOnlyLODDirectory and
98 _CurrentMirrorWritableLODDirectory, depending on whether the
99 node has already been copied during this revision.
102 CurrentMirrorSubdirectory -- A CurrentMirrorDirectory representing
103 a subdirectory within a line of development's directory tree
104 in the current revision. This class has two concrete
105 subclasses, _CurrentMirrorReadOnlySubdirectory and
106 _CurrentMirrorWritableSubdirectory, depending on whether the
107 node has already been copied during this revision.
109 DeletedCurrentMirrorDirectory -- a MirrorDirectory that has been
110 deleted. Such an instance is disabled so that it cannot
111 accidentally be used.
113 While a revision is being processed, RepositoryMirror._new_nodes holds
114 every writable CurrentMirrorDirectory instance (i.e., every node that
115 has been created in the revision). Since these nodes are mutable, it
116 is important that there be exactly one instance associated with each
117 node; otherwise there would be problems keeping the instances
118 synchronized. These are written to the database by
119 RepositoryMirror.end_commit().
121 OldMirrorDirectory and read-only CurrentMirrorDirectory instances are
122 *not* cached; they are recreated whenever they are referenced. There
123 might be multiple instances referring to the same node. A read-only
124 CurrentMirrorDirectory instance is mutated in place into a writable
125 CurrentMirrorDirectory instance if it needs to be modified.
127 FIXME: The rules for when a MirrorDirectory instance can continue to
128 be used vs. when it has to be read again (because it has been modified
129 indirectly and therefore copied) are confusing and error-prone.
130 Probably the semantics should be changed.
137 from cvs2svn_lib
import config
138 from cvs2svn_lib
.common
import DB_OPEN_NEW
139 from cvs2svn_lib
.common
import InternalError
140 from cvs2svn_lib
.log
import logger
141 from cvs2svn_lib
.context
import Ctx
142 from cvs2svn_lib
.cvs_path
import CVSFile
143 from cvs2svn_lib
.cvs_path
import CVSDirectory
144 from cvs2svn_lib
.key_generator
import KeyGenerator
145 from cvs2svn_lib
.artifact_manager
import artifact_manager
146 from cvs2svn_lib
.serializer
import MarshalSerializer
147 from cvs2svn_lib
.indexed_database
import IndexedDatabase
150 class RepositoryMirrorError(Exception):
151 """An error related to the RepositoryMirror."""
156 class LODExistsError(RepositoryMirrorError
):
157 """The LOD already exists in the repository.
159 Exception raised if an attempt is made to add an LOD to the
160 repository mirror and that LOD already exists in the youngest
161 revision of the repository."""
166 class PathExistsError(RepositoryMirrorError
):
167 """The path already exists in the repository.
169 Exception raised if an attempt is made to add a path to the
170 repository mirror and that path already exists in the youngest
171 revision of the repository."""
176 class DeletedNodeReusedError(RepositoryMirrorError
):
177 """The MirrorDirectory has already been deleted and shouldn't be reused."""
182 class CopyFromCurrentNodeError(RepositoryMirrorError
):
183 """A CurrentMirrorDirectory cannot be copied to the current revision."""
188 class MirrorDirectory(object):
189 """Represent a node within the RepositoryMirror.
191 Instances of this class act like a map {CVSPath : MirrorDirectory},
192 where CVSPath is an item within this directory (i.e., a file or
193 subdirectory within this directory). The value is either another
194 MirrorDirectory instance (for directories) or None (for files)."""
196 def __init__(self
, repo
, id, entries
):
197 # The RepositoryMirror containing this directory:
200 # The id of this node:
203 # The entries within this directory, stored as a map {CVSPath :
204 # node_id}. The node_ids are integers for CVSDirectories, None
206 self
._entries
= entries
208 def __getitem__(self
, cvs_path
):
209 """Return the MirrorDirectory associated with the specified subnode.
211 Return a MirrorDirectory instance if the subnode is a
212 CVSDirectory; None if it is a CVSFile. Raise KeyError if the
213 specified subnode does not exist."""
215 raise NotImplementedError()
218 """Return the number of CVSPaths within this node."""
220 return len(self
._entries
)
222 def __contains__(self
, cvs_path
):
223 """Return True iff CVS_PATH is contained in this node."""
225 return cvs_path
in self
._entries
228 """Iterate over the CVSPaths within this node."""
230 return self
._entries
.__iter
__()
232 def _format_entries(self
):
233 """Format the entries map for output in subclasses' __repr__() methods."""
235 def format_item(key
, value
):
239 return '%s -> %x' % (key
, value
,)
241 items
= self
._entries
.items()
243 return '{%s}' % (', '.join([format_item(*item
) for item
in items
]),)
246 """For convenience only. The format is subject to change at any time."""
248 return '%s<%x>' % (self
.__class
__.__name
__, self
.id,)
251 class OldMirrorDirectory(MirrorDirectory
):
252 """Represent a historical directory within the RepositoryMirror."""
254 def __getitem__(self
, cvs_path
):
255 id = self
._entries
[cvs_path
]
257 # This represents a leaf node.
260 return OldMirrorDirectory(self
.repo
, id, self
.repo
._node
_db
[id])
263 """For convenience only. The format is subject to change at any time."""
265 return '%s(%s)' % (self
, self
._format
_entries
(),)
268 class CurrentMirrorDirectory(MirrorDirectory
):
269 """Represent a directory that currently exists in the RepositoryMirror."""
271 def __init__(self
, repo
, id, lod
, cvs_path
, entries
):
272 MirrorDirectory
.__init
__(self
, repo
, id, entries
)
274 self
.cvs_path
= cvs_path
276 def __getitem__(self
, cvs_path
):
277 id = self
._entries
[cvs_path
]
279 # This represents a leaf node.
283 return self
.repo
._new
_nodes
[id]
285 return _CurrentMirrorReadOnlySubdirectory(
286 self
.repo
, id, self
.lod
, cvs_path
, self
,
287 self
.repo
._node
_db
[id]
290 def __setitem__(self
, cvs_path
, node
):
291 """Create or overwrite a subnode of this node.
293 CVS_PATH is the path of the subnode. NODE will be the new value
294 of the node; for CVSDirectories it should be a MirrorDirectory
295 instance; for CVSFiles it should be None."""
297 if isinstance(node
, DeletedCurrentMirrorDirectory
):
298 raise DeletedNodeReusedError(
299 '%r has already been deleted and should not be reused' % (node
,)
301 elif isinstance(node
, CurrentMirrorDirectory
):
302 raise CopyFromCurrentNodeError(
303 '%r was created in the current node and cannot be copied' % (node
,)
306 self
._set
_entry
(cvs_path
, node
)
308 def __delitem__(self
, cvs_path
):
309 """Remove the subnode of this node at CVS_PATH.
311 If the node does not exist, then raise a KeyError."""
313 node
= self
[cvs_path
]
314 self
._del
_entry
(cvs_path
)
315 if isinstance(node
, _WritableMirrorDirectoryMixin
):
318 def mkdir(self
, cvs_directory
):
319 """Create an empty subdirectory of this node at CVS_PATH.
321 Return the CurrentDirectory that was created."""
323 assert isinstance(cvs_directory
, CVSDirectory
)
324 if cvs_directory
in self
:
325 raise PathExistsError(
326 'Attempt to create directory \'%s\' in %s in repository mirror '
327 'when it already exists.'
328 % (cvs_directory
, self
.lod
,)
331 new_node
= _CurrentMirrorWritableSubdirectory(
332 self
.repo
, self
.repo
._key
_generator
.gen_id(), self
.lod
, cvs_directory
,
335 self
._set
_entry
(cvs_directory
, new_node
)
336 self
.repo
._new
_nodes
[new_node
.id] = new_node
339 def add_file(self
, cvs_file
):
340 """Create a file within this node at CVS_FILE."""
342 assert isinstance(cvs_file
, CVSFile
)
344 raise PathExistsError(
345 'Attempt to create file \'%s\' in %s in repository mirror '
346 'when it already exists.'
347 % (cvs_file
, self
.lod
,)
350 self
._set
_entry
(cvs_file
, None)
353 """For convenience only. The format is subject to change at any time."""
355 return '%s(%r, %r, %s)' % (
356 self
, self
.lod
, self
.cvs_path
, self
._format
_entries
(),
360 class DeletedCurrentMirrorDirectory(object):
361 """A MirrorDirectory that has been deleted.
363 A MirrorDirectory that used to be a _WritableMirrorDirectoryMixin
364 but then was deleted. Such instances are turned into this class so
365 that nobody can accidentally mutate them again."""
370 class _WritableMirrorDirectoryMixin
:
371 """Mixin for MirrorDirectories that are already writable.
373 A MirrorDirectory is writable if it has already been recreated
374 during the current revision."""
376 def _set_entry(self
, cvs_path
, node
):
377 """Create or overwrite a subnode of this node, with no checks."""
380 self
._entries
[cvs_path
] = None
382 self
._entries
[cvs_path
] = node
.id
384 def _del_entry(self
, cvs_path
):
385 """Remove the subnode of this node at CVS_PATH, with no checks."""
387 del self
._entries
[cvs_path
]
389 def _mark_deleted(self
):
390 """Mark this object and any writable descendants as being deleted."""
392 self
.__class
__ = DeletedCurrentMirrorDirectory
394 for (cvs_path
, id) in self
._entries
.iteritems():
395 if id in self
.repo
._new
_nodes
:
396 node
= self
[cvs_path
]
397 if isinstance(node
, _WritableMirrorDirectoryMixin
):
398 # Mark deleted and recurse:
402 class _ReadOnlyMirrorDirectoryMixin
:
403 """Mixin for a CurrentMirrorDirectory that hasn't yet been made writable."""
405 def _make_writable(self
):
406 raise NotImplementedError()
408 def _set_entry(self
, cvs_path
, node
):
409 """Create or overwrite a subnode of this node, with no checks."""
411 self
._make
_writable
()
412 self
._set
_entry
(cvs_path
, node
)
414 def _del_entry(self
, cvs_path
):
415 """Remove the subnode of this node at CVS_PATH, with no checks."""
417 self
._make
_writable
()
418 self
._del
_entry
(cvs_path
)
421 class CurrentMirrorLODDirectory(CurrentMirrorDirectory
):
422 """Represent an LOD's main directory in the mirror's current version."""
424 def __init__(self
, repo
, id, lod
, entries
):
425 CurrentMirrorDirectory
.__init
__(
426 self
, repo
, id, lod
, lod
.project
.get_root_cvs_directory(), entries
430 """Remove the directory represented by this object."""
432 lod_history
= self
.repo
._get
_lod
_history
(self
.lod
)
433 assert lod_history
.exists()
434 lod_history
.update(self
.repo
._youngest
, None)
438 class _CurrentMirrorReadOnlyLODDirectory(
439 CurrentMirrorLODDirectory
, _ReadOnlyMirrorDirectoryMixin
441 """Represent an LOD's main directory in the mirror's current version."""
443 def _make_writable(self
):
444 self
.__class
__ = _CurrentMirrorWritableLODDirectory
446 self
.id = self
.repo
._key
_generator
.gen_id()
447 self
.repo
._new
_nodes
[self
.id] = self
448 self
.repo
._get
_lod
_history
(self
.lod
).update(self
.repo
._youngest
, self
.id)
449 self
._entries
= self
._entries
.copy()
452 class _CurrentMirrorWritableLODDirectory(
453 CurrentMirrorLODDirectory
, _WritableMirrorDirectoryMixin
458 class CurrentMirrorSubdirectory(CurrentMirrorDirectory
):
459 """Represent a subdirectory in the mirror's current version."""
461 def __init__(self
, repo
, id, lod
, cvs_path
, parent_mirror_dir
, entries
):
462 CurrentMirrorDirectory
.__init
__(self
, repo
, id, lod
, cvs_path
, entries
)
463 self
.parent_mirror_dir
= parent_mirror_dir
466 """Remove the directory represented by this object."""
468 del self
.parent_mirror_dir
[self
.cvs_path
]
471 class _CurrentMirrorReadOnlySubdirectory(
472 CurrentMirrorSubdirectory
, _ReadOnlyMirrorDirectoryMixin
474 """Represent a subdirectory in the mirror's current version."""
476 def _make_writable(self
):
477 self
.__class
__ = _CurrentMirrorWritableSubdirectory
479 self
.id = self
.repo
._key
_generator
.gen_id()
480 self
.repo
._new
_nodes
[self
.id] = self
481 self
.parent_mirror_dir
._set
_entry
(self
.cvs_path
, self
)
482 self
._entries
= self
._entries
.copy()
485 class _CurrentMirrorWritableSubdirectory(
486 CurrentMirrorSubdirectory
, _WritableMirrorDirectoryMixin
491 class LODHistory(object):
492 """The history of root nodes for a line of development.
496 _mirror -- (RepositoryMirror) the RepositoryMirror that manages
499 lod -- (LineOfDevelopment) the LOD described by this LODHistory.
501 revnums -- (list of int) the revision numbers in which the id
502 changed, in numerical order.
504 ids -- (list of (int or None)) the ID of the node describing the
505 root of this LOD starting at the corresponding revision
506 number, or None if the LOD did not exist in that revision.
508 To find the root id for a given revision number, a binary search is
509 done within REVNUMS to find the index of the most recent revision at
510 the time of REVNUM, then that index is used to read the id out of
513 A sentry is written at the zeroth index of both arrays to describe
514 the initial situation, namely, that the LOD doesn't exist in
517 __slots__
= ['_mirror', 'lod', 'revnums', 'ids']
519 def __init__(self
, mirror
, lod
):
520 self
._mirror
= mirror
525 def get_id(self
, revnum
):
526 """Get the ID of the root path for this LOD in REVNUM.
528 Raise KeyError if this LOD didn't exist in REVNUM."""
530 index
= bisect
.bisect_right(self
.revnums
, revnum
) - 1
534 raise KeyError(revnum
)
538 def get_current_id(self
):
539 """Get the ID of the root path for this LOD in the current revision.
541 Raise KeyError if this LOD doesn't currently exist."""
551 """Return True iff LOD exists in the current revision."""
553 return self
.ids
[-1] is not None
555 def update(self
, revnum
, id):
556 """Indicate that the root node of this LOD changed to ID at REVNUM.
558 REVNUM is a revision number that must be the same as that of the
559 previous recorded change (in which case the previous change is
560 overwritten) or later (in which the new change is appended).
562 ID can be a node ID, or it can be None to indicate that this LOD
563 ceased to exist in REVNUM."""
565 if revnum
< self
.revnums
[-1]:
566 raise KeyError(revnum
)
567 elif revnum
== self
.revnums
[-1]:
568 # This is an attempt to overwrite an entry that was already
569 # updated during this revision. Don't allow the replacement
570 # None -> None or allow one new id to be replaced with another:
571 old_id
= self
.ids
[-1]
572 if old_id
is None and id is None:
574 'ID changed from None -> None for %s, r%d' % (self
.lod
, revnum
,)
576 elif (old_id
is not None and id is not None
577 and old_id
in self
._mirror
._new
_nodes
):
579 'ID changed from %x -> %x for %s, r%d'
580 % (old_id
, id, self
.lod
, revnum
,)
584 self
.revnums
.append(revnum
)
588 class _NodeDatabase(object):
589 """A database storing all of the directory nodes.
591 The nodes are written in groups every time write_new_nodes() is
592 called. To the database is written a dictionary {node_id :
593 [(cvs_path.id, node_id),...]}, where the keys are the node_ids of
594 the new nodes. When a node is read, its whole group is read and
595 cached under the assumption that the other nodes in the group are
596 likely to be needed soon. The cache is retained across revisions
597 and cleared when _cache_max_size is exceeded.
599 The dictionaries for nodes that have been read from the database
600 during the current revision are cached by node_id in the _cache
601 member variable. The corresponding dictionaries are *not* copied
602 when read. To avoid cross-talk between distinct MirrorDirectory
603 instances that have the same node_id, users of these dictionaries
604 have to copy them before modification."""
606 # How many entries should be allowed in the cache for each
607 # CVSDirectory in the repository. (This number is very roughly the
608 # number of complete lines of development that can be stored in the
609 # cache at one time.)
610 CACHE_SIZE_MULTIPLIER
= 5
612 # But the cache will never be limited to less than this number:
613 MIN_CACHE_LIMIT
= 5000
616 self
.cvs_path_db
= Ctx()._cvs
_path
_db
617 self
.db
= IndexedDatabase(
618 artifact_manager
.get_temp_file(config
.MIRROR_NODES_STORE
),
619 artifact_manager
.get_temp_file(config
.MIRROR_NODES_INDEX_TABLE
),
620 DB_OPEN_NEW
, serializer
=MarshalSerializer(),
623 # A list of the maximum node_id stored by each call to
625 self
._max
_node
_ids
= [0]
627 # A map {node_id : {cvs_path : node_id}}:
630 # The number of directories in the repository:
633 for cvs_path
in self
.cvs_path_db
.itervalues()
634 if isinstance(cvs_path
, CVSDirectory
)
637 self
._cache
_max
_size
= max(
638 int(self
.CACHE_SIZE_MULTIPLIER
* num_dirs
),
639 self
.MIN_CACHE_LIMIT
,
642 def _load(self
, items
):
644 for (id, value
) in items
:
645 retval
[self
.cvs_path_db
.get_path(id)] = value
648 def _dump(self
, node
):
651 for (cvs_path
, value
) in node
.iteritems()
654 def _determine_index(self
, id):
655 """Return the index of the record holding the node with ID."""
657 return bisect
.bisect_left(self
._max
_node
_ids
, id)
659 def __getitem__(self
, id):
661 items
= self
._cache
[id]
663 index
= self
._determine
_index
(id)
664 for (node_id
, items
) in self
.db
[index
].items():
665 self
._cache
[node_id
] = self
._load
(items
)
666 items
= self
._cache
[id]
670 def write_new_nodes(self
, nodes
):
671 """Write NODES to the database.
673 NODES is an iterable of writable CurrentMirrorDirectory instances."""
675 if len(self
._cache
) > self
._cache
_max
_size
:
676 # The size of the cache has exceeded the threshold. Discard the
677 # old cache values (but still store the new nodes into the
679 logger
.debug('Clearing node cache')
685 max_node_id
= max(max_node_id
, node
.id)
686 data
[node
.id] = self
._dump
(node
._entries
)
687 self
._cache
[node
.id] = node
._entries
689 self
.db
[len(self
._max
_node
_ids
)] = data
692 # Rewrite last value:
693 self
._max
_node
_ids
.append(self
._max
_node
_ids
[-1])
695 self
._max
_node
_ids
.append(max_node_id
)
703 class RepositoryMirror
:
704 """Mirror a repository and its history.
706 Mirror a repository as it is constructed, one revision at a time.
707 For each LineOfDevelopment we store a skeleton of the directory
708 structure within that LOD for each revnum in which it changed.
710 For each LOD that has been seen so far, an LODHistory instance is
711 stored in self._lod_histories. An LODHistory keeps track of each
712 revnum in which files were added to or deleted from that LOD, as
713 well as the node id of the root of the node tree describing the LOD
714 contents at that revision.
716 The LOD trees themselves are stored in the _node_db database, which
717 maps node ids to nodes. A node is a map from CVSPath to ids of the
718 corresponding subnodes. The _node_db is stored on disk and each
721 The _node_db database only holds the nodes for old revisions. The
722 revision that is being constructed is kept in memory in the
723 _new_nodes map, which is cheap to access.
725 You must invoke start_commit() before each commit and end_commit()
728 def register_artifacts(self
, which_pass
):
729 """Register the artifacts that will be needed for this object."""
731 artifact_manager
.register_temp_file(
732 config
.MIRROR_NODES_INDEX_TABLE
, which_pass
734 artifact_manager
.register_temp_file(
735 config
.MIRROR_NODES_STORE
, which_pass
739 """Set up the RepositoryMirror and prepare it for commits."""
741 self
._key
_generator
= KeyGenerator()
743 # A map from LOD to LODHistory instance for all LODs that have
744 # been referenced so far:
745 self
._lod
_histories
= {}
747 # This corresponds to the 'nodes' table in a Subversion fs. (We
748 # don't need a 'representations' or 'strings' table because we
749 # only track file existence, not file contents.)
750 self
._node
_db
= _NodeDatabase()
752 # Start at revision 0 without a root node.
755 def start_commit(self
, revnum
):
756 """Start a new commit."""
758 assert revnum
> self
._youngest
759 self
._youngest
= revnum
761 # A map {node_id : _WritableMirrorDirectoryMixin}.
764 def end_commit(self
):
765 """Called at the end of each commit.
767 This method copies the newly created nodes to the on-disk nodes
770 # Copy the new nodes to the _node_db
771 self
._node
_db
.write_new_nodes([
773 for node
in self
._new
_nodes
.values()
774 if not isinstance(node
, DeletedCurrentMirrorDirectory
)
779 def _get_lod_history(self
, lod
):
780 """Return the LODHistory instance describing LOD.
782 Create a new (empty) LODHistory if it doesn't yet exist."""
785 return self
._lod
_histories
[lod
]
787 lod_history
= LODHistory(self
, lod
)
788 self
._lod
_histories
[lod
] = lod_history
791 def get_old_lod_directory(self
, lod
, revnum
):
792 """Return the directory for the root path of LOD at revision REVNUM.
794 Return an instance of MirrorDirectory if the path exists;
795 otherwise, raise KeyError."""
797 lod_history
= self
._get
_lod
_history
(lod
)
798 id = lod_history
.get_id(revnum
)
799 return OldMirrorDirectory(self
, id, self
._node
_db
[id])
801 def get_old_path(self
, cvs_path
, lod
, revnum
):
802 """Return the node for CVS_PATH from LOD at REVNUM.
804 If CVS_PATH is a CVSDirectory, then return an instance of
805 OldMirrorDirectory. If CVS_PATH is a CVSFile, return None.
807 If CVS_PATH does not exist in the specified LOD and REVNUM, raise
810 node
= self
.get_old_lod_directory(lod
, revnum
)
812 for sub_path
in cvs_path
.get_ancestry()[1:]:
813 node
= node
[sub_path
]
817 def get_current_lod_directory(self
, lod
):
818 """Return the directory for the root path of LOD in the current revision.
820 Return an instance of CurrentMirrorDirectory. Raise KeyError if
821 the path doesn't already exist."""
823 lod_history
= self
._get
_lod
_history
(lod
)
824 id = lod_history
.get_current_id()
826 return self
._new
_nodes
[id]
828 return _CurrentMirrorReadOnlyLODDirectory(
829 self
, id, lod
, self
._node
_db
[id]
832 def get_current_path(self
, cvs_path
, lod
):
833 """Return the node for CVS_PATH from LOD in the current revision.
835 If CVS_PATH is a CVSDirectory, then return an instance of
836 CurrentMirrorDirectory. If CVS_PATH is a CVSFile, return None.
838 If CVS_PATH does not exist in the current revision of the
839 specified LOD, raise KeyError."""
841 node
= self
.get_current_lod_directory(lod
)
843 for sub_path
in cvs_path
.get_ancestry()[1:]:
844 node
= node
[sub_path
]
848 def add_lod(self
, lod
):
849 """Create a new LOD in this repository.
851 Return the CurrentMirrorDirectory that was created. If the LOD
852 already exists, raise LODExistsError."""
854 lod_history
= self
._get
_lod
_history
(lod
)
855 if lod_history
.exists():
856 raise LODExistsError(
857 'Attempt to create %s in repository mirror when it already exists.'
860 new_node
= _CurrentMirrorWritableLODDirectory(
861 self
, self
._key
_generator
.gen_id(), lod
, {}
863 lod_history
.update(self
._youngest
, new_node
.id)
864 self
._new
_nodes
[new_node
.id] = new_node
867 def copy_lod(self
, src_lod
, dest_lod
, src_revnum
):
868 """Copy all of SRC_LOD at SRC_REVNUM to DST_LOD.
870 In the youngest revision of the repository, the destination LOD
871 *must not* already exist.
873 Return the new node at DEST_LOD, as a CurrentMirrorDirectory."""
875 # Get the node of our src_path
876 src_node
= self
.get_old_lod_directory(src_lod
, src_revnum
)
878 dest_lod_history
= self
._get
_lod
_history
(dest_lod
)
879 if dest_lod_history
.exists():
880 raise LODExistsError(
881 'Attempt to copy to %s in repository mirror when it already exists.'
885 dest_lod_history
.update(self
._youngest
, src_node
.id)
887 # Return src_node, except packaged up as a CurrentMirrorDirectory:
888 return self
.get_current_lod_directory(dest_lod
)
891 """Free resources and close databases."""
893 self
._lod
_histories
= None
894 self
._node
_db
.close()