53
# TODO: option to show delta summaries for merged-in revisions
56
from bzrlib.delta import compare_trees
53
57
import bzrlib.errors as errors
58
from bzrlib.trace import mutter
54
59
from bzrlib.tree import EmptyTree
55
from bzrlib.delta import compare_trees
56
from bzrlib.trace import mutter
60
from bzrlib.tsort import merge_sort
60
63
def find_touching_revisions(branch, file_id):
201
204
# list indexes are 0-based; revisions are 1-based
202
205
cut_revs = which_revs[(start_revision-1):(end_revision)]
208
# override the mainline to look like the revision history.
209
mainline_revs = [revision_id for index, revision_id in cut_revs]
210
if cut_revs[0][0] == 1:
211
mainline_revs.insert(0, None)
213
mainline_revs.insert(0, which_revs[start_revision-2][1])
215
merge_sorted_revisions = merge_sort(
216
branch.repository.get_revision_graph(mainline_revs[-1]),
204
220
if direction == 'reverse':
205
221
cut_revs.reverse()
206
222
elif direction == 'forward':
223
# forward means oldest first.
224
merge_sorted_revisions.reverse()
209
226
raise ValueError('invalid direction %r' % direction)
211
228
revision_history = branch.revision_history()
212
for revno, rev_id in cut_revs:
213
if verbose or specific_fileid:
214
delta = _get_revision_delta(branch, revno)
217
if not delta.touches_file_id(specific_fileid):
221
# although we calculated it, throw it away without display
224
rev = branch.get_revision(rev_id)
230
# convert the revision history to a dictionary:
232
for index, rev_id in cut_revs:
233
rev_nos[rev_id] = index
235
# now we just print all the revisions
236
for sequence, rev_id, merge_depth, end_of_merge in merge_sorted_revisions:
237
rev = branch.repository.get_revision(rev_id)
227
240
if not searchRE.search(rev.message):
230
lf.show(revno, rev, delta)
231
if hasattr(lf, 'show_merge'):
235
# revno is 1 based, so -2 to get back 1 less.
236
excludes = set(branch.get_ancestry(revision_history[revno - 2]))
237
pending = list(rev.parent_ids)
239
rev_id = pending.pop()
240
if rev_id in excludes:
242
# prevent showing merged revs twice if they multi-path.
245
rev = branch.get_revision(rev_id)
246
except errors.NoSuchRevision:
248
pending.extend(rev.parent_ids)
244
# a mainline revision.
245
if verbose or specific_fileid:
246
delta = _get_revision_delta(branch, rev_nos[rev_id])
249
if not delta.touches_file_id(specific_fileid):
253
# although we calculated it, throw it away without display
256
lf.show(rev_nos[rev_id], rev, delta)
257
elif hasattr(lf, 'show_merge'):
258
lf.show_merge(rev, merge_depth)
252
261
def deltas_for_log_dummy(branch, which_revs):
342
351
def show(self, revno, rev, delta):
343
352
raise NotImplementedError('not implemented in abstract base')
354
def short_committer(self, rev):
355
return re.sub('<.*@.*>', '', rev.committer).strip(' ')
346
358
class LongLogFormatter(LogFormatter):
347
359
def show(self, revno, rev, delta):
348
from osutils import format_date
350
to_file = self.to_file
352
print >>to_file, '-' * 60
353
print >>to_file, 'revno:', revno
355
print >>to_file, 'revision-id:', rev.revision_id
357
for parent_id in rev.parent_ids:
358
print >>to_file, 'parent:', parent_id
360
print >>to_file, 'committer:', rev.committer
362
date_str = format_date(rev.timestamp,
365
print >>to_file, 'timestamp: %s' % date_str
367
print >>to_file, 'message:'
369
print >>to_file, ' (no message)'
371
for l in rev.message.split('\n'):
372
print >>to_file, ' ' + l
375
delta.show(to_file, self.show_ids)
377
def show_merge(self, rev):
378
from osutils import format_date
380
to_file = self.to_file
360
return self._show_helper(revno=revno, rev=rev, delta=delta)
362
def show_merge(self, rev, merge_depth):
363
return self._show_helper(rev=rev, indent=' '*merge_depth, merged=True, delta=None)
365
def _show_helper(self, rev=None, revno=None, indent='', merged=False, delta=None):
366
"""Show a revision, either merged or not."""
367
from bzrlib.osutils import format_date
368
to_file = self.to_file
384
369
print >>to_file, indent+'-' * 60
385
print >>to_file, indent+'merged:', rev.revision_id
370
if revno is not None:
371
print >>to_file, 'revno:', revno
373
print >>to_file, indent+'merged:', rev.revision_id
375
print >>to_file, indent+'revision-id:', rev.revision_id
386
376
if self.show_ids:
387
377
for parent_id in rev.parent_ids:
388
378
print >>to_file, indent+'parent:', parent_id
390
379
print >>to_file, indent+'committer:', rev.committer
381
print >>to_file, indent+'branch nick: %s' % \
382
rev.properties['branch-nick']
392
385
date_str = format_date(rev.timestamp,
393
386
rev.timezone or 0,
394
387
self.show_timezone)
409
405
to_file = self.to_file
410
406
date_str = format_date(rev.timestamp, rev.timezone or 0,
411
407
self.show_timezone)
412
print >>to_file, "%5d %s\t%s" % (revno, rev.committer,
408
print >>to_file, "%5d %s\t%s" % (revno, self.short_committer(rev),
413
409
format_date(rev.timestamp, rev.timezone or 0,
410
self.show_timezone, date_fmt="%Y-%m-%d",
415
412
if self.show_ids:
416
413
print >>to_file, ' revision-id:', rev.revision_id
417
414
if not rev.message:
418
415
print >>to_file, ' (no message)'
420
for l in rev.message.split('\n'):
417
message = rev.message.rstrip('\r\n')
418
for l in message.split('\n'):
421
419
print >>to_file, ' ' + l
423
421
# TODO: Why not show the modified files in a shorter form as
424
422
# well? rewrap them single lines of appropriate length
425
423
if delta != None:
426
424
delta.show(to_file, self.show_ids)
429
427
class LineLogFormatter(LogFormatter):
430
428
def truncate(self, str, max_len):
460
455
lf = LineLogFormatter(None)
461
456
return lf.log_string(rev, max_chars)
463
FORMATTERS = {'long': LongLogFormatter,
459
'long': LongLogFormatter,
464
460
'short': ShortLogFormatter,
465
461
'line': LineLogFormatter,
464
def register_formatter(name, formatter):
465
FORMATTERS[name] = formatter
469
467
def log_formatter(name, *args, **kwargs):
470
468
"""Construct a formatter from arguments.
475
473
from bzrlib.errors import BzrCommandError
477
475
return FORMATTERS[name](*args, **kwargs)
479
477
raise BzrCommandError("unknown log formatter: %r" % name)
481
479
def show_one_log(revno, rev, delta, verbose, to_file, show_timezone):
482
480
# deprecated; for compatability
483
481
lf = LongLogFormatter(to_file=to_file, show_timezone=show_timezone)
484
482
lf.show(revno, rev, delta)
484
def show_changed_revisions(branch, old_rh, new_rh, to_file=None, log_format='long'):
485
"""Show the change in revision history comparing the old revision history to the new one.
487
:param branch: The branch where the revisions exist
488
:param old_rh: The old revision history
489
:param new_rh: The new revision history
490
:param to_file: A file to write the results to. If None, stdout will be used
496
to_file = codecs.getwriter(bzrlib.user_encoding)(sys.stdout, errors='replace')
497
lf = log_formatter(log_format,
500
show_timezone='original')
502
# This is the first index which is different between
505
for i in xrange(max(len(new_rh),
509
or new_rh[i] != old_rh[i]):
514
to_file.write('Nothing seems to have changed\n')
516
## TODO: It might be nice to do something like show_log
517
## and show the merged entries. But since this is the
518
## removed revisions, it shouldn't be as important
519
if base_idx < len(old_rh):
520
to_file.write('*'*60)
521
to_file.write('\nRemoved Revisions:\n')
522
for i in range(base_idx, len(old_rh)):
523
rev = branch.repository.get_revision(old_rh[i])
524
lf.show(i+1, rev, None)
525
to_file.write('*'*60)
526
to_file.write('\n\n')
527
if base_idx < len(new_rh):
528
to_file.write('Added Revisions:\n')
534
start_revision=base_idx+1,
535
end_revision=len(new_rh),