aboutsummaryrefslogtreecommitdiff
blob: 5c01a24b8e2ac2a711d039c936ad3f3c899c65d2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
# (Be in -*- python -*- mode.)
#
# ====================================================================
# Copyright (c) 2000-2008 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 classes to store atomic CVS events.

A CVSItem is a single event, pertaining to a single file, that can be
determined to have occured based on the information in the CVS
repository.

The inheritance tree is as follows:

CVSItem
|
+--CVSRevision
|  |
|  +--CVSRevisionModification (* -> 'Exp')
|  |  |
|  |  +--CVSRevisionAdd ('dead' -> 'Exp')
|  |  |
|  |  +--CVSRevisionChange ('Exp' -> 'Exp')
|  |
|  +--CVSRevisionAbsent (* -> 'dead')
|     |
|     +--CVSRevisionDelete ('Exp' -> 'dead')
|     |
|     +--CVSRevisionNoop ('dead' -> 'dead')
|
+--CVSSymbol
   |
   +--CVSBranch
   |  |
   |  +--CVSBranchNoop
   |
   +--CVSTag
      |
      +--CVSTagNoop

"""


from cvs2svn_lib.context import Ctx


class CVSItem(object):
  __slots__ = [
      'id',
      'cvs_file',
      'revision_recorder_token',
      ]

  def __init__(self, id, cvs_file, revision_recorder_token):
    self.id = id
    self.cvs_file = cvs_file
    self.revision_recorder_token = revision_recorder_token

  def __eq__(self, other):
    return self.id == other.id

  def __cmp__(self, other):
    return cmp(self.id, other.id)

  def __hash__(self):
    return self.id

  def __getstate__(self):
    raise NotImplementedError()

  def __setstate__(self, data):
    raise NotImplementedError()

  def get_svn_path(self):
    """Return the SVN path associated with this CVSItem."""

    raise NotImplementedError()

  def get_pred_ids(self):
    """Return the CVSItem.ids of direct predecessors of SELF.

    A predecessor is defined to be a CVSItem that has to have been
    committed before this one."""

    raise NotImplementedError()

  def get_succ_ids(self):
    """Return the CVSItem.ids of direct successors of SELF.

    A direct successor is defined to be a CVSItem that has this one as
    a direct predecessor."""

    raise NotImplementedError()

  def get_cvs_symbol_ids_opened(self):
    """Return an iterable over the ids of CVSSymbols that this item opens.

    The definition of 'open' is that the path corresponding to this
    CVSItem will have to be copied when filling the corresponding
    symbol."""

    raise NotImplementedError()

  def get_ids_closed(self):
    """Return an iterable over the CVSItem.ids of CVSItems closed by this one.

    A CVSItem A is said to close a CVSItem B if committing A causes B
    to be overwritten or deleted (no longer available) in the SVN
    repository.  This is interesting because it sets the last SVN
    revision number from which the contents of B can be copied (for
    example, to fill a symbol).  See the concrete implementations of
    this method for the exact rules about what closes what."""

    raise NotImplementedError()

  def check_links(self, cvs_file_items):
    """Check for consistency of links to other CVSItems.

    Other items can be looked up in CVS_FILE_ITEMS, which is an
    instance of CVSFileItems.  Raise an AssertionError if there is a
    problem."""

    raise NotImplementedError()

  def __repr__(self):
    return '%s(%s)' % (self.__class__.__name__, self,)


class CVSRevision(CVSItem):
  """Information about a single CVS revision.

  A CVSRevision holds the information known about a single version of
  a single file.

  Members:

    id -- (int) unique ID for this revision.

    cvs_file -- (CVSFile) CVSFile affected by this revision.

    timestamp -- (int) date stamp for this revision.

    metadata_id -- (int) id of metadata instance record in
        metadata_db.

    prev_id -- (int) id of the logically previous CVSRevision, either
        on the same or the source branch (or None).

    next_id -- (int) id of the logically next CVSRevision (or None).

    rev -- (string) the CVS revision number, e.g., '1.3'.

    deltatext_exists -- (bool) true iff this revision's deltatext is
        not empty.

    lod -- (LineOfDevelopment) LOD on which this revision occurred.

    first_on_branch_id -- (int or None) if this revision is the first
        on its branch, the cvs_branch_id of that branch; else, None.

    ntdbr -- (bool) true iff this is a non-trunk default branch
        revision.

    ntdbr_prev_id -- (int or None) Iff this is the 1.2 revision after
        the end of a default branch, the id of the last rev on the
        default branch; else, None.

    ntdbr_next_id -- (int or None) Iff this is the last revision on a
        default branch preceding a 1.2 rev, the id of the 1.2
        revision; else, None.

    tag_ids -- (list of int) ids of all CVSTags rooted at this
        CVSRevision.

    branch_ids -- (list of int) ids of all CVSBranches rooted at this
        CVSRevision.

    branch_commit_ids -- (list of int) ids of first CVSRevision
        committed on each branch rooted in this revision (for branches
        with commits).

    opened_symbols -- (None or list of (symbol_id, cvs_symbol_id)
        tuples) information about all CVSSymbols opened by this
        revision.  This member is set in FilterSymbolsPass; before
        then, it is None.

    closed_symbols -- (None or list of (symbol_id, cvs_symbol_id)
        tuples) information about all CVSSymbols closed by this
        revision.  This member is set in FilterSymbolsPass; before
        then, it is None.

    revision_recorder_token -- (arbitrary) a token that can be set by
        RevisionRecorder for the later use of RevisionReader.

  """

  __slots__ = [
      'timestamp',
      'metadata_id',
      'prev_id',
      'next_id',
      'rev',
      'deltatext_exists',
      'lod',
      'first_on_branch_id',
      'ntdbr',
      'ntdbr_prev_id',
      'ntdbr_next_id',
      'tag_ids',
      'branch_ids',
      'branch_commit_ids',
      'opened_symbols',
      'closed_symbols',
      ]

  def __init__(self,
               id, cvs_file,
               timestamp, metadata_id,
               prev_id, next_id,
               rev, deltatext_exists,
               lod, first_on_branch_id, ntdbr,
               ntdbr_prev_id, ntdbr_next_id,
               tag_ids, branch_ids, branch_commit_ids,
               revision_recorder_token):
    """Initialize a new CVSRevision object."""

    CVSItem.__init__(self, id, cvs_file, revision_recorder_token)

    self.timestamp = timestamp
    self.metadata_id = metadata_id
    self.prev_id = prev_id
    self.next_id = next_id
    self.rev = rev
    self.deltatext_exists = deltatext_exists
    self.lod = lod
    self.first_on_branch_id = first_on_branch_id
    self.ntdbr = ntdbr
    self.ntdbr_prev_id = ntdbr_prev_id
    self.ntdbr_next_id = ntdbr_next_id
    self.tag_ids = tag_ids
    self.branch_ids = branch_ids
    self.branch_commit_ids = branch_commit_ids
    self.opened_symbols = None
    self.closed_symbols = None

  def _get_cvs_path(self):
    return self.cvs_file.cvs_path

  cvs_path = property(_get_cvs_path)

  def get_svn_path(self):
    return self.lod.get_path(self.cvs_file.cvs_path)

  def __getstate__(self):
    """Return the contents of this instance, for pickling.

    The presence of this method improves the space efficiency of
    pickling CVSRevision instances."""

    return (
        self.id, self.cvs_file.id,
        self.timestamp, self.metadata_id,
        self.prev_id, self.next_id,
        self.rev,
        self.deltatext_exists,
        self.lod.id,
        self.first_on_branch_id,
        self.ntdbr,
        self.ntdbr_prev_id, self.ntdbr_next_id,
        self.tag_ids, self.branch_ids, self.branch_commit_ids,
        self.opened_symbols, self.closed_symbols,
        self.revision_recorder_token,
        )

  def __setstate__(self, data):
    (self.id, cvs_file_id,
     self.timestamp, self.metadata_id,
     self.prev_id, self.next_id,
     self.rev,
     self.deltatext_exists,
     lod_id,
     self.first_on_branch_id,
     self.ntdbr,
     self.ntdbr_prev_id, self.ntdbr_next_id,
     self.tag_ids, self.branch_ids, self.branch_commit_ids,
     self.opened_symbols, self.closed_symbols,
     self.revision_recorder_token) = data
    self.cvs_file = Ctx()._cvs_file_db.get_file(cvs_file_id)
    self.lod = Ctx()._symbol_db.get_symbol(lod_id)

  def get_effective_prev_id(self):
    """Return the ID of the effective predecessor of this item.

    This is the ID of the item that determines whether the object
    existed before this CVSRevision."""

    if self.ntdbr_prev_id is not None:
      return self.ntdbr_prev_id
    else:
      return self.prev_id

  def get_symbol_pred_ids(self):
    """Return the pred_ids for symbol predecessors."""

    retval = set()
    if self.first_on_branch_id is not None:
      retval.add(self.first_on_branch_id)
    return retval

  def get_pred_ids(self):
    retval = self.get_symbol_pred_ids()
    if self.prev_id is not None:
      retval.add(self.prev_id)
    if self.ntdbr_prev_id is not None:
      retval.add(self.ntdbr_prev_id)
    return retval

  def get_symbol_succ_ids(self):
    """Return the succ_ids for symbol successors."""

    retval = set()
    for id in self.branch_ids + self.tag_ids:
      retval.add(id)
    return retval

  def get_succ_ids(self):
    retval = self.get_symbol_succ_ids()
    if self.next_id is not None:
      retval.add(self.next_id)
    if self.ntdbr_next_id is not None:
      retval.add(self.ntdbr_next_id)
    for id in self.branch_commit_ids:
      retval.add(id)
    return retval

  def get_ids_closed(self):
    # Special handling is needed in the case of non-trunk default
    # branches.  The following cases have to be handled:
    #
    # Case 1: Revision 1.1 not deleted; revision 1.2 exists:
    #
    #         1.1 -----------------> 1.2
    #           \    ^          ^    /
    #            \   |          |   /
    #             1.1.1.1 -> 1.1.1.2
    #
    # * 1.1.1.1 closes 1.1 (because its post-commit overwrites 1.1
    #   on trunk)
    #
    # * 1.1.1.2 closes 1.1.1.1
    #
    # * 1.2 doesn't close anything (the post-commit from 1.1.1.1
    #   already closed 1.1, and no symbols can sprout from the
    #   post-commit of 1.1.1.2)
    #
    # Case 2: Revision 1.1 not deleted; revision 1.2 does not exist:
    #
    #         1.1 ..................
    #           \    ^          ^
    #            \   |          |
    #             1.1.1.1 -> 1.1.1.2
    #
    # * 1.1.1.1 closes 1.1 (because its post-commit overwrites 1.1
    #   on trunk)
    #
    # * 1.1.1.2 closes 1.1.1.1
    #
    # Case 3: Revision 1.1 deleted; revision 1.2 exists:
    #
    #                ............... 1.2
    #                ^          ^    /
    #                |          |   /
    #             1.1.1.1 -> 1.1.1.2
    #
    # * 1.1.1.1 doesn't close anything
    #
    # * 1.1.1.2 closes 1.1.1.1
    #
    # * 1.2 doesn't close anything (no symbols can sprout from the
    #   post-commit of 1.1.1.2)
    #
    # Case 4: Revision 1.1 deleted; revision 1.2 doesn't exist:
    #
    #                ...............
    #                ^          ^
    #                |          |
    #             1.1.1.1 -> 1.1.1.2
    #
    # * 1.1.1.1 doesn't close anything
    #
    # * 1.1.1.2 closes 1.1.1.1

    if self.first_on_branch_id is not None:
      # The first CVSRevision on a branch is considered to close the
      # branch:
      yield self.first_on_branch_id
      if self.ntdbr:
        # If the 1.1 revision was not deleted, the 1.1.1.1 revision is
        # considered to close it:
        yield self.prev_id
    elif self.ntdbr_prev_id is not None:
      # This is the special case of a 1.2 revision that follows a
      # non-trunk default branch.  Either 1.1 was deleted or the first
      # default branch revision closed 1.1, so we don't have to close
      # 1.1.  Technically, we close the revision on trunk that was
      # copied from the last non-trunk default branch revision in a
      # post-commit, but for now no symbols can sprout from that
      # revision so we ignore that one, too.
      pass
    elif self.prev_id is not None:
      # Since this CVSRevision is not the first on a branch, its
      # prev_id is on the same LOD and this item closes that one:
      yield self.prev_id

  def _get_branch_ids_recursively(self, cvs_file_items):
    """Return the set of all CVSBranches that sprout from this CVSRevision.

    After parent adjustment in FilterSymbolsPass, it is possible for
    branches to sprout directly from a CVSRevision, or from those
    branches, etc.  Return all branches that sprout from this
    CVSRevision, directly or indirectly."""

    retval = set()
    branch_ids_to_process = list(self.branch_ids)
    while branch_ids_to_process:
      branch = cvs_file_items[branch_ids_to_process.pop()]
      retval.add(branch)
      branch_ids_to_process.extend(branch.branch_ids)

    return retval

  def check_links(self, cvs_file_items):
    assert self.cvs_file == cvs_file_items.cvs_file

    prev = cvs_file_items.get(self.prev_id)
    next = cvs_file_items.get(self.next_id)
    first_on_branch = cvs_file_items.get(self.first_on_branch_id)
    ntdbr_next = cvs_file_items.get(self.ntdbr_next_id)
    ntdbr_prev = cvs_file_items.get(self.ntdbr_prev_id)
    effective_prev = cvs_file_items.get(self.get_effective_prev_id())

    if prev is None:
      # This is the first CVSRevision on trunk or a detached branch:
      assert self.id in cvs_file_items.root_ids
    elif first_on_branch is not None:
      # This is the first CVSRevision on an existing branch:
      assert isinstance(first_on_branch, CVSBranch)
      assert first_on_branch.symbol == self.lod
      assert first_on_branch.next_id == self.id
      cvs_revision_source = first_on_branch.get_cvs_revision_source(
          cvs_file_items
          )
      assert cvs_revision_source.id == prev.id
      assert self.id in prev.branch_commit_ids
    else:
      # This revision follows another revision on the same LOD:
      assert prev.next_id == self.id
      assert prev.lod == self.lod

    if next is not None:
      assert next.prev_id == self.id
      assert next.lod == self.lod

    if ntdbr_next is not None:
      assert self.ntdbr
      assert ntdbr_next.ntdbr_prev_id == self.id

    if ntdbr_prev is not None:
      assert ntdbr_prev.ntdbr_next_id == self.id

    for tag_id in self.tag_ids:
      tag = cvs_file_items[tag_id]
      assert isinstance(tag, CVSTag)
      assert tag.source_id == self.id
      assert tag.source_lod == self.lod

    for branch_id in self.branch_ids:
      branch = cvs_file_items[branch_id]
      assert isinstance(branch, CVSBranch)
      assert branch.source_id == self.id
      assert branch.source_lod == self.lod

    branch_commit_ids = list(self.branch_commit_ids)

    for branch in self._get_branch_ids_recursively(cvs_file_items):
      assert isinstance(branch, CVSBranch)
      if branch.next_id is not None:
        assert branch.next_id in branch_commit_ids
        branch_commit_ids.remove(branch.next_id)

    assert not branch_commit_ids

    assert self.__class__ == cvs_revision_type_map[(
        isinstance(self, CVSRevisionModification),
        effective_prev is not None
            and isinstance(effective_prev, CVSRevisionModification),
        )]

  def __str__(self):
    """For convenience only.  The format is subject to change at any time."""

    return '%s:%s<%x>' % (self.cvs_file, self.rev, self.id,)


class CVSRevisionModification(CVSRevision):
  """Base class for CVSRevisionAdd or CVSRevisionChange."""

  __slots__ = []

  def get_cvs_symbol_ids_opened(self):
    return self.tag_ids + self.branch_ids


class CVSRevisionAdd(CVSRevisionModification):
  """A CVSRevision that creates a file that previously didn't exist.

  The file might have never existed on this LOD, or it might have
  existed previously but been deleted by a CVSRevisionDelete."""

  __slots__ = []


class CVSRevisionChange(CVSRevisionModification):
  """A CVSRevision that modifies a file that already existed on this LOD."""

  __slots__ = []


class CVSRevisionAbsent(CVSRevision):
  """A CVSRevision for which the file is nonexistent on this LOD."""

  __slots__ = []

  def get_cvs_symbol_ids_opened(self):
    return []


class CVSRevisionDelete(CVSRevisionAbsent):
  """A CVSRevision that deletes a file that existed on this LOD."""

  __slots__ = []


class CVSRevisionNoop(CVSRevisionAbsent):
  """A CVSRevision that doesn't do anything.

  The revision was 'dead' and the predecessor either didn't exist or
  was also 'dead'.  These revisions can't necessarily be thrown away
  because (1) they impose ordering constraints on other items; (2)
  they might have a nontrivial log message that we don't want to throw
  away."""

  __slots__ = []


# A map
#
#   {(nondead(cvs_rev), nondead(prev_cvs_rev)) : cvs_revision_subtype}
#
# , where nondead() means that the cvs revision exists and is not
# 'dead', and CVS_REVISION_SUBTYPE is the subtype of CVSRevision that
# should be used for CVS_REV.
cvs_revision_type_map = {
    (False, False) : CVSRevisionNoop,
    (False, True) : CVSRevisionDelete,
    (True, False) : CVSRevisionAdd,
    (True, True) : CVSRevisionChange,
    }


class CVSSymbol(CVSItem):
  """Represent a symbol on a particular CVSFile.

  This is the base class for CVSBranch and CVSTag.

  Members:

    id -- (int) unique ID for this item.

    cvs_file -- (CVSFile) CVSFile affected by this item.

    symbol -- (Symbol) the symbol affected by this CVSSymbol.

    source_lod -- (LineOfDevelopment) the LOD that is the source for
        this CVSSymbol.

    source_id -- (int) the ID of the CVSRevision or CVSBranch that is
        the source for this item.  This initially points to a
        CVSRevision, but can be changed to a CVSBranch via parent
        adjustment in FilterSymbolsPass.

    revision_recorder_token -- (arbitrary) a token that can be set by
        RevisionRecorder for the later use of RevisionReader.

  """

  __slots__ = [
      'symbol',
      'source_lod',
      'source_id',
      ]

  def __init__(
      self, id, cvs_file, symbol, source_lod, source_id,
      revision_recorder_token
      ):
    """Initialize a CVSSymbol object."""

    CVSItem.__init__(self, id, cvs_file, revision_recorder_token)

    self.symbol = symbol
    self.source_lod = source_lod
    self.source_id = source_id

  def get_cvs_revision_source(self, cvs_file_items):
    """Return the CVSRevision that is the ultimate source of this symbol."""

    cvs_source = cvs_file_items[self.source_id]
    while not isinstance(cvs_source, CVSRevision):
      cvs_source = cvs_file_items[cvs_source.source_id]

    return cvs_source

  def get_svn_path(self):
    return self.symbol.get_path(self.cvs_file.cvs_path)

  def get_ids_closed(self):
    # A Symbol does not close any other CVSItems:
    return []


class CVSBranch(CVSSymbol):
  """Represent the creation of a branch in a particular CVSFile.

  Members:

    id -- (int) unique ID for this item.

    cvs_file -- (CVSFile) CVSFile affected by this item.

    symbol -- (Symbol) the symbol affected by this CVSSymbol.

    branch_number -- (string) the number of this branch (e.g.,
        '1.3.4'), or None if this is a converted CVSTag.

    source_lod -- (LineOfDevelopment) the LOD that is the source for
        this CVSSymbol.

    source_id -- (int) id of the CVSRevision or CVSBranch from which
        this branch sprouts.  This initially points to a CVSRevision,
        but can be changed to a CVSBranch via parent adjustment in
        FilterSymbolsPass.

    next_id -- (int or None) id of first CVSRevision on this branch,
        if any; else, None.

    tag_ids -- (list of int) ids of all CVSTags rooted at this
        CVSBranch (can be set due to parent adjustment in
        FilterSymbolsPass).

    branch_ids -- (list of int) ids of all CVSBranches rooted at this
        CVSBranch (can be set due to parent adjustment in
        FilterSymbolsPass).

    opened_symbols -- (None or list of (symbol_id, cvs_symbol_id)
        tuples) information about all CVSSymbols opened by this
        branch.  This member is set in FilterSymbolsPass; before then,
        it is None.

    revision_recorder_token -- (arbitrary) a token that can be set by
        RevisionRecorder for the later use of RevisionReader.

  """

  __slots__ = [
      'branch_number',
      'next_id',
      'tag_ids',
      'branch_ids',
      'opened_symbols',
      ]

  def __init__(
      self, id, cvs_file, symbol, branch_number,
      source_lod, source_id, next_id,
      revision_recorder_token,
      ):
    """Initialize a CVSBranch."""

    CVSSymbol.__init__(
        self, id, cvs_file, symbol, source_lod, source_id,
        revision_recorder_token
        )
    self.branch_number = branch_number
    self.next_id = next_id
    self.tag_ids = []
    self.branch_ids = []
    self.opened_symbols = None

  def __getstate__(self):
    return (
        self.id, self.cvs_file.id,
        self.symbol.id, self.branch_number,
        self.source_lod.id, self.source_id, self.next_id,
        self.tag_ids, self.branch_ids,
        self.opened_symbols,
        self.revision_recorder_token,
        )

  def __setstate__(self, data):
    (
        self.id, cvs_file_id,
        symbol_id, self.branch_number,
        source_lod_id, self.source_id, self.next_id,
        self.tag_ids, self.branch_ids,
        self.opened_symbols,
        self.revision_recorder_token,
        ) = data
    self.cvs_file = Ctx()._cvs_file_db.get_file(cvs_file_id)
    self.symbol = Ctx()._symbol_db.get_symbol(symbol_id)
    self.source_lod = Ctx()._symbol_db.get_symbol(source_lod_id)

  def get_pred_ids(self):
    return set([self.source_id])

  def get_succ_ids(self):
    retval = set(self.tag_ids + self.branch_ids)
    if self.next_id is not None:
      retval.add(self.next_id)
    return retval

  def get_cvs_symbol_ids_opened(self):
    return self.tag_ids + self.branch_ids

  def check_links(self, cvs_file_items):
    source = cvs_file_items.get(self.source_id)
    next = cvs_file_items.get(self.next_id)

    assert self.id in source.branch_ids
    if isinstance(source, CVSRevision):
      assert self.source_lod == source.lod
    elif isinstance(source, CVSBranch):
      assert self.source_lod == source.symbol
    else:
      assert False

    if next is not None:
      assert isinstance(next, CVSRevision)
      assert next.lod == self.symbol
      assert next.first_on_branch_id == self.id

    for tag_id in self.tag_ids:
      tag = cvs_file_items[tag_id]
      assert isinstance(tag, CVSTag)
      assert tag.source_id == self.id
      assert tag.source_lod == self.symbol

    for branch_id in self.branch_ids:
      branch = cvs_file_items[branch_id]
      assert isinstance(branch, CVSBranch)
      assert branch.source_id == self.id
      assert branch.source_lod == self.symbol

  def __str__(self):
    """For convenience only.  The format is subject to change at any time."""

    return '%s:%s:%s<%x>' \
           % (self.cvs_file, self.symbol, self.branch_number, self.id,)


class CVSBranchNoop(CVSBranch):
  """A CVSBranch whose source is a CVSRevisionAbsent."""

  __slots__ = []

  def get_cvs_symbol_ids_opened(self):
    return []


# A map
#
#   {nondead(source_cvs_rev) : cvs_branch_subtype}
#
# , where nondead() means that the cvs revision exists and is not
# 'dead', and CVS_BRANCH_SUBTYPE is the subtype of CVSBranch that
# should be used.
cvs_branch_type_map = {
    False : CVSBranchNoop,
    True : CVSBranch,
    }


class CVSTag(CVSSymbol):
  """Represent the creation of a tag on a particular CVSFile.

  Members:

    id -- (int) unique ID for this item.

    cvs_file -- (CVSFile) CVSFile affected by this item.

    symbol -- (Symbol) the symbol affected by this CVSSymbol.

    source_lod -- (LineOfDevelopment) the LOD that is the source for
        this CVSSymbol.

    source_id -- (int) the ID of the CVSRevision or CVSBranch that is
        being tagged.  This initially points to a CVSRevision, but can
        be changed to a CVSBranch via parent adjustment in
        FilterSymbolsPass.

    revision_recorder_token -- (arbitrary) a token that can be set by
        RevisionRecorder for the later use of RevisionReader.

  """

  __slots__ = []

  def __init__(
      self, id, cvs_file, symbol, source_lod, source_id,
      revision_recorder_token,
      ):
    """Initialize a CVSTag."""

    CVSSymbol.__init__(
        self, id, cvs_file, symbol, source_lod, source_id,
        revision_recorder_token,
        )

  def __getstate__(self):
    return (
        self.id, self.cvs_file.id, self.symbol.id,
        self.source_lod.id, self.source_id,
        self.revision_recorder_token,
        )

  def __setstate__(self, data):
    (
        self.id, cvs_file_id, symbol_id, source_lod_id, self.source_id,
        self.revision_recorder_token,
        ) = data
    self.cvs_file = Ctx()._cvs_file_db.get_file(cvs_file_id)
    self.symbol = Ctx()._symbol_db.get_symbol(symbol_id)
    self.source_lod = Ctx()._symbol_db.get_symbol(source_lod_id)

  def get_pred_ids(self):
    return set([self.source_id])

  def get_succ_ids(self):
    return set()

  def get_cvs_symbol_ids_opened(self):
    return []

  def check_links(self, cvs_file_items):
    source = cvs_file_items.get(self.source_id)

    assert self.id in source.tag_ids
    if isinstance(source, CVSRevision):
      assert self.source_lod == source.lod
    elif isinstance(source, CVSBranch):
      assert self.source_lod == source.symbol
    else:
      assert False

  def __str__(self):
    """For convenience only.  The format is subject to change at any time."""

    return '%s:%s<%x>' \
           % (self.cvs_file, self.symbol, self.id,)


class CVSTagNoop(CVSTag):
  """A CVSTag whose source is a CVSRevisionAbsent."""

  __slots__ = []


# A map
#
#   {nondead(source_cvs_rev) : cvs_tag_subtype}
#
# , where nondead() means that the cvs revision exists and is not
# 'dead', and CVS_TAG_SUBTYPE is the subtype of CVSTag that should be
# used.
cvs_tag_type_map = {
    False : CVSTagNoop,
    True : CVSTag,
    }