~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/log.py

 * The internal storage of history, and logical branch identity have now
   been split into Branch, and Repository. The common locking and file 
   management routines are now in bzrlib.lockablefiles. 
   (Aaron Bentley, Robert Collins, Martin Pool)

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
 
 
55
import bzrlib.errors as errors
53
56
from bzrlib.tree import EmptyTree
54
57
from bzrlib.delta import compare_trees
55
58
from bzrlib.trace import mutter
 
59
import re
56
60
 
57
61
 
58
62
def find_touching_revisions(branch, file_id):
70
74
    last_path = None
71
75
    revno = 1
72
76
    for revision_id in branch.revision_history():
73
 
        this_inv = branch.get_revision_inventory(revision_id)
 
77
        this_inv = branch.repository.get_revision_inventory(revision_id)
74
78
        if file_id in this_inv:
75
79
            this_ie = this_inv[file_id]
76
80
            this_path = this_inv.id2path(file_id)
114
118
    
115
119
    This is used to show summaries in verbose logs, and also for finding 
116
120
    revisions which touch a given file."""
117
 
    # FIXME: The current version is very inefficient; it retrieves all revisions
118
 
    # twice and reads the weave twice.  We ought to keep revisions in memory 
119
 
    # in case they're used again, either in a general cache or perhaps 
120
 
    # in this code.
121
121
    # XXX: What are we supposed to do when showing a summary for something 
122
122
    # other than a mainline revision.  The delta to it's first parent, or
123
123
    # (more useful) the delta to a nominated other revision.
154
154
    end_revision
155
155
        If not None, only show revisions <= end_revision
156
156
    """
 
157
    branch.lock_read()
 
158
    try:
 
159
        _show_log(branch, lf, specific_fileid, verbose, direction,
 
160
                  start_revision, end_revision, search)
 
161
    finally:
 
162
        branch.unlock()
 
163
    
 
164
def _show_log(branch,
 
165
             lf,
 
166
             specific_fileid=None,
 
167
             verbose=False,
 
168
             direction='reverse',
 
169
             start_revision=None,
 
170
             end_revision=None,
 
171
             search=None):
 
172
    """Worker function for show_log - see show_log."""
157
173
    from bzrlib.osutils import format_date
158
174
    from bzrlib.errors import BzrCheckError
159
175
    from bzrlib.textui import show_status
164
180
        warn("not a LogFormatter instance: %r" % lf)
165
181
 
166
182
    if specific_fileid:
167
 
        mutter('get log for file_id %r' % specific_fileid)
 
183
        mutter('get log for file_id %r', specific_fileid)
168
184
 
169
185
    if search is not None:
170
186
        import re
194
210
    else:
195
211
        raise ValueError('invalid direction %r' % direction)
196
212
 
 
213
    revision_history = branch.revision_history()
197
214
    for revno, rev_id in cut_revs:
198
215
        if verbose or specific_fileid:
199
216
            delta = _get_revision_delta(branch, revno)
206
223
            # although we calculated it, throw it away without display
207
224
            delta = None
208
225
 
209
 
        rev = branch.get_revision(rev_id)
 
226
        rev = branch.repository.get_revision(rev_id)
210
227
 
211
228
        if searchRE:
212
229
            if not searchRE.search(rev.message):
213
230
                continue
214
231
 
215
232
        lf.show(revno, rev, delta)
216
 
 
 
233
        if hasattr(lf, 'show_merge'):
 
234
            if revno == 1:
 
235
                excludes = set()
 
236
            else:
 
237
                # revno is 1 based, so -2 to get back 1 less.
 
238
                repository = branch.repository
 
239
                excludes = repository.get_ancestry(revision_history[revno - 2])
 
240
                excludes = set(excludes)
 
241
            pending = list(rev.parent_ids)
 
242
            while pending:
 
243
                rev_id = pending.pop()
 
244
                if rev_id in excludes:
 
245
                    continue
 
246
                # prevent showing merged revs twice if they multi-path.
 
247
                excludes.add(rev_id)
 
248
                try:
 
249
                    rev = branch.repository.get_revision(rev_id)
 
250
                except errors.NoSuchRevision:
 
251
                    continue
 
252
                pending.extend(rev.parent_ids)
 
253
                lf.show_merge(rev)
217
254
 
218
255
 
219
256
def deltas_for_log_dummy(branch, which_revs):
309
346
    def show(self, revno, rev, delta):
310
347
        raise NotImplementedError('not implemented in abstract base')
311
348
 
 
349
    def short_committer(self, rev):
 
350
        return re.sub('<.*@.*>', '', rev.committer).strip(' ')
 
351
    
312
352
    
313
353
class LongLogFormatter(LogFormatter):
314
354
    def show(self, revno, rev, delta):
315
 
        from osutils import format_date
316
 
 
 
355
        return self._show_helper(revno=revno, rev=rev, delta=delta)
 
356
 
 
357
    def show_merge(self, rev):
 
358
        return self._show_helper(rev=rev, indent='    ', merged=True, delta=None)
 
359
 
 
360
    def _show_helper(self, rev=None, revno=None, indent='', merged=False, delta=None):
 
361
        """Show a revision, either merged or not."""
 
362
        from bzrlib.osutils import format_date
317
363
        to_file = self.to_file
318
 
 
319
 
        print >>to_file,  '-' * 60
320
 
        print >>to_file,  'revno:', revno
 
364
        print >>to_file,  indent+'-' * 60
 
365
        if revno is not None:
 
366
            print >>to_file,  'revno:', revno
 
367
        if merged:
 
368
            print >>to_file,  indent+'merged:', rev.revision_id
 
369
        elif self.show_ids:
 
370
            print >>to_file,  indent+'revision-id:', rev.revision_id
321
371
        if self.show_ids:
322
 
            print >>to_file,  'revision-id:', rev.revision_id
323
 
 
324
372
            for parent_id in rev.parent_ids:
325
 
                print >>to_file, 'parent:', parent_id
326
 
            
327
 
        print >>to_file,  'committer:', rev.committer
328
 
 
 
373
                print >>to_file, indent+'parent:', parent_id
 
374
        print >>to_file,  indent+'committer:', rev.committer
 
375
        try:
 
376
            print >>to_file, indent+'branch nick: %s' % \
 
377
                rev.properties['branch-nick']
 
378
        except KeyError:
 
379
            pass
329
380
        date_str = format_date(rev.timestamp,
330
381
                               rev.timezone or 0,
331
382
                               self.show_timezone)
332
 
        print >>to_file,  'timestamp: %s' % date_str
 
383
        print >>to_file,  indent+'timestamp: %s' % date_str
333
384
 
334
 
        print >>to_file,  'message:'
 
385
        print >>to_file,  indent+'message:'
335
386
        if not rev.message:
336
 
            print >>to_file,  '  (no message)'
 
387
            print >>to_file,  indent+'  (no message)'
337
388
        else:
338
 
            for l in rev.message.split('\n'):
339
 
                print >>to_file,  '  ' + l
340
 
 
 
389
            message = rev.message.rstrip('\r\n')
 
390
            for l in message.split('\n'):
 
391
                print >>to_file,  indent+'  ' + l
341
392
        if delta != None:
342
393
            delta.show(to_file, self.show_ids)
343
394
 
344
395
 
345
 
 
346
396
class ShortLogFormatter(LogFormatter):
347
397
    def show(self, revno, rev, delta):
348
398
        from bzrlib.osutils import format_date
349
399
 
350
400
        to_file = self.to_file
351
 
 
352
 
        print >>to_file, "%5d %s\t%s" % (revno, rev.committer,
 
401
        date_str = format_date(rev.timestamp, rev.timezone or 0,
 
402
                            self.show_timezone)
 
403
        print >>to_file, "%5d %s\t%s" % (revno, self.short_committer(rev),
353
404
                format_date(rev.timestamp, rev.timezone or 0,
354
 
                            self.show_timezone))
 
405
                            self.show_timezone, date_fmt="%Y-%m-%d",
 
406
                           show_offset=False))
355
407
        if self.show_ids:
356
408
            print >>to_file,  '      revision-id:', rev.revision_id
357
409
        if not rev.message:
358
410
            print >>to_file,  '      (no message)'
359
411
        else:
360
 
            for l in rev.message.split('\n'):
 
412
            message = rev.message.rstrip('\r\n')
 
413
            for l in message.split('\n'):
361
414
                print >>to_file,  '      ' + l
362
415
 
363
416
        # TODO: Why not show the modified files in a shorter form as
364
417
        # well? rewrap them single lines of appropriate length
365
418
        if delta != None:
366
419
            delta.show(to_file, self.show_ids)
367
 
        print
368
 
 
369
 
 
 
420
        print >>to_file, ''
 
421
 
 
422
class LineLogFormatter(LogFormatter):
 
423
    def truncate(self, str, max_len):
 
424
        if len(str) <= max_len:
 
425
            return str
 
426
        return str[:max_len-3]+'...'
 
427
 
 
428
    def date_string(self, rev):
 
429
        from bzrlib.osutils import format_date
 
430
        return format_date(rev.timestamp, rev.timezone or 0, 
 
431
                           self.show_timezone, date_fmt="%Y-%m-%d",
 
432
                           show_offset=False)
 
433
 
 
434
    def message(self, rev):
 
435
        if not rev.message:
 
436
            return '(no message)'
 
437
        else:
 
438
            return rev.message
 
439
 
 
440
    def show(self, revno, rev, delta):
 
441
        print >> self.to_file, self.log_string(rev, 79) 
 
442
 
 
443
    def log_string(self, rev, max_chars):
 
444
        out = [self.truncate(self.short_committer(rev), 20)]
 
445
        out.append(self.date_string(rev))
 
446
        out.append(self.message(rev).replace('\n', ' '))
 
447
        return self.truncate(" ".join(out).rstrip('\n'), max_chars)
 
448
 
 
449
def line_log(rev, max_chars):
 
450
    lf = LineLogFormatter(None)
 
451
    return lf.log_string(rev, max_chars)
370
452
 
371
453
FORMATTERS = {'long': LongLogFormatter,
372
454
              'short': ShortLogFormatter,
 
455
              'line': LineLogFormatter,
373
456
              }
374
457
 
375
458
 
376
459
def log_formatter(name, *args, **kwargs):
377
460
    """Construct a formatter from arguments.
378
461
 
379
 
    name -- Name of the formatter to construct; currently 'long' and
380
 
        'short' are supported.
 
462
    name -- Name of the formatter to construct; currently 'long', 'short' and
 
463
        'line' are supported.
381
464
    """
382
465
    from bzrlib.errors import BzrCommandError
383
466
    try:
389
472
    # deprecated; for compatability
390
473
    lf = LongLogFormatter(to_file=to_file, show_timezone=show_timezone)
391
474
    lf.show(revno, rev, delta)
 
475
 
 
476
def show_changed_revisions(branch, old_rh, new_rh, to_file=None, log_format='long'):
 
477
    """Show the change in revision history comparing the old revision history to the new one.
 
478
 
 
479
    :param branch: The branch where the revisions exist
 
480
    :param old_rh: The old revision history
 
481
    :param new_rh: The new revision history
 
482
    :param to_file: A file to write the results to. If None, stdout will be used
 
483
    """
 
484
    if to_file is None:
 
485
        import sys
 
486
        import codecs
 
487
        import bzrlib
 
488
        to_file = codecs.getwriter(bzrlib.user_encoding)(sys.stdout, errors='replace')
 
489
    lf = log_formatter(log_format,
 
490
                       show_ids=False,
 
491
                       to_file=to_file,
 
492
                       show_timezone='original')
 
493
 
 
494
    # This is the first index which is different between
 
495
    # old and new
 
496
    base_idx = None
 
497
    for i in xrange(max(len(new_rh),
 
498
                        len(old_rh))):
 
499
        if (len(new_rh) <= i
 
500
            or len(old_rh) <= i
 
501
            or new_rh[i] != old_rh[i]):
 
502
            base_idx = i
 
503
            break
 
504
 
 
505
    if base_idx is None:
 
506
        to_file.write('Nothing seems to have changed\n')
 
507
        return
 
508
    ## TODO: It might be nice to do something like show_log
 
509
    ##       and show the merged entries. But since this is the
 
510
    ##       removed revisions, it shouldn't be as important
 
511
    if base_idx < len(old_rh):
 
512
        to_file.write('*'*60)
 
513
        to_file.write('\nRemoved Revisions:\n')
 
514
        for i in range(base_idx, len(old_rh)):
 
515
            rev = branch.repository.get_revision(old_rh[i])
 
516
            lf.show(i+1, rev, None)
 
517
        to_file.write('*'*60)
 
518
        to_file.write('\n\n')
 
519
    if base_idx < len(new_rh):
 
520
        to_file.write('Added Revisions:\n')
 
521
        show_log(branch,
 
522
                 lf,
 
523
                 None,
 
524
                 verbose=True,
 
525
                 direction='forward',
 
526
                 start_revision=base_idx+1,
 
527
                 end_revision=len(new_rh),
 
528
                 search=None)
 
529