~bzr-pqm/bzr/bzr.dev

369 by Martin Pool
- Split out log printing into new show_log function
1
# Copyright (C) 2005 Canonical Ltd
2
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
375 by Martin Pool
- New command touching-revisions and function to trace
17
18
527 by Martin Pool
- refactor log command
19
"""Code to show logs of changes.
20
21
Various flavors of log can be produced:
22
23
* for one file, or the whole tree, and (not done yet) for
24
  files in a given directory
25
26
* in "verbose" mode with a description of what changed from one
27
  version to the next
28
29
* with file-ids and revision-ids shown
30
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
31
Logs are actually written out through an abstract LogFormatter
32
interface, which allows for different preferred formats.  Plugins can
33
register formats too.
34
35
Logs can be produced in either forward (oldest->newest) or reverse
36
(newest->oldest) order.
37
38
Logs can be filtered to show only revisions matching a particular
39
search string, or within a particular range of revisions.  The range
40
can be given as date/times, which are reduced to revisions before
41
calling in here.
42
43
In verbose mode we show a summary of what changed in each particular
44
revision.  Note that this is the delta for changes in that revision
45
relative to its mainline parent, not the delta relative to the last
46
logged revision.  So for example if you ask for a verbose log of
47
changes touching hello.c you will get a list of those revisions also
48
listing other things that were changed in the same revision, but not
49
all the changes since the previous revision that touched hello.c.
527 by Martin Pool
- refactor log command
50
"""
51
52
1433 by Robert Collins
merge in and make incremental Gustavo Niemeyers nested log patch, and remove all bare exceptions in store and transport packages.
53
import bzrlib.errors as errors
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
54
from bzrlib.tree import EmptyTree
55
from bzrlib.delta import compare_trees
56
from bzrlib.trace import mutter
57
375 by Martin Pool
- New command touching-revisions and function to trace
58
59
def find_touching_revisions(branch, file_id):
60
    """Yield a description of revisions which affect the file_id.
61
62
    Each returned element is (revno, revision_id, description)
63
64
    This is the list of revisions where the file is either added,
65
    modified, renamed or deleted.
66
67
    TODO: Perhaps some way to limit this to only particular revisions,
522 by Martin Pool
todo
68
    or to traverse a non-mainline set of revisions?
375 by Martin Pool
- New command touching-revisions and function to trace
69
    """
70
    last_ie = None
71
    last_path = None
72
    revno = 1
73
    for revision_id in branch.revision_history():
74
        this_inv = branch.get_revision_inventory(revision_id)
75
        if file_id in this_inv:
76
            this_ie = this_inv[file_id]
77
            this_path = this_inv.id2path(file_id)
78
        else:
79
            this_ie = this_path = None
80
81
        # now we know how it was last time, and how it is in this revision.
82
        # are those two states effectively the same or not?
83
84
        if not this_ie and not last_ie:
85
            # not present in either
86
            pass
87
        elif this_ie and not last_ie:
88
            yield revno, revision_id, "added " + this_path
89
        elif not this_ie and last_ie:
90
            # deleted here
91
            yield revno, revision_id, "deleted " + last_path
92
        elif this_path != last_path:
93
            yield revno, revision_id, ("renamed %s => %s" % (last_path, this_path))
94
        elif (this_ie.text_size != last_ie.text_size
95
              or this_ie.text_sha1 != last_ie.text_sha1):
96
            yield revno, revision_id, "modified " + this_path
97
98
        last_ie = this_ie
99
        last_path = this_path
100
        revno += 1
101
102
527 by Martin Pool
- refactor log command
103
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
104
def _enumerate_history(branch):
105
    rh = []
106
    revno = 1
107
    for rev_id in branch.revision_history():
108
        rh.append((revno, rev_id))
109
        revno += 1
110
    return rh
111
112
1393.1.56 by Martin Pool
- doc and small refactoring of log code
113
def _get_revision_delta(branch, revno):
114
    """Return the delta for a mainline revision.
115
    
116
    This is used to show summaries in verbose logs, and also for finding 
117
    revisions which touch a given file."""
118
    # XXX: What are we supposed to do when showing a summary for something 
119
    # other than a mainline revision.  The delta to it's first parent, or
120
    # (more useful) the delta to a nominated other revision.
121
    return branch.get_revision_delta(revno)
122
123
378 by Martin Pool
- New usage bzr log FILENAME
124
def show_log(branch,
794 by Martin Pool
- Merge John's nice short-log format.
125
             lf,
527 by Martin Pool
- refactor log command
126
             specific_fileid=None,
378 by Martin Pool
- New usage bzr log FILENAME
127
             verbose=False,
567 by Martin Pool
- New form 'bzr log -r FROM:TO'
128
             direction='reverse',
129
             start_revision=None,
900 by Martin Pool
- patch from john to search for matching commits
130
             end_revision=None,
131
             search=None):
369 by Martin Pool
- Split out log printing into new show_log function
132
    """Write out human-readable log of commits to this branch.
133
794 by Martin Pool
- Merge John's nice short-log format.
134
    lf
135
        LogFormatter object to show the output.
136
527 by Martin Pool
- refactor log command
137
    specific_fileid
378 by Martin Pool
- New usage bzr log FILENAME
138
        If true, list only the commits affecting the specified
139
        file, rather than all commits.
140
369 by Martin Pool
- Split out log printing into new show_log function
141
    verbose
142
        If true show added/changed/deleted/renamed files.
143
527 by Martin Pool
- refactor log command
144
    direction
145
        'reverse' (default) is latest to earliest;
146
        'forward' is earliest to latest.
567 by Martin Pool
- New form 'bzr log -r FROM:TO'
147
148
    start_revision
149
        If not None, only show revisions >= start_revision
150
151
    end_revision
152
        If not None, only show revisions <= end_revision
369 by Martin Pool
- Split out log printing into new show_log function
153
    """
1417.1.7 by Robert Collins
teach log it needs a read lock
154
    branch.lock_read()
155
    try:
156
        _show_log(branch, lf, specific_fileid, verbose, direction,
157
                  start_revision, end_revision, search)
158
    finally:
159
        branch.unlock()
160
    
161
def _show_log(branch,
162
             lf,
163
             specific_fileid=None,
164
             verbose=False,
165
             direction='reverse',
166
             start_revision=None,
167
             end_revision=None,
168
             search=None):
169
    """Worker function for show_log - see show_log."""
794 by Martin Pool
- Merge John's nice short-log format.
170
    from bzrlib.osutils import format_date
171
    from bzrlib.errors import BzrCheckError
172
    from bzrlib.textui import show_status
173
    
174
    from warnings import warn
369 by Martin Pool
- Split out log printing into new show_log function
175
794 by Martin Pool
- Merge John's nice short-log format.
176
    if not isinstance(lf, LogFormatter):
177
        warn("not a LogFormatter instance: %r" % lf)
533 by Martin Pool
- fix up asking for the log for the root of a remote branch
178
179
    if specific_fileid:
180
        mutter('get log for file_id %r' % specific_fileid)
181
900 by Martin Pool
- patch from john to search for matching commits
182
    if search is not None:
183
        import re
184
        searchRE = re.compile(search, re.IGNORECASE)
185
    else:
186
        searchRE = None
187
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
188
    which_revs = _enumerate_history(branch)
189
    
190
    if start_revision is None:
191
        start_revision = 1
974.1.54 by aaron.bentley at utoronto
Fixed the revno bug in log
192
    else:
193
        branch.check_real_revno(start_revision)
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
194
    
195
    if end_revision is None:
196
        end_revision = len(which_revs)
974.1.54 by aaron.bentley at utoronto
Fixed the revno bug in log
197
    else:
198
        branch.check_real_revno(end_revision)
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
199
200
    # list indexes are 0-based; revisions are 1-based
201
    cut_revs = which_revs[(start_revision-1):(end_revision)]
202
203
    if direction == 'reverse':
204
        cut_revs.reverse()
205
    elif direction == 'forward':
206
        pass
207
    else:
208
        raise ValueError('invalid direction %r' % direction)
209
1433 by Robert Collins
merge in and make incremental Gustavo Niemeyers nested log patch, and remove all bare exceptions in store and transport packages.
210
    revision_history = branch.revision_history()
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
211
    for revno, rev_id in cut_revs:
212
        if verbose or specific_fileid:
1393.1.56 by Martin Pool
- doc and small refactoring of log code
213
            delta = _get_revision_delta(branch, revno)
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
214
            
532 by Martin Pool
- put back support for log on specific files
215
        if specific_fileid:
216
            if not delta.touches_file_id(specific_fileid):
217
                continue
567 by Martin Pool
- New form 'bzr log -r FROM:TO'
218
532 by Martin Pool
- put back support for log on specific files
219
        if not verbose:
220
            # although we calculated it, throw it away without display
221
            delta = None
794 by Martin Pool
- Merge John's nice short-log format.
222
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
223
        rev = branch.get_revision(rev_id)
224
225
        if searchRE:
226
            if not searchRE.search(rev.message):
227
                continue
228
229
        lf.show(revno, rev, delta)
1433 by Robert Collins
merge in and make incremental Gustavo Niemeyers nested log patch, and remove all bare exceptions in store and transport packages.
230
        if revno == 1:
231
            excludes = set()
232
        else:
233
            # revno is 1 based, so -2 to get back 1 less.
234
            excludes = set(branch.get_ancestry(revision_history[revno - 2]))
235
        pending = list(rev.parent_ids)
236
        while pending:
237
            rev_id = pending.pop()
238
            if rev_id in excludes:
239
                continue
240
            # prevent showing merged revs twice if they multi-path.
241
            excludes.add(rev_id)
242
            try:
243
                rev = branch.get_revision(rev_id)
244
            except errors.NoSuchRevision:
245
                continue
246
            pending.extend(rev.parent_ids)
247
            lf.show_merge(rev)
527 by Martin Pool
- refactor log command
248
530 by Martin Pool
- put back verbose log support for reversed logs
249
250
def deltas_for_log_dummy(branch, which_revs):
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
251
    """Return all the revisions without intermediate deltas.
252
253
    Useful for log commands that won't need the delta information.
254
    """
255
    
530 by Martin Pool
- put back verbose log support for reversed logs
256
    for revno, revision_id in which_revs:
257
        yield revno, branch.get_revision(revision_id), None
258
259
260
def deltas_for_log_reverse(branch, which_revs):
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
261
    """Compute deltas for display in latest-to-earliest order.
262
263
    branch
264
        Branch to traverse
265
266
    which_revs
267
        Sequence of (revno, revision_id) for the subset of history to examine
268
269
    returns 
270
        Sequence of (revno, rev, delta)
530 by Martin Pool
- put back verbose log support for reversed logs
271
272
    The delta is from the given revision to the next one in the
273
    sequence, which makes sense if the log is being displayed from
274
    newest to oldest.
275
    """
276
    last_revno = last_revision_id = last_tree = None
277
    for revno, revision_id in which_revs:
278
        this_tree = branch.revision_tree(revision_id)
279
        this_revision = branch.get_revision(revision_id)
280
        
281
        if last_revno:
282
            yield last_revno, last_revision, compare_trees(this_tree, last_tree, False)
283
909 by Martin Pool
- merge John's code to give the tree root an explicit file id
284
        this_tree = EmptyTree(branch.get_root_id())
285
530 by Martin Pool
- put back verbose log support for reversed logs
286
        last_revno = revno
287
        last_revision = this_revision
288
        last_tree = this_tree
289
290
    if last_revno:
805 by Martin Pool
Merge John's log patch:
291
        if last_revno == 1:
909.1.5 by Aaron Bentley
Fixed log -v (mostly)
292
            this_tree = EmptyTree(branch.get_root_id())
805 by Martin Pool
Merge John's log patch:
293
        else:
294
            this_revno = last_revno - 1
295
            this_revision_id = branch.revision_history()[this_revno]
296
            this_tree = branch.revision_tree(this_revision_id)
530 by Martin Pool
- put back verbose log support for reversed logs
297
        yield last_revno, last_revision, compare_trees(this_tree, last_tree, False)
298
299
805 by Martin Pool
Merge John's log patch:
300
def deltas_for_log_forward(branch, which_revs):
301
    """Compute deltas for display in forward log.
302
303
    Given a sequence of (revno, revision_id) pairs, return
304
    (revno, rev, delta).
305
306
    The delta is from the given revision to the next one in the
307
    sequence, which makes sense if the log is being displayed from
308
    newest to oldest.
309
    """
310
    last_revno = last_revision_id = last_tree = None
909 by Martin Pool
- merge John's code to give the tree root an explicit file id
311
    prev_tree = EmptyTree(branch.get_root_id())
312
805 by Martin Pool
Merge John's log patch:
313
    for revno, revision_id in which_revs:
314
        this_tree = branch.revision_tree(revision_id)
315
        this_revision = branch.get_revision(revision_id)
316
317
        if not last_revno:
318
            if revno == 1:
909.1.5 by Aaron Bentley
Fixed log -v (mostly)
319
                last_tree = EmptyTree(branch.get_root_id())
805 by Martin Pool
Merge John's log patch:
320
            else:
321
                last_revno = revno - 1
322
                last_revision_id = branch.revision_history()[last_revno]
323
                last_tree = branch.revision_tree(last_revision_id)
324
325
        yield revno, this_revision, compare_trees(last_tree, this_tree, False)
326
327
        last_revno = revno
328
        last_revision = this_revision
329
        last_tree = this_tree
530 by Martin Pool
- put back verbose log support for reversed logs
330
527 by Martin Pool
- refactor log command
331
794 by Martin Pool
- Merge John's nice short-log format.
332
class LogFormatter(object):
333
    """Abstract class to display log messages."""
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
334
    def __init__(self, to_file, show_ids=False, show_timezone='original'):
794 by Martin Pool
- Merge John's nice short-log format.
335
        self.to_file = to_file
336
        self.show_ids = show_ids
337
        self.show_timezone = show_timezone
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
338
339
340
    def show(self, revno, rev, delta):
341
        raise NotImplementedError('not implemented in abstract base')
1393.1.56 by Martin Pool
- doc and small refactoring of log code
342
1433 by Robert Collins
merge in and make incremental Gustavo Niemeyers nested log patch, and remove all bare exceptions in store and transport packages.
343
    def show_merge(self, rev):
344
        pass
345
1393.1.56 by Martin Pool
- doc and small refactoring of log code
346
    
794 by Martin Pool
- Merge John's nice short-log format.
347
class LongLogFormatter(LogFormatter):
348
    def show(self, revno, rev, delta):
349
        from osutils import format_date
350
351
        to_file = self.to_file
352
353
        print >>to_file,  '-' * 60
354
        print >>to_file,  'revno:', revno
355
        if self.show_ids:
356
            print >>to_file,  'revision-id:', rev.revision_id
1138 by Martin Pool
- bzr log --show-ids includes parent ids
357
1313 by Martin Pool
- rename to Revision.parent_ids to avoid confusion with old usage
358
            for parent_id in rev.parent_ids:
1311 by Martin Pool
- remove RevisionReference; just hold parent ids directly
359
                print >>to_file, 'parent:', parent_id
1138 by Martin Pool
- bzr log --show-ids includes parent ids
360
            
794 by Martin Pool
- Merge John's nice short-log format.
361
        print >>to_file,  'committer:', rev.committer
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
362
363
        date_str = format_date(rev.timestamp,
364
                               rev.timezone or 0,
365
                               self.show_timezone)
366
        print >>to_file,  'timestamp: %s' % date_str
794 by Martin Pool
- Merge John's nice short-log format.
367
368
        print >>to_file,  'message:'
369
        if not rev.message:
370
            print >>to_file,  '  (no message)'
371
        else:
372
            for l in rev.message.split('\n'):
373
                print >>to_file,  '  ' + l
374
375
        if delta != None:
376
            delta.show(to_file, self.show_ids)
377
1433 by Robert Collins
merge in and make incremental Gustavo Niemeyers nested log patch, and remove all bare exceptions in store and transport packages.
378
    def show_merge(self, rev):
379
        from osutils import format_date
380
381
        to_file = self.to_file
382
383
        indent = '    '
384
385
        print >>to_file,  indent+'-' * 60
386
        print >>to_file,  indent+'merged:', rev.revision_id
387
        if self.show_ids:
388
            for parent_id in rev.parent_ids:
389
                print >>to_file, indent+'parent:', parent_id
390
            
391
        print >>to_file,  indent+'committer:', rev.committer
392
393
        date_str = format_date(rev.timestamp,
394
                               rev.timezone or 0,
395
                               self.show_timezone)
396
        print >>to_file,  indent+'timestamp: %s' % date_str
397
398
        print >>to_file,  indent+'message:'
399
        if not rev.message:
400
            print >>to_file,  indent+'  (no message)'
401
        else:
402
            for l in rev.message.split('\n'):
403
                print >>to_file,  indent+'  ' + l
794 by Martin Pool
- Merge John's nice short-log format.
404
405
406
class ShortLogFormatter(LogFormatter):
407
    def show(self, revno, rev, delta):
408
        from bzrlib.osutils import format_date
409
410
        to_file = self.to_file
411
412
        print >>to_file, "%5d %s\t%s" % (revno, rev.committer,
413
                format_date(rev.timestamp, rev.timezone or 0,
414
                            self.show_timezone))
415
        if self.show_ids:
416
            print >>to_file,  '      revision-id:', rev.revision_id
417
        if not rev.message:
418
            print >>to_file,  '      (no message)'
419
        else:
420
            for l in rev.message.split('\n'):
421
                print >>to_file,  '      ' + l
422
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
423
        # TODO: Why not show the modified files in a shorter form as
424
        # well? rewrap them single lines of appropriate length
794 by Martin Pool
- Merge John's nice short-log format.
425
        if delta != None:
426
            delta.show(to_file, self.show_ids)
427
        print
428
429
430
431
FORMATTERS = {'long': LongLogFormatter,
432
              'short': ShortLogFormatter,
433
              }
434
435
436
def log_formatter(name, *args, **kwargs):
1393.1.56 by Martin Pool
- doc and small refactoring of log code
437
    """Construct a formatter from arguments.
438
439
    name -- Name of the formatter to construct; currently 'long' and
440
        'short' are supported.
441
    """
794 by Martin Pool
- Merge John's nice short-log format.
442
    from bzrlib.errors import BzrCommandError
443
    try:
444
        return FORMATTERS[name](*args, **kwargs)
445
    except IndexError:
446
        raise BzrCommandError("unknown log formatter: %r" % name)
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
447
448
def show_one_log(revno, rev, delta, verbose, to_file, show_timezone):
449
    # deprecated; for compatability
450
    lf = LongLogFormatter(to_file=to_file, show_timezone=show_timezone)
451
    lf.show(revno, rev, delta)