/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

Fix formatting, remove catch-all for exceptions when opening local repositories.

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]