1 # (Be in -*- python -*- mode.)
3 # ====================================================================
4 # Copyright (c) 2000-2008 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 classes to store atomic CVS events.
19 A CVSItem is a single event, pertaining to a single file, that can be
20 determined to have occured based on the information in the CVS
23 The inheritance tree is as follows:
29 | +--CVSRevisionModification (* -> 'Exp')
31 | | +--CVSRevisionAdd ('dead' -> 'Exp')
33 | | +--CVSRevisionChange ('Exp' -> 'Exp')
35 | +--CVSRevisionAbsent (* -> 'dead')
37 | +--CVSRevisionDelete ('Exp' -> 'dead')
39 | +--CVSRevisionNoop ('dead' -> 'dead')
54 from cvs2svn_lib
.context
import Ctx
57 class CVSItem(object):
61 'revision_reader_token',
64 def __init__(self
, id, cvs_file
, revision_reader_token
):
66 self
.cvs_file
= cvs_file
67 self
.revision_reader_token
= revision_reader_token
69 def __eq__(self
, other
):
70 return self
.id == other
.id
72 def __cmp__(self
, other
):
73 return cmp(self
.id, other
.id)
78 def __getstate__(self
):
79 raise NotImplementedError()
81 def __setstate__(self
, data
):
82 raise NotImplementedError()
84 def get_svn_path(self
):
85 """Return the SVN path associated with this CVSItem."""
87 raise NotImplementedError()
89 def get_pred_ids(self
):
90 """Return the CVSItem.ids of direct predecessors of SELF.
92 A predecessor is defined to be a CVSItem that has to have been
93 committed before this one."""
95 raise NotImplementedError()
97 def get_succ_ids(self
):
98 """Return the CVSItem.ids of direct successors of SELF.
100 A direct successor is defined to be a CVSItem that has this one as
101 a direct predecessor."""
103 raise NotImplementedError()
105 def get_cvs_symbol_ids_opened(self
):
106 """Return an iterable over the ids of CVSSymbols that this item opens.
108 The definition of 'open' is that the path corresponding to this
109 CVSItem will have to be copied when filling the corresponding
112 raise NotImplementedError()
114 def get_ids_closed(self
):
115 """Return an iterable over the CVSItem.ids of CVSItems closed by this one.
117 A CVSItem A is said to close a CVSItem B if committing A causes B
118 to be overwritten or deleted (no longer available) in the SVN
119 repository. This is interesting because it sets the last SVN
120 revision number from which the contents of B can be copied (for
121 example, to fill a symbol). See the concrete implementations of
122 this method for the exact rules about what closes what."""
124 raise NotImplementedError()
126 def check_links(self
, cvs_file_items
):
127 """Check for consistency of links to other CVSItems.
129 Other items can be looked up in CVS_FILE_ITEMS, which is an
130 instance of CVSFileItems. Raise an AssertionError if there is a
133 raise NotImplementedError()
136 return '%s(%s)' % (self
.__class
__.__name
__, self
,)
139 class CVSRevision(CVSItem
):
140 """Information about a single CVS revision.
142 A CVSRevision holds the information known about a single version of
147 id -- (int) unique ID for this revision.
149 cvs_file -- (CVSFile) CVSFile affected by this revision.
151 timestamp -- (int) date stamp for this revision.
153 metadata_id -- (int) id of metadata instance record in
156 prev_id -- (int) id of the logically previous CVSRevision, either
157 on the same or the source branch (or None).
159 next_id -- (int) id of the logically next CVSRevision (or None).
161 rev -- (string) the CVS revision number, e.g., '1.3'.
163 deltatext_exists -- (bool) true iff this revision's deltatext is
166 lod -- (LineOfDevelopment) LOD on which this revision occurred.
168 first_on_branch_id -- (int or None) if this revision is the first
169 on its branch, the cvs_branch_id of that branch; else, None.
171 ntdbr -- (bool) true iff this is a non-trunk default branch
174 ntdbr_prev_id -- (int or None) Iff this is the 1.2 revision after
175 the end of a default branch, the id of the last rev on the
176 default branch; else, None.
178 ntdbr_next_id -- (int or None) Iff this is the last revision on a
179 default branch preceding a 1.2 rev, the id of the 1.2
180 revision; else, None.
182 tag_ids -- (list of int) ids of all CVSTags rooted at this
185 branch_ids -- (list of int) ids of all CVSBranches rooted at this
188 branch_commit_ids -- (list of int) ids of first CVSRevision
189 committed on each branch rooted in this revision (for branches
192 opened_symbols -- (None or list of (symbol_id, cvs_symbol_id)
193 tuples) information about all CVSSymbols opened by this
194 revision. This member is set in FilterSymbolsPass; before
197 closed_symbols -- (None or list of (symbol_id, cvs_symbol_id)
198 tuples) information about all CVSSymbols closed by this
199 revision. This member is set in FilterSymbolsPass; before
202 revision_reader_token -- (arbitrary) a token that can be set by
203 RevisionCollector for the later use of RevisionReader.
215 'first_on_branch_id',
229 timestamp
, metadata_id
,
231 rev
, deltatext_exists
,
232 lod
, first_on_branch_id
, ntdbr
,
233 ntdbr_prev_id
, ntdbr_next_id
,
234 tag_ids
, branch_ids
, branch_commit_ids
,
235 revision_reader_token
,
237 """Initialize a new CVSRevision object."""
239 CVSItem
.__init
__(self
, id, cvs_file
, revision_reader_token
)
241 self
.timestamp
= timestamp
242 self
.metadata_id
= metadata_id
243 self
.prev_id
= prev_id
244 self
.next_id
= next_id
246 self
.deltatext_exists
= deltatext_exists
248 self
.first_on_branch_id
= first_on_branch_id
250 self
.ntdbr_prev_id
= ntdbr_prev_id
251 self
.ntdbr_next_id
= ntdbr_next_id
252 self
.tag_ids
= tag_ids
253 self
.branch_ids
= branch_ids
254 self
.branch_commit_ids
= branch_commit_ids
255 self
.opened_symbols
= None
256 self
.closed_symbols
= None
258 def _get_cvs_path(self
):
259 return self
.cvs_file
.cvs_path
261 cvs_path
= property(_get_cvs_path
)
263 def get_svn_path(self
):
264 return self
.lod
.get_path(self
.cvs_file
.cvs_path
)
266 def __getstate__(self
):
267 """Return the contents of this instance, for pickling.
269 The presence of this method improves the space efficiency of
270 pickling CVSRevision instances."""
273 self
.id, self
.cvs_file
.id,
274 self
.timestamp
, self
.metadata_id
,
275 self
.prev_id
, self
.next_id
,
277 self
.deltatext_exists
,
279 self
.first_on_branch_id
,
281 self
.ntdbr_prev_id
, self
.ntdbr_next_id
,
282 self
.tag_ids
, self
.branch_ids
, self
.branch_commit_ids
,
283 self
.opened_symbols
, self
.closed_symbols
,
284 self
.revision_reader_token
,
287 def __setstate__(self
, data
):
288 (self
.id, cvs_file_id
,
289 self
.timestamp
, self
.metadata_id
,
290 self
.prev_id
, self
.next_id
,
292 self
.deltatext_exists
,
294 self
.first_on_branch_id
,
296 self
.ntdbr_prev_id
, self
.ntdbr_next_id
,
297 self
.tag_ids
, self
.branch_ids
, self
.branch_commit_ids
,
298 self
.opened_symbols
, self
.closed_symbols
,
299 self
.revision_reader_token
) = data
300 self
.cvs_file
= Ctx()._cvs
_path
_db
.get_path(cvs_file_id
)
301 self
.lod
= Ctx()._symbol
_db
.get_symbol(lod_id
)
303 def get_effective_prev_id(self
):
304 """Return the ID of the effective predecessor of this item.
306 This is the ID of the item that determines whether the object
307 existed before this CVSRevision."""
309 if self
.ntdbr_prev_id
is not None:
310 return self
.ntdbr_prev_id
314 def get_symbol_pred_ids(self
):
315 """Return the pred_ids for symbol predecessors."""
318 if self
.first_on_branch_id
is not None:
319 retval
.add(self
.first_on_branch_id
)
322 def get_pred_ids(self
):
323 retval
= self
.get_symbol_pred_ids()
324 if self
.prev_id
is not None:
325 retval
.add(self
.prev_id
)
326 if self
.ntdbr_prev_id
is not None:
327 retval
.add(self
.ntdbr_prev_id
)
330 def get_symbol_succ_ids(self
):
331 """Return the succ_ids for symbol successors."""
334 for id in self
.branch_ids
+ self
.tag_ids
:
338 def get_succ_ids(self
):
339 retval
= self
.get_symbol_succ_ids()
340 if self
.next_id
is not None:
341 retval
.add(self
.next_id
)
342 if self
.ntdbr_next_id
is not None:
343 retval
.add(self
.ntdbr_next_id
)
344 for id in self
.branch_commit_ids
:
348 def get_ids_closed(self
):
349 # Special handling is needed in the case of non-trunk default
350 # branches. The following cases have to be handled:
352 # Case 1: Revision 1.1 not deleted; revision 1.2 exists:
354 # 1.1 -----------------> 1.2
359 # * 1.1.1.1 closes 1.1 (because its post-commit overwrites 1.1
362 # * 1.1.1.2 closes 1.1.1.1
364 # * 1.2 doesn't close anything (the post-commit from 1.1.1.1
365 # already closed 1.1, and no symbols can sprout from the
366 # post-commit of 1.1.1.2)
368 # Case 2: Revision 1.1 not deleted; revision 1.2 does not exist:
370 # 1.1 ..................
375 # * 1.1.1.1 closes 1.1 (because its post-commit overwrites 1.1
378 # * 1.1.1.2 closes 1.1.1.1
380 # Case 3: Revision 1.1 deleted; revision 1.2 exists:
382 # ............... 1.2
387 # * 1.1.1.1 doesn't close anything
389 # * 1.1.1.2 closes 1.1.1.1
391 # * 1.2 doesn't close anything (no symbols can sprout from the
392 # post-commit of 1.1.1.2)
394 # Case 4: Revision 1.1 deleted; revision 1.2 doesn't exist:
401 # * 1.1.1.1 doesn't close anything
403 # * 1.1.1.2 closes 1.1.1.1
405 if self
.first_on_branch_id
is not None:
406 # The first CVSRevision on a branch is considered to close the
408 yield self
.first_on_branch_id
410 # If the 1.1 revision was not deleted, the 1.1.1.1 revision is
411 # considered to close it:
413 elif self
.ntdbr_prev_id
is not None:
414 # This is the special case of a 1.2 revision that follows a
415 # non-trunk default branch. Either 1.1 was deleted or the first
416 # default branch revision closed 1.1, so we don't have to close
417 # 1.1. Technically, we close the revision on trunk that was
418 # copied from the last non-trunk default branch revision in a
419 # post-commit, but for now no symbols can sprout from that
420 # revision so we ignore that one, too.
422 elif self
.prev_id
is not None:
423 # Since this CVSRevision is not the first on a branch, its
424 # prev_id is on the same LOD and this item closes that one:
427 def _get_branch_ids_recursively(self
, cvs_file_items
):
428 """Return the set of all CVSBranches that sprout from this CVSRevision.
430 After parent adjustment in FilterSymbolsPass, it is possible for
431 branches to sprout directly from a CVSRevision, or from those
432 branches, etc. Return all branches that sprout from this
433 CVSRevision, directly or indirectly."""
436 branch_ids_to_process
= list(self
.branch_ids
)
437 while branch_ids_to_process
:
438 branch
= cvs_file_items
[branch_ids_to_process
.pop()]
440 branch_ids_to_process
.extend(branch
.branch_ids
)
444 def check_links(self
, cvs_file_items
):
445 assert self
.cvs_file
== cvs_file_items
.cvs_file
447 prev
= cvs_file_items
.get(self
.prev_id
)
448 next
= cvs_file_items
.get(self
.next_id
)
449 first_on_branch
= cvs_file_items
.get(self
.first_on_branch_id
)
450 ntdbr_next
= cvs_file_items
.get(self
.ntdbr_next_id
)
451 ntdbr_prev
= cvs_file_items
.get(self
.ntdbr_prev_id
)
452 effective_prev
= cvs_file_items
.get(self
.get_effective_prev_id())
455 # This is the first CVSRevision on trunk or a detached branch:
456 assert self
.id in cvs_file_items
.root_ids
457 elif first_on_branch
is not None:
458 # This is the first CVSRevision on an existing branch:
459 assert isinstance(first_on_branch
, CVSBranch
)
460 assert first_on_branch
.symbol
== self
.lod
461 assert first_on_branch
.next_id
== self
.id
462 cvs_revision_source
= first_on_branch
.get_cvs_revision_source(
465 assert cvs_revision_source
.id == prev
.id
466 assert self
.id in prev
.branch_commit_ids
468 # This revision follows another revision on the same LOD:
469 assert prev
.next_id
== self
.id
470 assert prev
.lod
== self
.lod
473 assert next
.prev_id
== self
.id
474 assert next
.lod
== self
.lod
476 if ntdbr_next
is not None:
478 assert ntdbr_next
.ntdbr_prev_id
== self
.id
480 if ntdbr_prev
is not None:
481 assert ntdbr_prev
.ntdbr_next_id
== self
.id
483 for tag_id
in self
.tag_ids
:
484 tag
= cvs_file_items
[tag_id
]
485 assert isinstance(tag
, CVSTag
)
486 assert tag
.source_id
== self
.id
487 assert tag
.source_lod
== self
.lod
489 for branch_id
in self
.branch_ids
:
490 branch
= cvs_file_items
[branch_id
]
491 assert isinstance(branch
, CVSBranch
)
492 assert branch
.source_id
== self
.id
493 assert branch
.source_lod
== self
.lod
495 branch_commit_ids
= list(self
.branch_commit_ids
)
497 for branch
in self
._get
_branch
_ids
_recursively
(cvs_file_items
):
498 assert isinstance(branch
, CVSBranch
)
499 if branch
.next_id
is not None:
500 assert branch
.next_id
in branch_commit_ids
501 branch_commit_ids
.remove(branch
.next_id
)
503 assert not branch_commit_ids
505 assert self
.__class
__ == cvs_revision_type_map
[(
506 isinstance(self
, CVSRevisionModification
),
507 effective_prev
is not None
508 and isinstance(effective_prev
, CVSRevisionModification
),
512 """For convenience only. The format is subject to change at any time."""
514 return '%s:%s<%x>' % (self
.cvs_file
, self
.rev
, self
.id,)
517 class CVSRevisionModification(CVSRevision
):
518 """Base class for CVSRevisionAdd or CVSRevisionChange."""
522 def get_cvs_symbol_ids_opened(self
):
523 return self
.tag_ids
+ self
.branch_ids
526 class CVSRevisionAdd(CVSRevisionModification
):
527 """A CVSRevision that creates a file that previously didn't exist.
529 The file might have never existed on this LOD, or it might have
530 existed previously but been deleted by a CVSRevisionDelete."""
535 class CVSRevisionChange(CVSRevisionModification
):
536 """A CVSRevision that modifies a file that already existed on this LOD."""
541 class CVSRevisionAbsent(CVSRevision
):
542 """A CVSRevision for which the file is nonexistent on this LOD."""
546 def get_cvs_symbol_ids_opened(self
):
550 class CVSRevisionDelete(CVSRevisionAbsent
):
551 """A CVSRevision that deletes a file that existed on this LOD."""
556 class CVSRevisionNoop(CVSRevisionAbsent
):
557 """A CVSRevision that doesn't do anything.
559 The revision was 'dead' and the predecessor either didn't exist or
560 was also 'dead'. These revisions can't necessarily be thrown away
561 because (1) they impose ordering constraints on other items; (2)
562 they might have a nontrivial log message that we don't want to throw
570 # {(nondead(cvs_rev), nondead(prev_cvs_rev)) : cvs_revision_subtype}
572 # , where nondead() means that the cvs revision exists and is not
573 # 'dead', and CVS_REVISION_SUBTYPE is the subtype of CVSRevision that
574 # should be used for CVS_REV.
575 cvs_revision_type_map
= {
576 (False, False) : CVSRevisionNoop
,
577 (False, True) : CVSRevisionDelete
,
578 (True, False) : CVSRevisionAdd
,
579 (True, True) : CVSRevisionChange
,
583 class CVSSymbol(CVSItem
):
584 """Represent a symbol on a particular CVSFile.
586 This is the base class for CVSBranch and CVSTag.
590 id -- (int) unique ID for this item.
592 cvs_file -- (CVSFile) CVSFile affected by this item.
594 symbol -- (Symbol) the symbol affected by this CVSSymbol.
596 source_lod -- (LineOfDevelopment) the LOD that is the source for
599 source_id -- (int) the ID of the CVSRevision or CVSBranch that is
600 the source for this item. This initially points to a
601 CVSRevision, but can be changed to a CVSBranch via parent
602 adjustment in FilterSymbolsPass.
604 revision_reader_token -- (arbitrary) a token that can be set by
605 RevisionCollector for the later use of RevisionReader.
616 self
, id, cvs_file
, symbol
, source_lod
, source_id
,
617 revision_reader_token
,
619 """Initialize a CVSSymbol object."""
621 CVSItem
.__init
__(self
, id, cvs_file
, revision_reader_token
)
624 self
.source_lod
= source_lod
625 self
.source_id
= source_id
627 def get_cvs_revision_source(self
, cvs_file_items
):
628 """Return the CVSRevision that is the ultimate source of this symbol."""
630 cvs_source
= cvs_file_items
[self
.source_id
]
631 while not isinstance(cvs_source
, CVSRevision
):
632 cvs_source
= cvs_file_items
[cvs_source
.source_id
]
636 def get_svn_path(self
):
637 return self
.symbol
.get_path(self
.cvs_file
.cvs_path
)
639 def get_ids_closed(self
):
640 # A Symbol does not close any other CVSItems:
644 class CVSBranch(CVSSymbol
):
645 """Represent the creation of a branch in a particular CVSFile.
649 id -- (int) unique ID for this item.
651 cvs_file -- (CVSFile) CVSFile affected by this item.
653 symbol -- (Symbol) the symbol affected by this CVSSymbol.
655 branch_number -- (string) the number of this branch (e.g.,
656 '1.3.4'), or None if this is a converted CVSTag.
658 source_lod -- (LineOfDevelopment) the LOD that is the source for
661 source_id -- (int) id of the CVSRevision or CVSBranch from which
662 this branch sprouts. This initially points to a CVSRevision,
663 but can be changed to a CVSBranch via parent adjustment in
666 next_id -- (int or None) id of first CVSRevision on this branch,
669 tag_ids -- (list of int) ids of all CVSTags rooted at this
670 CVSBranch (can be set due to parent adjustment in
673 branch_ids -- (list of int) ids of all CVSBranches rooted at this
674 CVSBranch (can be set due to parent adjustment in
677 opened_symbols -- (None or list of (symbol_id, cvs_symbol_id)
678 tuples) information about all CVSSymbols opened by this
679 branch. This member is set in FilterSymbolsPass; before then,
682 revision_reader_token -- (arbitrary) a token that can be set by
683 RevisionCollector for the later use of RevisionReader.
696 self
, id, cvs_file
, symbol
, branch_number
,
697 source_lod
, source_id
, next_id
,
698 revision_reader_token
,
700 """Initialize a CVSBranch."""
703 self
, id, cvs_file
, symbol
, source_lod
, source_id
,
704 revision_reader_token
,
706 self
.branch_number
= branch_number
707 self
.next_id
= next_id
710 self
.opened_symbols
= None
712 def __getstate__(self
):
714 self
.id, self
.cvs_file
.id,
715 self
.symbol
.id, self
.branch_number
,
716 self
.source_lod
.id, self
.source_id
, self
.next_id
,
717 self
.tag_ids
, self
.branch_ids
,
719 self
.revision_reader_token
,
722 def __setstate__(self
, data
):
724 self
.id, cvs_file_id
,
725 symbol_id
, self
.branch_number
,
726 source_lod_id
, self
.source_id
, self
.next_id
,
727 self
.tag_ids
, self
.branch_ids
,
729 self
.revision_reader_token
,
731 self
.cvs_file
= Ctx()._cvs
_path
_db
.get_path(cvs_file_id
)
732 self
.symbol
= Ctx()._symbol
_db
.get_symbol(symbol_id
)
733 self
.source_lod
= Ctx()._symbol
_db
.get_symbol(source_lod_id
)
735 def get_pred_ids(self
):
736 return set([self
.source_id
])
738 def get_succ_ids(self
):
739 retval
= set(self
.tag_ids
+ self
.branch_ids
)
740 if self
.next_id
is not None:
741 retval
.add(self
.next_id
)
744 def get_cvs_symbol_ids_opened(self
):
745 return self
.tag_ids
+ self
.branch_ids
747 def check_links(self
, cvs_file_items
):
748 source
= cvs_file_items
.get(self
.source_id
)
749 next
= cvs_file_items
.get(self
.next_id
)
751 assert self
.id in source
.branch_ids
752 if isinstance(source
, CVSRevision
):
753 assert self
.source_lod
== source
.lod
754 elif isinstance(source
, CVSBranch
):
755 assert self
.source_lod
== source
.symbol
760 assert isinstance(next
, CVSRevision
)
761 assert next
.lod
== self
.symbol
762 assert next
.first_on_branch_id
== self
.id
764 for tag_id
in self
.tag_ids
:
765 tag
= cvs_file_items
[tag_id
]
766 assert isinstance(tag
, CVSTag
)
767 assert tag
.source_id
== self
.id
768 assert tag
.source_lod
== self
.symbol
770 for branch_id
in self
.branch_ids
:
771 branch
= cvs_file_items
[branch_id
]
772 assert isinstance(branch
, CVSBranch
)
773 assert branch
.source_id
== self
.id
774 assert branch
.source_lod
== self
.symbol
777 """For convenience only. The format is subject to change at any time."""
779 return '%s:%s:%s<%x>' \
780 % (self
.cvs_file
, self
.symbol
, self
.branch_number
, self
.id,)
783 class CVSBranchNoop(CVSBranch
):
784 """A CVSBranch whose source is a CVSRevisionAbsent."""
788 def get_cvs_symbol_ids_opened(self
):
794 # {nondead(source_cvs_rev) : cvs_branch_subtype}
796 # , where nondead() means that the cvs revision exists and is not
797 # 'dead', and CVS_BRANCH_SUBTYPE is the subtype of CVSBranch that
799 cvs_branch_type_map
= {
800 False : CVSBranchNoop
,
805 class CVSTag(CVSSymbol
):
806 """Represent the creation of a tag on a particular CVSFile.
810 id -- (int) unique ID for this item.
812 cvs_file -- (CVSFile) CVSFile affected by this item.
814 symbol -- (Symbol) the symbol affected by this CVSSymbol.
816 source_lod -- (LineOfDevelopment) the LOD that is the source for
819 source_id -- (int) the ID of the CVSRevision or CVSBranch that is
820 being tagged. This initially points to a CVSRevision, but can
821 be changed to a CVSBranch via parent adjustment in
824 revision_reader_token -- (arbitrary) a token that can be set by
825 RevisionCollector for the later use of RevisionReader.
832 self
, id, cvs_file
, symbol
, source_lod
, source_id
,
833 revision_reader_token
,
835 """Initialize a CVSTag."""
838 self
, id, cvs_file
, symbol
, source_lod
, source_id
,
839 revision_reader_token
,
842 def __getstate__(self
):
844 self
.id, self
.cvs_file
.id, self
.symbol
.id,
845 self
.source_lod
.id, self
.source_id
,
846 self
.revision_reader_token
,
849 def __setstate__(self
, data
):
851 self
.id, cvs_file_id
, symbol_id
, source_lod_id
, self
.source_id
,
852 self
.revision_reader_token
,
854 self
.cvs_file
= Ctx()._cvs
_path
_db
.get_path(cvs_file_id
)
855 self
.symbol
= Ctx()._symbol
_db
.get_symbol(symbol_id
)
856 self
.source_lod
= Ctx()._symbol
_db
.get_symbol(source_lod_id
)
858 def get_pred_ids(self
):
859 return set([self
.source_id
])
861 def get_succ_ids(self
):
864 def get_cvs_symbol_ids_opened(self
):
867 def check_links(self
, cvs_file_items
):
868 source
= cvs_file_items
.get(self
.source_id
)
870 assert self
.id in source
.tag_ids
871 if isinstance(source
, CVSRevision
):
872 assert self
.source_lod
== source
.lod
873 elif isinstance(source
, CVSBranch
):
874 assert self
.source_lod
== source
.symbol
879 """For convenience only. The format is subject to change at any time."""
882 % (self
.cvs_file
, self
.symbol
, self
.id,)
885 class CVSTagNoop(CVSTag
):
886 """A CVSTag whose source is a CVSRevisionAbsent."""
893 # {nondead(source_cvs_rev) : cvs_tag_subtype}
895 # , where nondead() means that the cvs revision exists and is not
896 # 'dead', and CVS_TAG_SUBTYPE is the subtype of CVSTag that should be