114
106
def get_summary(self):
115
107
"""Get the first line of the log message for this revision.
117
Return an empty string if message is None.
120
return self.message.lstrip().split('\n', 1)[0]
124
@symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((1, 13, 0)))
125
def get_apparent_author(self):
126
"""Return the apparent author of this revision.
128
This method is deprecated in favour of get_apparent_authors.
130
If the revision properties contain any author names,
131
return the first. Otherwise return the committer name.
133
authors = self.get_apparent_authors()
139
def get_apparent_authors(self):
140
"""Return the apparent authors of this revision.
142
If the revision properties contain the names of the authors,
143
return them. Otherwise return the committer name.
145
The return value will be a list containing at least one element.
147
authors = self.properties.get('authors', None)
149
author = self.properties.get('author', self.committer)
154
return authors.split("\n")
157
"""Iterate over the bugs associated with this revision."""
158
bug_property = self.properties.get('bugs', None)
159
if bug_property is None:
161
for line in bug_property.splitlines():
163
url, status = line.split(None, 2)
165
raise errors.InvalidLineInBugsProperty(line)
166
if status not in bugtracker.ALLOWED_BUG_STATUSES:
167
raise errors.InvalidBugStatus(status)
109
return self.message.split('\n', 1)[0]
112
def is_ancestor(revision_id, candidate_id, branch):
113
"""Return true if candidate_id is an ancestor of revision_id.
115
A false negative will be returned if any intermediate descendent of
116
candidate_id is not present in any of the revision_sources.
118
revisions_source is an object supporting a get_revision operation that
119
behaves like Branch's.
121
return (candidate_id in branch.repository.get_ancestry(revision_id))
171
124
def iter_ancestors(revision_id, revision_source, only_present=False):
204
157
if anc_id not in found_ancestors:
205
158
found_ancestors[anc_id] = (anc_order, anc_distance)
206
159
return found_ancestors
209
162
def __get_closest(intersection):
210
163
intersection.sort()
212
165
for entry in intersection:
213
166
if entry[0] == intersection[0][0]:
214
167
matches.append(entry[2])
218
def is_reserved_id(revision_id):
219
"""Determine whether a revision id is reserved
221
:return: True if the revision is reserved, False otherwise
171
def revision_graph(revision, revision_source):
172
"""Produce a graph of the ancestry of the specified revision.
174
:return: root, ancestors map, descendants map
223
return isinstance(revision_id, basestring) and revision_id.endswith(':')
226
def check_not_reserved_id(revision_id):
227
"""Raise ReservedId if the supplied revision_id is reserved"""
228
if is_reserved_id(revision_id):
229
raise errors.ReservedId(revision_id)
232
def ensure_null(revision_id):
233
"""Ensure only NULL_REVISION is used to represent the null revision"""
234
if revision_id is None:
235
symbol_versioning.warn('NULL_REVISION should be used for the null'
236
' revision instead of None, as of bzr 0.91.',
237
DeprecationWarning, stacklevel=2)
176
revision_source.lock_read()
178
return _revision_graph(revision, revision_source)
180
revision_source.unlock()
183
def _revision_graph(revision, revision_source):
184
"""See revision_graph."""
185
from bzrlib.tsort import topo_sort
186
graph = revision_source.get_revision_graph(revision)
187
# mark all no-parent revisions as being NULL_REVISION parentage.
188
for node, parents in graph.items():
189
if len(parents) == 0:
190
graph[node] = [NULL_REVISION]
191
# add NULL_REVISION to the graph
192
graph[NULL_REVISION] = []
194
# pick a root. If there are multiple roots
195
# this could pick a random one.
196
topo_order = topo_sort(graph.items())
202
# map the descendants of the graph.
203
# and setup our set based return graph.
204
for node in graph.keys():
205
descendants[node] = {}
206
for node, parents in graph.items():
207
for parent in parents:
208
descendants[parent][node] = 1
209
ancestors[node] = set(parents)
211
assert root not in descendants[root]
212
assert root not in ancestors[root]
213
return root, ancestors, descendants
216
def combined_graph(revision_a, revision_b, revision_source):
217
"""Produce a combined ancestry graph.
218
Return graph root, ancestors map, descendants map, set of common nodes"""
219
root, ancestors, descendants = revision_graph(
220
revision_a, revision_source)
221
root_b, ancestors_b, descendants_b = revision_graph(
222
revision_b, revision_source)
224
raise errors.NoCommonRoot(revision_a, revision_b)
226
for node, node_anc in ancestors_b.iteritems():
227
if node in ancestors:
230
ancestors[node] = set()
231
ancestors[node].update(node_anc)
232
for node, node_dec in descendants_b.iteritems():
233
if node not in descendants:
234
descendants[node] = {}
235
descendants[node].update(node_dec)
236
return root, ancestors, descendants, common
239
def common_ancestor(revision_a, revision_b, revision_source,
241
if None in (revision_a, revision_b):
243
if NULL_REVISION in (revision_a, revision_b):
238
244
return NULL_REVISION
243
def is_null(revision_id):
244
if revision_id is None:
245
symbol_versioning.warn('NULL_REVISION should be used for the null'
246
' revision instead of None, as of bzr 0.90.',
247
DeprecationWarning, stacklevel=2)
248
return revision_id in (None, NULL_REVISION)
245
# trivial optimisation
246
if revision_a == revision_b:
250
pb.update('Picking ancestor', 1, 3)
251
graph = revision_source.get_revision_graph_with_ghosts(
252
[revision_a, revision_b])
253
# convert to a NULL_REVISION based graph.
254
ancestors = graph.get_ancestors()
255
descendants = graph.get_descendants()
256
common = set(graph.get_ancestry(revision_a)).intersection(
257
set(graph.get_ancestry(revision_b)))
258
descendants[NULL_REVISION] = {}
259
ancestors[NULL_REVISION] = []
260
for root in graph.roots:
261
descendants[NULL_REVISION][root] = 1
262
ancestors[root].append(NULL_REVISION)
263
for ghost in graph.ghosts:
264
# ghosts act as roots for the purpose of finding
265
# the longest paths from the root: any ghost *might*
266
# be directly attached to the root, so we treat them
268
# ghost now descends from NULL
269
descendants[NULL_REVISION][ghost] = 1
270
# that is it has an ancestor of NULL
271
ancestors[ghost] = [NULL_REVISION]
272
# ghost is common if any of ghosts descendants are common:
273
for ghost_descendant in descendants[ghost]:
274
if ghost_descendant in common:
278
common.add(NULL_REVISION)
279
except errors.NoCommonRoot:
280
raise errors.NoCommonAncestor(revision_a, revision_b)
282
pb.update('Picking ancestor', 2, 3)
283
distances = node_distances (descendants, ancestors, root)
284
pb.update('Picking ancestor', 3, 2)
285
farthest = select_farthest(distances, common)
286
if farthest is None or farthest == NULL_REVISION:
287
raise errors.NoCommonAncestor(revision_a, revision_b)
293
class MultipleRevisionSources(object):
294
"""Proxy that looks in multiple branches for revisions."""
295
def __init__(self, *args):
296
object.__init__(self)
297
assert len(args) != 0
298
self._revision_sources = args
300
def revision_parents(self, revision_id):
301
for source in self._revision_sources:
303
return source.revision_parents(revision_id)
304
except (errors.WeaveRevisionNotPresent, errors.NoSuchRevision), e:
308
def get_revision(self, revision_id):
309
for source in self._revision_sources:
311
return source.get_revision(revision_id)
312
except errors.NoSuchRevision, e:
316
def get_revision_graph(self, revision_id):
317
# we could probe incrementally until the pending
318
# ghosts list stop growing, but its cheaper for now
319
# to just ask for the complete graph for each repository.
321
for source in self._revision_sources:
322
ghost_graph = source.get_revision_graph_with_ghosts()
323
graphs.append(ghost_graph)
326
if not revision_id in graph.get_ancestors():
328
if absent == len(graphs):
329
raise errors.NoSuchRevision(self._revision_sources[0], revision_id)
333
pending = set([revision_id])
334
def find_parents(node_id):
335
"""find the parents for node_id."""
337
ancestors = graph.get_ancestors()
339
return ancestors[node_id]
342
raise errors.NoSuchRevision(self._revision_sources[0], node_id)
344
# all the graphs should have identical parent lists
345
node_id = pending.pop()
347
result[node_id] = find_parents(node_id)
348
for parent_node in result[node_id]:
349
if not parent_node in result:
350
pending.add(parent_node)
351
except errors.NoSuchRevision:
356
def get_revision_graph_with_ghosts(self, revision_ids):
357
# query all the sources for their entire graphs
358
# and then build a combined graph for just
361
for source in self._revision_sources:
362
ghost_graph = source.get_revision_graph_with_ghosts()
363
graphs.append(ghost_graph.get_ancestors())
364
for revision_id in revision_ids:
367
if not revision_id in graph:
369
if absent == len(graphs):
370
raise errors.NoSuchRevision(self._revision_sources[0],
375
pending = set(revision_ids)
377
def find_parents(node_id):
378
"""find the parents for node_id."""
381
return graph[node_id]
384
raise errors.NoSuchRevision(self._revision_sources[0], node_id)
386
# all the graphs should have identical parent lists
387
node_id = pending.pop()
389
parents = find_parents(node_id)
390
for parent_node in parents:
392
if (parent_node not in pending and
393
parent_node not in done):
395
pending.add(parent_node)
396
result.add_node(node_id, parents)
398
except errors.NoSuchRevision:
400
result.add_ghost(node_id)
405
for source in self._revision_sources:
409
for source in self._revision_sources:
413
@deprecated_function(zero_eight)
414
def get_intervening_revisions(ancestor_id, rev_id, rev_source,
415
revision_history=None):
416
"""Find the longest line of descent from maybe_ancestor to revision.
417
Revision history is followed where possible.
419
If ancestor_id == rev_id, list will be empty.
420
Otherwise, rev_id will be the last entry. ancestor_id will never appear.
421
If ancestor_id is not an ancestor, NotAncestor will be thrown
423
root, ancestors, descendants = revision_graph(rev_id, rev_source)
424
if len(descendants) == 0:
425
raise errors.NoSuchRevision(rev_source, rev_id)
426
if ancestor_id not in descendants:
427
rev_source.get_revision(ancestor_id)
428
raise errors.NotAncestor(rev_id, ancestor_id)
429
root_descendants = all_descendants(descendants, ancestor_id)
430
root_descendants.add(ancestor_id)
431
if rev_id not in root_descendants:
432
raise errors.NotAncestor(rev_id, ancestor_id)
433
distances = node_distances(descendants, ancestors, ancestor_id,
434
root_descendants=root_descendants)
436
def best_ancestor(rev_id):
438
for anc_id in ancestors[rev_id]:
440
distance = distances[anc_id]
443
if revision_history is not None and anc_id in revision_history:
445
elif best is None or distance > best[1]:
446
best = (anc_id, distance)
451
while next != ancestor_id:
453
next = best_ancestor(next)