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.
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.
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
19
from bzrlib.graph import node_distances, select_farthest, all_descendants
20
from bzrlib.osutils import contains_whitespace
24
class Revision(object):
25
"""Single revision on a branch.
27
Revisions may know their revision_hash, but only once they've been
28
written out. This is not stored because you cannot write the hash
29
into the file it describes.
31
After bzr 0.0.5 revisions are allowed to have multiple parents.
34
List of parent revision_ids
37
Dictionary of revision properties. These are attached to the
38
revision as extra metadata. The name must be a single
39
word; the value can be an arbitrary string.
42
def __init__(self, revision_id, properties=None, **args):
43
self.revision_id = revision_id
44
self.properties = properties or {}
45
self._check_properties()
46
self.__dict__.update(args)
48
self.parent_sha1s = []
51
return "<Revision id %s>" % self.revision_id
53
def __eq__(self, other):
54
if not isinstance(other, Revision):
56
# FIXME: rbc 20050930 parent_ids are not being compared
58
self.inventory_sha1 == other.inventory_sha1
59
and self.revision_id == other.revision_id
60
and self.timestamp == other.timestamp
61
and self.message == other.message
62
and self.timezone == other.timezone
63
and self.committer == other.committer
64
and self.properties == other.properties)
66
def __ne__(self, other):
67
return not self.__eq__(other)
69
def _check_properties(self):
70
"""Verify that all revision properties are OK.
72
for name, value in self.properties.iteritems():
73
if not isinstance(name, basestring) or contains_whitespace(name):
74
raise ValueError("invalid property name %r" % name)
75
if not isinstance(value, basestring):
76
raise ValueError("invalid property value %r for %r" %
80
def is_ancestor(revision_id, candidate_id, branch):
81
"""Return true if candidate_id is an ancestor of revision_id.
83
A false negative will be returned if any intermediate descendent of
84
candidate_id is not present in any of the revision_sources.
86
revisions_source is an object supporting a get_revision operation that
87
behaves like Branch's.
89
return candidate_id in branch.get_ancestry(revision_id)
92
def iter_ancestors(revision_id, revision_source, only_present=False):
93
ancestors = (revision_id,)
95
while len(ancestors) > 0:
97
for ancestor in ancestors:
99
yield ancestor, distance
101
revision = revision_source.get_revision(ancestor)
102
except bzrlib.errors.NoSuchRevision, e:
103
if e.revision == revision_id:
108
yield ancestor, distance
109
new_ancestors.extend(revision.parent_ids)
110
ancestors = new_ancestors
114
def find_present_ancestors(revision_id, revision_source):
115
"""Return the ancestors of a revision present in a branch.
117
It's possible that a branch won't have the complete ancestry of
118
one of its revisions.
122
anc_iter = enumerate(iter_ancestors(revision_id, revision_source,
124
for anc_order, (anc_id, anc_distance) in anc_iter:
125
if not found_ancestors.has_key(anc_id):
126
found_ancestors[anc_id] = (anc_order, anc_distance)
127
return found_ancestors
130
def __get_closest(intersection):
133
for entry in intersection:
134
if entry[0] == intersection[0][0]:
135
matches.append(entry[2])
139
def old_common_ancestor(revision_a, revision_b, revision_source):
140
"""Find the ancestor common to both revisions that is closest to both.
142
from bzrlib.trace import mutter
143
a_ancestors = find_present_ancestors(revision_a, revision_source)
144
b_ancestors = find_present_ancestors(revision_b, revision_source)
147
# a_order is used as a tie-breaker when two equally-good bases are found
148
for revision, (a_order, a_distance) in a_ancestors.iteritems():
149
if b_ancestors.has_key(revision):
150
a_intersection.append((a_distance, a_order, revision))
151
b_intersection.append((b_ancestors[revision][1], a_order, revision))
152
mutter("a intersection: %r" % a_intersection)
153
mutter("b intersection: %r" % b_intersection)
155
a_closest = __get_closest(a_intersection)
156
if len(a_closest) == 0:
158
b_closest = __get_closest(b_intersection)
159
assert len(b_closest) != 0
160
mutter ("a_closest %r" % a_closest)
161
mutter ("b_closest %r" % b_closest)
162
if a_closest[0] in b_closest:
164
elif b_closest[0] in a_closest:
167
raise bzrlib.errors.AmbiguousBase((a_closest[0], b_closest[0]))
170
def revision_graph(revision, revision_source):
171
"""Produce a graph of the ancestry of the specified revision.
172
Return root, ancestors map, descendants map
174
TODO: Produce graphs with the NULL revision as root, so that we can find
175
a common even when trees are not branches don't represent a single line
182
descendants[revision] = {}
183
while len(lines) > 0:
186
if line == NULL_REVISION:
191
rev = revision_source.get_revision(line)
192
parents = list(rev.parent_ids)
193
if len(parents) == 0:
194
parents = [NULL_REVISION]
195
except bzrlib.errors.NoSuchRevision:
199
if parents is not None:
200
for parent in parents:
201
if parent not in ancestors:
202
new_lines.add(parent)
203
if parent not in descendants:
204
descendants[parent] = {}
205
descendants[parent][line] = 1
206
if parents is not None:
207
ancestors[line] = set(parents)
209
assert root not in descendants[root]
210
assert root not in ancestors[root]
211
return root, ancestors, descendants
214
def combined_graph(revision_a, revision_b, revision_source):
215
"""Produce a combined ancestry graph.
216
Return graph root, ancestors map, descendants map, set of common nodes"""
217
root, ancestors, descendants = revision_graph(revision_a, revision_source)
218
root_b, ancestors_b, descendants_b = revision_graph(revision_b,
221
raise bzrlib.errors.NoCommonRoot(revision_a, revision_b)
223
for node, node_anc in ancestors_b.iteritems():
224
if node in ancestors:
227
ancestors[node] = set()
228
ancestors[node].update(node_anc)
229
for node, node_dec in descendants_b.iteritems():
230
if node not in descendants:
231
descendants[node] = {}
232
descendants[node].update(node_dec)
233
return root, ancestors, descendants, common
236
def common_ancestor(revision_a, revision_b, revision_source):
238
root, ancestors, descendants, common = \
239
combined_graph(revision_a, revision_b, revision_source)
240
except bzrlib.errors.NoCommonRoot:
241
raise bzrlib.errors.NoCommonAncestor(revision_a, revision_b)
243
distances = node_distances (descendants, ancestors, root)
244
farthest = select_farthest(distances, common)
245
if farthest is None or farthest == NULL_REVISION:
246
raise bzrlib.errors.NoCommonAncestor(revision_a, revision_b)
250
class MultipleRevisionSources(object):
251
"""Proxy that looks in multiple branches for revisions."""
252
def __init__(self, *args):
253
object.__init__(self)
254
assert len(args) != 0
255
self._revision_sources = args
257
def get_revision(self, revision_id):
258
for source in self._revision_sources:
260
return source.get_revision(revision_id)
261
except bzrlib.errors.NoSuchRevision, e:
265
def get_intervening_revisions(ancestor_id, rev_id, rev_source,
266
revision_history=None):
267
"""Find the longest line of descent from maybe_ancestor to revision.
268
Revision history is followed where possible.
270
If ancestor_id == rev_id, list will be empty.
271
Otherwise, rev_id will be the last entry. ancestor_id will never appear.
272
If ancestor_id is not an ancestor, NotAncestor will be thrown
274
root, ancestors, descendants = revision_graph(rev_id, rev_source)
275
if len(descendants) == 0:
276
raise NoSuchRevision(rev_source, rev_id)
277
if ancestor_id not in descendants:
278
rev_source.get_revision(ancestor_id)
279
raise bzrlib.errors.NotAncestor(rev_id, ancestor_id)
280
root_descendants = all_descendants(descendants, ancestor_id)
281
root_descendants.add(ancestor_id)
282
if rev_id not in root_descendants:
283
raise bzrlib.errors.NotAncestor(rev_id, ancestor_id)
284
distances = node_distances(descendants, ancestors, ancestor_id,
285
root_descendants=root_descendants)
287
def best_ancestor(rev_id):
289
for anc_id in ancestors[rev_id]:
291
distance = distances[anc_id]
294
if revision_history is not None and anc_id in revision_history:
296
elif best is None or distance > best[1]:
297
best = (anc_id, distance)
302
while next != ancestor_id:
304
next = best_ancestor(next)