/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/log.py

  • Committer: Martin Pool
  • Date: 2006-06-20 03:57:11 UTC
  • mto: This revision was merged to the branch mainline in revision 1798.
  • Revision ID: mbp@sourcefrog.net-20060620035711-400bb6b6bc6ff95b
Add pyflakes makefile target; fix many warnings

Show diffs side-by-side

added added

removed removed

Lines of Context:
50
50
"""
51
51
 
52
52
 
53
 
from bzrlib.tree import EmptyTree
 
53
# TODO: option to show delta summaries for merged-in revisions
 
54
#
 
55
# TODO: deltas_for_log_reverse seems no longer called; delete it?
 
56
 
 
57
import re
 
58
 
54
59
from bzrlib.delta import compare_trees
 
60
import bzrlib.errors as errors
55
61
from bzrlib.trace import mutter
56
 
from bzrlib.errors import InvalidRevisionNumber
 
62
from bzrlib.tree import EmptyTree
 
63
from bzrlib.tsort import merge_sort
57
64
 
58
65
 
59
66
def find_touching_revisions(branch, file_id):
71
78
    last_path = None
72
79
    revno = 1
73
80
    for revision_id in branch.revision_history():
74
 
        this_inv = branch.get_revision_inventory(revision_id)
 
81
        this_inv = branch.repository.get_revision_inventory(revision_id)
75
82
        if file_id in this_inv:
76
83
            this_ie = this_inv[file_id]
77
84
            this_path = this_inv.id2path(file_id)
110
117
    return rh
111
118
 
112
119
 
 
120
def _get_revision_delta(branch, revno):
 
121
    """Return the delta for a mainline revision.
 
122
    
 
123
    This is used to show summaries in verbose logs, and also for finding 
 
124
    revisions which touch a given file."""
 
125
    # XXX: What are we supposed to do when showing a summary for something 
 
126
    # other than a mainline revision.  The delta to it's first parent, or
 
127
    # (more useful) the delta to a nominated other revision.
 
128
    return branch.get_revision_delta(revno)
 
129
 
 
130
 
113
131
def show_log(branch,
114
132
             lf,
115
133
             specific_fileid=None,
140
158
    end_revision
141
159
        If not None, only show revisions <= end_revision
142
160
    """
 
161
    branch.lock_read()
 
162
    try:
 
163
        _show_log(branch, lf, specific_fileid, verbose, direction,
 
164
                  start_revision, end_revision, search)
 
165
    finally:
 
166
        branch.unlock()
 
167
    
 
168
def _show_log(branch,
 
169
             lf,
 
170
             specific_fileid=None,
 
171
             verbose=False,
 
172
             direction='reverse',
 
173
             start_revision=None,
 
174
             end_revision=None,
 
175
             search=None):
 
176
    """Worker function for show_log - see show_log."""
143
177
    from bzrlib.osutils import format_date
144
178
    from bzrlib.errors import BzrCheckError
145
179
    from bzrlib.textui import show_status
150
184
        warn("not a LogFormatter instance: %r" % lf)
151
185
 
152
186
    if specific_fileid:
153
 
        mutter('get log for file_id %r' % specific_fileid)
 
187
        mutter('get log for file_id %r', specific_fileid)
154
188
 
155
189
    if search is not None:
156
190
        import re
162
196
    
163
197
    if start_revision is None:
164
198
        start_revision = 1
165
 
    elif start_revision < 1 or start_revision >= len(which_revs):
166
 
        raise InvalidRevisionNumber(start_revision)
 
199
    else:
 
200
        branch.check_real_revno(start_revision)
167
201
    
168
202
    if end_revision is None:
169
203
        end_revision = len(which_revs)
170
 
    elif end_revision < 1 or end_revision >= len(which_revs):
171
 
        raise InvalidRevisionNumber(end_revision)
 
204
    else:
 
205
        branch.check_real_revno(end_revision)
172
206
 
173
207
    # list indexes are 0-based; revisions are 1-based
174
208
    cut_revs = which_revs[(start_revision-1):(end_revision)]
 
209
    if not cut_revs:
 
210
        return
 
211
    # override the mainline to look like the revision history.
 
212
    mainline_revs = [revision_id for index, revision_id in cut_revs]
 
213
    if cut_revs[0][0] == 1:
 
214
        mainline_revs.insert(0, None)
 
215
    else:
 
216
        mainline_revs.insert(0, which_revs[start_revision-2][1])
 
217
 
 
218
    merge_sorted_revisions = merge_sort(
 
219
        branch.repository.get_revision_graph(mainline_revs[-1]),
 
220
        mainline_revs[-1],
 
221
        mainline_revs)
175
222
 
176
223
    if direction == 'reverse':
177
224
        cut_revs.reverse()
178
225
    elif direction == 'forward':
179
 
        pass
 
226
        # forward means oldest first.
 
227
        merge_sorted_revisions.reverse()
180
228
    else:
181
229
        raise ValueError('invalid direction %r' % direction)
182
230
 
183
 
    for revno, rev_id in cut_revs:
184
 
        if verbose or specific_fileid:
185
 
            delta = branch.get_revision_delta(revno)
186
 
            
187
 
        if specific_fileid:
188
 
            if not delta.touches_file_id(specific_fileid):
189
 
                continue
190
 
 
191
 
        if not verbose:
192
 
            # although we calculated it, throw it away without display
193
 
            delta = None
194
 
 
195
 
        rev = branch.get_revision(rev_id)
 
231
    revision_history = branch.revision_history()
 
232
 
 
233
    # convert the revision history to a dictionary:
 
234
    rev_nos = {}
 
235
    for index, rev_id in cut_revs:
 
236
        rev_nos[rev_id] = index
 
237
 
 
238
    # now we just print all the revisions
 
239
    for sequence, rev_id, merge_depth, end_of_merge in merge_sorted_revisions:
 
240
        rev = branch.repository.get_revision(rev_id)
196
241
 
197
242
        if searchRE:
198
243
            if not searchRE.search(rev.message):
199
244
                continue
200
245
 
201
 
        lf.show(revno, rev, delta)
202
 
 
203
 
 
204
 
 
205
 
def deltas_for_log_dummy(branch, which_revs):
206
 
    """Return all the revisions without intermediate deltas.
207
 
 
208
 
    Useful for log commands that won't need the delta information.
209
 
    """
 
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
210
254
    
211
 
    for revno, revision_id in which_revs:
212
 
        yield revno, branch.get_revision(revision_id), None
213
 
 
214
 
 
215
 
def deltas_for_log_reverse(branch, which_revs):
216
 
    """Compute deltas for display in latest-to-earliest order.
217
 
 
218
 
    branch
219
 
        Branch to traverse
220
 
 
221
 
    which_revs
222
 
        Sequence of (revno, revision_id) for the subset of history to examine
223
 
 
224
 
    returns 
225
 
        Sequence of (revno, rev, delta)
226
 
 
227
 
    The delta is from the given revision to the next one in the
228
 
    sequence, which makes sense if the log is being displayed from
229
 
    newest to oldest.
230
 
    """
231
 
    last_revno = last_revision_id = last_tree = None
232
 
    for revno, revision_id in which_revs:
233
 
        this_tree = branch.revision_tree(revision_id)
234
 
        this_revision = branch.get_revision(revision_id)
235
 
        
236
 
        if last_revno:
237
 
            yield last_revno, last_revision, compare_trees(this_tree, last_tree, False)
238
 
 
239
 
        this_tree = EmptyTree(branch.get_root_id())
240
 
 
241
 
        last_revno = revno
242
 
        last_revision = this_revision
243
 
        last_tree = this_tree
244
 
 
245
 
    if last_revno:
246
 
        if last_revno == 1:
247
 
            this_tree = EmptyTree(branch.get_root_id())
248
 
        else:
249
 
            this_revno = last_revno - 1
250
 
            this_revision_id = branch.revision_history()[this_revno]
251
 
            this_tree = branch.revision_tree(this_revision_id)
252
 
        yield last_revno, last_revision, compare_trees(this_tree, last_tree, False)
253
 
 
254
 
 
255
 
def deltas_for_log_forward(branch, which_revs):
256
 
    """Compute deltas for display in forward log.
257
 
 
258
 
    Given a sequence of (revno, revision_id) pairs, return
259
 
    (revno, rev, delta).
260
 
 
261
 
    The delta is from the given revision to the next one in the
262
 
    sequence, which makes sense if the log is being displayed from
263
 
    newest to oldest.
264
 
    """
265
 
    last_revno = last_revision_id = last_tree = None
266
 
    prev_tree = EmptyTree(branch.get_root_id())
267
 
 
268
 
    for revno, revision_id in which_revs:
269
 
        this_tree = branch.revision_tree(revision_id)
270
 
        this_revision = branch.get_revision(revision_id)
271
 
 
272
 
        if not last_revno:
273
 
            if revno == 1:
274
 
                last_tree = EmptyTree(branch.get_root_id())
275
 
            else:
276
 
                last_revno = revno - 1
277
 
                last_revision_id = branch.revision_history()[last_revno]
278
 
                last_tree = branch.revision_tree(last_revision_id)
279
 
 
280
 
        yield revno, this_revision, compare_trees(last_tree, this_tree, False)
281
 
 
282
 
        last_revno = revno
283
 
        last_revision = this_revision
284
 
        last_tree = this_tree
 
255
            if not verbose:
 
256
                # although we calculated it, throw it away without display
 
257
                delta = None
 
258
 
 
259
            lf.show(rev_nos[rev_id], rev, delta)
 
260
        elif hasattr(lf, 'show_merge'):
 
261
            lf.show_merge(rev, merge_depth)
285
262
 
286
263
 
287
264
class LogFormatter(object):
288
265
    """Abstract class to display log messages."""
 
266
 
289
267
    def __init__(self, to_file, show_ids=False, show_timezone='original'):
290
268
        self.to_file = to_file
291
269
        self.show_ids = show_ids
292
270
        self.show_timezone = show_timezone
293
271
 
294
 
 
295
272
    def show(self, revno, rev, delta):
296
273
        raise NotImplementedError('not implemented in abstract base')
297
 
        
298
 
 
299
 
 
300
 
 
301
 
 
302
 
 
 
274
 
 
275
    def short_committer(self, rev):
 
276
        return re.sub('<.*@.*>', '', rev.committer).strip(' ')
 
277
    
 
278
    
303
279
class LongLogFormatter(LogFormatter):
304
280
    def show(self, revno, rev, delta):
305
 
        from osutils import format_date
306
 
 
 
281
        return self._show_helper(revno=revno, rev=rev, delta=delta)
 
282
 
 
283
    def show_merge(self, rev, merge_depth):
 
284
        return self._show_helper(rev=rev, indent='    '*merge_depth, merged=True, delta=None)
 
285
 
 
286
    def _show_helper(self, rev=None, revno=None, indent='', merged=False, delta=None):
 
287
        """Show a revision, either merged or not."""
 
288
        from bzrlib.osutils import format_date
307
289
        to_file = self.to_file
308
 
 
309
 
        print >>to_file,  '-' * 60
310
 
        print >>to_file,  'revno:', revno
 
290
        print >>to_file,  indent+'-' * 60
 
291
        if revno is not None:
 
292
            print >>to_file,  'revno:', revno
 
293
        if merged:
 
294
            print >>to_file,  indent+'merged:', rev.revision_id
 
295
        elif self.show_ids:
 
296
            print >>to_file,  indent+'revision-id:', rev.revision_id
311
297
        if self.show_ids:
312
 
            print >>to_file,  'revision-id:', rev.revision_id
313
 
        print >>to_file,  'committer:', rev.committer
314
 
 
 
298
            for parent_id in rev.parent_ids:
 
299
                print >>to_file, indent+'parent:', parent_id
 
300
        print >>to_file,  indent+'committer:', rev.committer
 
301
        try:
 
302
            print >>to_file, indent+'branch nick: %s' % \
 
303
                rev.properties['branch-nick']
 
304
        except KeyError:
 
305
            pass
315
306
        date_str = format_date(rev.timestamp,
316
307
                               rev.timezone or 0,
317
308
                               self.show_timezone)
318
 
        print >>to_file,  'timestamp: %s' % date_str
 
309
        print >>to_file,  indent+'timestamp: %s' % date_str
319
310
 
320
 
        print >>to_file,  'message:'
 
311
        print >>to_file,  indent+'message:'
321
312
        if not rev.message:
322
 
            print >>to_file,  '  (no message)'
 
313
            print >>to_file,  indent+'  (no message)'
323
314
        else:
324
 
            for l in rev.message.split('\n'):
325
 
                print >>to_file,  '  ' + l
326
 
 
 
315
            message = rev.message.rstrip('\r\n')
 
316
            for l in message.split('\n'):
 
317
                print >>to_file,  indent+'  ' + l
327
318
        if delta != None:
328
319
            delta.show(to_file, self.show_ids)
329
320
 
330
321
 
331
 
 
332
322
class ShortLogFormatter(LogFormatter):
333
323
    def show(self, revno, rev, delta):
334
324
        from bzrlib.osutils import format_date
335
325
 
336
326
        to_file = self.to_file
337
 
 
338
 
        print >>to_file, "%5d %s\t%s" % (revno, rev.committer,
 
327
        date_str = format_date(rev.timestamp, rev.timezone or 0,
 
328
                            self.show_timezone)
 
329
        print >>to_file, "%5d %s\t%s" % (revno, self.short_committer(rev),
339
330
                format_date(rev.timestamp, rev.timezone or 0,
340
 
                            self.show_timezone))
 
331
                            self.show_timezone, date_fmt="%Y-%m-%d",
 
332
                           show_offset=False))
341
333
        if self.show_ids:
342
334
            print >>to_file,  '      revision-id:', rev.revision_id
343
335
        if not rev.message:
344
336
            print >>to_file,  '      (no message)'
345
337
        else:
346
 
            for l in rev.message.split('\n'):
 
338
            message = rev.message.rstrip('\r\n')
 
339
            for l in message.split('\n'):
347
340
                print >>to_file,  '      ' + l
348
341
 
349
342
        # TODO: Why not show the modified files in a shorter form as
350
343
        # well? rewrap them single lines of appropriate length
351
344
        if delta != None:
352
345
            delta.show(to_file, self.show_ids)
353
 
        print
354
 
 
355
 
 
356
 
 
357
 
FORMATTERS = {'long': LongLogFormatter,
 
346
        print >>to_file, ''
 
347
 
 
348
 
 
349
class LineLogFormatter(LogFormatter):
 
350
    def truncate(self, str, max_len):
 
351
        if len(str) <= max_len:
 
352
            return str
 
353
        return str[:max_len-3]+'...'
 
354
 
 
355
    def date_string(self, rev):
 
356
        from bzrlib.osutils import format_date
 
357
        return format_date(rev.timestamp, rev.timezone or 0, 
 
358
                           self.show_timezone, date_fmt="%Y-%m-%d",
 
359
                           show_offset=False)
 
360
 
 
361
    def message(self, rev):
 
362
        if not rev.message:
 
363
            return '(no message)'
 
364
        else:
 
365
            return rev.message
 
366
 
 
367
    def show(self, revno, rev, delta):
 
368
        from bzrlib.osutils import terminal_width
 
369
        print >> self.to_file, self.log_string(revno, rev, terminal_width()-1)
 
370
 
 
371
    def log_string(self, revno, rev, max_chars):
 
372
        """Format log info into one string. Truncate tail of string
 
373
        :param  revno:      revision number (int) or None.
 
374
                            Revision numbers counts from 1.
 
375
        :param  rev:        revision info object
 
376
        :param  max_chars:  maximum length of resulting string
 
377
        :return:            formatted truncated string
 
378
        """
 
379
        out = []
 
380
        if revno:
 
381
            # show revno only when is not None
 
382
            out.append("%d:" % revno)
 
383
        out.append(self.truncate(self.short_committer(rev), 20))
 
384
        out.append(self.date_string(rev))
 
385
        out.append(rev.get_summary())
 
386
        return self.truncate(" ".join(out).rstrip('\n'), max_chars)
 
387
 
 
388
 
 
389
def line_log(rev, max_chars):
 
390
    lf = LineLogFormatter(None)
 
391
    return lf.log_string(None, rev, max_chars)
 
392
 
 
393
FORMATTERS = {
 
394
              'long': LongLogFormatter,
358
395
              'short': ShortLogFormatter,
 
396
              'line': LineLogFormatter,
359
397
              }
360
398
 
 
399
def register_formatter(name, formatter):
 
400
    FORMATTERS[name] = formatter
361
401
 
362
402
def log_formatter(name, *args, **kwargs):
 
403
    """Construct a formatter from arguments.
 
404
 
 
405
    name -- Name of the formatter to construct; currently 'long', 'short' and
 
406
        'line' are supported.
 
407
    """
363
408
    from bzrlib.errors import BzrCommandError
364
 
    
365
409
    try:
366
410
        return FORMATTERS[name](*args, **kwargs)
367
 
    except IndexError:
 
411
    except KeyError:
368
412
        raise BzrCommandError("unknown log formatter: %r" % name)
369
413
 
370
414
def show_one_log(revno, rev, delta, verbose, to_file, show_timezone):
371
 
    # deprecated; for compatability
 
415
    # deprecated; for compatibility
372
416
    lf = LongLogFormatter(to_file=to_file, show_timezone=show_timezone)
373
417
    lf.show(revno, rev, delta)
 
418
 
 
419
def show_changed_revisions(branch, old_rh, new_rh, to_file=None, log_format='long'):
 
420
    """Show the change in revision history comparing the old revision history to the new one.
 
421
 
 
422
    :param branch: The branch where the revisions exist
 
423
    :param old_rh: The old revision history
 
424
    :param new_rh: The new revision history
 
425
    :param to_file: A file to write the results to. If None, stdout will be used
 
426
    """
 
427
    if to_file is None:
 
428
        import sys
 
429
        import codecs
 
430
        import bzrlib
 
431
        to_file = codecs.getwriter(bzrlib.user_encoding)(sys.stdout, errors='replace')
 
432
    lf = log_formatter(log_format,
 
433
                       show_ids=False,
 
434
                       to_file=to_file,
 
435
                       show_timezone='original')
 
436
 
 
437
    # This is the first index which is different between
 
438
    # old and new
 
439
    base_idx = None
 
440
    for i in xrange(max(len(new_rh),
 
441
                        len(old_rh))):
 
442
        if (len(new_rh) <= i
 
443
            or len(old_rh) <= i
 
444
            or new_rh[i] != old_rh[i]):
 
445
            base_idx = i
 
446
            break
 
447
 
 
448
    if base_idx is None:
 
449
        to_file.write('Nothing seems to have changed\n')
 
450
        return
 
451
    ## TODO: It might be nice to do something like show_log
 
452
    ##       and show the merged entries. But since this is the
 
453
    ##       removed revisions, it shouldn't be as important
 
454
    if base_idx < len(old_rh):
 
455
        to_file.write('*'*60)
 
456
        to_file.write('\nRemoved Revisions:\n')
 
457
        for i in range(base_idx, len(old_rh)):
 
458
            rev = branch.repository.get_revision(old_rh[i])
 
459
            lf.show(i+1, rev, None)
 
460
        to_file.write('*'*60)
 
461
        to_file.write('\n\n')
 
462
    if base_idx < len(new_rh):
 
463
        to_file.write('Added Revisions:\n')
 
464
        show_log(branch,
 
465
                 lf,
 
466
                 None,
 
467
                 verbose=True,
 
468
                 direction='forward',
 
469
                 start_revision=base_idx+1,
 
470
                 end_revision=len(new_rh),
 
471
                 search=None)
 
472