Merge lp:~ian-clatworthy/bzr/faster-log into lp:bzr

Proposed by Ian Clatworthy
Status: Merged
Approved by: John A Meinel
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~ian-clatworthy/bzr/faster-log
Merge into: lp:bzr
Diff against target: 331 lines (+121/-49)
5 files modified
NEWS (+2/-0)
bzrlib/log.py (+77/-47)
bzrlib/osutils.py (+29/-0)
bzrlib/revision.py (+5/-2)
bzrlib/tests/test_osutils.py (+8/-0)
To merge this branch: bzr merge lp:~ian-clatworthy/bzr/faster-log
Reviewer Review Type Date Requested Status
John A Meinel Approve
Review via email: mp+15416@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Ian Clatworthy (ian-clatworthy) wrote :

This is an old patch that I still think we ought to land. The previous reviews asked for more tests because there's a small risk I might have broken something. I agree that additional tests would be nice. I disagree that they ought to block the patch because, IMO, the patch's value outweighs that risk.

Perhaps this weeks patch pilot could add some more tests if they still feel they are necessary?

Revision history for this message
John A Meinel (jameinel) wrote :

As I understand, you don't actually change the actions, just how things are formatted, right? (You build up more stuff into fewer strings that get written out.)

I didn't go over it line-by-line, but as long as that is the general change it seems reasonable to me.

review: Approve
Revision history for this message
Ian Clatworthy (ian-clatworthy) wrote :

John A Meinel wrote:
> Review: Approve
> As I understand, you don't actually change the actions, just how things are formatted, right? (You build up more stuff into fewer strings that get written out.)

Right.

Ian C.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'NEWS'
--- NEWS 2009-11-30 04:49:31 +0000
+++ NEWS 2009-11-30 12:45:30 +0000
@@ -60,6 +60,8 @@
60Improvements60Improvements
61************61************
6262
63* ``bzr log`` is now faster. (Ian Clatworthy)
64
63* ``bzrlib.urlutils.local_path_from_url`` now accepts65* ``bzrlib.urlutils.local_path_from_url`` now accepts
64 'file://localhost/' as well as 'file:///' URLs on POSIX. (Michael66 'file://localhost/' as well as 'file:///' URLs on POSIX. (Michael
65 Hudson)67 Hudson)
6668
=== modified file 'bzrlib/log.py'
--- bzrlib/log.py 2009-10-29 03:32:42 +0000
+++ bzrlib/log.py 2009-11-30 12:45:30 +0000
@@ -83,6 +83,7 @@
83 )83 )
84from bzrlib.osutils import (84from bzrlib.osutils import (
85 format_date,85 format_date,
86 format_date_with_offset_in_original_timezone,
86 get_terminal_encoding,87 get_terminal_encoding,
87 re_compile_checked,88 re_compile_checked,
88 terminal_width,89 terminal_width,
@@ -384,27 +385,28 @@
384 :return: An iterator yielding LogRevision objects.385 :return: An iterator yielding LogRevision objects.
385 """386 """
386 rqst = self.rqst387 rqst = self.rqst
388 levels = rqst.get('levels')
389 limit = rqst.get('limit')
390 diff_type = rqst.get('diff_type')
387 log_count = 0391 log_count = 0
388 revision_iterator = self._create_log_revision_iterator()392 revision_iterator = self._create_log_revision_iterator()
389 for revs in revision_iterator:393 for revs in revision_iterator:
390 for (rev_id, revno, merge_depth), rev, delta in revs:394 for (rev_id, revno, merge_depth), rev, delta in revs:
391 # 0 levels means show everything; merge_depth counts from 0395 # 0 levels means show everything; merge_depth counts from 0
392 levels = rqst.get('levels')
393 if levels != 0 and merge_depth >= levels:396 if levels != 0 and merge_depth >= levels:
394 continue397 continue
395 diff = self._format_diff(rev, rev_id)398 if diff_type is None:
399 diff = None
400 else:
401 diff = self._format_diff(rev, rev_id, diff_type)
396 yield LogRevision(rev, revno, merge_depth, delta,402 yield LogRevision(rev, revno, merge_depth, delta,
397 self.rev_tag_dict.get(rev_id), diff)403 self.rev_tag_dict.get(rev_id), diff)
398 limit = rqst.get('limit')
399 if limit:404 if limit:
400 log_count += 1405 log_count += 1
401 if log_count >= limit:406 if log_count >= limit:
402 return407 return
403408
404 def _format_diff(self, rev, rev_id):409 def _format_diff(self, rev, rev_id, diff_type):
405 diff_type = self.rqst.get('diff_type')
406 if diff_type is None:
407 return None
408 repo = self.branch.repository410 repo = self.branch.repository
409 if len(rev.parent_ids) == 0:411 if len(rev.parent_ids) == 0:
410 ancestor_id = _mod_revision.NULL_REVISION412 ancestor_id = _mod_revision.NULL_REVISION
@@ -1291,7 +1293,7 @@
1291 preferred_levels = 01293 preferred_levels = 0
12921294
1293 def __init__(self, to_file, show_ids=False, show_timezone='original',1295 def __init__(self, to_file, show_ids=False, show_timezone='original',
1294 delta_format=None, levels=None, show_advice=False):1296 delta_format=None, levels=None, show_advice=False):
1295 """Create a LogFormatter.1297 """Create a LogFormatter.
12961298
1297 :param to_file: the file to output to1299 :param to_file: the file to output to
@@ -1367,48 +1369,64 @@
1367 else:1369 else:
1368 return ''1370 return ''
13691371
1370 def show_foreign_info(self, rev, indent):1372 def show_properties(self, revision, indent):
1373 """Displays the custom properties returned by each registered handler.
1374
1375 If a registered handler raises an error it is propagated.
1376 """
1377 for line in self.custom_properties(revision):
1378 self.to_file.write("%s%s\n" % (indent, line))
1379
1380 def custom_properties(self, revision):
1381 """Format the custom properties returned by each registered handler.
1382
1383 If a registered handler raises an error it is propagated.
1384
1385 :return: a list of formatted lines (excluding trailing newlines)
1386 """
1387 lines = self._foreign_info_properties(revision)
1388 for key, handler in properties_handler_registry.iteritems():
1389 lines.extend(self._format_properties(handler(revision)))
1390 return lines
1391
1392 def _foreign_info_properties(self, rev):
1371 """Custom log displayer for foreign revision identifiers.1393 """Custom log displayer for foreign revision identifiers.
13721394
1373 :param rev: Revision object.1395 :param rev: Revision object.
1374 """1396 """
1375 # Revision comes directly from a foreign repository1397 # Revision comes directly from a foreign repository
1376 if isinstance(rev, foreign.ForeignRevision):1398 if isinstance(rev, foreign.ForeignRevision):
1377 self._write_properties(indent, rev.mapping.vcs.show_foreign_revid(1399 return rev.mapping.vcs.show_foreign_revid(rev.foreign_revid)
1378 rev.foreign_revid))
1379 return
13801400
1381 # Imported foreign revision revision ids always contain :1401 # Imported foreign revision revision ids always contain :
1382 if not ":" in rev.revision_id:1402 if not ":" in rev.revision_id:
1383 return1403 return []
13841404
1385 # Revision was once imported from a foreign repository1405 # Revision was once imported from a foreign repository
1386 try:1406 try:
1387 foreign_revid, mapping = \1407 foreign_revid, mapping = \
1388 foreign.foreign_vcs_registry.parse_revision_id(rev.revision_id)1408 foreign.foreign_vcs_registry.parse_revision_id(rev.revision_id)
1389 except errors.InvalidRevisionId:1409 except errors.InvalidRevisionId:
1390 return1410 return []
13911411
1392 self._write_properties(indent, 1412 return self._format_properties(
1393 mapping.vcs.show_foreign_revid(foreign_revid))1413 mapping.vcs.show_foreign_revid(foreign_revid))
13941414
1395 def show_properties(self, revision, indent):1415 def _format_properties(self, properties):
1396 """Displays the custom properties returned by each registered handler.1416 lines = []
1397
1398 If a registered handler raises an error it is propagated.
1399 """
1400 for key, handler in properties_handler_registry.iteritems():
1401 self._write_properties(indent, handler(revision))
1402
1403 def _write_properties(self, indent, properties):
1404 for key, value in properties.items():1417 for key, value in properties.items():
1405 self.to_file.write(indent + key + ': ' + value + '\n')1418 lines.append(key + ': ' + value)
1419 return lines
14061420
1407 def show_diff(self, to_file, diff, indent):1421 def show_diff(self, to_file, diff, indent):
1408 for l in diff.rstrip().split('\n'):1422 for l in diff.rstrip().split('\n'):
1409 to_file.write(indent + '%s\n' % (l,))1423 to_file.write(indent + '%s\n' % (l,))
14101424
14111425
1426# Separator between revisions in long format
1427_LONG_SEP = '-' * 60
1428
1429
1412class LongLogFormatter(LogFormatter):1430class LongLogFormatter(LogFormatter):
14131431
1414 supports_merge_revisions = True1432 supports_merge_revisions = True
@@ -1417,46 +1435,59 @@
1417 supports_tags = True1435 supports_tags = True
1418 supports_diff = True1436 supports_diff = True
14191437
1438 def __init__(self, *args, **kwargs):
1439 super(LongLogFormatter, self).__init__(*args, **kwargs)
1440 if self.show_timezone == 'original':
1441 self.date_string = self._date_string_original_timezone
1442 else:
1443 self.date_string = self._date_string_with_timezone
1444
1445 def _date_string_with_timezone(self, rev):
1446 return format_date(rev.timestamp, rev.timezone or 0,
1447 self.show_timezone)
1448
1449 def _date_string_original_timezone(self, rev):
1450 return format_date_with_offset_in_original_timezone(rev.timestamp,
1451 rev.timezone or 0)
1452
1420 def log_revision(self, revision):1453 def log_revision(self, revision):
1421 """Log a revision, either merged or not."""1454 """Log a revision, either merged or not."""
1422 indent = ' ' * revision.merge_depth1455 indent = ' ' * revision.merge_depth
1423 to_file = self.to_file1456 lines = [_LONG_SEP]
1424 to_file.write(indent + '-' * 60 + '\n')
1425 if revision.revno is not None:1457 if revision.revno is not None:
1426 to_file.write(indent + 'revno: %s%s\n' % (revision.revno,1458 lines.append('revno: %s%s' % (revision.revno,
1427 self.merge_marker(revision)))1459 self.merge_marker(revision)))
1428 if revision.tags:1460 if revision.tags:
1429 to_file.write(indent + 'tags: %s\n' % (', '.join(revision.tags)))1461 lines.append('tags: %s' % (', '.join(revision.tags)))
1430 if self.show_ids:1462 if self.show_ids:
1431 to_file.write(indent + 'revision-id: ' + revision.rev.revision_id)1463 lines.append('revision-id: %s' % (revision.rev.revision_id,))
1432 to_file.write('\n')
1433 for parent_id in revision.rev.parent_ids:1464 for parent_id in revision.rev.parent_ids:
1434 to_file.write(indent + 'parent: %s\n' % (parent_id,))1465 lines.append('parent: %s' % (parent_id,))
1435 self.show_foreign_info(revision.rev, indent)1466 lines.extend(self.custom_properties(revision.rev))
1436 self.show_properties(revision.rev, indent)
14371467
1438 committer = revision.rev.committer1468 committer = revision.rev.committer
1439 authors = revision.rev.get_apparent_authors()1469 authors = revision.rev.get_apparent_authors()
1440 if authors != [committer]:1470 if authors != [committer]:
1441 to_file.write(indent + 'author: %s\n' % (", ".join(authors),))1471 lines.append('author: %s' % (", ".join(authors),))
1442 to_file.write(indent + 'committer: %s\n' % (committer,))1472 lines.append('committer: %s' % (committer,))
14431473
1444 branch_nick = revision.rev.properties.get('branch-nick', None)1474 branch_nick = revision.rev.properties.get('branch-nick', None)
1445 if branch_nick is not None:1475 if branch_nick is not None:
1446 to_file.write(indent + 'branch nick: %s\n' % (branch_nick,))1476 lines.append('branch nick: %s' % (branch_nick,))
14471477
1448 date_str = format_date(revision.rev.timestamp,1478 lines.append('timestamp: %s' % (self.date_string(revision.rev),))
1449 revision.rev.timezone or 0,1479
1450 self.show_timezone)1480 lines.append('message:')
1451 to_file.write(indent + 'timestamp: %s\n' % (date_str,))
1452
1453 to_file.write(indent + 'message:\n')
1454 if not revision.rev.message:1481 if not revision.rev.message:
1455 to_file.write(indent + ' (no message)\n')1482 lines.append(' (no message)')
1456 else:1483 else:
1457 message = revision.rev.message.rstrip('\r\n')1484 message = revision.rev.message.rstrip('\r\n')
1458 for l in message.split('\n'):1485 for l in message.split('\n'):
1459 to_file.write(indent + ' %s\n' % (l,))1486 lines.append(' %s' % (l,))
1487
1488 # Dump the output, appending the delta and diff if requested
1489 to_file = self.to_file
1490 to_file.write("%s%s\n" % (indent, ('\n' + indent).join(lines)))
1460 if revision.delta is not None:1491 if revision.delta is not None:
1461 # We don't respect delta_format for compatibility1492 # We don't respect delta_format for compatibility
1462 revision.delta.show(to_file, self.show_ids, indent=indent,1493 revision.delta.show(to_file, self.show_ids, indent=indent,
@@ -1515,7 +1546,6 @@
1515 self.show_timezone, date_fmt="%Y-%m-%d",1546 self.show_timezone, date_fmt="%Y-%m-%d",
1516 show_offset=False),1547 show_offset=False),
1517 tags, self.merge_marker(revision)))1548 tags, self.merge_marker(revision)))
1518 self.show_foreign_info(revision.rev, indent+offset)
1519 self.show_properties(revision.rev, indent+offset)1549 self.show_properties(revision.rev, indent+offset)
1520 if self.show_ids:1550 if self.show_ids:
1521 to_file.write(indent + offset + 'revision-id:%s\n'1551 to_file.write(indent + offset + 'revision-id:%s\n'
15221552
=== modified file 'bzrlib/osutils.py'
--- bzrlib/osutils.py 2009-11-10 07:01:56 +0000
+++ bzrlib/osutils.py 2009-11-30 12:45:30 +0000
@@ -695,6 +695,8 @@
695 return offset.days * 86400 + offset.seconds695 return offset.days * 86400 + offset.seconds
696696
697weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']697weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
698_default_format_by_weekday_num = [wd + " %Y-%m-%d %H:%M:%S" for wd in weekdays]
699
698700
699def format_date(t, offset=0, timezone='original', date_fmt=None,701def format_date(t, offset=0, timezone='original', date_fmt=None,
700 show_offset=True):702 show_offset=True):
@@ -714,6 +716,32 @@
714 date_str = time.strftime(date_fmt, tt)716 date_str = time.strftime(date_fmt, tt)
715 return date_str + offset_str717 return date_str + offset_str
716718
719
720# Cache of formatted offset strings
721_offset_cache = {}
722
723
724def format_date_with_offset_in_original_timezone(t, offset=0,
725 _cache=_offset_cache):
726 """Return a formatted date string in the original timezone.
727
728 This routine may be faster then format_date.
729
730 :param t: Seconds since the epoch.
731 :param offset: Timezone offset in seconds east of utc.
732 """
733 if offset is None:
734 offset = 0
735 tt = time.gmtime(t + offset)
736 date_fmt = _default_format_by_weekday_num[tt[6]]
737 date_str = time.strftime(date_fmt, tt)
738 offset_str = _cache.get(offset, None)
739 if offset_str is None:
740 offset_str = ' %+03d%02d' % (offset / 3600, (offset / 60) % 60)
741 _cache[offset] = offset_str
742 return date_str + offset_str
743
744
717def format_local_date(t, offset=0, timezone='original', date_fmt=None,745def format_local_date(t, offset=0, timezone='original', date_fmt=None,
718 show_offset=True):746 show_offset=True):
719 """Return an unicode date string formatted according to the current locale.747 """Return an unicode date string formatted according to the current locale.
@@ -733,6 +761,7 @@
733 date_str = date_str.decode(get_user_encoding(), 'replace')761 date_str = date_str.decode(get_user_encoding(), 'replace')
734 return date_str + offset_str762 return date_str + offset_str
735763
764
736def _format_date(t, offset, timezone, date_fmt, show_offset):765def _format_date(t, offset, timezone, date_fmt, show_offset):
737 if timezone == 'utc':766 if timezone == 'utc':
738 tt = time.gmtime(t)767 tt = time.gmtime(t)
739768
=== modified file 'bzrlib/revision.py'
--- bzrlib/revision.py 2009-09-01 12:39:21 +0000
+++ bzrlib/revision.py 2009-11-30 12:45:30 +0000
@@ -54,8 +54,11 @@
5454
55 def __init__(self, revision_id, properties=None, **args):55 def __init__(self, revision_id, properties=None, **args):
56 self.revision_id = revision_id56 self.revision_id = revision_id
57 self.properties = properties or {}57 if properties is None:
58 self._check_properties()58 self.properties = {}
59 else:
60 self.properties = properties
61 self._check_properties()
59 self.committer = None62 self.committer = None
60 self.parent_ids = []63 self.parent_ids = []
61 self.parent_sha1s = []64 self.parent_sha1s = []
6265
=== modified file 'bzrlib/tests/test_osutils.py'
--- bzrlib/tests/test_osutils.py 2009-11-18 16:10:18 +0000
+++ bzrlib/tests/test_osutils.py 2009-11-30 12:45:30 +0000
@@ -378,6 +378,14 @@
378 # Instead blackbox.test_locale should check for localized378 # Instead blackbox.test_locale should check for localized
379 # dates once they do occur in output strings.379 # dates once they do occur in output strings.
380380
381 def test_format_date_with_offset_in_original_timezone(self):
382 self.assertEqual("Thu 1970-01-01 00:00:00 +0000",
383 osutils.format_date_with_offset_in_original_timezone(0))
384 self.assertEqual("Fri 1970-01-02 03:46:40 +0000",
385 osutils.format_date_with_offset_in_original_timezone(100000))
386 self.assertEqual("Fri 1970-01-02 05:46:40 +0200",
387 osutils.format_date_with_offset_in_original_timezone(100000, 7200))
388
381 def test_local_time_offset(self):389 def test_local_time_offset(self):
382 """Test that local_time_offset() returns a sane value."""390 """Test that local_time_offset() returns a sane value."""
383 offset = osutils.local_time_offset()391 offset = osutils.local_time_offset()