~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/log.py

  • Committer: Aaron Bentley
  • Date: 2005-09-21 15:33:23 UTC
  • mto: (1185.1.37)
  • mto: This revision was merged to the branch mainline in revision 1390.
  • Revision ID: abentley@panoramicfeedback.com-20050921153323-5db674d572d7649d
Fixed bug in distance-from-root graph operation

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
 
 
 
53
from bzrlib.tree import EmptyTree
56
54
from bzrlib.delta import compare_trees
57
 
import bzrlib.errors as errors
58
55
from bzrlib.trace import mutter
59
 
from bzrlib.tree import EmptyTree
60
 
from bzrlib.tsort import merge_sort
61
56
 
62
57
 
63
58
def find_touching_revisions(branch, file_id):
75
70
    last_path = None
76
71
    revno = 1
77
72
    for revision_id in branch.revision_history():
78
 
        this_inv = branch.repository.get_revision_inventory(revision_id)
 
73
        this_inv = branch.get_revision_inventory(revision_id)
79
74
        if file_id in this_inv:
80
75
            this_ie = this_inv[file_id]
81
76
            this_path = this_inv.id2path(file_id)
114
109
    return rh
115
110
 
116
111
 
117
 
def _get_revision_delta(branch, revno):
118
 
    """Return the delta for a mainline revision.
119
 
    
120
 
    This is used to show summaries in verbose logs, and also for finding 
121
 
    revisions which touch a given file."""
122
 
    # XXX: What are we supposed to do when showing a summary for something 
123
 
    # other than a mainline revision.  The delta to it's first parent, or
124
 
    # (more useful) the delta to a nominated other revision.
125
 
    return branch.get_revision_delta(revno)
126
 
 
127
 
 
128
112
def show_log(branch,
129
113
             lf,
130
114
             specific_fileid=None,
155
139
    end_revision
156
140
        If not None, only show revisions <= end_revision
157
141
    """
158
 
    branch.lock_read()
159
 
    try:
160
 
        _show_log(branch, lf, specific_fileid, verbose, direction,
161
 
                  start_revision, end_revision, search)
162
 
    finally:
163
 
        branch.unlock()
164
 
    
165
 
def _show_log(branch,
166
 
             lf,
167
 
             specific_fileid=None,
168
 
             verbose=False,
169
 
             direction='reverse',
170
 
             start_revision=None,
171
 
             end_revision=None,
172
 
             search=None):
173
 
    """Worker function for show_log - see show_log."""
174
142
    from bzrlib.osutils import format_date
175
143
    from bzrlib.errors import BzrCheckError
176
144
    from bzrlib.textui import show_status
181
149
        warn("not a LogFormatter instance: %r" % lf)
182
150
 
183
151
    if specific_fileid:
184
 
        mutter('get log for file_id %r', specific_fileid)
 
152
        mutter('get log for file_id %r' % specific_fileid)
185
153
 
186
154
    if search is not None:
187
155
        import re
203
171
 
204
172
    # list indexes are 0-based; revisions are 1-based
205
173
    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)
219
174
 
220
175
    if direction == 'reverse':
221
176
        cut_revs.reverse()
222
177
    elif direction == 'forward':
223
 
        # forward means oldest first.
224
 
        merge_sorted_revisions.reverse()
 
178
        pass
225
179
    else:
226
180
        raise ValueError('invalid direction %r' % direction)
227
181
 
228
 
    revision_history = branch.revision_history()
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
 
    revisions = branch.repository.get_revisions([r for s, r, m, e in
236
 
                                                 merge_sorted_revisions])
237
 
 
238
 
    # now we just print all the revisions
239
 
    for ((sequence, rev_id, merge_depth, end_of_merge), rev) in \
240
 
        zip(merge_sorted_revisions, revisions):
 
182
    for revno, rev_id in cut_revs:
 
183
        if verbose or specific_fileid:
 
184
            delta = branch.get_revision_delta(revno)
 
185
            
 
186
        if specific_fileid:
 
187
            if not delta.touches_file_id(specific_fileid):
 
188
                continue
 
189
 
 
190
        if not verbose:
 
191
            # although we calculated it, throw it away without display
 
192
            delta = None
 
193
 
 
194
        rev = branch.get_revision(rev_id)
241
195
 
242
196
        if searchRE:
243
197
            if not searchRE.search(rev.message):
244
198
                continue
245
199
 
246
 
        if merge_depth == 0:
247
 
            # a mainline revision.
248
 
            if verbose or specific_fileid:
249
 
                delta = _get_revision_delta(branch, rev_nos[rev_id])
250
 
                
251
 
            if specific_fileid:
252
 
                if not delta.touches_file_id(specific_fileid):
253
 
                    continue
254
 
    
255
 
            if not verbose:
256
 
                # although we calculated it, throw it away without display
257
 
                delta = None
 
200
        lf.show(revno, rev, delta)
258
201
 
259
 
            lf.show(rev_nos[rev_id], rev, delta)
260
 
        elif hasattr(lf, 'show_merge'):
261
 
            lf.show_merge(rev, merge_depth)
262
202
 
263
203
 
264
204
def deltas_for_log_dummy(branch, which_revs):
345
285
 
346
286
class LogFormatter(object):
347
287
    """Abstract class to display log messages."""
348
 
 
349
288
    def __init__(self, to_file, show_ids=False, show_timezone='original'):
350
289
        self.to_file = to_file
351
290
        self.show_ids = show_ids
352
291
        self.show_timezone = show_timezone
353
292
 
 
293
 
354
294
    def show(self, revno, rev, delta):
355
295
        raise NotImplementedError('not implemented in abstract base')
356
 
 
357
 
    def short_committer(self, rev):
358
 
        return re.sub('<.*@.*>', '', rev.committer).strip(' ')
359
 
    
360
 
    
 
296
        
 
297
 
 
298
 
 
299
 
 
300
 
 
301
 
361
302
class LongLogFormatter(LogFormatter):
362
303
    def show(self, revno, rev, delta):
363
 
        return self._show_helper(revno=revno, rev=rev, delta=delta)
364
 
 
365
 
    def show_merge(self, rev, merge_depth):
366
 
        return self._show_helper(rev=rev, indent='    '*merge_depth, merged=True, delta=None)
367
 
 
368
 
    def _show_helper(self, rev=None, revno=None, indent='', merged=False, delta=None):
369
 
        """Show a revision, either merged or not."""
370
 
        from bzrlib.osutils import format_date
 
304
        from osutils import format_date
 
305
 
371
306
        to_file = self.to_file
372
 
        print >>to_file,  indent+'-' * 60
373
 
        if revno is not None:
374
 
            print >>to_file,  'revno:', revno
375
 
        if merged:
376
 
            print >>to_file,  indent+'merged:', rev.revision_id
377
 
        elif self.show_ids:
378
 
            print >>to_file,  indent+'revision-id:', rev.revision_id
 
307
 
 
308
        print >>to_file,  '-' * 60
 
309
        print >>to_file,  'revno:', revno
379
310
        if self.show_ids:
380
 
            for parent_id in rev.parent_ids:
381
 
                print >>to_file, indent+'parent:', parent_id
382
 
        print >>to_file,  indent+'committer:', rev.committer
383
 
        try:
384
 
            print >>to_file, indent+'branch nick: %s' % \
385
 
                rev.properties['branch-nick']
386
 
        except KeyError:
387
 
            pass
 
311
            print >>to_file,  'revision-id:', rev.revision_id
 
312
 
 
313
            for parent in rev.parents:
 
314
                print >>to_file, 'parent:', parent.revision_id
 
315
            
 
316
        print >>to_file,  'committer:', rev.committer
 
317
 
388
318
        date_str = format_date(rev.timestamp,
389
319
                               rev.timezone or 0,
390
320
                               self.show_timezone)
391
 
        print >>to_file,  indent+'timestamp: %s' % date_str
 
321
        print >>to_file,  'timestamp: %s' % date_str
392
322
 
393
 
        print >>to_file,  indent+'message:'
 
323
        print >>to_file,  'message:'
394
324
        if not rev.message:
395
 
            print >>to_file,  indent+'  (no message)'
 
325
            print >>to_file,  '  (no message)'
396
326
        else:
397
 
            message = rev.message.rstrip('\r\n')
398
 
            for l in message.split('\n'):
399
 
                print >>to_file,  indent+'  ' + l
 
327
            for l in rev.message.split('\n'):
 
328
                print >>to_file,  '  ' + l
 
329
 
400
330
        if delta != None:
401
331
            delta.show(to_file, self.show_ids)
402
332
 
403
333
 
 
334
 
404
335
class ShortLogFormatter(LogFormatter):
405
336
    def show(self, revno, rev, delta):
406
337
        from bzrlib.osutils import format_date
407
338
 
408
339
        to_file = self.to_file
409
 
        date_str = format_date(rev.timestamp, rev.timezone or 0,
410
 
                            self.show_timezone)
411
 
        print >>to_file, "%5d %s\t%s" % (revno, self.short_committer(rev),
 
340
 
 
341
        print >>to_file, "%5d %s\t%s" % (revno, rev.committer,
412
342
                format_date(rev.timestamp, rev.timezone or 0,
413
 
                            self.show_timezone, date_fmt="%Y-%m-%d",
414
 
                           show_offset=False))
 
343
                            self.show_timezone))
415
344
        if self.show_ids:
416
345
            print >>to_file,  '      revision-id:', rev.revision_id
417
346
        if not rev.message:
418
347
            print >>to_file,  '      (no message)'
419
348
        else:
420
 
            message = rev.message.rstrip('\r\n')
421
 
            for l in message.split('\n'):
 
349
            for l in rev.message.split('\n'):
422
350
                print >>to_file,  '      ' + l
423
351
 
424
352
        # TODO: Why not show the modified files in a shorter form as
425
353
        # well? rewrap them single lines of appropriate length
426
354
        if delta != None:
427
355
            delta.show(to_file, self.show_ids)
428
 
        print >>to_file, ''
429
 
 
430
 
 
431
 
class LineLogFormatter(LogFormatter):
432
 
    def truncate(self, str, max_len):
433
 
        if len(str) <= max_len:
434
 
            return str
435
 
        return str[:max_len-3]+'...'
436
 
 
437
 
    def date_string(self, rev):
438
 
        from bzrlib.osutils import format_date
439
 
        return format_date(rev.timestamp, rev.timezone or 0, 
440
 
                           self.show_timezone, date_fmt="%Y-%m-%d",
441
 
                           show_offset=False)
442
 
 
443
 
    def message(self, rev):
444
 
        if not rev.message:
445
 
            return '(no message)'
446
 
        else:
447
 
            return rev.message
448
 
 
449
 
    def show(self, revno, rev, delta):
450
 
        from bzrlib.osutils import terminal_width
451
 
        print >> self.to_file, self.log_string(revno, rev, terminal_width()-1)
452
 
 
453
 
    def log_string(self, revno, rev, max_chars):
454
 
        """Format log info into one string. Truncate tail of string
455
 
        :param  revno:      revision number (int) or None.
456
 
                            Revision numbers counts from 1.
457
 
        :param  rev:        revision info object
458
 
        :param  max_chars:  maximum length of resulting string
459
 
        :return:            formatted truncated string
460
 
        """
461
 
        out = []
462
 
        if revno:
463
 
            # show revno only when is not None
464
 
            out.append("%d:" % revno)
465
 
        out.append(self.truncate(self.short_committer(rev), 20))
466
 
        out.append(self.date_string(rev))
467
 
        out.append(rev.get_summary())
468
 
        return self.truncate(" ".join(out).rstrip('\n'), max_chars)
469
 
 
470
 
 
471
 
def line_log(rev, max_chars):
472
 
    lf = LineLogFormatter(None)
473
 
    return lf.log_string(None, rev, max_chars)
474
 
 
475
 
FORMATTERS = {
476
 
              'long': LongLogFormatter,
 
356
        print
 
357
 
 
358
 
 
359
 
 
360
FORMATTERS = {'long': LongLogFormatter,
477
361
              'short': ShortLogFormatter,
478
 
              'line': LineLogFormatter,
479
362
              }
480
363
 
481
 
def register_formatter(name, formatter):
482
 
    FORMATTERS[name] = formatter
483
364
 
484
365
def log_formatter(name, *args, **kwargs):
485
 
    """Construct a formatter from arguments.
486
 
 
487
 
    name -- Name of the formatter to construct; currently 'long', 'short' and
488
 
        'line' are supported.
489
 
    """
490
366
    from bzrlib.errors import BzrCommandError
 
367
    
491
368
    try:
492
369
        return FORMATTERS[name](*args, **kwargs)
493
 
    except KeyError:
 
370
    except IndexError:
494
371
        raise BzrCommandError("unknown log formatter: %r" % name)
495
372
 
496
373
def show_one_log(revno, rev, delta, verbose, to_file, show_timezone):
497
 
    # deprecated; for compatibility
 
374
    # deprecated; for compatability
498
375
    lf = LongLogFormatter(to_file=to_file, show_timezone=show_timezone)
499
376
    lf.show(revno, rev, delta)
500
 
 
501
 
def show_changed_revisions(branch, old_rh, new_rh, to_file=None, log_format='long'):
502
 
    """Show the change in revision history comparing the old revision history to the new one.
503
 
 
504
 
    :param branch: The branch where the revisions exist
505
 
    :param old_rh: The old revision history
506
 
    :param new_rh: The new revision history
507
 
    :param to_file: A file to write the results to. If None, stdout will be used
508
 
    """
509
 
    if to_file is None:
510
 
        import sys
511
 
        import codecs
512
 
        import bzrlib
513
 
        to_file = codecs.getwriter(bzrlib.user_encoding)(sys.stdout, errors='replace')
514
 
    lf = log_formatter(log_format,
515
 
                       show_ids=False,
516
 
                       to_file=to_file,
517
 
                       show_timezone='original')
518
 
 
519
 
    # This is the first index which is different between
520
 
    # old and new
521
 
    base_idx = None
522
 
    for i in xrange(max(len(new_rh),
523
 
                        len(old_rh))):
524
 
        if (len(new_rh) <= i
525
 
            or len(old_rh) <= i
526
 
            or new_rh[i] != old_rh[i]):
527
 
            base_idx = i
528
 
            break
529
 
 
530
 
    if base_idx is None:
531
 
        to_file.write('Nothing seems to have changed\n')
532
 
        return
533
 
    ## TODO: It might be nice to do something like show_log
534
 
    ##       and show the merged entries. But since this is the
535
 
    ##       removed revisions, it shouldn't be as important
536
 
    if base_idx < len(old_rh):
537
 
        to_file.write('*'*60)
538
 
        to_file.write('\nRemoved Revisions:\n')
539
 
        for i in range(base_idx, len(old_rh)):
540
 
            rev = branch.repository.get_revision(old_rh[i])
541
 
            lf.show(i+1, rev, None)
542
 
        to_file.write('*'*60)
543
 
        to_file.write('\n\n')
544
 
    if base_idx < len(new_rh):
545
 
        to_file.write('Added Revisions:\n')
546
 
        show_log(branch,
547
 
                 lf,
548
 
                 None,
549
 
                 verbose=True,
550
 
                 direction='forward',
551
 
                 start_revision=base_idx+1,
552
 
                 end_revision=len(new_rh),
553
 
                 search=None)
554