150
149
oldtmpf.close() # and delete
155
def show_diff(b, revision, specific_files, external_diff_options=None):
153
@deprecated_function(zero_eight)
154
def show_diff(b, from_spec, specific_files, external_diff_options=None,
155
revision2=None, output=None, b2=None):
156
156
"""Shortcut for showing the diff to the working tree.
158
Please use show_diff_trees instead.
162
None for each, or otherwise the old revision to compare against.
164
None for 'basis tree', or otherwise the old revision to compare against.
166
The more general form is show_diff_trees(), where the caller
167
supplies any two trees.
173
if from_spec is None:
174
old_tree = b.bzrdir.open_workingtree()
176
old_tree = old_tree = old_tree.basis_tree()
178
old_tree = b.repository.revision_tree(from_spec.in_history(b).rev_id)
180
if revision2 is None:
182
new_tree = b.bzrdir.open_workingtree()
184
new_tree = b2.bzrdir.open_workingtree()
186
new_tree = b.repository.revision_tree(revision2.in_history(b).rev_id)
188
return show_diff_trees(old_tree, new_tree, output, specific_files,
189
external_diff_options)
192
def diff_cmd_helper(tree, specific_files, external_diff_options,
193
old_revision_spec=None, new_revision_spec=None,
194
old_label='a/', new_label='b/'):
195
"""Helper for cmd_diff.
201
The specific files to compare, or None
203
external_diff_options
204
If non-None, run an external diff, and pass it these options
207
If None, use basis tree as old revision, otherwise use the tree for
208
the specified revision.
211
If None, use working tree as new revision, otherwise use the tree for
212
the specified revision.
164
214
The more general form is show_diff_trees(), where the caller
165
215
supplies any two trees.
170
old_tree = b.basis_tree()
172
old_tree = b.revision_tree(b.lookup_revision(revision))
174
new_tree = b.working_tree()
176
show_diff_trees(old_tree, new_tree, sys.stdout, specific_files,
177
external_diff_options)
220
revision_id = spec.in_store(tree.branch).rev_id
221
return tree.branch.repository.revision_tree(revision_id)
222
if old_revision_spec is None:
223
old_tree = tree.basis_tree()
225
old_tree = spec_tree(old_revision_spec)
227
if new_revision_spec is None:
230
new_tree = spec_tree(new_revision_spec)
232
return show_diff_trees(old_tree, new_tree, sys.stdout, specific_files,
233
external_diff_options,
234
old_label=old_label, new_label=new_label)
181
237
def show_diff_trees(old_tree, new_tree, to_file, specific_files=None,
182
external_diff_options=None):
238
external_diff_options=None,
239
old_label='a/', new_label='b/'):
183
240
"""Show in text form the changes from one tree to another.
210
281
diff_file = internal_diff
213
283
delta = compare_trees(old_tree, new_tree, want_unchanged=False,
214
284
specific_files=specific_files)
216
287
for path, file_id, kind in delta.removed:
217
print >>to_file, '*** removed %s %r' % (kind, path)
219
diff_file(old_label + path,
220
old_tree.get_file(file_id).readlines(),
289
print >>to_file, '=== removed %s %r' % (kind, path)
290
old_tree.inventory[file_id].diff(diff_file, old_label + path, old_tree,
291
DEVNULL, None, None, to_file)
225
292
for path, file_id, kind in delta.added:
226
print >>to_file, '*** added %s %r' % (kind, path)
231
new_tree.get_file(file_id).readlines(),
234
for old_path, new_path, file_id, kind, text_modified in delta.renamed:
235
print >>to_file, '*** renamed %s %r => %r' % (kind, old_path, new_path)
294
print >>to_file, '=== added %s %r' % (kind, path)
295
new_tree.inventory[file_id].diff(diff_file, new_label + path, new_tree,
296
DEVNULL, None, None, to_file,
298
for (old_path, new_path, file_id, kind,
299
text_modified, meta_modified) in delta.renamed:
301
prop_str = get_prop_change(meta_modified)
302
print >>to_file, '=== renamed %s %r => %r%s' % (
303
kind, old_path, new_path, prop_str)
304
_maybe_diff_file_or_symlink(old_label, old_path, old_tree, file_id,
305
new_label, new_path, new_tree,
306
text_modified, kind, to_file, diff_file)
307
for path, file_id, kind, text_modified, meta_modified in delta.modified:
309
prop_str = get_prop_change(meta_modified)
310
print >>to_file, '=== modified %s %r%s' % (kind, path, prop_str)
236
311
if text_modified:
237
diff_file(old_label + old_path,
238
old_tree.get_file(file_id).readlines(),
239
new_label + new_path,
240
new_tree.get_file(file_id).readlines(),
243
for path, file_id, kind in delta.modified:
244
print >>to_file, '*** modified %s %r' % (kind, path)
246
diff_file(old_label + path,
247
old_tree.get_file(file_id).readlines(),
249
new_tree.get_file(file_id).readlines(),
254
class TreeDelta(object):
255
"""Describes changes from one tree to another.
264
(oldpath, newpath, id, kind, text_modified)
270
Each id is listed only once.
272
Files that are both modified and renamed are listed only in
273
renamed, with the text_modified flag true.
275
Files are only considered renamed if their name has changed or
276
their parent directory has changed. Renaming a directory
277
does not count as renaming all its contents.
279
The lists are normally sorted when the delta is created.
288
def __eq__(self, other):
289
if not isinstance(other, TreeDelta):
291
return self.added == other.added \
292
and self.removed == other.removed \
293
and self.renamed == other.renamed \
294
and self.modified == other.modified \
295
and self.unchanged == other.unchanged
297
def __ne__(self, other):
298
return not (self == other)
301
return "TreeDelta(added=%r, removed=%r, renamed=%r, modified=%r," \
302
" unchanged=%r)" % (self.added, self.removed, self.renamed,
303
self.modified, self.unchanged)
305
def has_changed(self):
306
changes = len(self.added) + len(self.removed) + len(self.renamed)
307
changes += len(self.modified)
308
return (changes != 0)
310
def touches_file_id(self, file_id):
311
"""Return True if file_id is modified by this delta."""
312
for l in self.added, self.removed, self.modified:
316
for v in self.renamed:
322
def show(self, to_file, show_ids=False, show_unchanged=False):
323
def show_list(files):
324
for path, fid, kind in files:
325
if kind == 'directory':
327
elif kind == 'symlink':
331
print >>to_file, ' %-30s %s' % (path, fid)
333
print >>to_file, ' ', path
336
print >>to_file, 'removed:'
337
show_list(self.removed)
340
print >>to_file, 'added:'
341
show_list(self.added)
344
print >>to_file, 'renamed:'
345
for oldpath, newpath, fid, kind, text_modified in self.renamed:
347
print >>to_file, ' %s => %s %s' % (oldpath, newpath, fid)
349
print >>to_file, ' %s => %s' % (oldpath, newpath)
352
print >>to_file, 'modified:'
353
show_list(self.modified)
355
if show_unchanged and self.unchanged:
356
print >>to_file, 'unchanged:'
357
show_list(self.unchanged)
361
def compare_trees(old_tree, new_tree, want_unchanged=False, specific_files=None):
362
"""Describe changes from one tree to another.
364
Returns a TreeDelta with details of added, modified, renamed, and
367
The root entry is specifically exempt.
369
This only considers versioned files.
372
If true, also list files unchanged from one version to
376
If true, only check for changes to specified names or
380
from osutils import is_inside_any
382
old_inv = old_tree.inventory
383
new_inv = new_tree.inventory
385
mutter('start compare_trees')
387
# TODO: match for specific files can be rather smarter by finding
388
# the IDs of those files up front and then considering only that.
390
for file_id in old_tree:
391
if file_id in new_tree:
392
old_ie = old_inv[file_id]
393
new_ie = new_inv[file_id]
396
assert kind == new_ie.kind
398
assert kind in ('file', 'directory', 'symlink', 'root_directory'), \
399
'invalid file kind %r' % kind
401
if kind == 'root_directory':
405
if (not is_inside_any(specific_files, old_inv.id2path(file_id))
406
and not is_inside_any(specific_files, new_inv.id2path(file_id))):
410
old_sha1 = old_tree.get_file_sha1(file_id)
411
new_sha1 = new_tree.get_file_sha1(file_id)
412
text_modified = (old_sha1 != new_sha1)
414
## mutter("no text to check for %r %r" % (file_id, kind))
415
text_modified = False
417
# TODO: Can possibly avoid calculating path strings if the
418
# two files are unchanged and their names and parents are
419
# the same and the parents are unchanged all the way up.
420
# May not be worthwhile.
422
if (old_ie.name != new_ie.name
423
or old_ie.parent_id != new_ie.parent_id):
424
delta.renamed.append((old_inv.id2path(file_id),
425
new_inv.id2path(file_id),
429
delta.modified.append((new_inv.id2path(file_id), file_id, kind))
431
delta.unchanged.append((new_inv.id2path(file_id), 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()
312
_maybe_diff_file_or_symlink(old_label, path, old_tree, file_id,
313
new_label, path, new_tree,
314
True, kind, to_file, diff_file)
319
def _raise_if_doubly_unversioned(specific_files, old_tree, new_tree):
320
"""Complain if paths are not versioned in either tree."""
321
if not specific_files:
323
old_unversioned = old_tree.filter_unversioned_files(specific_files)
324
new_unversioned = new_tree.filter_unversioned_files(specific_files)
325
unversioned = old_unversioned.intersection(new_unversioned)
327
raise errors.PathsNotVersionedError(sorted(unversioned))
330
def _raise_if_nonexistent(paths, old_tree, new_tree):
331
"""Complain if paths are not in either inventory or tree.
333
It's OK with the files exist in either tree's inventory, or
334
if they exist in the tree but are not versioned.
336
This can be used by operations such as bzr status that can accept
337
unknown or ignored files.
339
mutter("check paths: %r", paths)
342
s = old_tree.filter_unversioned_files(paths)
343
s = new_tree.filter_unversioned_files(s)
344
s = [path for path in s if not new_tree.has_filename(path)]
346
raise errors.PathsDoNotExist(sorted(s))
349
def get_prop_change(meta_modified):
351
return " (properties changed)"
356
def _maybe_diff_file_or_symlink(old_label, old_path, old_tree, file_id,
357
new_label, new_path, new_tree, text_modified,
358
kind, to_file, diff_file):
360
new_entry = new_tree.inventory[file_id]
361
old_tree.inventory[file_id].diff(diff_file,
362
old_label + old_path, old_tree,
363
new_label + new_path, new_entry,