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_recorder_token',
64 def __init__(self
, id, cvs_file
, revision_recorder_token
):
66 self
.cvs_file
= cvs_file
67 self
.revision_recorder_token
= revision_recorder_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_recorder_token -- (arbitrary) a token that can be set by
203 RevisionRecorder for the later use of RevisionReader.
215 'first_on_branch_id',
228 timestamp
, metadata_id
,
230 rev
, deltatext_exists
,
231 lod
, first_on_branch_id
, ntdbr
,
232 ntdbr_prev_id
, ntdbr_next_id
,
233 tag_ids
, branch_ids
, branch_commit_ids
,
234 revision_recorder_token
):
235 """Initialize a new CVSRevision object."""
237 CVSItem
.__init
__(self
, id, cvs_file
, revision_recorder_token
)
239 self
.timestamp
= timestamp
240 self
.metadata_id
= metadata_id
241 self
.prev_id
= prev_id
242 self
.next_id
= next_id
244 self
.deltatext_exists
= deltatext_exists
246 self
.first_on_branch_id
= first_on_branch_id
248 self
.ntdbr_prev_id
= ntdbr_prev_id
249 self
.ntdbr_next_id
= ntdbr_next_id
250 self
.tag_ids
= tag_ids
251 self
.branch_ids
= branch_ids
252 self
.branch_commit_ids
= branch_commit_ids
253 self
.opened_symbols
= None
254 self
.closed_symbols
= None
256 def _get_cvs_path(self
):
257 return self
.cvs_file
.cvs_path
259 cvs_path
= property(_get_cvs_path
)
261 def get_svn_path(self
):
262 return self
.lod
.get_path(self
.cvs_file
.cvs_path
)
264 def __getstate__(self
):
265 """Return the contents of this instance, for pickling.
267 The presence of this method improves the space efficiency of
268 pickling CVSRevision instances."""
271 self
.id, self
.cvs_file
.id,
272 self
.timestamp
, self
.metadata_id
,
273 self
.prev_id
, self
.next_id
,
275 self
.deltatext_exists
,
277 self
.first_on_branch_id
,
279 self
.ntdbr_prev_id
, self
.ntdbr_next_id
,
280 self
.tag_ids
, self
.branch_ids
, self
.branch_commit_ids
,
281 self
.opened_symbols
, self
.closed_symbols
,
282 self
.revision_recorder_token
,
285 def __setstate__(self
, data
):
286 (self
.id, cvs_file_id
,
287 self
.timestamp
, self
.metadata_id
,
288 self
.prev_id
, self
.next_id
,
290 self
.deltatext_exists
,
292 self
.first_on_branch_id
,
294 self
.ntdbr_prev_id
, self
.ntdbr_next_id
,
295 self
.tag_ids
, self
.branch_ids
, self
.branch_commit_ids
,
296 self
.opened_symbols
, self
.closed_symbols
,
297 self
.revision_recorder_token
) = data
298 self
.cvs_file
= Ctx()._cvs
_path
_db
.get_path(cvs_file_id
)
299 self
.lod
= Ctx()._symbol
_db
.get_symbol(lod_id
)
301 def get_effective_prev_id(self
):
302 """Return the ID of the effective predecessor of this item.
304 This is the ID of the item that determines whether the object
305 existed before this CVSRevision."""
307 if self
.ntdbr_prev_id
is not None:
308 return self
.ntdbr_prev_id
312 def get_symbol_pred_ids(self
):
313 """Return the pred_ids for symbol predecessors."""
316 if self
.first_on_branch_id
is not None:
317 retval
.add(self
.first_on_branch_id
)
320 def get_pred_ids(self
):
321 retval
= self
.get_symbol_pred_ids()
322 if self
.prev_id
is not None:
323 retval
.add(self
.prev_id
)
324 if self
.ntdbr_prev_id
is not None:
325 retval
.add(self
.ntdbr_prev_id
)
328 def get_symbol_succ_ids(self
):
329 """Return the succ_ids for symbol successors."""
332 for id in self
.branch_ids
+ self
.tag_ids
:
336 def get_succ_ids(self
):
337 retval
= self
.get_symbol_succ_ids()
338 if self
.next_id
is not None:
339 retval
.add(self
.next_id
)
340 if self
.ntdbr_next_id
is not None:
341 retval
.add(self
.ntdbr_next_id
)
342 for id in self
.branch_commit_ids
:
346 def get_ids_closed(self
):
347 # Special handling is needed in the case of non-trunk default
348 # branches. The following cases have to be handled:
350 # Case 1: Revision 1.1 not deleted; revision 1.2 exists:
352 # 1.1 -----------------> 1.2
357 # * 1.1.1.1 closes 1.1 (because its post-commit overwrites 1.1
360 # * 1.1.1.2 closes 1.1.1.1
362 # * 1.2 doesn't close anything (the post-commit from 1.1.1.1
363 # already closed 1.1, and no symbols can sprout from the
364 # post-commit of 1.1.1.2)
366 # Case 2: Revision 1.1 not deleted; revision 1.2 does not exist:
368 # 1.1 ..................
373 # * 1.1.1.1 closes 1.1 (because its post-commit overwrites 1.1
376 # * 1.1.1.2 closes 1.1.1.1
378 # Case 3: Revision 1.1 deleted; revision 1.2 exists:
380 # ............... 1.2
385 # * 1.1.1.1 doesn't close anything
387 # * 1.1.1.2 closes 1.1.1.1
389 # * 1.2 doesn't close anything (no symbols can sprout from the
390 # post-commit of 1.1.1.2)
392 # Case 4: Revision 1.1 deleted; revision 1.2 doesn't exist:
399 # * 1.1.1.1 doesn't close anything
401 # * 1.1.1.2 closes 1.1.1.1
403 if self
.first_on_branch_id
is not None:
404 # The first CVSRevision on a branch is considered to close the
406 yield self
.first_on_branch_id
408 # If the 1.1 revision was not deleted, the 1.1.1.1 revision is
409 # considered to close it:
411 elif self
.ntdbr_prev_id
is not None:
412 # This is the special case of a 1.2 revision that follows a
413 # non-trunk default branch. Either 1.1 was deleted or the first
414 # default branch revision closed 1.1, so we don't have to close
415 # 1.1. Technically, we close the revision on trunk that was
416 # copied from the last non-trunk default branch revision in a
417 # post-commit, but for now no symbols can sprout from that
418 # revision so we ignore that one, too.
420 elif self
.prev_id
is not None:
421 # Since this CVSRevision is not the first on a branch, its
422 # prev_id is on the same LOD and this item closes that one:
425 def _get_branch_ids_recursively(self
, cvs_file_items
):
426 """Return the set of all CVSBranches that sprout from this CVSRevision.
428 After parent adjustment in FilterSymbolsPass, it is possible for
429 branches to sprout directly from a CVSRevision, or from those
430 branches, etc. Return all branches that sprout from this
431 CVSRevision, directly or indirectly."""
434 branch_ids_to_process
= list(self
.branch_ids
)
435 while branch_ids_to_process
:
436 branch
= cvs_file_items
[branch_ids_to_process
.pop()]
438 branch_ids_to_process
.extend(branch
.branch_ids
)
442 def check_links(self
, cvs_file_items
):
443 assert self
.cvs_file
== cvs_file_items
.cvs_file
445 prev
= cvs_file_items
.get(self
.prev_id
)
446 next
= cvs_file_items
.get(self
.next_id
)
447 first_on_branch
= cvs_file_items
.get(self
.first_on_branch_id
)
448 ntdbr_next
= cvs_file_items
.get(self
.ntdbr_next_id
)
449 ntdbr_prev
= cvs_file_items
.get(self
.ntdbr_prev_id
)
450 effective_prev
= cvs_file_items
.get(self
.get_effective_prev_id())
453 # This is the first CVSRevision on trunk or a detached branch:
454 assert self
.id in cvs_file_items
.root_ids
455 elif first_on_branch
is not None:
456 # This is the first CVSRevision on an existing branch:
457 assert isinstance(first_on_branch
, CVSBranch
)
458 assert first_on_branch
.symbol
== self
.lod
459 assert first_on_branch
.next_id
== self
.id
460 cvs_revision_source
= first_on_branch
.get_cvs_revision_source(
463 assert cvs_revision_source
.id == prev
.id
464 assert self
.id in prev
.branch_commit_ids
466 # This revision follows another revision on the same LOD:
467 assert prev
.next_id
== self
.id
468 assert prev
.lod
== self
.lod
471 assert next
.prev_id
== self
.id
472 assert next
.lod
== self
.lod
474 if ntdbr_next
is not None:
476 assert ntdbr_next
.ntdbr_prev_id
== self
.id
478 if ntdbr_prev
is not None:
479 assert ntdbr_prev
.ntdbr_next_id
== self
.id
481 for tag_id
in self
.tag_ids
:
482 tag
= cvs_file_items
[tag_id
]
483 assert isinstance(tag
, CVSTag
)
484 assert tag
.source_id
== self
.id
485 assert tag
.source_lod
== self
.lod
487 for branch_id
in self
.branch_ids
:
488 branch
= cvs_file_items
[branch_id
]
489 assert isinstance(branch
, CVSBranch
)
490 assert branch
.source_id
== self
.id
491 assert branch
.source_lod
== self
.lod
493 branch_commit_ids
= list(self
.branch_commit_ids
)
495 for branch
in self
._get
_branch
_ids
_recursively
(cvs_file_items
):
496 assert isinstance(branch
, CVSBranch
)
497 if branch
.next_id
is not None:
498 assert branch
.next_id
in branch_commit_ids
499 branch_commit_ids
.remove(branch
.next_id
)
501 assert not branch_commit_ids
503 assert self
.__class
__ == cvs_revision_type_map
[(
504 isinstance(self
, CVSRevisionModification
),
505 effective_prev
is not None
506 and isinstance(effective_prev
, CVSRevisionModification
),
510 """For convenience only. The format is subject to change at any time."""
512 return '%s:%s<%x>' % (self
.cvs_file
, self
.rev
, self
.id,)
515 class CVSRevisionModification(CVSRevision
):
516 """Base class for CVSRevisionAdd or CVSRevisionChange."""
520 def get_cvs_symbol_ids_opened(self
):
521 return self
.tag_ids
+ self
.branch_ids
524 class CVSRevisionAdd(CVSRevisionModification
):
525 """A CVSRevision that creates a file that previously didn't exist.
527 The file might have never existed on this LOD, or it might have
528 existed previously but been deleted by a CVSRevisionDelete."""
533 class CVSRevisionChange(CVSRevisionModification
):
534 """A CVSRevision that modifies a file that already existed on this LOD."""
539 class CVSRevisionAbsent(CVSRevision
):
540 """A CVSRevision for which the file is nonexistent on this LOD."""
544 def get_cvs_symbol_ids_opened(self
):
548 class CVSRevisionDelete(CVSRevisionAbsent
):
549 """A CVSRevision that deletes a file that existed on this LOD."""
554 class CVSRevisionNoop(CVSRevisionAbsent
):
555 """A CVSRevision that doesn't do anything.
557 The revision was 'dead' and the predecessor either didn't exist or
558 was also 'dead'. These revisions can't necessarily be thrown away
559 because (1) they impose ordering constraints on other items; (2)
560 they might have a nontrivial log message that we don't want to throw
568 # {(nondead(cvs_rev), nondead(prev_cvs_rev)) : cvs_revision_subtype}
570 # , where nondead() means that the cvs revision exists and is not
571 # 'dead', and CVS_REVISION_SUBTYPE is the subtype of CVSRevision that
572 # should be used for CVS_REV.
573 cvs_revision_type_map
= {
574 (False, False) : CVSRevisionNoop
,
575 (False, True) : CVSRevisionDelete
,
576 (True, False) : CVSRevisionAdd
,
577 (True, True) : CVSRevisionChange
,
581 class CVSSymbol(CVSItem
):
582 """Represent a symbol on a particular CVSFile.
584 This is the base class for CVSBranch and CVSTag.
588 id -- (int) unique ID for this item.
590 cvs_file -- (CVSFile) CVSFile affected by this item.
592 symbol -- (Symbol) the symbol affected by this CVSSymbol.
594 source_lod -- (LineOfDevelopment) the LOD that is the source for
597 source_id -- (int) the ID of the CVSRevision or CVSBranch that is
598 the source for this item. This initially points to a
599 CVSRevision, but can be changed to a CVSBranch via parent
600 adjustment in FilterSymbolsPass.
602 revision_recorder_token -- (arbitrary) a token that can be set by
603 RevisionRecorder for the later use of RevisionReader.
614 self
, id, cvs_file
, symbol
, source_lod
, source_id
,
615 revision_recorder_token
617 """Initialize a CVSSymbol object."""
619 CVSItem
.__init
__(self
, id, cvs_file
, revision_recorder_token
)
622 self
.source_lod
= source_lod
623 self
.source_id
= source_id
625 def get_cvs_revision_source(self
, cvs_file_items
):
626 """Return the CVSRevision that is the ultimate source of this symbol."""
628 cvs_source
= cvs_file_items
[self
.source_id
]
629 while not isinstance(cvs_source
, CVSRevision
):
630 cvs_source
= cvs_file_items
[cvs_source
.source_id
]
634 def get_svn_path(self
):
635 return self
.symbol
.get_path(self
.cvs_file
.cvs_path
)
637 def get_ids_closed(self
):
638 # A Symbol does not close any other CVSItems:
642 class CVSBranch(CVSSymbol
):
643 """Represent the creation of a branch in a particular CVSFile.
647 id -- (int) unique ID for this item.
649 cvs_file -- (CVSFile) CVSFile affected by this item.
651 symbol -- (Symbol) the symbol affected by this CVSSymbol.
653 branch_number -- (string) the number of this branch (e.g.,
654 '1.3.4'), or None if this is a converted CVSTag.
656 source_lod -- (LineOfDevelopment) the LOD that is the source for
659 source_id -- (int) id of the CVSRevision or CVSBranch from which
660 this branch sprouts. This initially points to a CVSRevision,
661 but can be changed to a CVSBranch via parent adjustment in
664 next_id -- (int or None) id of first CVSRevision on this branch,
667 tag_ids -- (list of int) ids of all CVSTags rooted at this
668 CVSBranch (can be set due to parent adjustment in
671 branch_ids -- (list of int) ids of all CVSBranches rooted at this
672 CVSBranch (can be set due to parent adjustment in
675 opened_symbols -- (None or list of (symbol_id, cvs_symbol_id)
676 tuples) information about all CVSSymbols opened by this
677 branch. This member is set in FilterSymbolsPass; before then,
680 revision_recorder_token -- (arbitrary) a token that can be set by
681 RevisionRecorder for the later use of RevisionReader.
694 self
, id, cvs_file
, symbol
, branch_number
,
695 source_lod
, source_id
, next_id
,
696 revision_recorder_token
,
698 """Initialize a CVSBranch."""
701 self
, id, cvs_file
, symbol
, source_lod
, source_id
,
702 revision_recorder_token
704 self
.branch_number
= branch_number
705 self
.next_id
= next_id
708 self
.opened_symbols
= None
710 def __getstate__(self
):
712 self
.id, self
.cvs_file
.id,
713 self
.symbol
.id, self
.branch_number
,
714 self
.source_lod
.id, self
.source_id
, self
.next_id
,
715 self
.tag_ids
, self
.branch_ids
,
717 self
.revision_recorder_token
,
720 def __setstate__(self
, data
):
722 self
.id, cvs_file_id
,
723 symbol_id
, self
.branch_number
,
724 source_lod_id
, self
.source_id
, self
.next_id
,
725 self
.tag_ids
, self
.branch_ids
,
727 self
.revision_recorder_token
,
729 self
.cvs_file
= Ctx()._cvs
_path
_db
.get_path(cvs_file_id
)
730 self
.symbol
= Ctx()._symbol
_db
.get_symbol(symbol_id
)
731 self
.source_lod
= Ctx()._symbol
_db
.get_symbol(source_lod_id
)
733 def get_pred_ids(self
):
734 return set([self
.source_id
])
736 def get_succ_ids(self
):
737 retval
= set(self
.tag_ids
+ self
.branch_ids
)
738 if self
.next_id
is not None:
739 retval
.add(self
.next_id
)
742 def get_cvs_symbol_ids_opened(self
):
743 return self
.tag_ids
+ self
.branch_ids
745 def check_links(self
, cvs_file_items
):
746 source
= cvs_file_items
.get(self
.source_id
)
747 next
= cvs_file_items
.get(self
.next_id
)
749 assert self
.id in source
.branch_ids
750 if isinstance(source
, CVSRevision
):
751 assert self
.source_lod
== source
.lod
752 elif isinstance(source
, CVSBranch
):
753 assert self
.source_lod
== source
.symbol
758 assert isinstance(next
, CVSRevision
)
759 assert next
.lod
== self
.symbol
760 assert next
.first_on_branch_id
== self
.id
762 for tag_id
in self
.tag_ids
:
763 tag
= cvs_file_items
[tag_id
]
764 assert isinstance(tag
, CVSTag
)
765 assert tag
.source_id
== self
.id
766 assert tag
.source_lod
== self
.symbol
768 for branch_id
in self
.branch_ids
:
769 branch
= cvs_file_items
[branch_id
]
770 assert isinstance(branch
, CVSBranch
)
771 assert branch
.source_id
== self
.id
772 assert branch
.source_lod
== self
.symbol
775 """For convenience only. The format is subject to change at any time."""
777 return '%s:%s:%s<%x>' \
778 % (self
.cvs_file
, self
.symbol
, self
.branch_number
, self
.id,)
781 class CVSBranchNoop(CVSBranch
):
782 """A CVSBranch whose source is a CVSRevisionAbsent."""
786 def get_cvs_symbol_ids_opened(self
):
792 # {nondead(source_cvs_rev) : cvs_branch_subtype}
794 # , where nondead() means that the cvs revision exists and is not
795 # 'dead', and CVS_BRANCH_SUBTYPE is the subtype of CVSBranch that
797 cvs_branch_type_map
= {
798 False : CVSBranchNoop
,
803 class CVSTag(CVSSymbol
):
804 """Represent the creation of a tag on a particular CVSFile.
808 id -- (int) unique ID for this item.
810 cvs_file -- (CVSFile) CVSFile affected by this item.
812 symbol -- (Symbol) the symbol affected by this CVSSymbol.
814 source_lod -- (LineOfDevelopment) the LOD that is the source for
817 source_id -- (int) the ID of the CVSRevision or CVSBranch that is
818 being tagged. This initially points to a CVSRevision, but can
819 be changed to a CVSBranch via parent adjustment in
822 revision_recorder_token -- (arbitrary) a token that can be set by
823 RevisionRecorder for the later use of RevisionReader.
830 self
, id, cvs_file
, symbol
, source_lod
, source_id
,
831 revision_recorder_token
,
833 """Initialize a CVSTag."""
836 self
, id, cvs_file
, symbol
, source_lod
, source_id
,
837 revision_recorder_token
,
840 def __getstate__(self
):
842 self
.id, self
.cvs_file
.id, self
.symbol
.id,
843 self
.source_lod
.id, self
.source_id
,
844 self
.revision_recorder_token
,
847 def __setstate__(self
, data
):
849 self
.id, cvs_file_id
, symbol_id
, source_lod_id
, self
.source_id
,
850 self
.revision_recorder_token
,
852 self
.cvs_file
= Ctx()._cvs
_path
_db
.get_path(cvs_file_id
)
853 self
.symbol
= Ctx()._symbol
_db
.get_symbol(symbol_id
)
854 self
.source_lod
= Ctx()._symbol
_db
.get_symbol(source_lod_id
)
856 def get_pred_ids(self
):
857 return set([self
.source_id
])
859 def get_succ_ids(self
):
862 def get_cvs_symbol_ids_opened(self
):
865 def check_links(self
, cvs_file_items
):
866 source
= cvs_file_items
.get(self
.source_id
)
868 assert self
.id in source
.tag_ids
869 if isinstance(source
, CVSRevision
):
870 assert self
.source_lod
== source
.lod
871 elif isinstance(source
, CVSBranch
):
872 assert self
.source_lod
== source
.symbol
877 """For convenience only. The format is subject to change at any time."""
880 % (self
.cvs_file
, self
.symbol
, self
.id,)
883 class CVSTagNoop(CVSTag
):
884 """A CVSTag whose source is a CVSRevisionAbsent."""
891 # {nondead(source_cvs_rev) : cvs_tag_subtype}
893 # , where nondead() means that the cvs revision exists and is not
894 # 'dead', and CVS_TAG_SUBTYPE is the subtype of CVSTag that should be