280
def _get_trees_to_diff(path_list, revision_specs, old_url, new_url,
282
"""Get the trees and specific files to diff given a list of paths.
284
This method works out the trees to be diff'ed and the files of
285
interest within those trees.
288
the list of arguments passed to the diff command
289
:param revision_specs:
290
Zero, one or two RevisionSpecs from the diff command line,
291
saying what revisions to compare.
293
The url of the old branch or tree. If None, the tree to use is
294
taken from the first path, if any, or the current working tree.
296
The url of the new branch or tree. If None, the tree to use is
297
taken from the first path, if any, or the current working tree.
299
if True and a view is set, apply the view or check that the paths
302
a tuple of (old_tree, new_tree, specific_files, extra_trees) where
303
extra_trees is a sequence of additional trees to search in for
273
def diff_cmd_helper(tree, specific_files, external_diff_options,
274
old_revision_spec=None, new_revision_spec=None,
276
old_label='a/', new_label='b/'):
277
"""Helper for cmd_diff.
282
:param specific_files:
283
The specific files to compare, or None
285
:param external_diff_options:
286
If non-None, run an external diff, and pass it these options
288
:param old_revision_spec:
289
If None, use basis tree as old revision, otherwise use the tree for
290
the specified revision.
292
:param new_revision_spec:
293
If None, use working tree as new revision, otherwise use the tree for
294
the specified revision.
296
:param revision_specs:
297
Zero, one or two RevisionSpecs from the command line, saying what revisions
298
to compare. This can be passed as an alternative to the old_revision_spec
299
and new_revision_spec parameters.
301
The more general form is show_diff_trees(), where the caller
302
supplies any two trees.
306
# Get the old and new revision specs
307
old_revision_spec = None
308
new_revision_spec = None
305
# TODO: perhaps remove the old parameters old_revision_spec and
306
# new_revision_spec, since this is only really for use from cmd_diff and
307
# it now always passes through a sequence of revision_specs -- mbp
312
revision = spec.in_store(tree.branch)
314
revision = spec.in_store(None)
315
revision_id = revision.rev_id
316
branch = revision.branch
317
return branch.repository.revision_tree(revision_id)
309
319
if revision_specs is not None:
320
assert (old_revision_spec is None
321
and new_revision_spec is None)
310
322
if len(revision_specs) > 0:
311
323
old_revision_spec = revision_specs[0]
313
old_url = old_revision_spec.get_branch()
314
324
if len(revision_specs) > 1:
315
325
new_revision_spec = revision_specs[1]
317
new_url = new_revision_spec.get_branch()
320
make_paths_wt_relative = True
321
consider_relpath = True
322
if path_list is None or len(path_list) == 0:
323
# If no path is given, the current working tree is used
324
default_location = u'.'
325
consider_relpath = False
326
elif old_url is not None and new_url is not None:
327
other_paths = path_list
328
make_paths_wt_relative = False
330
default_location = path_list[0]
331
other_paths = path_list[1:]
333
# Get the old location
336
old_url = default_location
337
working_tree, branch, relpath = \
338
bzrdir.BzrDir.open_containing_tree_or_branch(old_url)
339
if consider_relpath and relpath != '':
340
if working_tree is not None and apply_view:
341
views.check_path_in_view(working_tree, relpath)
342
specific_files.append(relpath)
343
old_tree = _get_tree_to_diff(old_revision_spec, working_tree, branch)
345
# Get the new location
347
new_url = default_location
348
if new_url != old_url:
349
working_tree, branch, relpath = \
350
bzrdir.BzrDir.open_containing_tree_or_branch(new_url)
351
if consider_relpath and relpath != '':
352
if working_tree is not None and apply_view:
353
views.check_path_in_view(working_tree, relpath)
354
specific_files.append(relpath)
355
new_tree = _get_tree_to_diff(new_revision_spec, working_tree, branch,
356
basis_is_default=working_tree is None)
358
# Get the specific files (all files is None, no files is [])
359
if make_paths_wt_relative and working_tree is not None:
361
from bzrlib.builtins import safe_relpath_files
362
other_paths = safe_relpath_files(working_tree, other_paths,
363
apply_view=apply_view)
364
except errors.FileInWrongBranch:
365
raise errors.BzrCommandError("Files are in different branches")
366
specific_files.extend(other_paths)
367
if len(specific_files) == 0:
368
specific_files = None
369
if (working_tree is not None and working_tree.supports_views()
371
view_files = working_tree.views.lookup_view()
373
specific_files = view_files
374
view_str = views.view_display_str(view_files)
375
note("*** Ignoring files outside view. View is %s" % view_str)
377
# Get extra trees that ought to be searched for file-ids
379
if working_tree is not None and working_tree not in (old_tree, new_tree):
380
extra_trees = (working_tree,)
381
return old_tree, new_tree, specific_files, extra_trees
383
def _get_tree_to_diff(spec, tree=None, branch=None, basis_is_default=True):
384
if branch is None and tree is not None:
386
if spec is None or spec.spec is None:
389
return tree.basis_tree()
391
return branch.basis_tree()
394
return spec.as_tree(branch)
327
if old_revision_spec is None:
328
old_tree = tree.basis_tree()
330
old_tree = spec_tree(old_revision_spec)
332
if (new_revision_spec is None
333
or new_revision_spec.spec is None):
336
new_tree = spec_tree(new_revision_spec)
338
if new_tree is not tree:
339
extra_trees = (tree,)
343
return show_diff_trees(old_tree, new_tree, sys.stdout, specific_files,
344
external_diff_options,
345
old_label=old_label, new_label=new_label,
346
extra_trees=extra_trees)
397
349
def show_diff_trees(old_tree, new_tree, to_file, specific_files=None,
398
350
external_diff_options=None,
399
351
old_label='a/', new_label='b/',
400
352
extra_trees=None,
401
path_encoding='utf8',
353
path_encoding='utf8'):
403
354
"""Show in text form the changes from one tree to another.
409
Include only changes to these files - None for all changes.
357
If set, include only changes to these files.
411
359
external_diff_options
412
360
If set, use an external GNU diff and pass these options.
442
390
def _patch_header_date(tree, file_id, path):
443
391
"""Returns a timestamp suitable for use in a patch header."""
444
392
mtime = tree.get_file_mtime(file_id, path)
393
assert mtime is not None, \
394
"got an mtime of None for file-id %s, path %s in tree %s" % (
445
396
return timestamp.format_patch_date(mtime)
448
def get_executable_change(old_is_x, new_is_x):
449
descr = { True:"+x", False:"-x", None:"??" }
450
if old_is_x != new_is_x:
451
return ["%s to %s" % (descr[old_is_x], descr[new_is_x],)]
399
def _raise_if_nonexistent(paths, old_tree, new_tree):
400
"""Complain if paths are not in either inventory or tree.
402
It's OK with the files exist in either tree's inventory, or
403
if they exist in the tree but are not versioned.
405
This can be used by operations such as bzr status that can accept
406
unknown or ignored files.
408
mutter("check paths: %r", paths)
411
s = old_tree.filter_unversioned_files(paths)
412
s = new_tree.filter_unversioned_files(s)
413
s = [path for path in s if not new_tree.has_filename(path)]
415
raise errors.PathsDoNotExist(sorted(s))
418
def get_prop_change(meta_modified):
420
return " (properties changed)"
456
425
class DiffPath(object):
657
616
return self.CHANGED
660
class DiffFromTool(DiffPath):
662
def __init__(self, command_template, old_tree, new_tree, to_file,
663
path_encoding='utf-8'):
664
DiffPath.__init__(self, old_tree, new_tree, to_file, path_encoding)
665
self.command_template = command_template
666
self._root = osutils.mkdtemp(prefix='bzr-diff-')
669
def from_string(klass, command_string, old_tree, new_tree, to_file,
670
path_encoding='utf-8'):
671
command_template = commands.shlex_split_unicode(command_string)
672
command_template.extend(['%(old_path)s', '%(new_path)s'])
673
return klass(command_template, old_tree, new_tree, to_file,
677
def make_from_diff_tree(klass, command_string):
678
def from_diff_tree(diff_tree):
679
return klass.from_string(command_string, diff_tree.old_tree,
680
diff_tree.new_tree, diff_tree.to_file)
681
return from_diff_tree
683
def _get_command(self, old_path, new_path):
684
my_map = {'old_path': old_path, 'new_path': new_path}
685
return [t % my_map for t in self.command_template]
687
def _execute(self, old_path, new_path):
688
command = self._get_command(old_path, new_path)
690
proc = subprocess.Popen(command, stdout=subprocess.PIPE,
693
if e.errno == errno.ENOENT:
694
raise errors.ExecutableMissing(command[0])
697
self.to_file.write(proc.stdout.read())
700
def _try_symlink_root(self, tree, prefix):
701
if (getattr(tree, 'abspath', None) is None
702
or not osutils.host_os_dereferences_symlinks()):
705
os.symlink(tree.abspath(''), osutils.pathjoin(self._root, prefix))
707
if e.errno != errno.EEXIST:
711
def _write_file(self, file_id, tree, prefix, relpath):
712
full_path = osutils.pathjoin(self._root, prefix, relpath)
713
if self._try_symlink_root(tree, prefix):
715
parent_dir = osutils.dirname(full_path)
717
os.makedirs(parent_dir)
719
if e.errno != errno.EEXIST:
721
source = tree.get_file(file_id, relpath)
723
target = open(full_path, 'wb')
725
osutils.pumpfile(source, target)
730
osutils.make_readonly(full_path)
731
mtime = tree.get_file_mtime(file_id)
732
os.utime(full_path, (mtime, mtime))
735
def _prepare_files(self, file_id, old_path, new_path):
736
old_disk_path = self._write_file(file_id, self.old_tree, 'old',
738
new_disk_path = self._write_file(file_id, self.new_tree, 'new',
740
return old_disk_path, new_disk_path
744
osutils.rmtree(self._root)
746
if e.errno != errno.ENOENT:
747
mutter("The temporary directory \"%s\" was not "
748
"cleanly removed: %s." % (self._root, e))
750
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
751
if (old_kind, new_kind) != ('file', 'file'):
752
return DiffPath.CANNOT_DIFF
753
self._prepare_files(file_id, old_path, new_path)
754
self._execute(osutils.pathjoin('old', old_path),
755
osutils.pathjoin('new', new_path))
758
619
class DiffTree(object):
759
620
"""Provides textual representations of the difference between two trees.
835
691
:param sepecific_files: the specific files to compare (recursive)
836
692
:param extra_trees: extra trees to use for mapping paths to file_ids
839
return self._show_diff(specific_files, extra_trees)
841
for differ in self.differs:
844
def _show_diff(self, specific_files, extra_trees):
845
694
# TODO: Generation of pseudo-diffs for added/deleted files could
846
695
# be usefully made into a much faster special case.
847
iterator = self.new_tree.iter_changes(self.old_tree,
848
specific_files=specific_files,
849
extra_trees=extra_trees,
850
require_versioned=True)
697
delta = self.new_tree.changes_from(self.old_tree,
698
specific_files=specific_files,
699
extra_trees=extra_trees, require_versioned=True)
852
def changes_key(change):
853
old_path, new_path = change[1]
858
def get_encoded_path(path):
860
return path.encode(self.path_encoding, "replace")
861
for (file_id, paths, changed_content, versioned, parent, name, kind,
862
executable) in sorted(iterator, key=changes_key):
863
# The root does not get diffed, and items with no known kind (that
864
# is, missing) in both trees are skipped as well.
865
if parent == (None, None) or kind == (None, None):
867
oldpath, newpath = paths
868
oldpath_encoded = get_encoded_path(paths[0])
869
newpath_encoded = get_encoded_path(paths[1])
870
old_present = (kind[0] is not None and versioned[0])
871
new_present = (kind[1] is not None and versioned[1])
872
renamed = (parent[0], name[0]) != (parent[1], name[1])
874
properties_changed = []
875
properties_changed.extend(get_executable_change(executable[0], executable[1]))
877
if properties_changed:
878
prop_str = " (properties changed: %s)" % (", ".join(properties_changed),)
882
if (old_present, new_present) == (True, False):
883
self.to_file.write("=== removed %s '%s'\n" %
884
(kind[0], oldpath_encoded))
886
elif (old_present, new_present) == (False, True):
887
self.to_file.write("=== added %s '%s'\n" %
888
(kind[1], newpath_encoded))
891
self.to_file.write("=== renamed %s '%s' => '%s'%s\n" %
892
(kind[0], oldpath_encoded, newpath_encoded, prop_str))
894
# if it was produced by iter_changes, it must be
895
# modified *somehow*, either content or execute bit.
896
self.to_file.write("=== modified %s '%s'%s\n" % (kind[0],
897
newpath_encoded, prop_str))
899
self._diff(file_id, oldpath, newpath, kind[0], kind[1])
702
for path, file_id, kind in delta.removed:
704
path_encoded = path.encode(self.path_encoding, "replace")
705
self.to_file.write("=== removed %s '%s'\n" % (kind, path_encoded))
706
self.diff(file_id, path, path)
708
for path, file_id, kind in delta.added:
710
path_encoded = path.encode(self.path_encoding, "replace")
711
self.to_file.write("=== added %s '%s'\n" % (kind, path_encoded))
712
self.diff(file_id, path, path)
713
for (old_path, new_path, file_id, kind,
714
text_modified, meta_modified) in delta.renamed:
716
prop_str = get_prop_change(meta_modified)
717
oldpath_encoded = old_path.encode(self.path_encoding, "replace")
718
newpath_encoded = new_path.encode(self.path_encoding, "replace")
719
self.to_file.write("=== renamed %s '%s' => '%s'%s\n" % (kind,
720
oldpath_encoded, newpath_encoded, prop_str))
722
self.diff(file_id, old_path, new_path)
723
for path, file_id, kind, text_modified, meta_modified in\
726
prop_str = get_prop_change(meta_modified)
727
path_encoded = path.encode(self.path_encoding, "replace")
728
self.to_file.write("=== modified %s '%s'%s\n" % (kind,
729
path_encoded, prop_str))
730
# The file may be in a different location in the old tree (because
731
# the containing dir was renamed, but the file itself was not)
733
old_path = self.old_tree.id2path(file_id)
734
self.diff(file_id, old_path, path)
903
735
return has_changes
905
737
def diff(self, file_id, old_path, new_path):