Merge lp:~ian-clatworthy/bzr/faster-log.old into lp:~bzr/bzr/trunk-old
- faster-log.old
- Merge into trunk-old
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
bzr-core | Pending | ||
Review via email: mp+6769@code.launchpad.net |
Commit message
Description of the change
Ian Clatworthy (ian-clatworthy) wrote : | # |
John A Meinel (jameinel) wrote : | # |
-----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(
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_
2) Factor out "custom_properties" into a re-used helper function.
3) Add a new function: format_
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_
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_
+ _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_
+ date_str = time.strftime(
+ 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...
John A Meinel (jameinel) wrote : | # |
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(
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_
2) Factor out "custom_properties" into a re-used helper function.
3) Add a new function: format_
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_
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_
+ _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_
+ date_str = time.strftime(
+ 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...
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_
> 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.
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.
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://
Preview Diff
1 | === modified file 'NEWS' | |||
2 | --- NEWS 2009-08-30 23:51:10 +0000 | |||
3 | +++ NEWS 2009-08-31 04:38:13 +0000 | |||
4 | @@ -972,6 +972,8 @@ | |||
5 | 972 | (Alexander Belchenko, Jelmer Vernooij, John Arbash Meinel) | 972 | (Alexander Belchenko, Jelmer Vernooij, John Arbash Meinel) |
6 | 973 | 973 | ||
7 | 974 | 974 | ||
8 | 975 | * ``bzr log`` is now faster. (Ian Clatworthy) | ||
9 | 976 | |||
10 | 975 | Bug Fixes | 977 | Bug Fixes |
11 | 976 | ********* | 978 | ********* |
12 | 977 | 979 | ||
13 | 978 | 980 | ||
14 | === modified file 'bzrlib/log.py' | |||
15 | --- bzrlib/log.py 2009-06-10 03:56:49 +0000 | |||
16 | +++ bzrlib/log.py 2009-08-31 04:38:13 +0000 | |||
17 | @@ -83,6 +83,7 @@ | |||
18 | 83 | ) | 83 | ) |
19 | 84 | from bzrlib.osutils import ( | 84 | from bzrlib.osutils import ( |
20 | 85 | format_date, | 85 | format_date, |
21 | 86 | format_date_with_offset_in_original_timezone, | ||
22 | 86 | get_terminal_encoding, | 87 | get_terminal_encoding, |
23 | 87 | re_compile_checked, | 88 | re_compile_checked, |
24 | 88 | terminal_width, | 89 | terminal_width, |
25 | @@ -384,27 +385,28 @@ | |||
26 | 384 | :return: An iterator yielding LogRevision objects. | 385 | :return: An iterator yielding LogRevision objects. |
27 | 385 | """ | 386 | """ |
28 | 386 | rqst = self.rqst | 387 | rqst = self.rqst |
29 | 388 | levels = rqst.get('levels') | ||
30 | 389 | limit = rqst.get('limit') | ||
31 | 390 | diff_type = rqst.get('diff_type') | ||
32 | 387 | log_count = 0 | 391 | log_count = 0 |
33 | 388 | revision_iterator = self._create_log_revision_iterator() | 392 | revision_iterator = self._create_log_revision_iterator() |
34 | 389 | for revs in revision_iterator: | 393 | for revs in revision_iterator: |
35 | 390 | for (rev_id, revno, merge_depth), rev, delta in revs: | 394 | for (rev_id, revno, merge_depth), rev, delta in revs: |
36 | 391 | # 0 levels means show everything; merge_depth counts from 0 | 395 | # 0 levels means show everything; merge_depth counts from 0 |
37 | 392 | levels = rqst.get('levels') | ||
38 | 393 | if levels != 0 and merge_depth >= levels: | 396 | if levels != 0 and merge_depth >= levels: |
39 | 394 | continue | 397 | continue |
41 | 395 | diff = self._format_diff(rev, rev_id) | 398 | if diff_type is None: |
42 | 399 | diff = None | ||
43 | 400 | else: | ||
44 | 401 | diff = self._format_diff(rev, rev_id, diff_type) | ||
45 | 396 | yield LogRevision(rev, revno, merge_depth, delta, | 402 | yield LogRevision(rev, revno, merge_depth, delta, |
46 | 397 | self.rev_tag_dict.get(rev_id), diff) | 403 | self.rev_tag_dict.get(rev_id), diff) |
47 | 398 | limit = rqst.get('limit') | ||
48 | 399 | if limit: | 404 | if limit: |
49 | 400 | log_count += 1 | 405 | log_count += 1 |
50 | 401 | if log_count >= limit: | 406 | if log_count >= limit: |
51 | 402 | return | 407 | return |
52 | 403 | 408 | ||
57 | 404 | def _format_diff(self, rev, rev_id): | 409 | def _format_diff(self, rev, rev_id, diff_type): |
54 | 405 | diff_type = self.rqst.get('diff_type') | ||
55 | 406 | if diff_type is None: | ||
56 | 407 | return None | ||
58 | 408 | repo = self.branch.repository | 410 | repo = self.branch.repository |
59 | 409 | if len(rev.parent_ids) == 0: | 411 | if len(rev.parent_ids) == 0: |
60 | 410 | ancestor_id = _mod_revision.NULL_REVISION | 412 | ancestor_id = _mod_revision.NULL_REVISION |
61 | @@ -1291,7 +1293,7 @@ | |||
62 | 1291 | preferred_levels = 0 | 1293 | preferred_levels = 0 |
63 | 1292 | 1294 | ||
64 | 1293 | def __init__(self, to_file, show_ids=False, show_timezone='original', | 1295 | def __init__(self, to_file, show_ids=False, show_timezone='original', |
66 | 1294 | delta_format=None, levels=None, show_advice=False): | 1296 | delta_format=None, levels=None, show_advice=False): |
67 | 1295 | """Create a LogFormatter. | 1297 | """Create a LogFormatter. |
68 | 1296 | 1298 | ||
69 | 1297 | :param to_file: the file to output to | 1299 | :param to_file: the file to output to |
70 | @@ -1367,48 +1369,64 @@ | |||
71 | 1367 | else: | 1369 | else: |
72 | 1368 | return '' | 1370 | return '' |
73 | 1369 | 1371 | ||
75 | 1370 | def show_foreign_info(self, rev, indent): | 1372 | def show_properties(self, revision, indent): |
76 | 1373 | """Displays the custom properties returned by each registered handler. | ||
77 | 1374 | |||
78 | 1375 | If a registered handler raises an error it is propagated. | ||
79 | 1376 | """ | ||
80 | 1377 | for line in self.custom_properties(revision): | ||
81 | 1378 | self.to_file.write("%s%s\n" % (indent, line)) | ||
82 | 1379 | |||
83 | 1380 | def custom_properties(self, revision): | ||
84 | 1381 | """Format the custom properties returned by each registered handler. | ||
85 | 1382 | |||
86 | 1383 | If a registered handler raises an error it is propagated. | ||
87 | 1384 | |||
88 | 1385 | :return: a list of formatted lines (excluding trailing newlines) | ||
89 | 1386 | """ | ||
90 | 1387 | lines = self._foreign_info_properties(revision) | ||
91 | 1388 | for key, handler in properties_handler_registry.iteritems(): | ||
92 | 1389 | lines.extend(self._format_properties(handler(revision))) | ||
93 | 1390 | return lines | ||
94 | 1391 | |||
95 | 1392 | def _foreign_info_properties(self, rev): | ||
96 | 1371 | """Custom log displayer for foreign revision identifiers. | 1393 | """Custom log displayer for foreign revision identifiers. |
97 | 1372 | 1394 | ||
98 | 1373 | :param rev: Revision object. | 1395 | :param rev: Revision object. |
99 | 1374 | """ | 1396 | """ |
100 | 1375 | # Revision comes directly from a foreign repository | 1397 | # Revision comes directly from a foreign repository |
101 | 1376 | if isinstance(rev, foreign.ForeignRevision): | 1398 | if isinstance(rev, foreign.ForeignRevision): |
105 | 1377 | self._write_properties(indent, rev.mapping.vcs.show_foreign_revid( | 1399 | return rev.mapping.vcs.show_foreign_revid(rev.foreign_revid) |
103 | 1378 | rev.foreign_revid)) | ||
104 | 1379 | return | ||
106 | 1380 | 1400 | ||
107 | 1381 | # Imported foreign revision revision ids always contain : | 1401 | # Imported foreign revision revision ids always contain : |
108 | 1382 | if not ":" in rev.revision_id: | 1402 | if not ":" in rev.revision_id: |
110 | 1383 | return | 1403 | return [] |
111 | 1384 | 1404 | ||
112 | 1385 | # Revision was once imported from a foreign repository | 1405 | # Revision was once imported from a foreign repository |
113 | 1386 | try: | 1406 | try: |
114 | 1387 | foreign_revid, mapping = \ | 1407 | foreign_revid, mapping = \ |
115 | 1388 | foreign.foreign_vcs_registry.parse_revision_id(rev.revision_id) | 1408 | foreign.foreign_vcs_registry.parse_revision_id(rev.revision_id) |
116 | 1389 | except errors.InvalidRevisionId: | 1409 | except errors.InvalidRevisionId: |
118 | 1390 | return | 1410 | return [] |
119 | 1391 | 1411 | ||
121 | 1392 | self._write_properties(indent, | 1412 | return self._format_properties( |
122 | 1393 | mapping.vcs.show_foreign_revid(foreign_revid)) | 1413 | mapping.vcs.show_foreign_revid(foreign_revid)) |
123 | 1394 | 1414 | ||
133 | 1395 | def show_properties(self, revision, indent): | 1415 | def _format_properties(self, properties): |
134 | 1396 | """Displays the custom properties returned by each registered handler. | 1416 | lines = [] |
126 | 1397 | |||
127 | 1398 | If a registered handler raises an error it is propagated. | ||
128 | 1399 | """ | ||
129 | 1400 | for key, handler in properties_handler_registry.iteritems(): | ||
130 | 1401 | self._write_properties(indent, handler(revision)) | ||
131 | 1402 | |||
132 | 1403 | def _write_properties(self, indent, properties): | ||
135 | 1404 | for key, value in properties.items(): | 1417 | for key, value in properties.items(): |
137 | 1405 | self.to_file.write(indent + key + ': ' + value + '\n') | 1418 | lines.append(key + ': ' + value) |
138 | 1419 | return lines | ||
139 | 1406 | 1420 | ||
140 | 1407 | def show_diff(self, to_file, diff, indent): | 1421 | def show_diff(self, to_file, diff, indent): |
141 | 1408 | for l in diff.rstrip().split('\n'): | 1422 | for l in diff.rstrip().split('\n'): |
142 | 1409 | to_file.write(indent + '%s\n' % (l,)) | 1423 | to_file.write(indent + '%s\n' % (l,)) |
143 | 1410 | 1424 | ||
144 | 1411 | 1425 | ||
145 | 1426 | # Separator between revisions in long format | ||
146 | 1427 | _LONG_SEP = '-' * 60 | ||
147 | 1428 | |||
148 | 1429 | |||
149 | 1412 | class LongLogFormatter(LogFormatter): | 1430 | class LongLogFormatter(LogFormatter): |
150 | 1413 | 1431 | ||
151 | 1414 | supports_merge_revisions = True | 1432 | supports_merge_revisions = True |
152 | @@ -1417,46 +1435,59 @@ | |||
153 | 1417 | supports_tags = True | 1435 | supports_tags = True |
154 | 1418 | supports_diff = True | 1436 | supports_diff = True |
155 | 1419 | 1437 | ||
156 | 1438 | def __init__(self, *args, **kwargs): | ||
157 | 1439 | super(LongLogFormatter, self).__init__(*args, **kwargs) | ||
158 | 1440 | if self.show_timezone == 'original': | ||
159 | 1441 | self.date_string = self._date_string_original_timezone | ||
160 | 1442 | else: | ||
161 | 1443 | self.date_string = self._date_string_with_timezone | ||
162 | 1444 | |||
163 | 1445 | def _date_string_with_timezone(self, rev): | ||
164 | 1446 | return format_date(rev.timestamp, rev.timezone or 0, | ||
165 | 1447 | self.show_timezone) | ||
166 | 1448 | |||
167 | 1449 | def _date_string_original_timezone(self, rev): | ||
168 | 1450 | return format_date_with_offset_in_original_timezone(rev.timestamp, | ||
169 | 1451 | rev.timezone or 0) | ||
170 | 1452 | |||
171 | 1420 | def log_revision(self, revision): | 1453 | def log_revision(self, revision): |
172 | 1421 | """Log a revision, either merged or not.""" | 1454 | """Log a revision, either merged or not.""" |
173 | 1422 | indent = ' ' * revision.merge_depth | 1455 | indent = ' ' * revision.merge_depth |
176 | 1423 | to_file = self.to_file | 1456 | lines = [_LONG_SEP] |
175 | 1424 | to_file.write(indent + '-' * 60 + '\n') | ||
177 | 1425 | if revision.revno is not None: | 1457 | if revision.revno is not None: |
179 | 1426 | to_file.write(indent + 'revno: %s%s\n' % (revision.revno, | 1458 | lines.append('revno: %s%s' % (revision.revno, |
180 | 1427 | self.merge_marker(revision))) | 1459 | self.merge_marker(revision))) |
181 | 1428 | if revision.tags: | 1460 | if revision.tags: |
183 | 1429 | to_file.write(indent + 'tags: %s\n' % (', '.join(revision.tags))) | 1461 | lines.append('tags: %s' % (', '.join(revision.tags))) |
184 | 1430 | if self.show_ids: | 1462 | if self.show_ids: |
187 | 1431 | to_file.write(indent + 'revision-id: ' + revision.rev.revision_id) | 1463 | lines.append('revision-id: %s' % (revision.rev.revision_id,)) |
186 | 1432 | to_file.write('\n') | ||
188 | 1433 | for parent_id in revision.rev.parent_ids: | 1464 | for parent_id in revision.rev.parent_ids: |
192 | 1434 | to_file.write(indent + 'parent: %s\n' % (parent_id,)) | 1465 | lines.append('parent: %s' % (parent_id,)) |
193 | 1435 | self.show_foreign_info(revision.rev, indent) | 1466 | lines.extend(self.custom_properties(revision.rev)) |
191 | 1436 | self.show_properties(revision.rev, indent) | ||
194 | 1437 | 1467 | ||
195 | 1438 | committer = revision.rev.committer | 1468 | committer = revision.rev.committer |
196 | 1439 | authors = revision.rev.get_apparent_authors() | 1469 | authors = revision.rev.get_apparent_authors() |
197 | 1440 | if authors != [committer]: | 1470 | if authors != [committer]: |
200 | 1441 | to_file.write(indent + 'author: %s\n' % (", ".join(authors),)) | 1471 | lines.append('author: %s' % (", ".join(authors),)) |
201 | 1442 | to_file.write(indent + 'committer: %s\n' % (committer,)) | 1472 | lines.append('committer: %s' % (committer,)) |
202 | 1443 | 1473 | ||
203 | 1444 | branch_nick = revision.rev.properties.get('branch-nick', None) | 1474 | branch_nick = revision.rev.properties.get('branch-nick', None) |
204 | 1445 | if branch_nick is not None: | 1475 | if branch_nick is not None: |
213 | 1446 | to_file.write(indent + 'branch nick: %s\n' % (branch_nick,)) | 1476 | lines.append('branch nick: %s' % (branch_nick,)) |
214 | 1447 | 1477 | ||
215 | 1448 | date_str = format_date(revision.rev.timestamp, | 1478 | lines.append('timestamp: %s' % (self.date_string(revision.rev),)) |
216 | 1449 | revision.rev.timezone or 0, | 1479 | |
217 | 1450 | self.show_timezone) | 1480 | lines.append('message:') |
210 | 1451 | to_file.write(indent + 'timestamp: %s\n' % (date_str,)) | ||
211 | 1452 | |||
212 | 1453 | to_file.write(indent + 'message:\n') | ||
218 | 1454 | if not revision.rev.message: | 1481 | if not revision.rev.message: |
220 | 1455 | to_file.write(indent + ' (no message)\n') | 1482 | lines.append(' (no message)') |
221 | 1456 | else: | 1483 | else: |
222 | 1457 | message = revision.rev.message.rstrip('\r\n') | 1484 | message = revision.rev.message.rstrip('\r\n') |
223 | 1458 | for l in message.split('\n'): | 1485 | for l in message.split('\n'): |
225 | 1459 | to_file.write(indent + ' %s\n' % (l,)) | 1486 | lines.append(' %s' % (l,)) |
226 | 1487 | |||
227 | 1488 | # Dump the output, appending the delta and diff if requested | ||
228 | 1489 | to_file = self.to_file | ||
229 | 1490 | to_file.write("%s%s\n" % (indent, ('\n' + indent).join(lines))) | ||
230 | 1460 | if revision.delta is not None: | 1491 | if revision.delta is not None: |
231 | 1461 | # We don't respect delta_format for compatibility | 1492 | # We don't respect delta_format for compatibility |
232 | 1462 | revision.delta.show(to_file, self.show_ids, indent=indent, | 1493 | revision.delta.show(to_file, self.show_ids, indent=indent, |
233 | @@ -1515,7 +1546,6 @@ | |||
234 | 1515 | self.show_timezone, date_fmt="%Y-%m-%d", | 1546 | self.show_timezone, date_fmt="%Y-%m-%d", |
235 | 1516 | show_offset=False), | 1547 | show_offset=False), |
236 | 1517 | tags, self.merge_marker(revision))) | 1548 | tags, self.merge_marker(revision))) |
237 | 1518 | self.show_foreign_info(revision.rev, indent+offset) | ||
238 | 1519 | self.show_properties(revision.rev, indent+offset) | 1549 | self.show_properties(revision.rev, indent+offset) |
239 | 1520 | if self.show_ids: | 1550 | if self.show_ids: |
240 | 1521 | to_file.write(indent + offset + 'revision-id:%s\n' | 1551 | to_file.write(indent + offset + 'revision-id:%s\n' |
241 | 1522 | 1552 | ||
242 | === modified file 'bzrlib/osutils.py' | |||
243 | --- bzrlib/osutils.py 2009-07-23 16:01:17 +0000 | |||
244 | +++ bzrlib/osutils.py 2009-08-31 04:38:13 +0000 | |||
245 | @@ -687,6 +687,8 @@ | |||
246 | 687 | return offset.days * 86400 + offset.seconds | 687 | return offset.days * 86400 + offset.seconds |
247 | 688 | 688 | ||
248 | 689 | weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] | 689 | weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] |
249 | 690 | _default_format_by_weekday_num = [wd + " %Y-%m-%d %H:%M:%S" for wd in weekdays] | ||
250 | 691 | |||
251 | 690 | 692 | ||
252 | 691 | def format_date(t, offset=0, timezone='original', date_fmt=None, | 693 | def format_date(t, offset=0, timezone='original', date_fmt=None, |
253 | 692 | show_offset=True): | 694 | show_offset=True): |
254 | @@ -706,6 +708,32 @@ | |||
255 | 706 | date_str = time.strftime(date_fmt, tt) | 708 | date_str = time.strftime(date_fmt, tt) |
256 | 707 | return date_str + offset_str | 709 | return date_str + offset_str |
257 | 708 | 710 | ||
258 | 711 | |||
259 | 712 | # Cache of formatted offset strings | ||
260 | 713 | _offset_cache = {} | ||
261 | 714 | |||
262 | 715 | |||
263 | 716 | def format_date_with_offset_in_original_timezone(t, offset=0, | ||
264 | 717 | _cache=_offset_cache): | ||
265 | 718 | """Return a formatted date string in the original timezone. | ||
266 | 719 | |||
267 | 720 | This routine may be faster then format_date. | ||
268 | 721 | |||
269 | 722 | :param t: Seconds since the epoch. | ||
270 | 723 | :param offset: Timezone offset in seconds east of utc. | ||
271 | 724 | """ | ||
272 | 725 | if offset is None: | ||
273 | 726 | offset = 0 | ||
274 | 727 | tt = time.gmtime(t + offset) | ||
275 | 728 | date_fmt = _default_format_by_weekday_num[tt[6]] | ||
276 | 729 | date_str = time.strftime(date_fmt, tt) | ||
277 | 730 | offset_str = _cache.get(offset, None) | ||
278 | 731 | if offset_str is None: | ||
279 | 732 | offset_str = ' %+03d%02d' % (offset / 3600, (offset / 60) % 60) | ||
280 | 733 | _cache[offset] = offset_str | ||
281 | 734 | return date_str + offset_str | ||
282 | 735 | |||
283 | 736 | |||
284 | 709 | def format_local_date(t, offset=0, timezone='original', date_fmt=None, | 737 | def format_local_date(t, offset=0, timezone='original', date_fmt=None, |
285 | 710 | show_offset=True): | 738 | show_offset=True): |
286 | 711 | """Return an unicode date string formatted according to the current locale. | 739 | """Return an unicode date string formatted according to the current locale. |
287 | @@ -725,6 +753,7 @@ | |||
288 | 725 | date_str = date_str.decode(get_user_encoding(), 'replace') | 753 | date_str = date_str.decode(get_user_encoding(), 'replace') |
289 | 726 | return date_str + offset_str | 754 | return date_str + offset_str |
290 | 727 | 755 | ||
291 | 756 | |||
292 | 728 | def _format_date(t, offset, timezone, date_fmt, show_offset): | 757 | def _format_date(t, offset, timezone, date_fmt, show_offset): |
293 | 729 | if timezone == 'utc': | 758 | if timezone == 'utc': |
294 | 730 | tt = time.gmtime(t) | 759 | tt = time.gmtime(t) |
295 | 731 | 760 | ||
296 | === modified file 'bzrlib/revision.py' | |||
297 | --- bzrlib/revision.py 2009-06-30 16:16:55 +0000 | |||
298 | +++ bzrlib/revision.py 2009-08-31 04:38:14 +0000 | |||
299 | @@ -54,8 +54,11 @@ | |||
300 | 54 | 54 | ||
301 | 55 | def __init__(self, revision_id, properties=None, **args): | 55 | def __init__(self, revision_id, properties=None, **args): |
302 | 56 | self.revision_id = revision_id | 56 | self.revision_id = revision_id |
305 | 57 | self.properties = properties or {} | 57 | if properties is None: |
306 | 58 | self._check_properties() | 58 | self.properties = {} |
307 | 59 | else: | ||
308 | 60 | self.properties = properties | ||
309 | 61 | self._check_properties() | ||
310 | 59 | self.committer = None | 62 | self.committer = None |
311 | 60 | self.parent_ids = [] | 63 | self.parent_ids = [] |
312 | 61 | self.parent_sha1s = [] | 64 | self.parent_sha1s = [] |
313 | 62 | 65 | ||
314 | === modified file 'bzrlib/tests/test_osutils.py' | |||
315 | --- bzrlib/tests/test_osutils.py 2009-07-23 16:01:17 +0000 | |||
316 | +++ bzrlib/tests/test_osutils.py 2009-08-31 04:38:14 +0000 | |||
317 | @@ -365,6 +365,14 @@ | |||
318 | 365 | # Instead blackbox.test_locale should check for localized | 365 | # Instead blackbox.test_locale should check for localized |
319 | 366 | # dates once they do occur in output strings. | 366 | # dates once they do occur in output strings. |
320 | 367 | 367 | ||
321 | 368 | def test_format_date_with_offset_in_original_timezone(self): | ||
322 | 369 | self.assertEqual("Thu 1970-01-01 00:00:00 +0000", | ||
323 | 370 | osutils.format_date_with_offset_in_original_timezone(0)) | ||
324 | 371 | self.assertEqual("Fri 1970-01-02 03:46:40 +0000", | ||
325 | 372 | osutils.format_date_with_offset_in_original_timezone(100000)) | ||
326 | 373 | self.assertEqual("Fri 1970-01-02 05:46:40 +0200", | ||
327 | 374 | osutils.format_date_with_offset_in_original_timezone(100000, 7200)) | ||
328 | 375 | |||
329 | 368 | def test_local_time_offset(self): | 376 | def test_local_time_offset(self): |
330 | 369 | """Test that local_time_offset() returns a sane value.""" | 377 | """Test that local_time_offset() returns a sane value.""" |
331 | 370 | offset = osutils.local_time_offset() | 378 | offset = osutils.local_time_offset() |
This patch makes log --long faster by around 10%. For OpenOffice.org with 260K revisions, the time drops from 50 seconds to 46 seconds.