115
112
def get_summary(self):
116
113
"""Get the first line of the log message for this revision.
118
return self.message.split('\n', 1)[0]
121
def is_ancestor(revision_id, candidate_id, branch):
122
"""Return true if candidate_id is an ancestor of revision_id.
124
A false negative will be returned if any intermediate descendent of
125
candidate_id is not present in any of the revision_sources.
127
revisions_source is an object supporting a get_revision operation that
128
behaves like Branch's.
130
if is_null(candidate_id):
132
return (candidate_id in branch.repository.get_ancestry(revision_id,
115
return self.message.lstrip().split('\n', 1)[0]
117
@symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((1, 13, 0)))
118
def get_apparent_author(self):
119
"""Return the apparent author of this revision.
121
This method is deprecated in favour of get_apparent_authors.
123
If the revision properties contain any author names,
124
return the first. Otherwise return the committer name.
126
authors = self.get_apparent_authors()
132
def get_apparent_authors(self):
133
"""Return the apparent authors of this revision.
135
If the revision properties contain the names of the authors,
136
return them. Otherwise return the committer name.
138
The return value will be a list containing at least one element.
140
authors = self.properties.get('authors', None)
142
author = self.properties.get('author', self.committer)
147
return authors.split("\n")
150
"""Iterate over the bugs associated with this revision."""
151
bug_property = self.properties.get('bugs', None)
152
if bug_property is None:
154
for line in bug_property.splitlines():
156
url, status = line.split(None, 2)
158
raise errors.InvalidLineInBugsProperty(line)
159
if status not in bugtracker.ALLOWED_BUG_STATUSES:
160
raise errors.InvalidBugStatus(status)
136
164
def iter_ancestors(revision_id, revision_source, only_present=False):
169
197
if anc_id not in found_ancestors:
170
198
found_ancestors[anc_id] = (anc_order, anc_distance)
171
199
return found_ancestors
174
202
def __get_closest(intersection):
175
203
intersection.sort()
177
205
for entry in intersection:
178
206
if entry[0] == intersection[0][0]:
179
207
matches.append(entry[2])
183
def revision_graph(revision, revision_source):
184
"""Produce a graph of the ancestry of the specified revision.
186
:return: root, ancestors map, descendants map
188
revision_source.lock_read()
190
return _revision_graph(revision, revision_source)
192
revision_source.unlock()
195
def _revision_graph(revision, revision_source):
196
"""See revision_graph."""
197
from bzrlib.tsort import topo_sort
198
graph = revision_source.get_revision_graph(revision)
199
# mark all no-parent revisions as being NULL_REVISION parentage.
200
for node, parents in graph.items():
201
if len(parents) == 0:
202
graph[node] = [NULL_REVISION]
203
# add NULL_REVISION to the graph
204
graph[NULL_REVISION] = []
206
# pick a root. If there are multiple roots
207
# this could pick a random one.
208
topo_order = topo_sort(graph.items())
214
# map the descendants of the graph.
215
# and setup our set based return graph.
216
for node in graph.keys():
217
descendants[node] = {}
218
for node, parents in graph.items():
219
for parent in parents:
220
descendants[parent][node] = 1
221
ancestors[node] = set(parents)
223
assert root not in descendants[root]
224
assert root not in ancestors[root]
225
return root, ancestors, descendants
228
def combined_graph(revision_a, revision_b, revision_source):
229
"""Produce a combined ancestry graph.
230
Return graph root, ancestors map, descendants map, set of common nodes"""
231
root, ancestors, descendants = revision_graph(
232
revision_a, revision_source)
233
root_b, ancestors_b, descendants_b = revision_graph(
234
revision_b, revision_source)
236
raise errors.NoCommonRoot(revision_a, revision_b)
238
for node, node_anc in ancestors_b.iteritems():
239
if node in ancestors:
242
ancestors[node] = set()
243
ancestors[node].update(node_anc)
244
for node, node_dec in descendants_b.iteritems():
245
if node not in descendants:
246
descendants[node] = {}
247
descendants[node].update(node_dec)
248
return root, ancestors, descendants, common
251
def common_ancestor(revision_a, revision_b, revision_source,
253
if None in (revision_a, revision_b):
255
if NULL_REVISION in (revision_a, revision_b):
257
# trivial optimisation
258
if revision_a == revision_b:
262
pb.update('Picking ancestor', 1, 3)
263
graph = revision_source.get_revision_graph_with_ghosts(
264
[revision_a, revision_b])
265
# Shortcut the case where one of the tips is already included in
266
# the other graphs ancestry.
267
ancestry_a = graph.get_ancestry(revision_a, topo_sorted=False)
268
if revision_b in ancestry_a:
270
ancestry_b = graph.get_ancestry(revision_b, topo_sorted=False)
271
if revision_a in ancestry_b:
273
# convert to a NULL_REVISION based graph.
274
ancestors = graph.get_ancestors()
275
descendants = graph.get_descendants()
276
common = set(ancestry_a)
277
common.intersection_update(ancestry_b)
278
descendants[NULL_REVISION] = {}
279
ancestors[NULL_REVISION] = []
280
for root in graph.roots:
281
descendants[NULL_REVISION][root] = 1
282
ancestors[root].append(NULL_REVISION)
283
for ghost in graph.ghosts:
284
# ghosts act as roots for the purpose of finding
285
# the longest paths from the root: any ghost *might*
286
# be directly attached to the root, so we treat them
288
# ghost now descends from NULL
289
descendants[NULL_REVISION][ghost] = 1
290
# that is it has an ancestor of NULL
291
ancestors[ghost] = [NULL_REVISION]
292
# ghost is common if any of ghosts descendants are common:
293
for ghost_descendant in descendants[ghost]:
294
if ghost_descendant in common:
298
common.add(NULL_REVISION)
299
except errors.NoCommonRoot:
300
raise errors.NoCommonAncestor(revision_a, revision_b)
302
pb.update('Picking ancestor', 2, 3)
303
distances = node_distances (descendants, ancestors, root)
304
pb.update('Picking ancestor', 3, 2)
305
farthest = select_farthest(distances, common)
306
if farthest is None or farthest == NULL_REVISION:
307
raise errors.NoCommonAncestor(revision_a, revision_b)
313
class MultipleRevisionSources(object):
314
"""Proxy that looks in multiple branches for revisions."""
315
def __init__(self, *args):
316
object.__init__(self)
317
assert len(args) != 0
318
self._revision_sources = args
320
def revision_parents(self, revision_id):
321
for source in self._revision_sources:
323
return source.revision_parents(revision_id)
324
except (errors.WeaveRevisionNotPresent, errors.NoSuchRevision), e:
328
def get_revision(self, revision_id):
329
for source in self._revision_sources:
331
return source.get_revision(revision_id)
332
except errors.NoSuchRevision, e:
336
def get_revision_graph(self, revision_id):
337
# we could probe incrementally until the pending
338
# ghosts list stop growing, but its cheaper for now
339
# to just ask for the complete graph for each repository.
341
for source in self._revision_sources:
342
ghost_graph = source.get_revision_graph_with_ghosts()
343
graphs.append(ghost_graph)
346
if not revision_id in graph.get_ancestors():
348
if absent == len(graphs):
349
raise errors.NoSuchRevision(self._revision_sources[0], revision_id)
353
pending = set([revision_id])
354
def find_parents(node_id):
355
"""find the parents for node_id."""
357
ancestors = graph.get_ancestors()
359
return ancestors[node_id]
362
raise errors.NoSuchRevision(self._revision_sources[0], node_id)
364
# all the graphs should have identical parent lists
365
node_id = pending.pop()
367
result[node_id] = find_parents(node_id)
368
for parent_node in result[node_id]:
369
if not parent_node in result:
370
pending.add(parent_node)
371
except errors.NoSuchRevision:
376
def get_revision_graph_with_ghosts(self, revision_ids):
377
# query all the sources for their entire graphs
378
# and then build a combined graph for just
381
for source in self._revision_sources:
382
ghost_graph = source.get_revision_graph_with_ghosts()
383
graphs.append(ghost_graph.get_ancestors())
384
for revision_id in revision_ids:
387
if not revision_id in graph:
389
if absent == len(graphs):
390
raise errors.NoSuchRevision(self._revision_sources[0],
395
pending = set(revision_ids)
397
def find_parents(node_id):
398
"""find the parents for node_id."""
401
return graph[node_id]
404
raise errors.NoSuchRevision(self._revision_sources[0], node_id)
406
# all the graphs should have identical parent lists
407
node_id = pending.pop()
409
parents = find_parents(node_id)
410
for parent_node in parents:
412
if (parent_node not in pending and
413
parent_node not in done):
415
pending.add(parent_node)
416
result.add_node(node_id, parents)
418
except errors.NoSuchRevision:
420
result.add_ghost(node_id)
425
for source in self._revision_sources:
429
for source in self._revision_sources:
433
@deprecated_function(zero_eight)
434
def get_intervening_revisions(ancestor_id, rev_id, rev_source,
435
revision_history=None):
436
"""Find the longest line of descent from maybe_ancestor to revision.
437
Revision history is followed where possible.
439
If ancestor_id == rev_id, list will be empty.
440
Otherwise, rev_id will be the last entry. ancestor_id will never appear.
441
If ancestor_id is not an ancestor, NotAncestor will be thrown
443
root, ancestors, descendants = revision_graph(rev_id, rev_source)
444
if len(descendants) == 0:
445
raise errors.NoSuchRevision(rev_source, rev_id)
446
if ancestor_id not in descendants:
447
rev_source.get_revision(ancestor_id)
448
raise errors.NotAncestor(rev_id, ancestor_id)
449
root_descendants = all_descendants(descendants, ancestor_id)
450
root_descendants.add(ancestor_id)
451
if rev_id not in root_descendants:
452
raise errors.NotAncestor(rev_id, ancestor_id)
453
distances = node_distances(descendants, ancestors, ancestor_id,
454
root_descendants=root_descendants)
456
def best_ancestor(rev_id):
458
for anc_id in ancestors[rev_id]:
460
distance = distances[anc_id]
463
if revision_history is not None and anc_id in revision_history:
465
elif best is None or distance > best[1]:
466
best = (anc_id, distance)
471
while next != ancestor_id:
473
next = best_ancestor(next)
478
211
def is_reserved_id(revision_id):
479
212
"""Determine whether a revision id is reserved
481
:return: True if the revision is is reserved, False otherwise
214
:return: True if the revision is reserved, False otherwise
483
216
return isinstance(revision_id, basestring) and revision_id.endswith(':')