1
# Copyright (C) 2005 Canonical Ltd
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.
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.
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
19
"""Code to show logs of changes.
21
Various flavors of log can be produced:
23
* for one file, or the whole tree, and (not done yet) for
24
files in a given directory
26
* in "verbose" mode with a description of what changed from one
29
* with file-ids and revision-ids shown
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
35
* (not yet) in XML format
39
from trace import mutter
41
def find_touching_revisions(branch, file_id):
42
"""Yield a description of revisions which affect the file_id.
44
Each returned element is (revno, revision_id, description)
46
This is the list of revisions where the file is either added,
47
modified, renamed or deleted.
49
TODO: Perhaps some way to limit this to only particular revisions,
50
or to traverse a non-mainline set of revisions?
55
for revision_id in branch.revision_history():
56
this_inv = branch.get_revision_inventory(revision_id)
57
if file_id in this_inv:
58
this_ie = this_inv[file_id]
59
this_path = this_inv.id2path(file_id)
61
this_ie = this_path = None
63
# now we know how it was last time, and how it is in this revision.
64
# are those two states effectively the same or not?
66
if not this_ie and not last_ie:
67
# not present in either
69
elif this_ie and not last_ie:
70
yield revno, revision_id, "added " + this_path
71
elif not this_ie and last_ie:
73
yield revno, revision_id, "deleted " + last_path
74
elif this_path != last_path:
75
yield revno, revision_id, ("renamed %s => %s" % (last_path, this_path))
76
elif (this_ie.text_size != last_ie.text_size
77
or this_ie.text_sha1 != last_ie.text_sha1):
78
yield revno, revision_id, "modified " + this_path
93
"""Write out human-readable log of commits to this branch.
96
LogFormatter object to show the output.
99
If true, list only the commits affecting the specified
100
file, rather than all commits.
103
If true show added/changed/deleted/renamed files.
106
'reverse' (default) is latest to earliest;
107
'forward' is earliest to latest.
110
If not None, only show revisions >= start_revision
113
If not None, only show revisions <= end_revision
115
from bzrlib.osutils import format_date
116
from bzrlib.errors import BzrCheckError
117
from bzrlib.textui import show_status
119
from warnings import warn
121
if not isinstance(lf, LogFormatter):
122
warn("not a LogFormatter instance: %r" % lf)
125
mutter('get log for file_id %r' % specific_fileid)
127
which_revs = branch.enum_history(direction)
129
if not (verbose or specific_fileid):
130
# no need to know what changed between revisions
131
with_deltas = deltas_for_log_dummy(branch, which_revs)
132
elif direction == 'reverse':
133
with_deltas = deltas_for_log_reverse(branch, which_revs)
135
raise NotImplementedError("sorry, verbose forward logs not done yet")
137
for revno, rev, delta in with_deltas:
139
if not delta.touches_file_id(specific_fileid):
142
if start_revision is not None and revno < start_revision:
145
if end_revision is not None and revno > end_revision:
149
# although we calculated it, throw it away without display
152
lf.show(revno, rev, delta)
156
def deltas_for_log_dummy(branch, which_revs):
157
for revno, revision_id in which_revs:
158
yield revno, branch.get_revision(revision_id), None
161
def deltas_for_log_reverse(branch, which_revs):
162
"""Compute deltas for display in reverse log.
164
Given a sequence of (revno, revision_id) pairs, return
167
The delta is from the given revision to the next one in the
168
sequence, which makes sense if the log is being displayed from
171
from tree import EmptyTree
172
from diff import compare_trees
174
last_revno = last_revision_id = last_tree = None
175
for revno, revision_id in which_revs:
176
this_tree = branch.revision_tree(revision_id)
177
this_revision = branch.get_revision(revision_id)
180
yield last_revno, last_revision, compare_trees(this_tree, last_tree, False)
183
last_revision = this_revision
184
last_tree = this_tree
187
this_tree = EmptyTree()
188
yield last_revno, last_revision, compare_trees(this_tree, last_tree, False)
193
class LogFormatter(object):
194
"""Abstract class to display log messages."""
195
def __init__(self, to_file, show_ids=False, show_timezone=False):
196
self.to_file = to_file
197
self.show_ids = show_ids
198
self.show_timezone = show_timezone
205
class LongLogFormatter(LogFormatter):
206
def show(self, revno, rev, delta):
207
from osutils import format_date
209
to_file = self.to_file
211
print >>to_file, '-' * 60
212
print >>to_file, 'revno:', revno
214
print >>to_file, 'revision-id:', rev.revision_id
215
print >>to_file, 'committer:', rev.committer
216
print >>to_file, 'timestamp: %s' % (format_date(rev.timestamp, rev.timezone or 0,
219
print >>to_file, 'message:'
221
print >>to_file, ' (no message)'
223
for l in rev.message.split('\n'):
224
print >>to_file, ' ' + l
227
delta.show(to_file, self.show_ids)
231
class ShortLogFormatter(LogFormatter):
232
def show(self, revno, rev, delta):
233
from bzrlib.osutils import format_date
235
to_file = self.to_file
237
print >>to_file, "%5d %s\t%s" % (revno, rev.committer,
238
format_date(rev.timestamp, rev.timezone or 0,
241
print >>to_file, ' revision-id:', rev.revision_id
243
print >>to_file, ' (no message)'
245
for l in rev.message.split('\n'):
246
print >>to_file, ' ' + l
249
delta.show(to_file, self.show_ids)
254
FORMATTERS = {'long': LongLogFormatter,
255
'short': ShortLogFormatter,
259
def log_formatter(name, *args, **kwargs):
260
from bzrlib.errors import BzrCommandError
263
return FORMATTERS[name](*args, **kwargs)
265
raise BzrCommandError("unknown log formatter: %r" % name)