115
128
if old_kind == 'directory':
116
129
yield '.', new_id, old_name, new_name, new_kind
130
elif old_tree.get_file_size(old_id) != new_tree.get_file_size(old_id):
131
mutter(" file size has changed, must be different")
132
yield 'M', new_id, old_name, new_name, new_kind
117
133
elif old_tree.get_file_sha1(old_id) == new_tree.get_file_sha1(old_id):
134
mutter(" SHA1 indicates they're identical")
135
## assert compare_files(old_tree.get_file(i), new_tree.get_file(i))
119
136
yield '.', new_id, old_name, new_name, new_kind
138
mutter(" quick compare shows different")
122
139
yield 'M', new_id, old_name, new_name, new_kind
124
141
new_item = next(new_it)
125
142
old_item = next(old_it)
128
mutter("diff finished: %d SHA matches, %d modified"
129
% (sha_match_cnt, modified_cnt))
133
def show_diff(b, revision, file_list):
134
import difflib, sys, types
137
old_tree = b.basis_tree()
139
old_tree = b.revision_tree(b.lookup_revision(revision))
141
new_tree = b.working_tree()
143
# TODO: Options to control putting on a prefix or suffix, perhaps as a format string
147
DEVNULL = '/dev/null'
148
# Windows users, don't panic about this filename -- it is a
149
# special signal to GNU patch that the file should be created or
150
# deleted respectively.
152
# TODO: Generation of pseudo-diffs for added/deleted files could
153
# be usefully made into a much faster special case.
155
# TODO: Better to return them in sorted order I think.
158
file_list = [b.relpath(f) for f in file_list]
160
# FIXME: If given a file list, compare only those files rather
161
# than comparing everything and then throwing stuff away.
163
for file_state, fid, old_name, new_name, kind in diff_trees(old_tree, new_tree):
165
if file_list and (new_name not in file_list):
168
# Don't show this by default; maybe do it if an option is passed
169
# idlabel = ' {%s}' % fid
172
def diffit(oldlines, newlines, **kw):
174
# FIXME: difflib is wrong if there is no trailing newline.
175
# The syntax used by patch seems to be "\ No newline at
176
# end of file" following the last diff line from that
177
# file. This is not trivial to insert into the
178
# unified_diff output and it might be better to just fix
179
# or replace that function.
181
# In the meantime we at least make sure the patch isn't
185
# Special workaround for Python2.3, where difflib fails if
186
# both sequences are empty.
187
if not oldlines and not newlines:
192
if oldlines and (oldlines[-1][-1] != '\n'):
195
if newlines and (newlines[-1][-1] != '\n'):
199
ud = difflib.unified_diff(oldlines, newlines, **kw)
201
# work-around for difflib being too smart for its own good
202
# if /dev/null is "1,0", patch won't recognize it as /dev/null
205
ud[2] = ud[2].replace('-1,0', '-0,0')
208
ud[2] = ud[2].replace('+1,0', '+0,0')
210
sys.stdout.writelines(ud)
212
print "\\ No newline at end of file"
213
sys.stdout.write('\n')
215
if file_state in ['.', '?', 'I']:
217
elif file_state == 'A':
218
print '*** added %s %r' % (kind, new_name)
221
new_tree.get_file(fid).readlines(),
223
tofile=new_label + new_name + idlabel)
224
elif file_state == 'D':
225
assert isinstance(old_name, types.StringTypes)
226
print '*** deleted %s %r' % (kind, old_name)
228
diffit(old_tree.get_file(fid).readlines(), [],
229
fromfile=old_label + old_name + idlabel,
231
elif file_state in ['M', 'R']:
232
if file_state == 'M':
233
assert kind == 'file'
234
assert old_name == new_name
235
print '*** modified %s %r' % (kind, new_name)
236
elif file_state == 'R':
237
print '*** renamed %s %r => %r' % (kind, old_name, new_name)
240
diffit(old_tree.get_file(fid).readlines(),
241
new_tree.get_file(fid).readlines(),
242
fromfile=old_label + old_name + idlabel,
243
tofile=new_label + new_name)
245
raise BzrError("can't represent state %s {%s}" % (file_state, fid))
250
"""Describes changes from one tree to another.
259
(oldpath, newpath, id, text_modified)
265
Each id is listed only once.
267
Files that are both modified and renamed are listed only in
268
renamed, with the text_modified flag true.
270
The lists are normally sorted when the delta is created.
279
def show(self, to_file, show_ids=False, show_unchanged=False):
280
def show_list(files):
281
for path, fid in files:
283
print >>to_file, ' %-30s %s' % (path, fid)
285
print >>to_file, ' ', path
288
print >>to_file, 'removed files:'
289
show_list(self.removed)
292
print >>to_file, 'added files:'
293
show_list(self.added)
296
print >>to_file, 'renamed files:'
297
for oldpath, newpath, fid, text_modified in self.renamed:
299
print >>to_file, ' %s => %s %s' % (oldpath, newpath, fid)
301
print >>to_file, ' %s => %s' % (oldpath, newpath)
304
print >>to_file, 'modified files:'
305
show_list(self.modified)
307
if show_unchanged and self.unchanged:
308
print >>to_file, 'unchanged files:'
309
show_list(self.unchanged)
313
def compare_trees(old_tree, new_tree, want_unchanged):
314
old_inv = old_tree.inventory
315
new_inv = new_tree.inventory
317
for file_id in old_tree:
318
if file_id in new_tree:
319
old_path = old_inv.id2path(file_id)
320
new_path = new_inv.id2path(file_id)
322
kind = old_inv.get_file_kind(file_id)
323
assert kind in ('file', 'directory', 'symlink', 'root_directory'), \
324
'invalid file kind %r' % kind
326
old_sha1 = old_tree.get_file_sha1(file_id)
327
new_sha1 = new_tree.get_file_sha1(file_id)
328
text_modified = (old_sha1 != new_sha1)
330
## mutter("no text to check for %r %r" % (file_id, kind))
331
text_modified = False
333
# TODO: Can possibly avoid calculating path strings if the
334
# two files are unchanged and their names and parents are
335
# the same and the parents are unchanged all the way up.
336
# May not be worthwhile.
338
if old_path != new_path:
339
delta.renamed.append((old_path, new_path, file_id, text_modified))
341
delta.modified.append((new_path, file_id))
343
delta.unchanged.append((new_path, file_id))
345
delta.removed.append((old_inv.id2path(file_id), file_id))
346
for file_id in new_inv:
347
if file_id in old_inv:
349
delta.added.append((new_inv.id2path(file_id), file_id))
354
delta.modified.sort()