191
172
# list indexes are 0-based; revisions are 1-based
192
173
cut_revs = which_revs[(start_revision-1):(end_revision)]
196
# convert the revision history to a dictionary:
197
rev_nos = dict((k, v) for v, k in cut_revs)
199
# override the mainline to look like the revision history.
200
mainline_revs = [revision_id for index, revision_id in cut_revs]
201
if cut_revs[0][0] == 1:
202
mainline_revs.insert(0, None)
204
mainline_revs.insert(0, which_revs[start_revision-2][1])
205
if getattr(lf, 'show_merge', None) is not None:
206
include_merges = True
208
include_merges = False
209
view_revisions = list(get_view_revisions(mainline_revs, rev_nos, branch,
210
direction, include_merges=include_merges))
212
def iter_revisions():
213
# r = revision, n = revno, d = merge depth
214
revision_ids = [r for r, n, d in view_revisions]
215
zeros = set(r for r, n, d in view_revisions if d == 0)
217
repository = branch.repository
220
revisions = repository.get_revisions(revision_ids[:num])
221
if verbose or specific_fileid:
222
delta_revisions = [r for r in revisions if
223
r.revision_id in zeros]
224
deltas = repository.get_deltas_for_revisions(delta_revisions)
225
cur_deltas = dict(izip((r.revision_id for r in
226
delta_revisions), deltas))
227
for revision in revisions:
228
# The delta value will be None unless
229
# 1. verbose or specific_fileid is specified, and
230
# 2. the revision is a mainline revision
231
yield revision, cur_deltas.get(revision.revision_id)
232
revision_ids = revision_ids[num:]
175
if direction == 'reverse':
177
elif direction == 'forward':
180
raise ValueError('invalid direction %r' % direction)
182
for revno, rev_id in cut_revs:
183
if verbose or specific_fileid:
184
delta = branch.get_revision_delta(revno)
235
# now we just print all the revisions
236
for ((rev_id, revno, merge_depth), (rev, delta)) in \
237
izip(view_revisions, iter_revisions()):
187
if not delta.touches_file_id(specific_fileid):
191
# although we calculated it, throw it away without display
194
rev = branch.get_revision(rev_id)
240
197
if not searchRE.search(rev.message):
244
# a mainline revision.
247
if not delta.touches_file_id(specific_fileid):
200
lf.show(revno, rev, delta)
204
def deltas_for_log_dummy(branch, which_revs):
205
"""Return all the revisions without intermediate deltas.
207
Useful for log commands that won't need the delta information.
251
# although we calculated it, throw it away without display
254
lf.show(revno, rev, delta)
256
lf.show_merge(rev, merge_depth)
259
def get_view_revisions(mainline_revs, rev_nos, branch, direction,
260
include_merges=True):
261
"""Produce an iterator of revisions to show
262
:return: an iterator of (revision_id, revno, merge_depth)
263
(if there is no revno for a revision, None is supplied)
265
if include_merges is False:
266
revision_ids = mainline_revs[1:]
267
if direction == 'reverse':
268
revision_ids.reverse()
269
for revision_id in revision_ids:
270
yield revision_id, rev_nos[revision_id], 0
272
merge_sorted_revisions = merge_sort(
273
branch.repository.get_revision_graph(mainline_revs[-1]),
277
if direction == 'forward':
278
# forward means oldest first.
279
merge_sorted_revisions = reverse_by_depth(merge_sorted_revisions)
280
elif direction != 'reverse':
281
raise ValueError('invalid direction %r' % direction)
283
revision_history = branch.revision_history()
285
for sequence, rev_id, merge_depth, end_of_merge in merge_sorted_revisions:
286
yield rev_id, rev_nos.get(rev_id), merge_depth
289
def reverse_by_depth(merge_sorted_revisions, _depth=0):
290
"""Reverse revisions by depth.
292
Revisions with a different depth are sorted as a group with the previous
293
revision of that depth. There may be no topological justification for this,
294
but it looks much nicer.
297
for val in merge_sorted_revisions:
299
zd_revisions.append([val])
301
assert val[2] > _depth
302
zd_revisions[-1].append(val)
303
for revisions in zd_revisions:
304
if len(revisions) > 1:
305
revisions[1:] = reverse_by_depth(revisions[1:], _depth + 1)
306
zd_revisions.reverse()
308
for chunk in zd_revisions:
210
for revno, revision_id in which_revs:
211
yield revno, branch.get_revision(revision_id), None
214
def deltas_for_log_reverse(branch, which_revs):
215
"""Compute deltas for display in latest-to-earliest order.
221
Sequence of (revno, revision_id) for the subset of history to examine
224
Sequence of (revno, rev, delta)
226
The delta is from the given revision to the next one in the
227
sequence, which makes sense if the log is being displayed from
230
last_revno = last_revision_id = last_tree = None
231
for revno, revision_id in which_revs:
232
this_tree = branch.revision_tree(revision_id)
233
this_revision = branch.get_revision(revision_id)
236
yield last_revno, last_revision, compare_trees(this_tree, last_tree, False)
238
this_tree = EmptyTree(branch.get_root_id())
241
last_revision = this_revision
242
last_tree = this_tree
246
this_tree = EmptyTree(branch.get_root_id())
248
this_revno = last_revno - 1
249
this_revision_id = branch.revision_history()[this_revno]
250
this_tree = branch.revision_tree(this_revision_id)
251
yield last_revno, last_revision, compare_trees(this_tree, last_tree, False)
254
def deltas_for_log_forward(branch, which_revs):
255
"""Compute deltas for display in forward log.
257
Given a sequence of (revno, revision_id) pairs, return
260
The delta is from the given revision to the next one in the
261
sequence, which makes sense if the log is being displayed from
264
last_revno = last_revision_id = last_tree = None
265
prev_tree = EmptyTree(branch.get_root_id())
267
for revno, revision_id in which_revs:
268
this_tree = branch.revision_tree(revision_id)
269
this_revision = branch.get_revision(revision_id)
273
last_tree = EmptyTree(branch.get_root_id())
275
last_revno = revno - 1
276
last_revision_id = branch.revision_history()[last_revno]
277
last_tree = branch.revision_tree(last_revision_id)
279
yield revno, this_revision, compare_trees(last_tree, this_tree, False)
282
last_revision = this_revision
283
last_tree = this_tree
313
286
class LogFormatter(object):
314
287
"""Abstract class to display log messages."""
316
288
def __init__(self, to_file, show_ids=False, show_timezone='original'):
317
289
self.to_file = to_file
318
290
self.show_ids = show_ids
319
291
self.show_timezone = show_timezone
321
294
def show(self, revno, rev, delta):
322
295
raise NotImplementedError('not implemented in abstract base')
324
def short_committer(self, rev):
325
return re.sub('<.*@.*>', '', rev.committer).strip(' ')
328
302
class LongLogFormatter(LogFormatter):
329
303
def show(self, revno, rev, delta):
330
return self._show_helper(revno=revno, rev=rev, delta=delta)
332
def show_merge(self, rev, merge_depth):
333
return self._show_helper(rev=rev, indent=' '*merge_depth, merged=True, delta=None)
335
def _show_helper(self, rev=None, revno=None, indent='', merged=False, delta=None):
336
"""Show a revision, either merged or not."""
337
from bzrlib.osutils import format_date
304
from osutils import format_date
338
306
to_file = self.to_file
339
print >>to_file, indent+'-' * 60
340
if revno is not None:
341
print >>to_file, 'revno:', revno
343
print >>to_file, indent+'merged:', rev.revision_id
345
print >>to_file, indent+'revision-id:', rev.revision_id
308
print >>to_file, '-' * 60
309
print >>to_file, 'revno:', revno
346
310
if self.show_ids:
347
for parent_id in rev.parent_ids:
348
print >>to_file, indent+'parent:', parent_id
349
print >>to_file, indent+'committer:', rev.committer
351
print >>to_file, indent+'branch nick: %s' % \
352
rev.properties['branch-nick']
311
print >>to_file, 'revision-id:', rev.revision_id
313
for parent in rev.parents:
314
print >>to_file, 'parent:', parent.revision_id
316
print >>to_file, 'committer:', rev.committer
355
318
date_str = format_date(rev.timestamp,
356
319
rev.timezone or 0,
357
320
self.show_timezone)
358
print >>to_file, indent+'timestamp: %s' % date_str
321
print >>to_file, 'timestamp: %s' % date_str
360
print >>to_file, indent+'message:'
323
print >>to_file, 'message:'
361
324
if not rev.message:
362
print >>to_file, indent+' (no message)'
325
print >>to_file, ' (no message)'
364
message = rev.message.rstrip('\r\n')
365
for l in message.split('\n'):
366
print >>to_file, indent+' ' + l
327
for l in rev.message.split('\n'):
328
print >>to_file, ' ' + l
367
330
if delta != None:
368
331
delta.show(to_file, self.show_ids)
371
335
class ShortLogFormatter(LogFormatter):
372
336
def show(self, revno, rev, delta):
373
337
from bzrlib.osutils import format_date
375
339
to_file = self.to_file
376
date_str = format_date(rev.timestamp, rev.timezone or 0,
378
print >>to_file, "%5d %s\t%s" % (revno, self.short_committer(rev),
341
print >>to_file, "%5d %s\t%s" % (revno, rev.committer,
379
342
format_date(rev.timestamp, rev.timezone or 0,
380
self.show_timezone, date_fmt="%Y-%m-%d",
382
344
if self.show_ids:
383
345
print >>to_file, ' revision-id:', rev.revision_id
384
346
if not rev.message:
385
347
print >>to_file, ' (no message)'
387
message = rev.message.rstrip('\r\n')
388
for l in message.split('\n'):
349
for l in rev.message.split('\n'):
389
350
print >>to_file, ' ' + l
391
352
# TODO: Why not show the modified files in a shorter form as
392
353
# well? rewrap them single lines of appropriate length
393
354
if delta != None:
394
355
delta.show(to_file, self.show_ids)
398
class LineLogFormatter(LogFormatter):
399
def truncate(self, str, max_len):
400
if len(str) <= max_len:
402
return str[:max_len-3]+'...'
404
def date_string(self, rev):
405
from bzrlib.osutils import format_date
406
return format_date(rev.timestamp, rev.timezone or 0,
407
self.show_timezone, date_fmt="%Y-%m-%d",
410
def message(self, rev):
412
return '(no message)'
416
def show(self, revno, rev, delta):
417
from bzrlib.osutils import terminal_width
418
print >> self.to_file, self.log_string(revno, rev, terminal_width()-1)
420
def log_string(self, revno, rev, max_chars):
421
"""Format log info into one string. Truncate tail of string
422
:param revno: revision number (int) or None.
423
Revision numbers counts from 1.
424
:param rev: revision info object
425
:param max_chars: maximum length of resulting string
426
:return: formatted truncated string
430
# show revno only when is not None
431
out.append("%d:" % revno)
432
out.append(self.truncate(self.short_committer(rev), 20))
433
out.append(self.date_string(rev))
434
out.append(rev.get_summary())
435
return self.truncate(" ".join(out).rstrip('\n'), max_chars)
438
def line_log(rev, max_chars):
439
lf = LineLogFormatter(None)
440
return lf.log_string(None, rev, max_chars)
443
'long': LongLogFormatter,
360
FORMATTERS = {'long': LongLogFormatter,
444
361
'short': ShortLogFormatter,
445
'line': LineLogFormatter,
448
def register_formatter(name, formatter):
449
FORMATTERS[name] = formatter
451
365
def log_formatter(name, *args, **kwargs):
452
"""Construct a formatter from arguments.
454
name -- Name of the formatter to construct; currently 'long', 'short' and
455
'line' are supported.
457
366
from bzrlib.errors import BzrCommandError
459
369
return FORMATTERS[name](*args, **kwargs)
461
371
raise BzrCommandError("unknown log formatter: %r" % name)
463
373
def show_one_log(revno, rev, delta, verbose, to_file, show_timezone):
464
# deprecated; for compatibility
374
# deprecated; for compatability
465
375
lf = LongLogFormatter(to_file=to_file, show_timezone=show_timezone)
466
376
lf.show(revno, rev, delta)
468
def show_changed_revisions(branch, old_rh, new_rh, to_file=None, log_format='long'):
469
"""Show the change in revision history comparing the old revision history to the new one.
471
:param branch: The branch where the revisions exist
472
:param old_rh: The old revision history
473
:param new_rh: The new revision history
474
:param to_file: A file to write the results to. If None, stdout will be used
480
to_file = codecs.getwriter(bzrlib.user_encoding)(sys.stdout, errors='replace')
481
lf = log_formatter(log_format,
484
show_timezone='original')
486
# This is the first index which is different between
489
for i in xrange(max(len(new_rh),
493
or new_rh[i] != old_rh[i]):
498
to_file.write('Nothing seems to have changed\n')
500
## TODO: It might be nice to do something like show_log
501
## and show the merged entries. But since this is the
502
## removed revisions, it shouldn't be as important
503
if base_idx < len(old_rh):
504
to_file.write('*'*60)
505
to_file.write('\nRemoved Revisions:\n')
506
for i in range(base_idx, len(old_rh)):
507
rev = branch.repository.get_revision(old_rh[i])
508
lf.show(i+1, rev, None)
509
to_file.write('*'*60)
510
to_file.write('\n\n')
511
if base_idx < len(new_rh):
512
to_file.write('Added Revisions:\n')
518
start_revision=base_idx+1,
519
end_revision=len(new_rh),