/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/revision.py

  • Committer: Martin Pool
  • Date: 2005-09-22 12:57:22 UTC
  • Revision ID: mbp@sourcefrog.net-20050922125722-b267ac97a33cba4e
- turn off branch weave caches when we're done with checking

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# (C) 2005 Canonical
 
2
 
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
 
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
 
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
 
 
18
import bzrlib.errors
 
19
 
 
20
 
 
21
class Revision(object):
 
22
    """Single revision on a branch.
 
23
 
 
24
    Revisions may know their revision_hash, but only once they've been
 
25
    written out.  This is not stored because you cannot write the hash
 
26
    into the file it describes.
 
27
 
 
28
    After bzr 0.0.5 revisions are allowed to have multiple parents.
 
29
 
 
30
    parent_ids
 
31
        List of parent revision_ids
 
32
    """
 
33
    inventory_id = None
 
34
    inventory_sha1 = None
 
35
    revision_id = None
 
36
    timestamp = None
 
37
    message = None
 
38
    timezone = None
 
39
    committer = None
 
40
    
 
41
    def __init__(self, **args):
 
42
        self.__dict__.update(args)
 
43
        self.parent_ids = []
 
44
        self.parent_sha1s = []
 
45
 
 
46
 
 
47
    def __repr__(self):
 
48
        return "<Revision id %s>" % self.revision_id
 
49
 
 
50
    def __eq__(self, other):
 
51
        if not isinstance(other, Revision):
 
52
            return False
 
53
        return (self.inventory_id == other.inventory_id
 
54
                and self.inventory_sha1 == other.inventory_sha1
 
55
                and self.revision_id == other.revision_id
 
56
                and self.timestamp == other.timestamp
 
57
                and self.message == other.message
 
58
                and self.timezone == other.timezone
 
59
                and self.committer == other.committer)
 
60
 
 
61
    def __ne__(self, other):
 
62
        return not self.__eq__(other)
 
63
 
 
64
        
 
65
 
 
66
REVISION_ID_RE = None
 
67
 
 
68
def validate_revision_id(rid):
 
69
    """Check rid is syntactically valid for a revision id."""
 
70
    global REVISION_ID_RE
 
71
    if not REVISION_ID_RE:
 
72
        import re
 
73
        REVISION_ID_RE = re.compile('[\w.-]+@[\w.-]+--?\d+--?[0-9a-f]+\Z')
 
74
 
 
75
    if not REVISION_ID_RE.match(rid):
 
76
        raise ValueError("malformed revision-id %r" % rid)
 
77
 
 
78
 
 
79
def is_ancestor(revision_id, candidate_id, branch):
 
80
    """Return true if candidate_id is an ancestor of revision_id.
 
81
 
 
82
    A false negative will be returned if any intermediate descendent of
 
83
    candidate_id is not present in any of the revision_sources.
 
84
    
 
85
    revisions_source is an object supporting a get_revision operation that
 
86
    behaves like Branch's.
 
87
    """
 
88
    return candidate_id in branch.get_ancestry(revision_id)
 
89
 
 
90
 
 
91
def iter_ancestors(revision_id, revision_source, only_present=False):
 
92
    ancestors = (revision_id,)
 
93
    distance = 0
 
94
    while len(ancestors) > 0:
 
95
        new_ancestors = []
 
96
        for ancestor in ancestors:
 
97
            if not only_present:
 
98
                yield ancestor, distance
 
99
            try:
 
100
                revision = revision_source.get_revision(ancestor)
 
101
            except bzrlib.errors.NoSuchRevision, e:
 
102
                if e.revision == revision_id:
 
103
                    raise 
 
104
                else:
 
105
                    continue
 
106
            if only_present:
 
107
                yield ancestor, distance
 
108
            new_ancestors.extend(revision.parent_ids)
 
109
        ancestors = new_ancestors
 
110
        distance += 1
 
111
 
 
112
 
 
113
def find_present_ancestors(revision_id, revision_source):
 
114
    """Return the ancestors of a revision present in a branch.
 
115
 
 
116
    It's possible that a branch won't have the complete ancestry of
 
117
    one of its revisions.  
 
118
 
 
119
    """
 
120
    found_ancestors = {}
 
121
    anc_iter = enumerate(iter_ancestors(revision_id, revision_source,
 
122
                         only_present=True))
 
123
    for anc_order, (anc_id, anc_distance) in anc_iter:
 
124
        if not found_ancestors.has_key(anc_id):
 
125
            found_ancestors[anc_id] = (anc_order, anc_distance)
 
126
    return found_ancestors
 
127
    
 
128
 
 
129
def __get_closest(intersection):
 
130
    intersection.sort()
 
131
    matches = [] 
 
132
    for entry in intersection:
 
133
        if entry[0] == intersection[0][0]:
 
134
            matches.append(entry[2])
 
135
    return matches
 
136
 
 
137
 
 
138
def common_ancestor(revision_a, revision_b, revision_source):
 
139
    """Find the ancestor common to both revisions that is closest to both.
 
140
    """
 
141
    from bzrlib.trace import mutter
 
142
    a_ancestors = find_present_ancestors(revision_a, revision_source)
 
143
    b_ancestors = find_present_ancestors(revision_b, revision_source)
 
144
    a_intersection = []
 
145
    b_intersection = []
 
146
    # a_order is used as a tie-breaker when two equally-good bases are found
 
147
    for revision, (a_order, a_distance) in a_ancestors.iteritems():
 
148
        if b_ancestors.has_key(revision):
 
149
            a_intersection.append((a_distance, a_order, revision))
 
150
            b_intersection.append((b_ancestors[revision][1], a_order, revision))
 
151
    mutter("a intersection: %r" % a_intersection)
 
152
    mutter("b intersection: %r" % b_intersection)
 
153
 
 
154
    a_closest = __get_closest(a_intersection)
 
155
    if len(a_closest) == 0:
 
156
        return None
 
157
    b_closest = __get_closest(b_intersection)
 
158
    assert len(b_closest) != 0
 
159
    mutter ("a_closest %r" % a_closest)
 
160
    mutter ("b_closest %r" % b_closest)
 
161
    if a_closest[0] in b_closest:
 
162
        return a_closest[0]
 
163
    elif b_closest[0] in a_closest:
 
164
        return b_closest[0]
 
165
    else:
 
166
        raise bzrlib.errors.AmbiguousBase((a_closest[0], b_closest[0]))
 
167
    return a_closest[0]
 
168
 
 
169
class MultipleRevisionSources(object):
 
170
    """Proxy that looks in multiple branches for revisions."""
 
171
    def __init__(self, *args):
 
172
        object.__init__(self)
 
173
        assert len(args) != 0
 
174
        self._revision_sources = args
 
175
 
 
176
    def get_revision(self, revision_id):
 
177
        for source in self._revision_sources:
 
178
            try:
 
179
                return source.get_revision(revision_id)
 
180
            except bzrlib.errors.NoSuchRevision, e:
 
181
                pass
 
182
        raise e
 
183
 
 
184
def get_intervening_revisions(ancestor_id, rev_id, rev_source, 
 
185
                              revision_history=None):
 
186
    """Find the longest line of descent from maybe_ancestor to revision.
 
187
    Revision history is followed where possible.
 
188
 
 
189
    If ancestor_id == rev_id, list will be empty.
 
190
    Otherwise, rev_id will be the last entry.  ancestor_id will never appear.
 
191
    If ancestor_id is not an ancestor, NotAncestor will be thrown
 
192
    """
 
193
    [rev_source.get_revision(r) for r in (ancestor_id, rev_id)]
 
194
    if ancestor_id == rev_id:
 
195
        return []
 
196
    def historical_lines(line):
 
197
        """Return a tuple of historical/non_historical lines, for sorting.
 
198
        The non_historical count is negative, since non_historical lines are
 
199
        a bad thing.
 
200
        """
 
201
        good_count = 0
 
202
        bad_count = 0
 
203
        for revision in line:
 
204
            if revision in revision_history:
 
205
                good_count += 1
 
206
            else:
 
207
                bad_count -= 1
 
208
        return good_count, bad_count
 
209
    active = [[rev_id]]
 
210
    successful_lines = []
 
211
    while len(active) > 0:
 
212
        new_active = []
 
213
        for line in active:
 
214
            for parent in rev_source.get_revision(line[-1]).parent_ids:
 
215
                line_copy = line[:]
 
216
                if parent == ancestor_id:
 
217
                    successful_lines.append(line_copy)
 
218
                else:
 
219
                    line_copy.append(parent)
 
220
                    new_active.append(line_copy)
 
221
        active = new_active
 
222
    if len(successful_lines) == 0:
 
223
        raise bzrlib.errors.NotAncestor(rev_id, ancestor_id)
 
224
    for line in successful_lines:
 
225
        line.reverse()
 
226
    if revision_history is not None:
 
227
        by_historical_lines = []
 
228
        for line in successful_lines:
 
229
            count = historical_lines(line)
 
230
            by_historical_lines.append((count, line))
 
231
        by_historical_lines.sort()
 
232
        if by_historical_lines[-1][0][0] > 0:
 
233
            return by_historical_lines[-1][1]
 
234
    assert len(successful_lines)
 
235
    successful_lines.sort(cmp, len)
 
236
    return successful_lines[-1]