/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: 2005-07-29 22:21:25 UTC
  • Revision ID: mbp@sourcefrog.net-20050729222125-f1143d5c05e6707d
- split TreeDelta and compare_trees out into new module bzrlib.delta

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
 
 
17
 
 
18
 
 
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
 
 
31
* from last to first or (not anymore) from first to last;
 
32
  the default is "reversed" because it shows the likely most
 
33
  relevant and interesting information first
 
34
 
 
35
* (not yet) in XML format
 
36
"""
 
37
 
 
38
 
 
39
from bzrlib.tree import EmptyTree
 
40
from bzrlib.delta import compare_trees
 
41
from bzrlib.trace import mutter
 
42
 
 
43
 
 
44
def find_touching_revisions(branch, file_id):
 
45
    """Yield a description of revisions which affect the file_id.
 
46
 
 
47
    Each returned element is (revno, revision_id, description)
 
48
 
 
49
    This is the list of revisions where the file is either added,
 
50
    modified, renamed or deleted.
 
51
 
 
52
    TODO: Perhaps some way to limit this to only particular revisions,
 
53
    or to traverse a non-mainline set of revisions?
 
54
    """
 
55
    last_ie = None
 
56
    last_path = None
 
57
    revno = 1
 
58
    for revision_id in branch.revision_history():
 
59
        this_inv = branch.get_revision_inventory(revision_id)
 
60
        if file_id in this_inv:
 
61
            this_ie = this_inv[file_id]
 
62
            this_path = this_inv.id2path(file_id)
 
63
        else:
 
64
            this_ie = this_path = None
 
65
 
 
66
        # now we know how it was last time, and how it is in this revision.
 
67
        # are those two states effectively the same or not?
 
68
 
 
69
        if not this_ie and not last_ie:
 
70
            # not present in either
 
71
            pass
 
72
        elif this_ie and not last_ie:
 
73
            yield revno, revision_id, "added " + this_path
 
74
        elif not this_ie and last_ie:
 
75
            # deleted here
 
76
            yield revno, revision_id, "deleted " + last_path
 
77
        elif this_path != last_path:
 
78
            yield revno, revision_id, ("renamed %s => %s" % (last_path, this_path))
 
79
        elif (this_ie.text_size != last_ie.text_size
 
80
              or this_ie.text_sha1 != last_ie.text_sha1):
 
81
            yield revno, revision_id, "modified " + this_path
 
82
 
 
83
        last_ie = this_ie
 
84
        last_path = this_path
 
85
        revno += 1
 
86
 
 
87
 
 
88
 
 
89
def show_log(branch,
 
90
             lf,
 
91
             specific_fileid=None,
 
92
             verbose=False,
 
93
             direction='reverse',
 
94
             start_revision=None,
 
95
             end_revision=None,
 
96
             search=None):
 
97
    """Write out human-readable log of commits to this branch.
 
98
 
 
99
    lf
 
100
        LogFormatter object to show the output.
 
101
 
 
102
    specific_fileid
 
103
        If true, list only the commits affecting the specified
 
104
        file, rather than all commits.
 
105
 
 
106
    verbose
 
107
        If true show added/changed/deleted/renamed files.
 
108
 
 
109
    direction
 
110
        'reverse' (default) is latest to earliest;
 
111
        'forward' is earliest to latest.
 
112
 
 
113
    start_revision
 
114
        If not None, only show revisions >= start_revision
 
115
 
 
116
    end_revision
 
117
        If not None, only show revisions <= end_revision
 
118
    """
 
119
    from bzrlib.osutils import format_date
 
120
    from bzrlib.errors import BzrCheckError
 
121
    from bzrlib.textui import show_status
 
122
    
 
123
    from warnings import warn
 
124
 
 
125
    if not isinstance(lf, LogFormatter):
 
126
        warn("not a LogFormatter instance: %r" % lf)
 
127
 
 
128
    if specific_fileid:
 
129
        mutter('get log for file_id %r' % specific_fileid)
 
130
 
 
131
    if search is not None:
 
132
        import re
 
133
        searchRE = re.compile(search, re.IGNORECASE)
 
134
    else:
 
135
        searchRE = None
 
136
 
 
137
    which_revs = branch.enum_history(direction)
 
138
    which_revs = [x for x in which_revs if (
 
139
            (start_revision is None or x[0] >= start_revision)
 
140
            and (end_revision is None or x[0] <= end_revision))]
 
141
 
 
142
    if not (verbose or specific_fileid):
 
143
        # no need to know what changed between revisions
 
144
        with_deltas = deltas_for_log_dummy(branch, which_revs)
 
145
    elif direction == 'reverse':
 
146
        with_deltas = deltas_for_log_reverse(branch, which_revs)
 
147
    else:        
 
148
        with_deltas = deltas_for_log_forward(branch, which_revs)
 
149
 
 
150
    for revno, rev, delta in with_deltas:
 
151
        if specific_fileid:
 
152
            if not delta.touches_file_id(specific_fileid):
 
153
                continue
 
154
 
 
155
        if not verbose:
 
156
            # although we calculated it, throw it away without display
 
157
            delta = None
 
158
 
 
159
        if searchRE is None or searchRE.search(rev.message):
 
160
            lf.show(revno, rev, delta)
 
161
 
 
162
 
 
163
 
 
164
def deltas_for_log_dummy(branch, which_revs):
 
165
    """Return all the revisions without intermediate deltas.
 
166
 
 
167
    Useful for log commands that won't need the delta information.
 
168
    """
 
169
    
 
170
    for revno, revision_id in which_revs:
 
171
        yield revno, branch.get_revision(revision_id), None
 
172
 
 
173
 
 
174
def deltas_for_log_reverse(branch, which_revs):
 
175
    """Compute deltas for display in latest-to-earliest order.
 
176
 
 
177
    branch
 
178
        Branch to traverse
 
179
 
 
180
    which_revs
 
181
        Sequence of (revno, revision_id) for the subset of history to examine
 
182
 
 
183
    returns 
 
184
        Sequence of (revno, rev, delta)
 
185
 
 
186
    The delta is from the given revision to the next one in the
 
187
    sequence, which makes sense if the log is being displayed from
 
188
    newest to oldest.
 
189
    """
 
190
    last_revno = last_revision_id = last_tree = None
 
191
    for revno, revision_id in which_revs:
 
192
        this_tree = branch.revision_tree(revision_id)
 
193
        this_revision = branch.get_revision(revision_id)
 
194
        
 
195
        if last_revno:
 
196
            yield last_revno, last_revision, compare_trees(this_tree, last_tree, False)
 
197
 
 
198
        this_tree = EmptyTree(branch.get_root_id())
 
199
 
 
200
        last_revno = revno
 
201
        last_revision = this_revision
 
202
        last_tree = this_tree
 
203
 
 
204
    if last_revno:
 
205
        if last_revno == 1:
 
206
            this_tree = EmptyTree(branch.get_root_id())
 
207
        else:
 
208
            this_revno = last_revno - 1
 
209
            this_revision_id = branch.revision_history()[this_revno]
 
210
            this_tree = branch.revision_tree(this_revision_id)
 
211
        yield last_revno, last_revision, compare_trees(this_tree, last_tree, False)
 
212
 
 
213
 
 
214
def deltas_for_log_forward(branch, which_revs):
 
215
    """Compute deltas for display in forward log.
 
216
 
 
217
    Given a sequence of (revno, revision_id) pairs, return
 
218
    (revno, rev, delta).
 
219
 
 
220
    The delta is from the given revision to the next one in the
 
221
    sequence, which makes sense if the log is being displayed from
 
222
    newest to oldest.
 
223
    """
 
224
    last_revno = last_revision_id = last_tree = None
 
225
    prev_tree = EmptyTree(branch.get_root_id())
 
226
 
 
227
    for revno, revision_id in which_revs:
 
228
        this_tree = branch.revision_tree(revision_id)
 
229
        this_revision = branch.get_revision(revision_id)
 
230
 
 
231
        if not last_revno:
 
232
            if revno == 1:
 
233
                last_tree = EmptyTree(branch.get_root_id())
 
234
            else:
 
235
                last_revno = revno - 1
 
236
                last_revision_id = branch.revision_history()[last_revno]
 
237
                last_tree = branch.revision_tree(last_revision_id)
 
238
 
 
239
        yield revno, this_revision, compare_trees(last_tree, this_tree, False)
 
240
 
 
241
        last_revno = revno
 
242
        last_revision = this_revision
 
243
        last_tree = this_tree
 
244
 
 
245
 
 
246
class LogFormatter(object):
 
247
    """Abstract class to display log messages."""
 
248
    def __init__(self, to_file, show_ids=False, show_timezone='original'):
 
249
        self.to_file = to_file
 
250
        self.show_ids = show_ids
 
251
        self.show_timezone = show_timezone
 
252
 
 
253
 
 
254
    def show(self, revno, rev, delta):
 
255
        raise NotImplementedError('not implemented in abstract base')
 
256
        
 
257
 
 
258
 
 
259
 
 
260
 
 
261
 
 
262
class LongLogFormatter(LogFormatter):
 
263
    def show(self, revno, rev, delta):
 
264
        from osutils import format_date
 
265
 
 
266
        to_file = self.to_file
 
267
 
 
268
        print >>to_file,  '-' * 60
 
269
        print >>to_file,  'revno:', revno
 
270
        if self.show_ids:
 
271
            print >>to_file,  'revision-id:', rev.revision_id
 
272
        print >>to_file,  'committer:', rev.committer
 
273
 
 
274
        date_str = format_date(rev.timestamp,
 
275
                               rev.timezone or 0,
 
276
                               self.show_timezone)
 
277
        print >>to_file,  'timestamp: %s' % date_str
 
278
 
 
279
        print >>to_file,  'message:'
 
280
        if not rev.message:
 
281
            print >>to_file,  '  (no message)'
 
282
        else:
 
283
            for l in rev.message.split('\n'):
 
284
                print >>to_file,  '  ' + l
 
285
 
 
286
        if delta != None:
 
287
            delta.show(to_file, self.show_ids)
 
288
 
 
289
 
 
290
 
 
291
class ShortLogFormatter(LogFormatter):
 
292
    def show(self, revno, rev, delta):
 
293
        from bzrlib.osutils import format_date
 
294
 
 
295
        to_file = self.to_file
 
296
 
 
297
        print >>to_file, "%5d %s\t%s" % (revno, rev.committer,
 
298
                format_date(rev.timestamp, rev.timezone or 0,
 
299
                            self.show_timezone))
 
300
        if self.show_ids:
 
301
            print >>to_file,  '      revision-id:', rev.revision_id
 
302
        if not rev.message:
 
303
            print >>to_file,  '      (no message)'
 
304
        else:
 
305
            for l in rev.message.split('\n'):
 
306
                print >>to_file,  '      ' + l
 
307
 
 
308
        if delta != None:
 
309
            delta.show(to_file, self.show_ids)
 
310
        print
 
311
 
 
312
 
 
313
 
 
314
FORMATTERS = {'long': LongLogFormatter,
 
315
              'short': ShortLogFormatter,
 
316
              }
 
317
 
 
318
 
 
319
def log_formatter(name, *args, **kwargs):
 
320
    from bzrlib.errors import BzrCommandError
 
321
    
 
322
    try:
 
323
        return FORMATTERS[name](*args, **kwargs)
 
324
    except IndexError:
 
325
        raise BzrCommandError("unknown log formatter: %r" % name)
 
326
 
 
327
def show_one_log(revno, rev, delta, verbose, to_file, show_timezone):
 
328
    # deprecated; for compatability
 
329
    lf = LongLogFormatter(to_file=to_file, show_timezone=show_timezone)
 
330
    lf.show(revno, rev, delta)