aboutsummaryrefslogtreecommitdiff
blob: 9d0cc9dd66589ce37d2fbf09946c935adfb81fbc (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
# (Be in -*- python -*- mode.)
#
# ====================================================================
# Copyright (c) 2006-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/.
# ====================================================================

"""Keep track of counts of different types of changeset links."""



# A cvs_item doesn't depend on any cvs_items in either pred or succ:
LINK_NONE = 0

# A cvs_item depends on one or more cvs_items in pred but none in succ:
LINK_PRED = 1

# A cvs_item depends on one or more cvs_items in succ but none in pred:
LINK_SUCC = 2

# A cvs_item depends on one or more cvs_items in both pred and succ:
LINK_PASSTHRU = LINK_PRED | LINK_SUCC


class ChangesetGraphLink(object):
  def __init__(self, pred, changeset, succ):
    """Represent a link in a loop in a changeset graph.

    This is the link that goes from PRED -> CHANGESET -> SUCC.

    We are mainly concerned with how many CVSItems have LINK_PRED,
    LINK_SUCC, and LINK_PASSTHRU type links to the neighboring
    commitsets.  If necessary, this class can also break up CHANGESET
    into multiple changesets."""

    self.pred = pred
    self.pred_ids = set(pred.cvs_item_ids)

    self.changeset = changeset

    self.succ_ids = set(succ.cvs_item_ids)
    self.succ = succ

    # A count of each type of link for cvs_items in changeset
    # (indexed by LINK_* constants):
    link_counts = [0] * 4

    for cvs_item in list(changeset.iter_cvs_items()):
      link_counts[self.get_link_type(cvs_item)] += 1

    [self.pred_links, self.succ_links, self.passthru_links] = link_counts[1:]

  def get_link_type(self, cvs_item):
    """Return the type of links from CVS_ITEM to self.PRED and self.SUCC.

    The return value is one of LINK_NONE, LINK_PRED, LINK_SUCC, or
    LINK_PASSTHRU."""

    retval = LINK_NONE

    if cvs_item.get_pred_ids() & self.pred_ids:
      retval |= LINK_PRED
    if cvs_item.get_succ_ids() & self.succ_ids:
      retval |= LINK_SUCC

    return retval

  def get_links_to_move(self):
    """Return the number of items that would be moved to split changeset."""

    return min(self.pred_links, self.succ_links) \
           or max(self.pred_links, self.succ_links)

  def is_breakable(self):
    """Return True iff breaking the changeset will do any good."""

    return self.pred_links != 0 or self.succ_links != 0

  def __cmp__(self, other):
    """Compare SELF with OTHER in terms of which would be better to break.

    The one that is better to break is considered the lesser."""

    return (
        - cmp(int(self.is_breakable()), int(other.is_breakable()))
        or cmp(self.passthru_links, other.passthru_links)
        or cmp(self.get_links_to_move(), other.get_links_to_move())
        )

  def break_changeset(self, changeset_key_generator):
    """Break up self.changeset and return the fragments.

    Break it up in such a way that the link is weakened as efficiently
    as possible."""

    if not self.is_breakable():
      raise ValueError('Changeset is not breakable: %r' % self.changeset)

    pred_items = []
    succ_items = []

    # For each link type, should such CVSItems be moved to the
    # changeset containing the predecessor items or the one containing
    # the successor items?
    destination = {
        LINK_PRED : pred_items,
        LINK_SUCC : succ_items,
        }

    if self.pred_links == 0:
      destination[LINK_NONE] = pred_items
      destination[LINK_PASSTHRU] = pred_items
    elif self.succ_links == 0:
      destination[LINK_NONE] = succ_items
      destination[LINK_PASSTHRU] = succ_items
    elif self.pred_links < self.succ_links:
      destination[LINK_NONE] = succ_items
      destination[LINK_PASSTHRU] = succ_items
    else:
      destination[LINK_NONE] = pred_items
      destination[LINK_PASSTHRU] = pred_items

    for cvs_item in self.changeset.iter_cvs_items():
      link_type = self.get_link_type(cvs_item)
      destination[link_type].append(cvs_item.id)

    # Create new changesets of the same type as the old one:
    return [
        self.changeset.create_split_changeset(
            changeset_key_generator.gen_id(), pred_items),
        self.changeset.create_split_changeset(
            changeset_key_generator.gen_id(), succ_items),
        ]

  def __str__(self):
    return 'Link<%x>(%d, %d, %d)' % (
        self.changeset.id,
        self.pred_links, self.succ_links, self.passthru_links)