143
149
oldtmpf.close() # and delete
147
@deprecated_function(zero_eight)
148
def show_diff(b, from_spec, specific_files, external_diff_options=None,
149
revision2=None, output=None, b2=None):
154
def show_diff(b, revision, specific_files, external_diff_options=None):
150
155
"""Shortcut for showing the diff to the working tree.
152
Please use show_diff_trees instead.
158
None for 'basis tree', or otherwise the old revision to compare against.
160
The more general form is show_diff_trees(), where the caller
161
supplies any two trees.
167
if from_spec is None:
168
old_tree = b.bzrdir.open_workingtree()
170
old_tree = old_tree = old_tree.basis_tree()
172
old_tree = b.repository.revision_tree(from_spec.in_history(b).rev_id)
174
if revision2 is None:
176
new_tree = b.bzrdir.open_workingtree()
178
new_tree = b2.bzrdir.open_workingtree()
180
new_tree = b.repository.revision_tree(revision2.in_history(b).rev_id)
182
return show_diff_trees(old_tree, new_tree, output, specific_files,
183
external_diff_options)
186
def diff_cmd_helper(tree, specific_files, external_diff_options,
187
old_revision_spec=None, new_revision_spec=None):
188
"""Helper for cmd_diff.
194
The specific files to compare, or None
196
external_diff_options
197
If non-None, run an external diff, and pass it these options
200
If None, use basis tree as old revision, otherwise use the tree for
201
the specified revision.
204
If None, use working tree as new revision, otherwise use the tree for
205
the specified revision.
161
None for each, or otherwise the old revision to compare against.
207
163
The more general form is show_diff_trees(), where the caller
208
164
supplies any two trees.
213
revision_id = spec.in_store(tree.branch).rev_id
214
return tree.branch.repository.revision_tree(revision_id)
215
if old_revision_spec is None:
216
old_tree = tree.basis_tree()
218
old_tree = spec_tree(old_revision_spec)
220
if new_revision_spec is None:
223
new_tree = spec_tree(new_revision_spec)
225
return show_diff_trees(old_tree, new_tree, sys.stdout, specific_files,
226
external_diff_options)
169
old_tree = b.basis_tree()
171
old_tree = b.revision_tree(b.lookup_revision(revision))
173
new_tree = b.working_tree()
175
show_diff_trees(old_tree, new_tree, sys.stdout, specific_files,
176
external_diff_options)
229
180
def show_diff_trees(old_tree, new_tree, to_file, specific_files=None,
275
209
diff_file = internal_diff
277
212
delta = compare_trees(old_tree, new_tree, want_unchanged=False,
278
213
specific_files=specific_files)
281
215
for path, file_id, kind in delta.removed:
283
print >>to_file, '=== removed %s %r' % (kind, old_label + path)
284
old_tree.inventory[file_id].diff(diff_file, old_label + path, old_tree,
285
DEVNULL, None, None, to_file)
216
print >>to_file, '*** removed %s %r' % (kind, path)
218
diff_file(old_label + path,
219
old_tree.get_file(file_id).readlines(),
286
224
for path, file_id, kind in delta.added:
288
print >>to_file, '=== added %s %r' % (kind, new_label + path)
289
new_tree.inventory[file_id].diff(diff_file, new_label + path, new_tree,
290
DEVNULL, None, None, to_file,
292
for (old_path, new_path, file_id, kind,
293
text_modified, meta_modified) in delta.renamed:
295
prop_str = get_prop_change(meta_modified)
296
print >>to_file, '=== renamed %s %r => %r%s' % (
297
kind, old_label + old_path, new_label + new_path, prop_str)
298
_maybe_diff_file_or_symlink(old_label, old_path, old_tree, file_id,
299
new_label, new_path, new_tree,
300
text_modified, kind, to_file, diff_file)
301
for path, file_id, kind, text_modified, meta_modified in delta.modified:
303
prop_str = get_prop_change(meta_modified)
304
print >>to_file, '=== modified %s %r%s' % (kind, old_label + path,
225
print >>to_file, '*** added %s %r' % (kind, path)
230
new_tree.get_file(file_id).readlines(),
233
for old_path, new_path, file_id, kind, text_modified in delta.renamed:
234
print >>to_file, '*** renamed %s %r => %r' % (kind, old_path, new_path)
306
235
if text_modified:
307
_maybe_diff_file_or_symlink(old_label, path, old_tree, file_id,
308
new_label, path, new_tree,
309
True, kind, to_file, diff_file)
314
def _raise_if_doubly_unversioned(specific_files, old_tree, new_tree):
315
"""Complain if paths are not versioned in either tree."""
316
if not specific_files:
318
old_unversioned = old_tree.filter_unversioned_files(specific_files)
319
new_unversioned = new_tree.filter_unversioned_files(specific_files)
320
unversioned = old_unversioned.intersection(new_unversioned)
322
raise errors.PathsNotVersionedError(sorted(unversioned))
236
diff_file(old_label + old_path,
237
old_tree.get_file(file_id).readlines(),
238
new_label + new_path,
239
new_tree.get_file(file_id).readlines(),
242
for path, file_id, kind in delta.modified:
243
print >>to_file, '*** modified %s %r' % (kind, path)
245
diff_file(old_label + path,
246
old_tree.get_file(file_id).readlines(),
248
new_tree.get_file(file_id).readlines(),
253
class TreeDelta(object):
254
"""Describes changes from one tree to another.
263
(oldpath, newpath, id, kind, text_modified)
269
Each id is listed only once.
271
Files that are both modified and renamed are listed only in
272
renamed, with the text_modified flag true.
274
Files are only considered renamed if their name has changed or
275
their parent directory has changed. Renaming a directory
276
does not count as renaming all its contents.
278
The lists are normally sorted when the delta is created.
287
def __eq__(self, other):
288
if not isinstance(other, TreeDelta):
290
return self.added == other.added \
291
and self.removed == other.removed \
292
and self.renamed == other.renamed \
293
and self.modified == other.modified \
294
and self.unchanged == other.unchanged
296
def __ne__(self, other):
297
return not (self == other)
300
return "TreeDelta(added=%r, removed=%r, renamed=%r, modified=%r," \
301
" unchanged=%r)" % (self.added, self.removed, self.renamed,
302
self.modified, self.unchanged)
304
def has_changed(self):
305
changes = len(self.added) + len(self.removed) + len(self.renamed)
306
changes += len(self.modified)
307
return (changes != 0)
309
def touches_file_id(self, file_id):
310
"""Return True if file_id is modified by this delta."""
311
for l in self.added, self.removed, self.modified:
315
for v in self.renamed:
321
def show(self, to_file, show_ids=False, show_unchanged=False):
322
def show_list(files):
323
for path, fid, kind in files:
324
if kind == 'directory':
326
elif kind == 'symlink':
330
print >>to_file, ' %-30s %s' % (path, fid)
332
print >>to_file, ' ', path
335
print >>to_file, 'removed:'
336
show_list(self.removed)
339
print >>to_file, 'added:'
340
show_list(self.added)
343
print >>to_file, 'renamed:'
344
for oldpath, newpath, fid, kind, text_modified in self.renamed:
346
print >>to_file, ' %s => %s %s' % (oldpath, newpath, fid)
348
print >>to_file, ' %s => %s' % (oldpath, newpath)
351
print >>to_file, 'modified:'
352
show_list(self.modified)
354
if show_unchanged and self.unchanged:
355
print >>to_file, 'unchanged:'
356
show_list(self.unchanged)
360
def compare_trees(old_tree, new_tree, want_unchanged=False, specific_files=None):
361
"""Describe changes from one tree to another.
363
Returns a TreeDelta with details of added, modified, renamed, and
366
The root entry is specifically exempt.
368
This only considers versioned files.
371
If true, also list files unchanged from one version to
375
If true, only check for changes to specified names or
379
from osutils import is_inside_any
325
def get_prop_change(meta_modified):
327
return " (properties changed)"
332
def _maybe_diff_file_or_symlink(old_label, old_path, old_tree, file_id,
333
new_label, new_path, new_tree, text_modified,
334
kind, to_file, diff_file):
336
new_entry = new_tree.inventory[file_id]
337
old_tree.inventory[file_id].diff(diff_file,
338
old_label + old_path, old_tree,
339
new_label + new_path, new_entry,
381
old_inv = old_tree.inventory
382
new_inv = new_tree.inventory
384
mutter('start compare_trees')
386
# TODO: match for specific files can be rather smarter by finding
387
# the IDs of those files up front and then considering only that.
389
for file_id in old_tree:
390
if file_id in new_tree:
391
kind = old_inv.get_file_kind(file_id)
392
assert kind == new_inv.get_file_kind(file_id)
394
assert kind in ('file', 'directory', 'symlink', 'root_directory'), \
395
'invalid file kind %r' % kind
397
if kind == 'root_directory':
400
old_path = old_inv.id2path(file_id)
401
new_path = new_inv.id2path(file_id)
403
old_ie = old_inv[file_id]
404
new_ie = new_inv[file_id]
407
if (not is_inside_any(specific_files, old_path)
408
and not is_inside_any(specific_files, new_path)):
412
old_sha1 = old_tree.get_file_sha1(file_id)
413
new_sha1 = new_tree.get_file_sha1(file_id)
414
text_modified = (old_sha1 != new_sha1)
416
## mutter("no text to check for %r %r" % (file_id, kind))
417
text_modified = False
419
# TODO: Can possibly avoid calculating path strings if the
420
# two files are unchanged and their names and parents are
421
# the same and the parents are unchanged all the way up.
422
# May not be worthwhile.
424
if (old_ie.name != new_ie.name
425
or old_ie.parent_id != new_ie.parent_id):
426
delta.renamed.append((old_path, new_path, file_id, kind,
429
delta.modified.append((new_path, file_id, kind))
431
delta.unchanged.append((new_path, file_id, kind))
433
kind = old_inv.get_file_kind(file_id)
434
old_path = old_inv.id2path(file_id)
436
if not is_inside_any(specific_files, old_path):
438
delta.removed.append((old_path, file_id, kind))
440
mutter('start looking for new files')
441
for file_id in new_inv:
442
if file_id in old_inv:
444
new_path = new_inv.id2path(file_id)
446
if not is_inside_any(specific_files, new_path):
448
kind = new_inv.get_file_kind(file_id)
449
delta.added.append((new_path, file_id, kind))
454
delta.modified.sort()
455
delta.unchanged.sort()