~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/log.py

  • Committer: Robert Collins
  • Date: 2005-11-28 05:13:41 UTC
  • mfrom: (1185.33.54 merge-recovered)
  • Revision ID: robertc@robertcollins.net-20051128051341-059936f2f29a12c8
Merge from Martin. Adjust check to work with HTTP again.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# Copyright (C) 2005 Canonical Ltd
2
 
#
 
2
 
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
#
 
7
 
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
12
 
#
 
12
 
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
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
 
from bzrlib import symbol_versioning
58
55
import bzrlib.errors as errors
59
 
from bzrlib.symbol_versioning import deprecated_method, zero_eleven
 
56
from bzrlib.tree import EmptyTree
 
57
from bzrlib.delta import compare_trees
60
58
from bzrlib.trace import mutter
61
 
from bzrlib.tsort import merge_sort
 
59
import re
62
60
 
63
61
 
64
62
def find_touching_revisions(branch, file_id):
76
74
    last_path = None
77
75
    revno = 1
78
76
    for revision_id in branch.revision_history():
79
 
        this_inv = branch.repository.get_revision_inventory(revision_id)
 
77
        this_inv = branch.get_revision_inventory(revision_id)
80
78
        if file_id in this_inv:
81
79
            this_ie = this_inv[file_id]
82
80
            this_path = this_inv.id2path(file_id)
115
113
    return rh
116
114
 
117
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
 
118
127
def show_log(branch,
119
128
             lf,
120
129
             specific_fileid=None,
163
172
    """Worker function for show_log - see show_log."""
164
173
    from bzrlib.osutils import format_date
165
174
    from bzrlib.errors import BzrCheckError
 
175
    from bzrlib.textui import show_status
166
176
    
167
177
    from warnings import warn
168
178
 
192
202
 
193
203
    # list indexes are 0-based; revisions are 1-based
194
204
    cut_revs = which_revs[(start_revision-1):(end_revision)]
195
 
    if not cut_revs:
196
 
        return
197
 
 
198
 
    # convert the revision history to a dictionary:
199
 
    rev_nos = dict((k, v) for v, k in cut_revs)
200
 
 
201
 
    # override the mainline to look like the revision history.
202
 
    mainline_revs = [revision_id for index, revision_id in cut_revs]
203
 
    if cut_revs[0][0] == 1:
204
 
        mainline_revs.insert(0, None)
205
 
    else:
206
 
        mainline_revs.insert(0, which_revs[start_revision-2][1])
207
 
    # how should we show merged revisions ?
208
 
    # old api: show_merge. New api: show_merge_revno
209
 
    show_merge_revno = getattr(lf, 'show_merge_revno', None)
210
 
    show_merge = getattr(lf, 'show_merge', None)
211
 
    if show_merge is None and show_merge_revno is None:
212
 
        # no merged-revno support
213
 
        include_merges = False
214
 
    else:
215
 
        include_merges = True
216
 
    if show_merge is not None and show_merge_revno is None:
217
 
        # tell developers to update their code
218
 
        symbol_versioning.warn('LogFormatters should provide show_merge_revno '
219
 
            'instead of show_merge since bzr 0.11.',
220
 
            DeprecationWarning, stacklevel=3)
221
 
    view_revisions = list(get_view_revisions(mainline_revs, rev_nos, branch,
222
 
                          direction, include_merges=include_merges))
223
 
 
224
 
    def iter_revisions():
225
 
        # r = revision, n = revno, d = merge depth
226
 
        revision_ids = [r for r, n, d in view_revisions]
227
 
        zeros = set(r for r, n, d in view_revisions if d == 0)
228
 
        num = 9
229
 
        repository = branch.repository
230
 
        while revision_ids:
231
 
            cur_deltas = {}
232
 
            revisions = repository.get_revisions(revision_ids[:num])
233
 
            if verbose or specific_fileid:
234
 
                delta_revisions = [r for r in revisions if
235
 
                                   r.revision_id in zeros]
236
 
                deltas = repository.get_deltas_for_revisions(delta_revisions)
237
 
                cur_deltas = dict(izip((r.revision_id for r in 
238
 
                                        delta_revisions), deltas))
239
 
            for revision in revisions:
240
 
                # The delta value will be None unless
241
 
                # 1. verbose or specific_fileid is specified, and
242
 
                # 2. the revision is a mainline revision
243
 
                yield revision, cur_deltas.get(revision.revision_id)
244
 
            revision_ids  = revision_ids[num:]
245
 
            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)
246
217
            
247
 
    # now we just print all the revisions
248
 
    for ((rev_id, revno, merge_depth), (rev, delta)) in \
249
 
         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.get_revision(rev_id)
250
227
 
251
228
        if searchRE:
252
229
            if not searchRE.search(rev.message):
253
230
                continue
254
231
 
255
 
        if merge_depth == 0:
256
 
            # a mainline revision.
257
 
                
258
 
            if specific_fileid:
259
 
                if not delta.touches_file_id(specific_fileid):
260
 
                    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
                excludes = set(branch.get_ancestry(revision_history[revno - 2]))
 
239
            pending = list(rev.parent_ids)
 
240
            while pending:
 
241
                rev_id = pending.pop()
 
242
                if rev_id in excludes:
 
243
                    continue
 
244
                # prevent showing merged revs twice if they multi-path.
 
245
                excludes.add(rev_id)
 
246
                try:
 
247
                    rev = branch.get_revision(rev_id)
 
248
                except errors.NoSuchRevision:
 
249
                    continue
 
250
                pending.extend(rev.parent_ids)
 
251
                lf.show_merge(rev)
 
252
 
 
253
 
 
254
def deltas_for_log_dummy(branch, which_revs):
 
255
    """Return all the revisions without intermediate deltas.
 
256
 
 
257
    Useful for log commands that won't need the delta information.
 
258
    """
261
259
    
262
 
            if not verbose:
263
 
                # although we calculated it, throw it away without display
264
 
                delta = None
265
 
 
266
 
            lf.show(revno, rev, delta)
 
260
    for revno, revision_id in which_revs:
 
261
        yield revno, branch.get_revision(revision_id), None
 
262
 
 
263
 
 
264
def deltas_for_log_reverse(branch, which_revs):
 
265
    """Compute deltas for display in latest-to-earliest order.
 
266
 
 
267
    branch
 
268
        Branch to traverse
 
269
 
 
270
    which_revs
 
271
        Sequence of (revno, revision_id) for the subset of history to examine
 
272
 
 
273
    returns 
 
274
        Sequence of (revno, rev, delta)
 
275
 
 
276
    The delta is from the given revision to the next one in the
 
277
    sequence, which makes sense if the log is being displayed from
 
278
    newest to oldest.
 
279
    """
 
280
    last_revno = last_revision_id = last_tree = None
 
281
    for revno, revision_id in which_revs:
 
282
        this_tree = branch.revision_tree(revision_id)
 
283
        this_revision = branch.get_revision(revision_id)
 
284
        
 
285
        if last_revno:
 
286
            yield last_revno, last_revision, compare_trees(this_tree, last_tree, False)
 
287
 
 
288
        this_tree = EmptyTree(branch.get_root_id())
 
289
 
 
290
        last_revno = revno
 
291
        last_revision = this_revision
 
292
        last_tree = this_tree
 
293
 
 
294
    if last_revno:
 
295
        if last_revno == 1:
 
296
            this_tree = EmptyTree(branch.get_root_id())
267
297
        else:
268
 
            if show_merge_revno is None:
269
 
                lf.show_merge(rev, merge_depth)
 
298
            this_revno = last_revno - 1
 
299
            this_revision_id = branch.revision_history()[this_revno]
 
300
            this_tree = branch.revision_tree(this_revision_id)
 
301
        yield last_revno, last_revision, compare_trees(this_tree, last_tree, False)
 
302
 
 
303
 
 
304
def deltas_for_log_forward(branch, which_revs):
 
305
    """Compute deltas for display in forward log.
 
306
 
 
307
    Given a sequence of (revno, revision_id) pairs, return
 
308
    (revno, rev, delta).
 
309
 
 
310
    The delta is from the given revision to the next one in the
 
311
    sequence, which makes sense if the log is being displayed from
 
312
    newest to oldest.
 
313
    """
 
314
    last_revno = last_revision_id = last_tree = None
 
315
    prev_tree = EmptyTree(branch.get_root_id())
 
316
 
 
317
    for revno, revision_id in which_revs:
 
318
        this_tree = branch.revision_tree(revision_id)
 
319
        this_revision = branch.get_revision(revision_id)
 
320
 
 
321
        if not last_revno:
 
322
            if revno == 1:
 
323
                last_tree = EmptyTree(branch.get_root_id())
270
324
            else:
271
 
                lf.show_merge_revno(rev, merge_depth, revno)
272
 
 
273
 
 
274
 
def get_view_revisions(mainline_revs, rev_nos, branch, direction,
275
 
                       include_merges=True):
276
 
    """Produce an iterator of revisions to show
277
 
    :return: an iterator of (revision_id, revno, merge_depth)
278
 
    (if there is no revno for a revision, None is supplied)
279
 
    """
280
 
    if include_merges is False:
281
 
        revision_ids = mainline_revs[1:]
282
 
        if direction == 'reverse':
283
 
            revision_ids.reverse()
284
 
        for revision_id in revision_ids:
285
 
            yield revision_id, str(rev_nos[revision_id]), 0
286
 
        return
287
 
    merge_sorted_revisions = merge_sort(
288
 
        branch.repository.get_revision_graph(mainline_revs[-1]),
289
 
        mainline_revs[-1],
290
 
        mainline_revs,
291
 
        generate_revno=True)
292
 
 
293
 
    if direction == 'forward':
294
 
        # forward means oldest first.
295
 
        merge_sorted_revisions = reverse_by_depth(merge_sorted_revisions)
296
 
    elif direction != 'reverse':
297
 
        raise ValueError('invalid direction %r' % direction)
298
 
 
299
 
    revision_history = branch.revision_history()
300
 
 
301
 
    for sequence, rev_id, merge_depth, revno, end_of_merge in merge_sorted_revisions:
302
 
        yield rev_id, '.'.join(map(str, revno)), merge_depth
303
 
 
304
 
 
305
 
def reverse_by_depth(merge_sorted_revisions, _depth=0):
306
 
    """Reverse revisions by depth.
307
 
 
308
 
    Revisions with a different depth are sorted as a group with the previous
309
 
    revision of that depth.  There may be no topological justification for this,
310
 
    but it looks much nicer.
311
 
    """
312
 
    zd_revisions = []
313
 
    for val in merge_sorted_revisions:
314
 
        if val[2] == _depth:
315
 
            zd_revisions.append([val])
316
 
        else:
317
 
            assert val[2] > _depth
318
 
            zd_revisions[-1].append(val)
319
 
    for revisions in zd_revisions:
320
 
        if len(revisions) > 1:
321
 
            revisions[1:] = reverse_by_depth(revisions[1:], _depth + 1)
322
 
    zd_revisions.reverse()
323
 
    result = []
324
 
    for chunk in zd_revisions:
325
 
        result.extend(chunk)
326
 
    return result
 
325
                last_revno = revno - 1
 
326
                last_revision_id = branch.revision_history()[last_revno]
 
327
                last_tree = branch.revision_tree(last_revision_id)
 
328
 
 
329
        yield revno, this_revision, compare_trees(last_tree, this_tree, False)
 
330
 
 
331
        last_revno = revno
 
332
        last_revision = this_revision
 
333
        last_tree = this_tree
327
334
 
328
335
 
329
336
class LogFormatter(object):
330
337
    """Abstract class to display log messages."""
331
 
 
332
338
    def __init__(self, to_file, show_ids=False, show_timezone='original'):
333
339
        self.to_file = to_file
334
340
        self.show_ids = show_ids
335
341
        self.show_timezone = show_timezone
336
342
 
 
343
 
337
344
    def show(self, revno, rev, delta):
338
345
        raise NotImplementedError('not implemented in abstract base')
339
346
 
345
352
    def show(self, revno, rev, delta):
346
353
        return self._show_helper(revno=revno, rev=rev, delta=delta)
347
354
 
348
 
    @deprecated_method(zero_eleven)
349
 
    def show_merge(self, rev, merge_depth):
350
 
        return self._show_helper(rev=rev, indent='    '*merge_depth, merged=True, delta=None)
351
 
 
352
 
    def show_merge_revno(self, rev, merge_depth, revno):
353
 
        """Show a merged revision rev, with merge_depth and a revno."""
354
 
        return self._show_helper(rev=rev, revno=revno,
355
 
            indent='    '*merge_depth, merged=True, delta=None)
 
355
    def show_merge(self, rev):
 
356
        return self._show_helper(rev=rev, indent='    ', merged=True, delta=None)
356
357
 
357
358
    def _show_helper(self, rev=None, revno=None, indent='', merged=False, delta=None):
358
 
        """Show a revision, either merged or not."""
 
359
        """Show a revision, either merged or not."""
359
360
        from bzrlib.osutils import format_date
360
361
        to_file = self.to_file
361
362
        print >>to_file,  indent+'-' * 60
362
363
        if revno is not None:
363
 
            print >>to_file,  indent+'revno:', revno
 
364
            print >>to_file,  'revno:', revno
364
365
        if merged:
365
366
            print >>to_file,  indent+'merged:', rev.revision_id
366
367
        elif self.show_ids:
386
387
            message = rev.message.rstrip('\r\n')
387
388
            for l in message.split('\n'):
388
389
                print >>to_file,  indent+'  ' + l
389
 
        if delta is not None:
 
390
        if delta != None:
390
391
            delta.show(to_file, self.show_ids)
391
392
 
392
393
 
397
398
        to_file = self.to_file
398
399
        date_str = format_date(rev.timestamp, rev.timezone or 0,
399
400
                            self.show_timezone)
400
 
        print >>to_file, "%5s %s\t%s" % (revno, self.short_committer(rev),
 
401
        print >>to_file, "%5d %s\t%s" % (revno, self.short_committer(rev),
401
402
                format_date(rev.timestamp, rev.timezone or 0,
402
403
                            self.show_timezone, date_fmt="%Y-%m-%d",
403
404
                           show_offset=False))
412
413
 
413
414
        # TODO: Why not show the modified files in a shorter form as
414
415
        # well? rewrap them single lines of appropriate length
415
 
        if delta is not None:
 
416
        if delta != None:
416
417
            delta.show(to_file, self.show_ids)
417
418
        print >>to_file, ''
418
419
 
419
 
 
420
420
class LineLogFormatter(LogFormatter):
421
421
    def truncate(self, str, max_len):
422
422
        if len(str) <= max_len:
436
436
            return rev.message
437
437
 
438
438
    def show(self, revno, rev, delta):
439
 
        from bzrlib.osutils import terminal_width
440
 
        print >> self.to_file, self.log_string(revno, rev, terminal_width()-1)
 
439
        print >> self.to_file, self.log_string(rev, 79) 
441
440
 
442
 
    def log_string(self, revno, rev, max_chars):
443
 
        """Format log info into one string. Truncate tail of string
444
 
        :param  revno:      revision number (int) or None.
445
 
                            Revision numbers counts from 1.
446
 
        :param  rev:        revision info object
447
 
        :param  max_chars:  maximum length of resulting string
448
 
        :return:            formatted truncated string
449
 
        """
450
 
        out = []
451
 
        if revno:
452
 
            # show revno only when is not None
453
 
            out.append("%s:" % revno)
454
 
        out.append(self.truncate(self.short_committer(rev), 20))
 
441
    def log_string(self, rev, max_chars):
 
442
        out = [self.truncate(self.short_committer(rev), 20)]
455
443
        out.append(self.date_string(rev))
456
 
        out.append(rev.get_summary())
 
444
        out.append(self.message(rev).replace('\n', ' '))
457
445
        return self.truncate(" ".join(out).rstrip('\n'), max_chars)
458
446
 
459
 
 
460
447
def line_log(rev, max_chars):
461
448
    lf = LineLogFormatter(None)
462
 
    return lf.log_string(None, rev, max_chars)
 
449
    return lf.log_string(rev, max_chars)
463
450
 
464
 
FORMATTERS = {
465
 
              'long': LongLogFormatter,
 
451
FORMATTERS = {'long': LongLogFormatter,
466
452
              'short': ShortLogFormatter,
467
453
              'line': LineLogFormatter,
468
454
              }
469
455
 
470
 
def register_formatter(name, formatter):
471
 
    FORMATTERS[name] = formatter
472
456
 
473
457
def log_formatter(name, *args, **kwargs):
474
458
    """Construct a formatter from arguments.
479
463
    from bzrlib.errors import BzrCommandError
480
464
    try:
481
465
        return FORMATTERS[name](*args, **kwargs)
482
 
    except KeyError:
 
466
    except IndexError:
483
467
        raise BzrCommandError("unknown log formatter: %r" % name)
484
468
 
485
469
def show_one_log(revno, rev, delta, verbose, to_file, show_timezone):
486
 
    # deprecated; for compatibility
 
470
    # deprecated; for compatability
487
471
    lf = LongLogFormatter(to_file=to_file, show_timezone=show_timezone)
488
472
    lf.show(revno, rev, delta)
489
473
 
526
510
        to_file.write('*'*60)
527
511
        to_file.write('\nRemoved Revisions:\n')
528
512
        for i in range(base_idx, len(old_rh)):
529
 
            rev = branch.repository.get_revision(old_rh[i])
 
513
            rev = branch.get_revision(old_rh[i])
530
514
            lf.show(i+1, rev, None)
531
515
        to_file.write('*'*60)
532
516
        to_file.write('\n\n')