107
111
def get_summary(self):
108
112
"""Get the first line of the log message for this revision.
110
return self.message.split('\n', 1)[0]
113
def is_ancestor(revision_id, candidate_id, branch):
114
"""Return true if candidate_id is an ancestor of revision_id.
116
A false negative will be returned if any intermediate descendent of
117
candidate_id is not present in any of the revision_sources.
119
revisions_source is an object supporting a get_revision operation that
120
behaves like Branch's.
122
return (candidate_id in branch.repository.get_ancestry(revision_id))
114
return self.message.lstrip().split('\n', 1)[0]
116
@symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((1, 13, 0)))
117
def get_apparent_author(self):
118
"""Return the apparent author of this revision.
120
This method is deprecated in favour of get_apparent_authors.
122
If the revision properties contain any author names,
123
return the first. Otherwise return the committer name.
125
return self.get_apparent_authors()[0]
127
def get_apparent_authors(self):
128
"""Return the apparent authors of this revision.
130
If the revision properties contain the names of the authors,
131
return them. Otherwise return the committer name.
133
The return value will be a list containing at least one element.
135
authors = self.properties.get('authors', None)
137
author = self.properties.get('author', None)
139
return [self.committer]
142
return authors.split("\n")
145
"""Iterate over the bugs associated with this revision."""
146
bug_property = self.properties.get('bugs', None)
147
if bug_property is None:
149
for line in bug_property.splitlines():
151
url, status = line.split(None, 2)
153
raise errors.InvalidLineInBugsProperty(line)
154
if status not in bugtracker.ALLOWED_BUG_STATUSES:
155
raise errors.InvalidBugStatus(status)
125
159
def iter_ancestors(revision_id, revision_source, only_present=False):
158
192
if anc_id not in found_ancestors:
159
193
found_ancestors[anc_id] = (anc_order, anc_distance)
160
194
return found_ancestors
163
197
def __get_closest(intersection):
164
198
intersection.sort()
166
200
for entry in intersection:
167
201
if entry[0] == intersection[0][0]:
168
202
matches.append(entry[2])
172
def revision_graph(revision, revision_source):
173
"""Produce a graph of the ancestry of the specified revision.
175
:return: root, ancestors map, descendants map
177
revision_source.lock_read()
179
return _revision_graph(revision, revision_source)
181
revision_source.unlock()
184
def _revision_graph(revision, revision_source):
185
"""See revision_graph."""
186
from bzrlib.tsort import topo_sort
187
graph = revision_source.get_revision_graph(revision)
188
# mark all no-parent revisions as being NULL_REVISION parentage.
189
for node, parents in graph.items():
190
if len(parents) == 0:
191
graph[node] = [NULL_REVISION]
192
# add NULL_REVISION to the graph
193
graph[NULL_REVISION] = []
195
# pick a root. If there are multiple roots
196
# this could pick a random one.
197
topo_order = topo_sort(graph.items())
203
# map the descendants of the graph.
204
# and setup our set based return graph.
205
for node in graph.keys():
206
descendants[node] = {}
207
for node, parents in graph.items():
208
for parent in parents:
209
descendants[parent][node] = 1
210
ancestors[node] = set(parents)
212
assert root not in descendants[root]
213
assert root not in ancestors[root]
214
return root, ancestors, descendants
217
def combined_graph(revision_a, revision_b, revision_source):
218
"""Produce a combined ancestry graph.
219
Return graph root, ancestors map, descendants map, set of common nodes"""
220
root, ancestors, descendants = revision_graph(
221
revision_a, revision_source)
222
root_b, ancestors_b, descendants_b = revision_graph(
223
revision_b, revision_source)
225
raise errors.NoCommonRoot(revision_a, revision_b)
227
for node, node_anc in ancestors_b.iteritems():
228
if node in ancestors:
231
ancestors[node] = set()
232
ancestors[node].update(node_anc)
233
for node, node_dec in descendants_b.iteritems():
234
if node not in descendants:
235
descendants[node] = {}
236
descendants[node].update(node_dec)
237
return root, ancestors, descendants, common
240
def common_ancestor(revision_a, revision_b, revision_source,
242
if None in (revision_a, revision_b):
244
if NULL_REVISION in (revision_a, revision_b):
246
# trivial optimisation
247
if revision_a == revision_b:
251
pb.update('Picking ancestor', 1, 3)
252
graph = revision_source.get_revision_graph_with_ghosts(
253
[revision_a, revision_b])
254
# Shortcut the case where one of the tips is already included in
255
# the other graphs ancestry.
256
ancestry_a = graph.get_ancestry(revision_a)
257
if revision_b in ancestry_a:
259
ancestry_b = graph.get_ancestry(revision_b)
260
if revision_a in ancestry_b:
262
# convert to a NULL_REVISION based graph.
263
ancestors = graph.get_ancestors()
264
descendants = graph.get_descendants()
265
common = set(ancestry_a)
266
common.intersection_update(ancestry_b)
267
descendants[NULL_REVISION] = {}
268
ancestors[NULL_REVISION] = []
269
for root in graph.roots:
270
descendants[NULL_REVISION][root] = 1
271
ancestors[root].append(NULL_REVISION)
272
for ghost in graph.ghosts:
273
# ghosts act as roots for the purpose of finding
274
# the longest paths from the root: any ghost *might*
275
# be directly attached to the root, so we treat them
277
# ghost now descends from NULL
278
descendants[NULL_REVISION][ghost] = 1
279
# that is it has an ancestor of NULL
280
ancestors[ghost] = [NULL_REVISION]
281
# ghost is common if any of ghosts descendants are common:
282
for ghost_descendant in descendants[ghost]:
283
if ghost_descendant in common:
287
common.add(NULL_REVISION)
288
except errors.NoCommonRoot:
289
raise errors.NoCommonAncestor(revision_a, revision_b)
291
pb.update('Picking ancestor', 2, 3)
292
distances = node_distances (descendants, ancestors, root)
293
pb.update('Picking ancestor', 3, 2)
294
farthest = select_farthest(distances, common)
295
if farthest is None or farthest == NULL_REVISION:
296
raise errors.NoCommonAncestor(revision_a, revision_b)
302
class MultipleRevisionSources(object):
303
"""Proxy that looks in multiple branches for revisions."""
304
def __init__(self, *args):
305
object.__init__(self)
306
assert len(args) != 0
307
self._revision_sources = args
309
def revision_parents(self, revision_id):
310
for source in self._revision_sources:
312
return source.revision_parents(revision_id)
313
except (errors.WeaveRevisionNotPresent, errors.NoSuchRevision), e:
317
def get_revision(self, revision_id):
318
for source in self._revision_sources:
320
return source.get_revision(revision_id)
321
except errors.NoSuchRevision, e:
325
def get_revision_graph(self, revision_id):
326
# we could probe incrementally until the pending
327
# ghosts list stop growing, but its cheaper for now
328
# to just ask for the complete graph for each repository.
330
for source in self._revision_sources:
331
ghost_graph = source.get_revision_graph_with_ghosts()
332
graphs.append(ghost_graph)
335
if not revision_id in graph.get_ancestors():
337
if absent == len(graphs):
338
raise errors.NoSuchRevision(self._revision_sources[0], revision_id)
342
pending = set([revision_id])
343
def find_parents(node_id):
344
"""find the parents for node_id."""
346
ancestors = graph.get_ancestors()
348
return ancestors[node_id]
351
raise errors.NoSuchRevision(self._revision_sources[0], node_id)
353
# all the graphs should have identical parent lists
354
node_id = pending.pop()
356
result[node_id] = find_parents(node_id)
357
for parent_node in result[node_id]:
358
if not parent_node in result:
359
pending.add(parent_node)
360
except errors.NoSuchRevision:
365
def get_revision_graph_with_ghosts(self, revision_ids):
366
# query all the sources for their entire graphs
367
# and then build a combined graph for just
370
for source in self._revision_sources:
371
ghost_graph = source.get_revision_graph_with_ghosts()
372
graphs.append(ghost_graph.get_ancestors())
373
for revision_id in revision_ids:
376
if not revision_id in graph:
378
if absent == len(graphs):
379
raise errors.NoSuchRevision(self._revision_sources[0],
384
pending = set(revision_ids)
386
def find_parents(node_id):
387
"""find the parents for node_id."""
390
return graph[node_id]
393
raise errors.NoSuchRevision(self._revision_sources[0], node_id)
395
# all the graphs should have identical parent lists
396
node_id = pending.pop()
398
parents = find_parents(node_id)
399
for parent_node in parents:
401
if (parent_node not in pending and
402
parent_node not in done):
404
pending.add(parent_node)
405
result.add_node(node_id, parents)
407
except errors.NoSuchRevision:
409
result.add_ghost(node_id)
414
for source in self._revision_sources:
418
for source in self._revision_sources:
422
@deprecated_function(zero_eight)
423
def get_intervening_revisions(ancestor_id, rev_id, rev_source,
424
revision_history=None):
425
"""Find the longest line of descent from maybe_ancestor to revision.
426
Revision history is followed where possible.
428
If ancestor_id == rev_id, list will be empty.
429
Otherwise, rev_id will be the last entry. ancestor_id will never appear.
430
If ancestor_id is not an ancestor, NotAncestor will be thrown
432
root, ancestors, descendants = revision_graph(rev_id, rev_source)
433
if len(descendants) == 0:
434
raise errors.NoSuchRevision(rev_source, rev_id)
435
if ancestor_id not in descendants:
436
rev_source.get_revision(ancestor_id)
437
raise errors.NotAncestor(rev_id, ancestor_id)
438
root_descendants = all_descendants(descendants, ancestor_id)
439
root_descendants.add(ancestor_id)
440
if rev_id not in root_descendants:
441
raise errors.NotAncestor(rev_id, ancestor_id)
442
distances = node_distances(descendants, ancestors, ancestor_id,
443
root_descendants=root_descendants)
445
def best_ancestor(rev_id):
447
for anc_id in ancestors[rev_id]:
449
distance = distances[anc_id]
452
if revision_history is not None and anc_id in revision_history:
454
elif best is None or distance > best[1]:
455
best = (anc_id, distance)
460
while next != ancestor_id:
462
next = best_ancestor(next)
467
206
def is_reserved_id(revision_id):
468
207
"""Determine whether a revision id is reserved
470
:return: True if the revision is is reserved, False otherwise
209
:return: True if the revision is reserved, False otherwise
472
211
return isinstance(revision_id, basestring) and revision_id.endswith(':')