89
88
# regular named file (e.g. in the working directory) then we can
90
89
# compare directly to that, rather than copying it.
92
# TODO: Set the labels appropriately
94
91
oldtmpf.writelines(oldlines)
95
92
newtmpf.writelines(newlines)
100
system('diff -u --label %s %s --label %s %s' % (old_label, oldtmpf.name, new_label, newtmpf.name))
100
'--label', old_label,
102
'--label', new_label,
105
# diff only allows one style to be specified; they don't override.
106
# note that some of these take optargs, and the optargs can be
107
# directly appended to the options.
108
# this is only an approximate parser; it doesn't properly understand
110
for s in ['-c', '-u', '-C', '-U',
115
'-y', '--side-by-side',
127
diffcmd.extend(diff_opts)
129
rc = os.spawnvp(os.P_WAIT, 'diff', diffcmd)
131
if rc != 0 and rc != 1:
132
# returns 1 if files differ; that's OK
134
msg = 'signal %d' % (-rc)
136
msg = 'exit code %d' % rc
138
raise BzrError('external diff failed with %s; command: %r' % (rc, diffcmd))
102
140
oldtmpf.close() # and delete
143
def show_diff(b, from_spec, specific_files, external_diff_options=None,
144
revision2=None, output=None):
145
"""Shortcut for showing the diff to the working tree.
151
None for 'basis tree', or otherwise the old revision to compare against.
107
def diff_file(old_label, oldlines, new_label, newlines, to_file):
109
differ = external_diff
111
differ = internal_diff
113
differ(old_label, oldlines, new_label, newlines, to_file)
117
def show_diff(b, revision, specific_files):
153
The more general form is show_diff_trees(), where the caller
154
supplies any two trees.
160
if from_spec is None:
121
161
old_tree = b.basis_tree()
123
old_tree = b.revision_tree(b.lookup_revision(revision))
125
new_tree = b.working_tree()
127
show_diff_trees(old_tree, new_tree, sys.stdout, specific_files)
131
def show_diff_trees(old_tree, new_tree, to_file, specific_files=None):
163
old_tree = b.revision_tree(from_spec.in_history(b).rev_id)
165
if revision2 is None:
166
new_tree = b.working_tree()
168
new_tree = b.revision_tree(revision2.in_history(b).rev_id)
170
show_diff_trees(old_tree, new_tree, output, specific_files,
171
external_diff_options)
175
def show_diff_trees(old_tree, new_tree, to_file, specific_files=None,
176
external_diff_options=None):
132
177
"""Show in text form the changes from one tree to another.
135
180
If set, include only changes to these files.
182
external_diff_options
183
If set, use an external GNU diff and pass these options.
138
186
# TODO: Options to control putting on a prefix or suffix, perhaps as a format string
147
195
# TODO: Generation of pseudo-diffs for added/deleted files could
148
196
# be usefully made into a much faster special case.
198
if external_diff_options:
199
assert isinstance(external_diff_options, basestring)
200
opts = external_diff_options.split()
201
def diff_file(olab, olines, nlab, nlines, to_file):
202
external_diff(olab, olines, nlab, nlines, to_file, opts)
204
diff_file = internal_diff
150
207
delta = compare_trees(old_tree, new_tree, want_unchanged=False,
151
208
specific_files=specific_files)
153
210
for path, file_id, kind in delta.removed:
154
print '*** removed %s %r' % (kind, path)
156
diff_file(old_label + path,
157
old_tree.get_file(file_id).readlines(),
211
print >>to_file, '=== removed %s %r' % (kind, path)
212
old_tree.inventory[file_id].diff(diff_file, old_label + path, old_tree,
213
DEVNULL, None, None, to_file)
162
214
for path, file_id, kind in delta.added:
163
print '*** added %s %r' % (kind, path)
168
new_tree.get_file(file_id).readlines(),
171
for old_path, new_path, file_id, kind, text_modified in delta.renamed:
172
print '*** renamed %s %r => %r' % (kind, old_path, new_path)
215
print >>to_file, '=== added %s %r' % (kind, path)
216
new_tree.inventory[file_id].diff(diff_file, new_label + path, new_tree,
217
DEVNULL, None, None, to_file,
219
for (old_path, new_path, file_id, kind,
220
text_modified, meta_modified) in delta.renamed:
221
prop_str = get_prop_change(meta_modified)
222
print >>to_file, '=== renamed %s %r => %r%s' % (
223
kind, old_path, new_path, prop_str)
224
_maybe_diff_file_or_symlink(old_label, old_path, old_tree, file_id,
225
new_label, new_path, new_tree,
226
text_modified, kind, to_file, diff_file)
227
for path, file_id, kind, text_modified, meta_modified in delta.modified:
228
prop_str = get_prop_change(meta_modified)
229
print >>to_file, '=== modified %s %r%s' % (kind, path, prop_str)
173
230
if text_modified:
174
diff_file(old_label + old_path,
175
old_tree.get_file(file_id).readlines(),
176
new_label + new_path,
177
new_tree.get_file(file_id).readlines(),
180
for path, file_id, kind in delta.modified:
181
print '*** modified %s %r' % (kind, path)
183
diff_file(old_label + path,
184
old_tree.get_file(file_id).readlines(),
186
new_tree.get_file(file_id).readlines(),
191
class TreeDelta(object):
192
"""Describes changes from one tree to another.
201
(oldpath, newpath, id, kind, text_modified)
207
Each id is listed only once.
209
Files that are both modified and renamed are listed only in
210
renamed, with the text_modified flag true.
212
The lists are normally sorted when the delta is created.
222
def touches_file_id(self, file_id):
223
"""Return True if file_id is modified by this delta."""
224
for l in self.added, self.removed, self.modified:
228
for v in self.renamed:
234
def show(self, to_file, show_ids=False, show_unchanged=False):
235
def show_list(files):
236
for path, fid, kind in files:
237
if kind == 'directory':
239
elif kind == 'symlink':
243
print >>to_file, ' %-30s %s' % (path, fid)
245
print >>to_file, ' ', path
248
print >>to_file, 'removed:'
249
show_list(self.removed)
252
print >>to_file, 'added:'
253
show_list(self.added)
256
print >>to_file, 'renamed:'
257
for oldpath, newpath, fid, kind, text_modified in self.renamed:
259
print >>to_file, ' %s => %s %s' % (oldpath, newpath, fid)
261
print >>to_file, ' %s => %s' % (oldpath, newpath)
264
print >>to_file, 'modified:'
265
show_list(self.modified)
267
if show_unchanged and self.unchanged:
268
print >>to_file, 'unchanged:'
269
show_list(self.unchanged)
273
def compare_trees(old_tree, new_tree, want_unchanged, specific_files=None):
274
"""Describe changes from one tree to another.
276
Returns a TreeDelta with details of added, modified, renamed, and
279
The root entry is specifically exempt.
281
This only considers versioned files.
284
If true, also list files unchanged from one version to
288
If true, only check for changes to specified names or
292
from osutils import is_inside_any
231
_maybe_diff_file_or_symlink(old_label, path, old_tree, file_id,
232
new_label, path, new_tree,
233
True, kind, to_file, diff_file)
294
old_inv = old_tree.inventory
295
new_inv = new_tree.inventory
297
mutter('start compare_trees')
299
# TODO: match for specific files can be rather smarter by finding
300
# the IDs of those files up front and then considering only that.
302
for file_id in old_tree:
303
if file_id in new_tree:
304
kind = old_inv.get_file_kind(file_id)
305
assert kind == new_inv.get_file_kind(file_id)
307
assert kind in ('file', 'directory', 'symlink', 'root_directory'), \
308
'invalid file kind %r' % kind
310
if kind == 'root_directory':
313
old_path = old_inv.id2path(file_id)
314
new_path = new_inv.id2path(file_id)
317
if (not is_inside_any(specific_files, old_path)
318
and not is_inside_any(specific_files, new_path)):
322
old_sha1 = old_tree.get_file_sha1(file_id)
323
new_sha1 = new_tree.get_file_sha1(file_id)
324
text_modified = (old_sha1 != new_sha1)
326
## mutter("no text to check for %r %r" % (file_id, kind))
327
text_modified = False
329
# TODO: Can possibly avoid calculating path strings if the
330
# two files are unchanged and their names and parents are
331
# the same and the parents are unchanged all the way up.
332
# May not be worthwhile.
334
if old_path != new_path:
335
delta.renamed.append((old_path, new_path, file_id, kind,
338
delta.modified.append((new_path, file_id, kind))
340
delta.unchanged.append((new_path, file_id, kind))
342
kind = old_inv.get_file_kind(file_id)
343
old_path = old_inv.id2path(file_id)
345
if not is_inside_any(specific_files, old_path):
347
delta.removed.append((old_path, file_id, kind))
349
mutter('start looking for new files')
350
for file_id in new_inv:
351
if file_id in old_inv:
353
new_path = new_inv.id2path(file_id)
355
if not is_inside_any(specific_files, new_path):
357
kind = new_inv.get_file_kind(file_id)
358
delta.added.append((new_path, file_id, kind))
363
delta.modified.sort()
364
delta.unchanged.sort()
236
def get_prop_change(meta_modified):
238
return " (properties changed)"
243
def _maybe_diff_file_or_symlink(old_label, old_path, old_tree, file_id,
244
new_label, new_path, new_tree, text_modified,
245
kind, to_file, diff_file):
247
new_entry = new_tree.inventory[file_id]
248
old_tree.inventory[file_id].diff(diff_file,
249
old_label + old_path, old_tree,
250
new_label + new_path, new_entry,