~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/log.py

  • Committer: Michael Ellerman
  • Date: 2006-02-28 14:45:51 UTC
  • mto: (1558.1.18 Aaron's integration)
  • mto: This revision was merged to the branch mainline in revision 1586.
  • Revision ID: michael@ellerman.id.au-20060228144551-3d9941ecde4a0b0a
Update contrib/pwk for -p1 diffs from bzr

Show diffs side-by-side

added added

removed removed

Lines of Context:
49
49
all the changes since the previous revision that touched hello.c.
50
50
"""
51
51
 
 
52
 
52
53
# TODO: option to show delta summaries for merged-in revisions
53
54
 
54
 
from itertools import izip
55
 
import re
56
 
 
57
55
import bzrlib.errors as errors
 
56
from bzrlib.tree import EmptyTree
 
57
from bzrlib.delta import compare_trees
58
58
from bzrlib.trace import mutter
59
 
from bzrlib.tsort import merge_sort
 
59
import re
60
60
 
61
61
 
62
62
def find_touching_revisions(branch, file_id):
113
113
    return rh
114
114
 
115
115
 
 
116
def _get_revision_delta(branch, revno):
 
117
    """Return the delta for a mainline revision.
 
118
    
 
119
    This is used to show summaries in verbose logs, and also for finding 
 
120
    revisions which touch a given file."""
 
121
    # XXX: What are we supposed to do when showing a summary for something 
 
122
    # other than a mainline revision.  The delta to it's first parent, or
 
123
    # (more useful) the delta to a nominated other revision.
 
124
    return branch.get_revision_delta(revno)
 
125
 
 
126
 
116
127
def show_log(branch,
117
128
             lf,
118
129
             specific_fileid=None,
161
172
    """Worker function for show_log - see show_log."""
162
173
    from bzrlib.osutils import format_date
163
174
    from bzrlib.errors import BzrCheckError
 
175
    from bzrlib.textui import show_status
164
176
    
165
177
    from warnings import warn
166
178
 
190
202
 
191
203
    # list indexes are 0-based; revisions are 1-based
192
204
    cut_revs = which_revs[(start_revision-1):(end_revision)]
193
 
    if not cut_revs:
194
 
        return
195
 
 
196
 
    # convert the revision history to a dictionary:
197
 
    rev_nos = dict((k, v) for v, k in cut_revs)
198
 
 
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)
203
 
    else:
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 
207
 
    else:
208
 
        include_merges = False 
209
 
    view_revisions = list(get_view_revisions(mainline_revs, rev_nos, branch,
210
 
                          direction, include_merges=include_merges))
211
 
 
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)
216
 
        num = 9
217
 
        repository = branch.repository
218
 
        while revision_ids:
219
 
            cur_deltas = {}
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:]
233
 
            num = int(num * 1.5)
 
205
 
 
206
    if direction == 'reverse':
 
207
        cut_revs.reverse()
 
208
    elif direction == 'forward':
 
209
        pass
 
210
    else:
 
211
        raise ValueError('invalid direction %r' % direction)
 
212
 
 
213
    revision_history = branch.revision_history()
 
214
    for revno, rev_id in cut_revs:
 
215
        if verbose or specific_fileid:
 
216
            delta = _get_revision_delta(branch, revno)
234
217
            
235
 
    # now we just print all the revisions
236
 
    for ((rev_id, revno, merge_depth), (rev, delta)) in \
237
 
         izip(view_revisions, iter_revisions()):
 
218
        if specific_fileid:
 
219
            if not delta.touches_file_id(specific_fileid):
 
220
                continue
 
221
 
 
222
        if not verbose:
 
223
            # although we calculated it, throw it away without display
 
224
            delta = None
 
225
 
 
226
        rev = branch.repository.get_revision(rev_id)
238
227
 
239
228
        if searchRE:
240
229
            if not searchRE.search(rev.message):
241
230
                continue
242
231
 
243
 
        if merge_depth == 0:
244
 
            # a mainline revision.
245
 
                
246
 
            if specific_fileid:
247
 
                if not delta.touches_file_id(specific_fileid):
248
 
                    continue
 
232
        lf.show(revno, rev, delta)
 
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)
 
254
 
 
255
 
 
256
def deltas_for_log_dummy(branch, which_revs):
 
257
    """Return all the revisions without intermediate deltas.
 
258
 
 
259
    Useful for log commands that won't need the delta information.
 
260
    """
249
261
    
250
 
            if not verbose:
251
 
                # although we calculated it, throw it away without display
252
 
                delta = None
253
 
 
254
 
            lf.show(revno, rev, delta)
255
 
        else:
256
 
            lf.show_merge(rev, merge_depth)
257
 
 
258
 
 
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)
264
 
    """
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
271
 
        return
272
 
    merge_sorted_revisions = merge_sort(
273
 
        branch.repository.get_revision_graph(mainline_revs[-1]),
274
 
        mainline_revs[-1],
275
 
        mainline_revs)
276
 
 
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)
282
 
 
283
 
    revision_history = branch.revision_history()
284
 
 
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
287
 
 
288
 
 
289
 
def reverse_by_depth(merge_sorted_revisions, _depth=0):
290
 
    """Reverse revisions by depth.
291
 
 
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.
295
 
    """
296
 
    zd_revisions = []
297
 
    for val in merge_sorted_revisions:
298
 
        if val[2] == _depth:
299
 
            zd_revisions.append([val])
300
 
        else:
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()
307
 
    result = []
308
 
    for chunk in zd_revisions:
309
 
        result.extend(chunk)
310
 
    return result
 
262
    for revno, revision_id in which_revs:
 
263
        yield revno, branch.get_revision(revision_id), None
 
264
 
 
265
 
 
266
def deltas_for_log_reverse(branch, which_revs):
 
267
    """Compute deltas for display in latest-to-earliest order.
 
268
 
 
269
    branch
 
270
        Branch to traverse
 
271
 
 
272
    which_revs
 
273
        Sequence of (revno, revision_id) for the subset of history to examine
 
274
 
 
275
    returns 
 
276
        Sequence of (revno, rev, delta)
 
277
 
 
278
    The delta is from the given revision to the next one in the
 
279
    sequence, which makes sense if the log is being displayed from
 
280
    newest to oldest.
 
281
    """
 
282
    last_revno = last_revision_id = last_tree = None
 
283
    for revno, revision_id in which_revs:
 
284
        this_tree = branch.revision_tree(revision_id)
 
285
        this_revision = branch.get_revision(revision_id)
 
286
        
 
287
        if last_revno:
 
288
            yield last_revno, last_revision, compare_trees(this_tree, last_tree, False)
 
289
 
 
290
        this_tree = EmptyTree(branch.get_root_id())
 
291
 
 
292
        last_revno = revno
 
293
        last_revision = this_revision
 
294
        last_tree = this_tree
 
295
 
 
296
    if last_revno:
 
297
        if last_revno == 1:
 
298
            this_tree = EmptyTree(branch.get_root_id())
 
299
        else:
 
300
            this_revno = last_revno - 1
 
301
            this_revision_id = branch.revision_history()[this_revno]
 
302
            this_tree = branch.revision_tree(this_revision_id)
 
303
        yield last_revno, last_revision, compare_trees(this_tree, last_tree, False)
 
304
 
 
305
 
 
306
def deltas_for_log_forward(branch, which_revs):
 
307
    """Compute deltas for display in forward log.
 
308
 
 
309
    Given a sequence of (revno, revision_id) pairs, return
 
310
    (revno, rev, delta).
 
311
 
 
312
    The delta is from the given revision to the next one in the
 
313
    sequence, which makes sense if the log is being displayed from
 
314
    newest to oldest.
 
315
    """
 
316
    last_revno = last_revision_id = last_tree = None
 
317
    prev_tree = EmptyTree(branch.get_root_id())
 
318
 
 
319
    for revno, revision_id in which_revs:
 
320
        this_tree = branch.revision_tree(revision_id)
 
321
        this_revision = branch.get_revision(revision_id)
 
322
 
 
323
        if not last_revno:
 
324
            if revno == 1:
 
325
                last_tree = EmptyTree(branch.get_root_id())
 
326
            else:
 
327
                last_revno = revno - 1
 
328
                last_revision_id = branch.revision_history()[last_revno]
 
329
                last_tree = branch.revision_tree(last_revision_id)
 
330
 
 
331
        yield revno, this_revision, compare_trees(last_tree, this_tree, False)
 
332
 
 
333
        last_revno = revno
 
334
        last_revision = this_revision
 
335
        last_tree = this_tree
311
336
 
312
337
 
313
338
class LogFormatter(object):
314
339
    """Abstract class to display log messages."""
315
 
 
316
340
    def __init__(self, to_file, show_ids=False, show_timezone='original'):
317
341
        self.to_file = to_file
318
342
        self.show_ids = show_ids
319
343
        self.show_timezone = show_timezone
320
344
 
 
345
 
321
346
    def show(self, revno, rev, delta):
322
347
        raise NotImplementedError('not implemented in abstract base')
323
348
 
329
354
    def show(self, revno, rev, delta):
330
355
        return self._show_helper(revno=revno, rev=rev, delta=delta)
331
356
 
332
 
    def show_merge(self, rev, merge_depth):
333
 
        return self._show_helper(rev=rev, indent='    '*merge_depth, merged=True, delta=None)
 
357
    def show_merge(self, rev):
 
358
        return self._show_helper(rev=rev, indent='    ', merged=True, delta=None)
334
359
 
335
360
    def _show_helper(self, rev=None, revno=None, indent='', merged=False, delta=None):
336
 
        """Show a revision, either merged or not."""
 
361
        """Show a revision, either merged or not."""
337
362
        from bzrlib.osutils import format_date
338
363
        to_file = self.to_file
339
364
        print >>to_file,  indent+'-' * 60
394
419
            delta.show(to_file, self.show_ids)
395
420
        print >>to_file, ''
396
421
 
397
 
 
398
422
class LineLogFormatter(LogFormatter):
399
423
    def truncate(self, str, max_len):
400
424
        if len(str) <= max_len:
414
438
            return rev.message
415
439
 
416
440
    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)
 
441
        print >> self.to_file, self.log_string(rev, 79) 
419
442
 
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
427
 
        """
428
 
        out = []
429
 
        if revno:
430
 
            # show revno only when is not None
431
 
            out.append("%d:" % revno)
432
 
        out.append(self.truncate(self.short_committer(rev), 20))
 
443
    def log_string(self, rev, max_chars):
 
444
        out = [self.truncate(self.short_committer(rev), 20)]
433
445
        out.append(self.date_string(rev))
434
 
        out.append(rev.get_summary())
 
446
        out.append(self.message(rev).replace('\n', ' '))
435
447
        return self.truncate(" ".join(out).rstrip('\n'), max_chars)
436
448
 
437
 
 
438
449
def line_log(rev, max_chars):
439
450
    lf = LineLogFormatter(None)
440
 
    return lf.log_string(None, rev, max_chars)
 
451
    return lf.log_string(rev, max_chars)
441
452
 
442
453
FORMATTERS = {
443
454
              'long': LongLogFormatter,
461
472
        raise BzrCommandError("unknown log formatter: %r" % name)
462
473
 
463
474
def show_one_log(revno, rev, delta, verbose, to_file, show_timezone):
464
 
    # deprecated; for compatibility
 
475
    # deprecated; for compatability
465
476
    lf = LongLogFormatter(to_file=to_file, show_timezone=show_timezone)
466
477
    lf.show(revno, rev, delta)
467
478