1
# Copyright (C) 2005-2011 Canonical Ltd
1
# Copyright (C) 2005-2010 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
73
74
repository as _mod_repository,
74
75
revision as _mod_revision,
81
82
from bzrlib import (
85
85
from bzrlib.osutils import (
87
87
format_date_with_offset_in_original_timezone,
88
get_diff_header_encoding,
89
88
get_terminal_encoding,
112
111
for revision_id in branch.revision_history():
113
112
this_inv = branch.repository.get_inventory(revision_id)
114
if this_inv.has_id(file_id):
113
if file_id in this_inv:
115
114
this_ie = this_inv[file_id]
116
115
this_path = this_inv.id2path(file_id)
233
232
diff_type=None, _match_using_deltas=True,
234
233
exclude_common_ancestry=False,
237
235
"""Convenience function for making a logging request dictionary.
262
260
generate; 1 for just the mainline; 0 for all levels.
264
262
:param generate_tags: If True, include tags for matched revisions.
266
264
:param delta_type: Either 'full', 'partial' or None.
267
265
'full' means generate the complete delta - adds/deletes/modifies/etc;
268
266
'partial' means filter the delta using specific_fileids;
295
291
'delta_type': delta_type,
296
292
'diff_type': diff_type,
297
293
'exclude_common_ancestry': exclude_common_ancestry,
298
'signature': signature,
299
294
# Add 'private' attributes for features that may be deprecated
300
295
'_match_using_deltas': _match_using_deltas,
304
299
def _apply_log_request_defaults(rqst):
305
300
"""Apply default values to a request dictionary."""
306
result = _DEFAULT_REQUEST_PARAMS.copy()
301
result = _DEFAULT_REQUEST_PARAMS
308
303
result.update(rqst)
312
def format_signature_validity(rev_id, repo):
313
"""get the signature validity
315
:param rev_id: revision id to validate
316
:param repo: repository of revision
317
:return: human readable string to print to log
319
from bzrlib import gpg
321
gpg_strategy = gpg.GPGStrategy(None)
322
result = repo.verify_revision(rev_id, gpg_strategy)
323
if result[0] == gpg.SIGNATURE_VALID:
324
return "valid signature from {0}".format(result[1])
325
if result[0] == gpg.SIGNATURE_KEY_MISSING:
326
return "unknown key {0}".format(result[1])
327
if result[0] == gpg.SIGNATURE_NOT_VALID:
328
return "invalid signature!"
329
if result[0] == gpg.SIGNATURE_NOT_SIGNED:
330
return "no signature"
333
307
class LogGenerator(object):
334
308
"""A generator of log revisions."""
387
361
rqst['delta_type'] = None
388
362
if not getattr(lf, 'supports_diff', False):
389
363
rqst['diff_type'] = None
390
if not getattr(lf, 'supports_signatures', False):
391
rqst['signature'] = False
393
365
# Find and print the interesting revisions
394
366
generator = self._generator_factory(self.branch, rqst)
428
400
levels = rqst.get('levels')
429
401
limit = rqst.get('limit')
430
402
diff_type = rqst.get('diff_type')
431
show_signature = rqst.get('signature')
433
404
revision_iterator = self._create_log_revision_iterator()
434
405
for revs in revision_iterator:
442
413
diff = self._format_diff(rev, rev_id, diff_type)
444
signature = format_signature_validity(rev_id,
445
self.branch.repository)
448
414
yield LogRevision(rev, revno, merge_depth, delta,
449
self.rev_tag_dict.get(rev_id), diff, signature)
415
self.rev_tag_dict.get(rev_id), diff)
452
418
if log_count >= limit:
467
433
specific_files = None
469
path_encoding = get_diff_header_encoding()
435
path_encoding = osutils.get_diff_header_encoding()
470
436
diff.show_diff_trees(tree_1, tree_2, s, specific_files, old_label='',
471
437
new_label='', path_encoding=path_encoding)
472
438
return s.getvalue()
575
541
return [(br_rev_id, br_revno, 0)]
577
revno_str = _compute_revno_str(branch, rev_id)
543
revno = branch.revision_id_to_dotted_revno(rev_id)
544
revno_str = '.'.join(str(n) for n in revno)
578
545
return [(rev_id, revno_str, 0)]
660
627
return len(parents) > 1
663
def _compute_revno_str(branch, rev_id):
664
"""Compute the revno string from a rev_id.
666
:return: The revno string, or None if the revision is not in the supplied
670
revno = branch.revision_id_to_dotted_revno(rev_id)
671
except errors.NoSuchRevision:
672
# The revision must be outside of this branch
675
return '.'.join(str(n) for n in revno)
678
630
def _is_obvious_ancestor(branch, start_rev_id, end_rev_id):
679
631
"""Is start_rev_id an obvious ancestor of end_rev_id?"""
680
632
if start_rev_id and end_rev_id:
682
start_dotted = branch.revision_id_to_dotted_revno(start_rev_id)
683
end_dotted = branch.revision_id_to_dotted_revno(end_rev_id)
684
except errors.NoSuchRevision:
685
# one or both is not in the branch; not obvious
633
start_dotted = branch.revision_id_to_dotted_revno(start_rev_id)
634
end_dotted = branch.revision_id_to_dotted_revno(end_rev_id)
687
635
if len(start_dotted) == 1 and len(end_dotted) == 1:
688
636
# both on mainline
689
637
return start_dotted[0] <= end_dotted[0]
714
662
br_revno, br_rev_id = branch.last_revision_info()
715
663
repo = branch.repository
716
graph = repo.get_graph()
717
664
if start_rev_id is None and end_rev_id is None:
718
665
cur_revno = br_revno
719
for revision_id in graph.iter_lefthand_ancestry(br_rev_id,
720
(_mod_revision.NULL_REVISION,)):
666
for revision_id in repo.iter_reverse_revision_history(br_rev_id):
721
667
yield revision_id, str(cur_revno), 0
724
670
if end_rev_id is None:
725
671
end_rev_id = br_rev_id
726
672
found_start = start_rev_id is None
727
for revision_id in graph.iter_lefthand_ancestry(end_rev_id,
728
(_mod_revision.NULL_REVISION,)):
729
revno_str = _compute_revno_str(branch, revision_id)
673
for revision_id in repo.iter_reverse_revision_history(end_rev_id):
674
revno = branch.revision_id_to_dotted_revno(revision_id)
675
revno_str = '.'.join(str(n) for n in revno)
730
676
if not found_start and revision_id == start_rev_id:
731
677
if not exclude_common_ancestry:
732
678
yield revision_id, revno_str, 0
865
811
if search is None:
866
812
return log_rev_iterator
867
searchRE = lazy_regex.lazy_compile(search, re.IGNORECASE)
813
searchRE = re.compile(search, re.IGNORECASE)
868
814
return _filter_message_re(searchRE, log_rev_iterator)
1124
1070
cur_revno = branch_revno
1126
1072
mainline_revs = []
1127
graph = branch.repository.get_graph()
1128
for revision_id in graph.iter_lefthand_ancestry(
1129
branch_last_revision, (_mod_revision.NULL_REVISION,)):
1073
for revision_id in branch.repository.iter_reverse_revision_history(
1074
branch_last_revision):
1130
1075
if cur_revno < start_revno:
1131
1076
# We have gone far enough, but we always add 1 more revision
1132
1077
rev_nos[revision_id] = cur_revno
1232
1176
# Lookup all possible text keys to determine which ones actually modified
1234
graph = branch.repository.get_file_graph()
1235
get_parent_map = graph.get_parent_map
1236
1178
text_keys = [(file_id, rev_id) for rev_id, revno, depth in view_revisions]
1237
1179
next_keys = None
1238
1180
# Looking up keys in batches of 1000 can cut the time in half, as well as
1242
1184
# indexing layer. We might consider passing in hints as to the known
1243
1185
# access pattern (sparse/clustered, high success rate/low success
1244
1186
# rate). This particular access is clustered with a low success rate.
1187
get_parent_map = branch.repository.texts.get_parent_map
1245
1188
modified_text_revisions = set()
1246
1189
chunk_size = 1000
1247
1190
for start in xrange(0, len(text_keys), chunk_size):
1357
1300
def __init__(self, rev=None, revno=None, merge_depth=0, delta=None,
1358
tags=None, diff=None, signature=None):
1301
tags=None, diff=None):
1363
self.revno = str(revno)
1303
self.revno = str(revno)
1364
1304
self.merge_depth = merge_depth
1365
1305
self.delta = delta
1366
1306
self.tags = tags
1367
1307
self.diff = diff
1368
self.signature = signature
1371
1310
class LogFormatter(object):
1380
1319
to indicate which LogRevision attributes it supports:
1382
1321
- supports_delta must be True if this log formatter supports delta.
1383
Otherwise the delta attribute may not be populated. The 'delta_format'
1384
attribute describes whether the 'short_status' format (1) or the long
1385
one (2) should be used.
1322
Otherwise the delta attribute may not be populated. The 'delta_format'
1323
attribute describes whether the 'short_status' format (1) or the long
1324
one (2) should be used.
1387
1326
- supports_merge_revisions must be True if this log formatter supports
1388
merge revisions. If not, then only mainline revisions will be passed
1327
merge revisions. If not, then only mainline revisions will be passed
1391
1330
- preferred_levels is the number of levels this formatter defaults to.
1392
The default value is zero meaning display all levels.
1393
This value is only relevant if supports_merge_revisions is True.
1331
The default value is zero meaning display all levels.
1332
This value is only relevant if supports_merge_revisions is True.
1395
1334
- supports_tags must be True if this log formatter supports tags.
1396
Otherwise the tags attribute may not be populated.
1335
Otherwise the tags attribute may not be populated.
1398
1337
- supports_diff must be True if this log formatter supports diffs.
1399
Otherwise the diff attribute may not be populated.
1401
- supports_signatures must be True if this log formatter supports GPG
1338
Otherwise the diff attribute may not be populated.
1404
1340
Plugins can register functions to show custom revision properties using
1405
1341
the properties_handler_registry. The registered function
1406
must respect the following interface description::
1342
must respect the following interface description:
1408
1343
def my_show_properties(properties_dict):
1409
1344
# code that returns a dict {'name':'value'} of the properties
1597
1532
supports_delta = True
1598
1533
supports_tags = True
1599
1534
supports_diff = True
1600
supports_signatures = True
1602
1536
def __init__(self, *args, **kwargs):
1603
1537
super(LongLogFormatter, self).__init__(*args, **kwargs)
1623
1557
self.merge_marker(revision)))
1624
1558
if revision.tags:
1625
1559
lines.append('tags: %s' % (', '.join(revision.tags)))
1626
if self.show_ids or revision.revno is None:
1627
1561
lines.append('revision-id: %s' % (revision.rev.revision_id,))
1629
1562
for parent_id in revision.rev.parent_ids:
1630
1563
lines.append('parent: %s' % (parent_id,))
1631
1564
lines.extend(self.custom_properties(revision.rev))
1643
1576
lines.append('timestamp: %s' % (self.date_string(revision.rev),))
1645
if revision.signature is not None:
1646
lines.append('signature: ' + revision.signature)
1648
1578
lines.append('message:')
1649
1579
if not revision.rev.message:
1650
1580
lines.append(' (no message)')
1697
1627
indent = ' ' * depth
1698
1628
revno_width = self.revno_width_by_depth.get(depth)
1699
1629
if revno_width is None:
1700
if revision.revno is None or revision.revno.find('.') == -1:
1630
if revision.revno.find('.') == -1:
1701
1631
# mainline revno, e.g. 12345
1702
1632
revno_width = 5
1711
1641
if revision.tags:
1712
1642
tags = ' {%s}' % (', '.join(revision.tags))
1713
1643
to_file.write(indent + "%*s %s\t%s%s%s\n" % (revno_width,
1714
revision.revno or "", self.short_author(revision.rev),
1644
revision.revno, self.short_author(revision.rev),
1715
1645
format_date(revision.rev.timestamp,
1716
1646
revision.rev.timezone or 0,
1717
1647
self.show_timezone, date_fmt="%Y-%m-%d",
1718
1648
show_offset=False),
1719
1649
tags, self.merge_marker(revision)))
1720
1650
self.show_properties(revision.rev, indent+offset)
1721
if self.show_ids or revision.revno is None:
1722
1652
to_file.write(indent + offset + 'revision-id:%s\n'
1723
1653
% (revision.rev.revision_id,))
1724
1654
if not revision.rev.message:
1778
1708
def log_string(self, revno, rev, max_chars, tags=None, prefix=''):
1779
1709
"""Format log info into one string. Truncate tail of string
1781
:param revno: revision number or None.
1782
Revision numbers counts from 1.
1783
:param rev: revision object
1784
:param max_chars: maximum length of resulting string
1785
:param tags: list of tags or None
1786
:param prefix: string to prefix each line
1787
:return: formatted truncated string
1710
:param revno: revision number or None.
1711
Revision numbers counts from 1.
1712
:param rev: revision object
1713
:param max_chars: maximum length of resulting string
1714
:param tags: list of tags or None
1715
:param prefix: string to prefix each line
1716
:return: formatted truncated string
1791
1720
# show revno only when is not None
1792
1721
out.append("%s:" % revno)
1793
if max_chars is not None:
1794
out.append(self.truncate(self.short_author(rev), (max_chars+3)/4))
1796
out.append(self.short_author(rev))
1722
out.append(self.truncate(self.short_author(rev), 20))
1797
1723
out.append(self.date_string(rev))
1798
1724
if len(rev.parent_ids) > 1:
1799
1725
out.append('[merge]')
1992
1918
old_revisions = set()
1993
1919
new_history = []
1994
1920
new_revisions = set()
1995
graph = repository.get_graph()
1996
new_iter = graph.iter_lefthand_ancestry(new_revision_id)
1997
old_iter = graph.iter_lefthand_ancestry(old_revision_id)
1921
new_iter = repository.iter_reverse_revision_history(new_revision_id)
1922
old_iter = repository.iter_reverse_revision_history(old_revision_id)
1998
1923
stop_revision = None