55
54
DEFAULT_CONTEXT_AMOUNT = 3
57
class AtTemplate(string.Template):
58
"""Templating class that uses @ instead of $."""
58
63
# TODO: Rather than building a changeset object, we should probably
59
64
# invoke callbacks on an object. That object can either accumulate a
60
65
# list, write them out directly, etc etc.
367
372
def get_trees_and_branches_to_diff_locked(
368
path_list, revision_specs, old_url, new_url, exit_stack, apply_view=True):
373
path_list, revision_specs, old_url, new_url, add_cleanup, apply_view=True):
369
374
"""Get the trees and specific files to diff given a list of paths.
371
376
This method works out the trees to be diff'ed and the files of
383
388
The url of the new branch or tree. If None, the tree to use is
384
389
taken from the first path, if any, or the current working tree.
386
an ExitStack object. get_trees_and_branches_to_diff
391
a callable like Command.add_cleanup. get_trees_and_branches_to_diff
387
392
will register cleanups that must be run to unlock the trees, etc.
388
393
:param apply_view:
389
394
if True and a view is set, apply the view or check that the paths
392
397
a tuple of (old_tree, new_tree, old_branch, new_branch,
393
398
specific_files, extra_trees) where extra_trees is a sequence of
394
399
additional trees to search in for file-ids. The trees and branches
395
will be read-locked until the cleanups registered via the exit_stack
400
will be read-locked until the cleanups registered via the add_cleanup
398
403
# Get the old and new revision specs
519
526
context = DEFAULT_CONTEXT_AMOUNT
520
527
if format_cls is None:
521
528
format_cls = DiffTree
522
with cleanup.ExitStack() as exit_stack:
523
exit_stack.enter_context(old_tree.lock_read())
529
with old_tree.lock_read():
524
530
if extra_trees is not None:
525
531
for tree in extra_trees:
526
exit_stack.enter_context(tree.lock_read())
527
exit_stack.enter_context(new_tree.lock_read())
528
differ = format_cls.from_trees_options(old_tree, new_tree, to_file,
530
external_diff_options,
531
old_label, new_label, using,
532
context_lines=context)
533
return differ.show_diff(specific_files, extra_trees)
535
differ = format_cls.from_trees_options(old_tree, new_tree, to_file,
537
external_diff_options,
538
old_label, new_label, using,
539
context_lines=context)
540
return differ.show_diff(specific_files, extra_trees)
543
if extra_trees is not None:
544
for tree in extra_trees:
536
548
def _patch_header_date(tree, path):
626
638
self.differs, old_path, new_path, None, new_kind)
629
class DiffTreeReference(DiffPath):
631
def diff(self, old_path, new_path, old_kind, new_kind):
632
"""Perform comparison between two tree references. (dummy)
635
if 'tree-reference' not in (old_kind, new_kind):
636
return self.CANNOT_DIFF
637
if old_kind not in ('tree-reference', None):
638
return self.CANNOT_DIFF
639
if new_kind not in ('tree-reference', None):
640
return self.CANNOT_DIFF
644
641
class DiffDirectory(DiffPath):
646
643
def diff(self, old_path, new_path, old_kind, new_kind):
764
761
context_lines=self.context_lines)
765
762
except errors.BinaryFile:
766
763
self.to_file.write(
767
("Binary files %s%s and %s%s differ\n" %
768
(self.old_label, from_path, self.new_label, to_path)).encode(self.path_encoding, 'replace'))
764
("Binary files %s and %s differ\n" %
765
(from_label, to_label)).encode(self.path_encoding, 'replace'))
769
766
return self.CHANGED
778
775
self._root = osutils.mkdtemp(prefix='brz-diff-')
781
def from_string(klass, command_template, old_tree, new_tree, to_file,
778
def from_string(klass, command_string, old_tree, new_tree, to_file,
782
779
path_encoding='utf-8'):
780
command_template = cmdline.split(command_string)
781
if '@' not in command_string:
782
command_template.extend(['@old_path', '@new_path'])
783
783
return klass(command_template, old_tree, new_tree, to_file,
796
796
def _get_command(self, old_path, new_path):
797
797
my_map = {'old_path': old_path, 'new_path': new_path}
798
command = [t.format(**my_map) for t in
798
command = [AtTemplate(t).substitute(my_map) for t in
799
799
self.command_template]
800
if command == self.command_template:
801
command += [old_path, new_path]
802
800
if sys.platform == 'win32': # Popen doesn't accept unicode on win32
803
801
command_encoded = []
804
802
for c in command:
879
877
except OSError as e:
880
878
if e.errno != errno.EEXIST:
882
with tree.get_file(relpath) as source, \
883
open(full_path, 'wb') as target:
884
osutils.pumpfile(source, target)
880
source = tree.get_file(relpath)
882
with open(full_path, 'wb') as target:
883
osutils.pumpfile(source, target)
886
887
mtime = tree.get_file_mtime(relpath)
887
888
except FileTimestampUnavailable:
947
948
# list of factories that can provide instances of DiffPath objects
948
949
# may be extended by plugins.
949
950
diff_factories = [DiffSymlink.from_diff_tree,
950
DiffDirectory.from_diff_tree,
951
DiffTreeReference.from_diff_tree]
951
DiffDirectory.from_diff_tree]
953
953
def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8',
954
954
diff_text=None, extra_factories=None):
1044
1044
def get_encoded_path(path):
1045
1045
if path is not None:
1046
1046
return path.encode(self.path_encoding, "replace")
1047
for change in sorted(iterator, key=changes_key):
1047
for (file_id, paths, changed_content, versioned, parent, name, kind,
1048
executable) in sorted(iterator, key=changes_key):
1048
1049
# The root does not get diffed, and items with no known kind (that
1049
1050
# is, missing) in both trees are skipped as well.
1050
if change.parent_id == (None, None) or change.kind == (None, None):
1052
if change.kind[0] == 'symlink' and not self.new_tree.supports_symlinks():
1054
'Ignoring "%s" as symlinks are not '
1055
'supported on this filesystem.' % (change.path[0],))
1057
oldpath, newpath = change.path
1058
oldpath_encoded = get_encoded_path(change.path[0])
1059
newpath_encoded = get_encoded_path(change.path[1])
1060
old_present = (change.kind[0] is not None and change.versioned[0])
1061
new_present = (change.kind[1] is not None and change.versioned[1])
1062
executable = change.executable
1064
renamed = (change.parent_id[0], change.name[0]) != (change.parent_id[1], change.name[1])
1051
if parent == (None, None) or kind == (None, None):
1053
oldpath, newpath = paths
1054
oldpath_encoded = get_encoded_path(paths[0])
1055
newpath_encoded = get_encoded_path(paths[1])
1056
old_present = (kind[0] is not None and versioned[0])
1057
new_present = (kind[1] is not None and versioned[1])
1058
renamed = (parent[0], name[0]) != (parent[1], name[1])
1066
1060
properties_changed = []
1067
1061
properties_changed.extend(
1089
1083
# modified *somehow*, either content or execute bit.
1090
1084
self.to_file.write(b"=== modified %s '%s'%s\n" % (kind[0].encode('ascii'),
1091
1085
newpath_encoded, prop_str))
1092
if change.changed_content:
1093
1087
self._diff(oldpath, newpath, kind[0], kind[1])
1094
1088
has_changes = 1