~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/log.py

(mbp) merge bzr.dev to 0.8, prepare for release

Show diffs side-by-side

added added

removed removed

Lines of Context:
50
50
"""
51
51
 
52
52
 
 
53
# TODO: option to show delta summaries for merged-in revisions
 
54
import re
 
55
 
 
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
57
 
import re
 
60
from bzrlib.tsort import merge_sort
58
61
 
59
62
 
60
63
def find_touching_revisions(branch, file_id):
72
75
    last_path = None
73
76
    revno = 1
74
77
    for revision_id in branch.revision_history():
75
 
        this_inv = branch.get_revision_inventory(revision_id)
 
78
        this_inv = branch.repository.get_revision_inventory(revision_id)
76
79
        if file_id in this_inv:
77
80
            this_ie = this_inv[file_id]
78
81
            this_path = this_inv.id2path(file_id)
178
181
        warn("not a LogFormatter instance: %r" % lf)
179
182
 
180
183
    if specific_fileid:
181
 
        mutter('get log for file_id %r' % specific_fileid)
 
184
        mutter('get log for file_id %r', specific_fileid)
182
185
 
183
186
    if search is not None:
184
187
        import re
200
203
 
201
204
    # list indexes are 0-based; revisions are 1-based
202
205
    cut_revs = which_revs[(start_revision-1):(end_revision)]
 
206
    if not cut_revs:
 
207
        return
 
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)
 
212
    else:
 
213
        mainline_revs.insert(0, which_revs[start_revision-2][1])
 
214
 
 
215
    merge_sorted_revisions = merge_sort(
 
216
        branch.repository.get_revision_graph(mainline_revs[-1]),
 
217
        mainline_revs[-1],
 
218
        mainline_revs)
203
219
 
204
220
    if direction == 'reverse':
205
221
        cut_revs.reverse()
206
222
    elif direction == 'forward':
207
 
        pass
 
223
        # forward means oldest first.
 
224
        merge_sorted_revisions.reverse()
208
225
    else:
209
226
        raise ValueError('invalid direction %r' % direction)
210
227
 
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)
215
 
            
216
 
        if specific_fileid:
217
 
            if not delta.touches_file_id(specific_fileid):
218
 
                continue
219
 
 
220
 
        if not verbose:
221
 
            # although we calculated it, throw it away without display
222
 
            delta = None
223
 
 
224
 
        rev = branch.get_revision(rev_id)
 
229
 
 
230
    # convert the revision history to a dictionary:
 
231
    rev_nos = {}
 
232
    for index, rev_id in cut_revs:
 
233
        rev_nos[rev_id] = index
 
234
 
 
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)
225
238
 
226
239
        if searchRE:
227
240
            if not searchRE.search(rev.message):
228
241
                continue
229
242
 
230
 
        lf.show(revno, rev, delta)
231
 
        if hasattr(lf, 'show_merge'):
232
 
            if revno == 1:
233
 
                excludes = set()
234
 
            else:
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)
238
 
            while pending:
239
 
                rev_id = pending.pop()
240
 
                if rev_id in excludes:
241
 
                    continue
242
 
                # prevent showing merged revs twice if they multi-path.
243
 
                excludes.add(rev_id)
244
 
                try:
245
 
                    rev = branch.get_revision(rev_id)
246
 
                except errors.NoSuchRevision:
247
 
                    continue
248
 
                pending.extend(rev.parent_ids)
249
 
                lf.show_merge(rev)
 
243
        if merge_depth == 0:
 
244
            # a mainline revision.
 
245
            if verbose or specific_fileid:
 
246
                delta = _get_revision_delta(branch, rev_nos[rev_id])
 
247
                
 
248
            if specific_fileid:
 
249
                if not delta.touches_file_id(specific_fileid):
 
250
                    continue
 
251
    
 
252
            if not verbose:
 
253
                # although we calculated it, throw it away without display
 
254
                delta = None
 
255
 
 
256
            lf.show(rev_nos[rev_id], rev, delta)
 
257
        elif hasattr(lf, 'show_merge'):
 
258
            lf.show_merge(rev, merge_depth)
250
259
 
251
260
 
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')
344
353
 
 
354
    def short_committer(self, rev):
 
355
        return re.sub('<.*@.*>', '', rev.committer).strip(' ')
 
356
    
345
357
    
346
358
class LongLogFormatter(LogFormatter):
347
359
    def show(self, revno, rev, delta):
348
 
        from osutils import format_date
349
 
 
350
 
        to_file = self.to_file
351
 
 
352
 
        print >>to_file,  '-' * 60
353
 
        print >>to_file,  'revno:', revno
354
 
        if self.show_ids:
355
 
            print >>to_file,  'revision-id:', rev.revision_id
356
 
 
357
 
            for parent_id in rev.parent_ids:
358
 
                print >>to_file, 'parent:', parent_id
359
 
            
360
 
        print >>to_file,  'committer:', rev.committer
361
 
 
362
 
        date_str = format_date(rev.timestamp,
363
 
                               rev.timezone or 0,
364
 
                               self.show_timezone)
365
 
        print >>to_file,  'timestamp: %s' % date_str
366
 
 
367
 
        print >>to_file,  'message:'
368
 
        if not rev.message:
369
 
            print >>to_file,  '  (no message)'
370
 
        else:
371
 
            for l in rev.message.split('\n'):
372
 
                print >>to_file,  '  ' + l
373
 
 
374
 
        if delta != None:
375
 
            delta.show(to_file, self.show_ids)
376
 
 
377
 
    def show_merge(self, rev):
378
 
        from osutils import format_date
379
 
 
380
 
        to_file = self.to_file
381
 
 
382
 
        indent = '    '
383
 
 
 
360
        return self._show_helper(revno=revno, rev=rev, delta=delta)
 
361
 
 
362
    def show_merge(self, rev, merge_depth):
 
363
        return self._show_helper(rev=rev, indent='    '*merge_depth, merged=True, delta=None)
 
364
 
 
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
 
372
        if merged:
 
373
            print >>to_file,  indent+'merged:', rev.revision_id
 
374
        elif self.show_ids:
 
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
389
 
            
390
379
        print >>to_file,  indent+'committer:', rev.committer
391
 
 
 
380
        try:
 
381
            print >>to_file, indent+'branch nick: %s' % \
 
382
                rev.properties['branch-nick']
 
383
        except KeyError:
 
384
            pass
392
385
        date_str = format_date(rev.timestamp,
393
386
                               rev.timezone or 0,
394
387
                               self.show_timezone)
398
391
        if not rev.message:
399
392
            print >>to_file,  indent+'  (no message)'
400
393
        else:
401
 
            for l in rev.message.split('\n'):
 
394
            message = rev.message.rstrip('\r\n')
 
395
            for l in message.split('\n'):
402
396
                print >>to_file,  indent+'  ' + l
 
397
        if delta != None:
 
398
            delta.show(to_file, self.show_ids)
403
399
 
404
400
 
405
401
class ShortLogFormatter(LogFormatter):
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,
414
 
                            self.show_timezone))
 
410
                            self.show_timezone, date_fmt="%Y-%m-%d",
 
411
                           show_offset=False))
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)'
419
416
        else:
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
422
420
 
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)
427
 
        print
 
425
        print >>to_file, ''
428
426
 
429
427
class LineLogFormatter(LogFormatter):
430
428
    def truncate(self, str, max_len):
444
442
        else:
445
443
            return rev.message
446
444
 
447
 
    def short_committer(self, rev):
448
 
        return re.sub('<.*@.*>', '', rev.committer).strip(' ')
449
 
    
450
445
    def show(self, revno, rev, delta):
451
446
        print >> self.to_file, self.log_string(rev, 79) 
452
447
 
460
455
    lf = LineLogFormatter(None)
461
456
    return lf.log_string(rev, max_chars)
462
457
 
463
 
FORMATTERS = {'long': LongLogFormatter,
 
458
FORMATTERS = {
 
459
              'long': LongLogFormatter,
464
460
              'short': ShortLogFormatter,
465
461
              'line': LineLogFormatter,
466
462
              }
467
463
 
 
464
def register_formatter(name, formatter):
 
465
    FORMATTERS[name] = formatter
468
466
 
469
467
def log_formatter(name, *args, **kwargs):
470
468
    """Construct a formatter from arguments.
475
473
    from bzrlib.errors import BzrCommandError
476
474
    try:
477
475
        return FORMATTERS[name](*args, **kwargs)
478
 
    except IndexError:
 
476
    except KeyError:
479
477
        raise BzrCommandError("unknown log formatter: %r" % name)
480
478
 
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)
 
483
 
 
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.
 
486
 
 
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
 
491
    """
 
492
    if to_file is None:
 
493
        import sys
 
494
        import codecs
 
495
        import bzrlib
 
496
        to_file = codecs.getwriter(bzrlib.user_encoding)(sys.stdout, errors='replace')
 
497
    lf = log_formatter(log_format,
 
498
                       show_ids=False,
 
499
                       to_file=to_file,
 
500
                       show_timezone='original')
 
501
 
 
502
    # This is the first index which is different between
 
503
    # old and new
 
504
    base_idx = None
 
505
    for i in xrange(max(len(new_rh),
 
506
                        len(old_rh))):
 
507
        if (len(new_rh) <= i
 
508
            or len(old_rh) <= i
 
509
            or new_rh[i] != old_rh[i]):
 
510
            base_idx = i
 
511
            break
 
512
 
 
513
    if base_idx is None:
 
514
        to_file.write('Nothing seems to have changed\n')
 
515
        return
 
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')
 
529
        show_log(branch,
 
530
                 lf,
 
531
                 None,
 
532
                 verbose=True,
 
533
                 direction='forward',
 
534
                 start_revision=base_idx+1,
 
535
                 end_revision=len(new_rh),
 
536
                 search=None)
 
537