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
88
show_timezone='original',
93
"""Write out human-readable log of commits to this branch.
96
If true, list only the commits affecting the specified
97
file, rather than all commits.
100
'original' (committer's timezone),
101
'utc' (universal time), or
102
'local' (local user's timezone)
105
If true show added/changed/deleted/renamed files.
108
If true, show revision and file ids.
111
File to send log to; by default stdout.
114
'reverse' (default) is latest to earliest;
115
'forward' is earliest to latest.
117
from osutils import format_date
118
from errors import BzrCheckError
119
from textui import show_status
123
mutter('get log for file_id %r' % specific_fileid)
129
which_revs = branch.enum_history(direction)
131
if not (verbose or specific_fileid):
132
# no need to know what changed between revisions
133
with_deltas = deltas_for_log_dummy(branch, which_revs)
134
elif direction == 'reverse':
135
with_deltas = deltas_for_log_reverse(branch, which_revs)
137
raise NotImplementedError("sorry, verbose forward logs not done yet")
139
for revno, rev, delta in with_deltas:
141
if not delta.touches_file_id(specific_fileid):
145
# although we calculated it, throw it away without display
148
show_one_log(revno, rev, delta, show_ids, to_file, show_timezone)
152
def deltas_for_log_dummy(branch, which_revs):
153
for revno, revision_id in which_revs:
154
yield revno, branch.get_revision(revision_id), None
157
def deltas_for_log_reverse(branch, which_revs):
158
"""Compute deltas for display in reverse log.
160
Given a sequence of (revno, revision_id) pairs, return
163
The delta is from the given revision to the next one in the
164
sequence, which makes sense if the log is being displayed from
167
from tree import EmptyTree
168
from diff import compare_trees
170
last_revno = last_revision_id = last_tree = None
171
for revno, revision_id in which_revs:
172
this_tree = branch.revision_tree(revision_id)
173
this_revision = branch.get_revision(revision_id)
176
yield last_revno, last_revision, compare_trees(this_tree, last_tree, False)
179
last_revision = this_revision
180
last_tree = this_tree
183
this_tree = EmptyTree()
184
yield last_revno, last_revision, compare_trees(this_tree, last_tree, False)
191
from tree import EmptyTree
192
prev_tree = EmptyTree()
193
for revno, revision_id in which_revs:
194
precursor = revision_id
196
if revision_id != rev.revision_id:
197
raise BzrCheckError("retrieved wrong revision: %r"
198
% (revision_id, rev.revision_id))
201
this_tree = branch.revision_tree(revision_id)
202
delta = compare_trees(prev_tree, this_tree, want_unchanged=False)
203
prev_tree = this_tree
209
def show_one_log(revno, rev, delta, show_ids, to_file, show_timezone):
210
from osutils import format_date
212
print >>to_file, '-' * 60
213
print >>to_file, 'revno:', revno
215
print >>to_file, 'revision-id:', rev.revision_id
216
print >>to_file, 'committer:', rev.committer
217
print >>to_file, 'timestamp: %s' % (format_date(rev.timestamp, rev.timezone or 0,
220
print >>to_file, 'message:'
222
print >>to_file, ' (no message)'
224
for l in rev.message.split('\n'):
225
print >>to_file, ' ' + l
228
delta.show(to_file, show_ids)