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

Proposed by Ian Clatworthy
Status: Merged
Merge reported by: Ian Clatworthy
Merged at revision: not available
Proposed branch: lp:~ian-clatworthy/bzr/faster-log.old
Merge into: lp:~bzr/bzr/trunk-old
Diff against target: 331 lines
To merge this branch: bzr merge lp:~ian-clatworthy/bzr/faster-log.old
Reviewer Review Type Date Requested Status
bzr-core Pending
Review via email: mp+6769@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Ian Clatworthy (ian-clatworthy) wrote :

This patch makes log --long faster by around 10%. For OpenOffice.org with 260K revisions, the time drops from 50 seconds to 46 seconds.

Revision history for this message
John A Meinel (jameinel) wrote :
Download full text (3.7 KiB)

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Ian Clatworthy wrote:
> Ian Clatworthy has proposed merging lp:~ian-clatworthy/bzr/faster-log into lp:bzr.
>
> Requested reviews:
> bzr-core (bzr-core)
>
> This patch makes log --long faster by around 10%. For OpenOffice.org with 260K revisions, the time drops from 50 seconds to 46 seconds.
>

I'll try to give a bit of a summary as to why I think this is faster,
though it would be nice if you would give a similar summary when submitting.

1) Pull things like
+ levels = rqst.get('levels')
+ limit = rqst.get('limit')
+ diff_type = rqst.get('diff_type')

Out of the inner loops, and set the variable once at the outside. This
also handles stuff like using an if in the inner loop rather than a
function call:

+ if diff_type is None:
+ diff = None
+ else:
+ diff = self._format_diff(rev, rev_id, diff_type)

2) Factor out "custom_properties" into a re-used helper function.

3) Add a new function: format_date_with_offset_in_original_timezone

4) Change log_revision(self, revision)

so that rather than calling outfile.write() for every line of the
revision, batching it up into a list, and then using ('\n' +
indent).join(lines) and writing that out as a single string.

IMO the batching of everything for one revision seems just about right.

I'm a bit concerned, though, about handling Unicode Author names and
UTF-8 'diff' strings. My guess is that this actually breaks it, and we
just don't know it yet.

Can you please ensure that we have a test with non-ascii file content
(even better if it isn't even UTF-8 since we don't assert anything about
file content encoding), and then also include a non-ascii Author name.
That ensures that we want to output Unicode for the name, and something
random for the diff content.

5) I'm a bit curious about this:
=== modified file 'bzrlib/osutils.py'
- --- bzrlib/osutils.py 2009-05-23 04:55:52 +0000
+++ bzrlib/osutils.py 2009-05-25 05:46:01 +0000
@@ -686,6 +686,8 @@
     return offset.days * 86400 + offset.seconds

 weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
+_default_format_by_weekday_num = [wd + " %Y-%m-%d %H:%M:%S" for wd in
weekdays]
+

^- Does this mean that we are skipping localization? So someone with
de_DE.UTF-8 will no longer see the German short-names for days of the week?

+def format_date_with_offset_in_original_timezone(t, offset=0,
+ _cache=_offset_cache):
+ """Return a formatted date string in the original timezone.
+
+ This routine may be faster then format_date.
+
+ :param t: Seconds since the epoch.
+ :param offset: Timezone offset in seconds east of utc.
+ """
+ if offset is None:
+ offset = 0
+ tt = time.gmtime(t + offset)
+ date_fmt = _default_format_by_weekday_num[tt[6]]
+ date_str = time.strftime(date_fmt, tt)
+ offset_str = _cache.get(offset, None)
+ if offset_str is None:
+ offset_str = ' %+03d%02d' % (offset / 3600, (offset / 60) % 60)
+ _cache[offset] = offset_str
+ return date_str + offset_str
+

^- I wonder if it might be faster to include the offset_str inside the
'date_fm...

Read more...

Revision history for this message
John A Meinel (jameinel) wrote :
Download full text (3.4 KiB)

Ian Clatworthy wrote:
> Ian Clatworthy has proposed merging lp:~ian-clatworthy/bzr/faster-log into lp:bzr.
>
> Requested reviews:
> bzr-core (bzr-core)
>
> This patch makes log --long faster by around 10%. For OpenOffice.org with 260K revisions, the time drops from 50 seconds to 46 seconds.
>

I'll try to give a bit of a summary as to why I think this is faster,
though it would be nice if you would give a similar summary when submitting.

1) Pull things like
+ levels = rqst.get('levels')
+ limit = rqst.get('limit')
+ diff_type = rqst.get('diff_type')

Out of the inner loops, and set the variable once at the outside. This
also handles stuff like using an if in the inner loop rather than a
function call:

+ if diff_type is None:
+ diff = None
+ else:
+ diff = self._format_diff(rev, rev_id, diff_type)

2) Factor out "custom_properties" into a re-used helper function.

3) Add a new function: format_date_with_offset_in_original_timezone

4) Change log_revision(self, revision)

so that rather than calling outfile.write() for every line of the
revision, batching it up into a list, and then using ('\n' +
indent).join(lines) and writing that out as a single string.

IMO the batching of everything for one revision seems just about right.

I'm a bit concerned, though, about handling Unicode Author names and
UTF-8 'diff' strings. My guess is that this actually breaks it, and we
just don't know it yet.

Can you please ensure that we have a test with non-ascii file content
(even better if it isn't even UTF-8 since we don't assert anything about
file content encoding), and then also include a non-ascii Author name.
That ensures that we want to output Unicode for the name, and something
random for the diff content.

5) I'm a bit curious about this:
=== modified file 'bzrlib/osutils.py'
--- bzrlib/osutils.py 2009-05-23 04:55:52 +0000
+++ bzrlib/osutils.py 2009-05-25 05:46:01 +0000
@@ -686,6 +686,8 @@
     return offset.days * 86400 + offset.seconds

 weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
+_default_format_by_weekday_num = [wd + " %Y-%m-%d %H:%M:%S" for wd in
weekdays]
+

^- Does this mean that we are skipping localization? So someone with
de_DE.UTF-8 will no longer see the German short-names for days of the week?

+def format_date_with_offset_in_original_timezone(t, offset=0,
+ _cache=_offset_cache):
+ """Return a formatted date string in the original timezone.
+
+ This routine may be faster then format_date.
+
+ :param t: Seconds since the epoch.
+ :param offset: Timezone offset in seconds east of utc.
+ """
+ if offset is None:
+ offset = 0
+ tt = time.gmtime(t + offset)
+ date_fmt = _default_format_by_weekday_num[tt[6]]
+ date_str = time.strftime(date_fmt, tt)
+ offset_str = _cache.get(offset, None)
+ if offset_str is None:
+ offset_str = ' %+03d%02d' % (offset / 3600, (offset / 60) % 60)
+ _cache[offset] = offset_str
+ return date_str + offset_str
+

^- I wonder if it might be faster to include the offset_str inside the
'date_fmt' string, since it would require one less string...

Read more...

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

John A Meinel wrote:
> I'll try to give a bit of a summary as to why I think this is faster,
> though it would be nice if you would give a similar summary when submitting.
>
>
[snip]
That's an excellent summary - thanks.
> IMO the batching of everything for one revision seems just about right.
>
> I'm a bit concerned, though, about handling Unicode Author names and
> UTF-8 'diff' strings. My guess is that this actually breaks it, and we
> just don't know it yet.
>
> Can you please ensure that we have a test with non-ascii file content
> (even better if it isn't even UTF-8 since we don't assert anything about
> file content encoding), and then also include a non-ascii Author name.
> That ensures that we want to output Unicode for the name, and something
> random for the diff content.
>
I'm confident diff strings are handled correctly because Alexander
submitted a bug fixing this for 1.13. Note that diff strings aren't
written to self.to_file (in the code), but to a wrapped version thereof.

I suspect we do have tests for Unicode authors but I'll confirm that
(probably tomorrow).
> 5) I'm a bit curious about this:
> === modified file 'bzrlib/osutils.py'
> --- bzrlib/osutils.py 2009-05-23 04:55:52 +0000
> +++ bzrlib/osutils.py 2009-05-25 05:46:01 +0000
> @@ -686,6 +686,8 @@
> return offset.days * 86400 + offset.seconds
>
> weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
> +_default_format_by_weekday_num = [wd + " %Y-%m-%d %H:%M:%S" for wd in
> weekdays]
> +
>
> ^- Does this mean that we are skipping localization? So someone with
> de_DE.UTF-8 will no longer see the German short-names for days of the week?
>
Yes it does. There's a separate function - format_local_date IIRC - that
doesn't use English day names. I'll run annotate and find out why we're
doing this but, at a wild guess, it might be related to ensuring tests
pass regardless of locale?

FWIW, I'm *not* changing current behaviour here. If we what to
change/fix the code, I'd like to make that a separate patch if that's OK.

Ian C.

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

So checking our existing test coverage, we have log tests for unicode messages and unicode diff content but not for unicode committer/authors. Do I need to add those before landing this? My preference is to put that task on my list but not do it immediately, given all I'm doing is replacing "multiple outf.write()" calls with "collecting lines & calling outf.write() once". I can't see how that can break things or how it reduces test coverage?

Otherwise, your suggested tweaks are fine by me.

Revision history for this message
Martin Pool (mbp) wrote :

2009/5/29 Ian Clatworthy <email address hidden>:
> So checking our existing test coverage, we have log tests for unicode messages and unicode diff content but not for unicode committer/authors. Do I need to add those before landing this? My preference is to put that task on my list but not do it immediately, given all I'm doing is replacing "multiple outf.write()" calls with "collecting lines & calling outf.write() once". I can't see how that can break things or how it reduces test coverage?

It's down to you, but I think those tests will be fairly easy to
write, and it's probably better not to have it hanging around.

--
Martin <http://launchpad.net/~mbp/>

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'NEWS'
--- NEWS 2009-08-30 23:51:10 +0000
+++ NEWS 2009-08-31 04:38:13 +0000
@@ -972,6 +972,8 @@
972 (Alexander Belchenko, Jelmer Vernooij, John Arbash Meinel)972 (Alexander Belchenko, Jelmer Vernooij, John Arbash Meinel)
973973
974974
975* ``bzr log`` is now faster. (Ian Clatworthy)
976
975Bug Fixes977Bug Fixes
976*********978*********
977979
978980
=== modified file 'bzrlib/log.py'
--- bzrlib/log.py 2009-06-10 03:56:49 +0000
+++ bzrlib/log.py 2009-08-31 04:38:13 +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-07-23 16:01:17 +0000
+++ bzrlib/osutils.py 2009-08-31 04:38:13 +0000
@@ -687,6 +687,8 @@
687 return offset.days * 86400 + offset.seconds687 return offset.days * 86400 + offset.seconds
688688
689weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']689weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
690_default_format_by_weekday_num = [wd + " %Y-%m-%d %H:%M:%S" for wd in weekdays]
691
690692
691def format_date(t, offset=0, timezone='original', date_fmt=None,693def format_date(t, offset=0, timezone='original', date_fmt=None,
692 show_offset=True):694 show_offset=True):
@@ -706,6 +708,32 @@
706 date_str = time.strftime(date_fmt, tt)708 date_str = time.strftime(date_fmt, tt)
707 return date_str + offset_str709 return date_str + offset_str
708710
711
712# Cache of formatted offset strings
713_offset_cache = {}
714
715
716def format_date_with_offset_in_original_timezone(t, offset=0,
717 _cache=_offset_cache):
718 """Return a formatted date string in the original timezone.
719
720 This routine may be faster then format_date.
721
722 :param t: Seconds since the epoch.
723 :param offset: Timezone offset in seconds east of utc.
724 """
725 if offset is None:
726 offset = 0
727 tt = time.gmtime(t + offset)
728 date_fmt = _default_format_by_weekday_num[tt[6]]
729 date_str = time.strftime(date_fmt, tt)
730 offset_str = _cache.get(offset, None)
731 if offset_str is None:
732 offset_str = ' %+03d%02d' % (offset / 3600, (offset / 60) % 60)
733 _cache[offset] = offset_str
734 return date_str + offset_str
735
736
709def format_local_date(t, offset=0, timezone='original', date_fmt=None,737def format_local_date(t, offset=0, timezone='original', date_fmt=None,
710 show_offset=True):738 show_offset=True):
711 """Return an unicode date string formatted according to the current locale.739 """Return an unicode date string formatted according to the current locale.
@@ -725,6 +753,7 @@
725 date_str = date_str.decode(get_user_encoding(), 'replace')753 date_str = date_str.decode(get_user_encoding(), 'replace')
726 return date_str + offset_str754 return date_str + offset_str
727755
756
728def _format_date(t, offset, timezone, date_fmt, show_offset):757def _format_date(t, offset, timezone, date_fmt, show_offset):
729 if timezone == 'utc':758 if timezone == 'utc':
730 tt = time.gmtime(t)759 tt = time.gmtime(t)
731760
=== modified file 'bzrlib/revision.py'
--- bzrlib/revision.py 2009-06-30 16:16:55 +0000
+++ bzrlib/revision.py 2009-08-31 04:38:14 +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-07-23 16:01:17 +0000
+++ bzrlib/tests/test_osutils.py 2009-08-31 04:38:14 +0000
@@ -365,6 +365,14 @@
365 # Instead blackbox.test_locale should check for localized365 # Instead blackbox.test_locale should check for localized
366 # dates once they do occur in output strings.366 # dates once they do occur in output strings.
367367
368 def test_format_date_with_offset_in_original_timezone(self):
369 self.assertEqual("Thu 1970-01-01 00:00:00 +0000",
370 osutils.format_date_with_offset_in_original_timezone(0))
371 self.assertEqual("Fri 1970-01-02 03:46:40 +0000",
372 osutils.format_date_with_offset_in_original_timezone(100000))
373 self.assertEqual("Fri 1970-01-02 05:46:40 +0200",
374 osutils.format_date_with_offset_in_original_timezone(100000, 7200))
375
368 def test_local_time_offset(self):376 def test_local_time_offset(self):
369 """Test that local_time_offset() returns a sane value."""377 """Test that local_time_offset() returns a sane value."""
370 offset = osutils.local_time_offset()378 offset = osutils.local_time_offset()