94
87
if sequence_matcher is None:
95
88
sequence_matcher = patiencediff.PatienceSequenceMatcher
96
ud = patiencediff.unified_diff_bytes(oldlines, newlines,
97
fromfile=old_label.encode(
98
path_encoding, 'replace'),
99
tofile=new_label.encode(
100
path_encoding, 'replace'),
101
n=context_lines, sequencematcher=sequence_matcher)
89
ud = unified_diff_bytes(
91
fromfile=old_label.encode(path_encoding, 'replace'),
92
tofile=new_label.encode(path_encoding, 'replace'),
93
n=context_lines, sequencematcher=sequence_matcher)
104
96
if len(ud) == 0: # Identical contents, nothing to do
117
109
to_file.write(b'\n')
112
def unified_diff_bytes(a, b, fromfile=b'', tofile=b'', fromfiledate=b'',
113
tofiledate=b'', n=3, lineterm=b'\n', sequencematcher=None):
115
Compare two sequences of lines; generate the delta as a unified diff.
117
Unified diffs are a compact way of showing line changes and a few
118
lines of context. The number of context lines is set by 'n' which
121
By default, the diff control lines (those with ---, +++, or @@) are
122
created with a trailing newline. This is helpful so that inputs
123
created from file.readlines() result in diffs that are suitable for
124
file.writelines() since both the inputs and outputs have trailing
127
For inputs that do not have trailing newlines, set the lineterm
128
argument to "" so that the output will be uniformly newline free.
130
The unidiff format normally has a header for filenames and modification
131
times. Any or all of these may be specified using strings for
132
'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'. The modification
133
times are normally expressed in the format returned by time.ctime().
137
>>> for line in bytes_unified_diff(b'one two three four'.split(),
138
... b'zero one tree four'.split(), b'Original', b'Current',
139
... b'Sat Jan 26 23:30:50 1991', b'Fri Jun 06 10:20:52 2003',
142
--- Original Sat Jan 26 23:30:50 1991
143
+++ Current Fri Jun 06 10:20:52 2003
152
if sequencematcher is None:
153
sequencematcher = difflib.SequenceMatcher
156
fromfiledate = b'\t' + bytes(fromfiledate)
158
tofiledate = b'\t' + bytes(tofiledate)
161
for group in sequencematcher(None, a, b).get_grouped_opcodes(n):
163
yield b'--- %s%s%s' % (fromfile, fromfiledate, lineterm)
164
yield b'+++ %s%s%s' % (tofile, tofiledate, lineterm)
166
i1, i2, j1, j2 = group[0][1], group[-1][2], group[0][3], group[-1][4]
167
yield b"@@ -%d,%d +%d,%d @@%s" % (i1 + 1, i2 - i1, j1 + 1, j2 - j1, lineterm)
168
for tag, i1, i2, j1, j2 in group:
170
for line in a[i1:i2]:
173
if tag == 'replace' or tag == 'delete':
174
for line in a[i1:i2]:
176
if tag == 'replace' or tag == 'insert':
177
for line in b[j1:j2]:
120
181
def _spawn_external_diff(diffcmd, capture_errors=True):
121
182
"""Spawn the external diff process, and return the child handle.
304
365
def get_trees_and_branches_to_diff_locked(
305
path_list, revision_specs, old_url, new_url, add_cleanup, apply_view=True):
366
path_list, revision_specs, old_url, new_url, exit_stack, apply_view=True):
306
367
"""Get the trees and specific files to diff given a list of paths.
308
369
This method works out the trees to be diff'ed and the files of
320
381
The url of the new branch or tree. If None, the tree to use is
321
382
taken from the first path, if any, or the current working tree.
323
a callable like Command.add_cleanup. get_trees_and_branches_to_diff
384
an ExitStack object. get_trees_and_branches_to_diff
324
385
will register cleanups that must be run to unlock the trees, etc.
325
386
:param apply_view:
326
387
if True and a view is set, apply the view or check that the paths
329
390
a tuple of (old_tree, new_tree, old_branch, new_branch,
330
391
specific_files, extra_trees) where extra_trees is a sequence of
331
392
additional trees to search in for file-ids. The trees and branches
332
will be read-locked until the cleanups registered via the add_cleanup
393
will be read-locked until the cleanups registered via the exit_stack
335
396
# Get the old and new revision specs
458
517
context = DEFAULT_CONTEXT_AMOUNT
459
518
if format_cls is None:
460
519
format_cls = DiffTree
461
with old_tree.lock_read():
520
with cleanup.ExitStack() as exit_stack:
521
exit_stack.enter_context(old_tree.lock_read())
462
522
if extra_trees is not None:
463
523
for tree in extra_trees:
467
differ = format_cls.from_trees_options(old_tree, new_tree, to_file,
469
external_diff_options,
470
old_label, new_label, using,
471
context_lines=context)
472
return differ.show_diff(specific_files, extra_trees)
475
if extra_trees is not None:
476
for tree in extra_trees:
524
exit_stack.enter_context(tree.lock_read())
525
exit_stack.enter_context(new_tree.lock_read())
526
differ = format_cls.from_trees_options(old_tree, new_tree, to_file,
528
external_diff_options,
529
old_label, new_label, using,
530
context_lines=context)
531
return differ.show_diff(specific_files, extra_trees)
480
534
def _patch_header_date(tree, path):
570
624
self.differs, old_path, new_path, None, new_kind)
627
class DiffTreeReference(DiffPath):
629
def diff(self, old_path, new_path, old_kind, new_kind):
630
"""Perform comparison between two tree references. (dummy)
633
if 'tree-reference' not in (old_kind, new_kind):
634
return self.CANNOT_DIFF
635
if old_kind not in ('tree-reference', None):
636
return self.CANNOT_DIFF
637
if new_kind not in ('tree-reference', None):
638
return self.CANNOT_DIFF
573
642
class DiffDirectory(DiffPath):
575
644
def diff(self, old_path, new_path, old_kind, new_kind):
693
762
context_lines=self.context_lines)
694
763
except errors.BinaryFile:
695
764
self.to_file.write(
696
("Binary files %s and %s differ\n" %
697
(from_label, to_label)).encode(self.path_encoding, 'replace'))
765
("Binary files %s%s and %s%s differ\n" %
766
(self.old_label, from_path, self.new_label, to_path)).encode(self.path_encoding, 'replace'))
698
767
return self.CHANGED
707
776
self._root = osutils.mkdtemp(prefix='brz-diff-')
710
def from_string(klass, command_string, old_tree, new_tree, to_file,
779
def from_string(klass, command_template, old_tree, new_tree, to_file,
711
780
path_encoding='utf-8'):
712
command_template = cmdline.split(command_string)
713
if '@' not in command_string:
714
command_template.extend(['@old_path', '@new_path'])
715
781
return klass(command_template, old_tree, new_tree, to_file,
728
794
def _get_command(self, old_path, new_path):
729
795
my_map = {'old_path': old_path, 'new_path': new_path}
730
command = [AtTemplate(t).substitute(my_map) for t in
796
command = [t.format(**my_map) for t in
731
797
self.command_template]
732
798
if command == self.command_template:
733
799
command += [old_path, new_path]
811
877
except OSError as e:
812
878
if e.errno != errno.EEXIST:
814
source = tree.get_file(relpath)
816
with open(full_path, 'wb') as target:
817
osutils.pumpfile(source, target)
880
with tree.get_file(relpath) as source, \
881
open(full_path, 'wb') as target:
882
osutils.pumpfile(source, target)
821
884
mtime = tree.get_file_mtime(relpath)
822
885
except FileTimestampUnavailable:
882
945
# list of factories that can provide instances of DiffPath objects
883
946
# may be extended by plugins.
884
947
diff_factories = [DiffSymlink.from_diff_tree,
885
DiffDirectory.from_diff_tree]
948
DiffDirectory.from_diff_tree,
949
DiffTreeReference.from_diff_tree]
887
951
def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8',
888
952
diff_text=None, extra_factories=None):
978
1042
def get_encoded_path(path):
979
1043
if path is not None:
980
1044
return path.encode(self.path_encoding, "replace")
981
for (file_id, paths, changed_content, versioned, parent, name, kind,
982
executable) in sorted(iterator, key=changes_key):
1045
for change in sorted(iterator, key=changes_key):
983
1046
# The root does not get diffed, and items with no known kind (that
984
1047
# is, missing) in both trees are skipped as well.
985
if parent == (None, None) or kind == (None, None):
987
oldpath, newpath = paths
988
oldpath_encoded = get_encoded_path(paths[0])
989
newpath_encoded = get_encoded_path(paths[1])
990
old_present = (kind[0] is not None and versioned[0])
991
new_present = (kind[1] is not None and versioned[1])
992
renamed = (parent[0], name[0]) != (parent[1], name[1])
1048
if change.parent_id == (None, None) or change.kind == (None, None):
1050
if change.kind[0] == 'symlink' and not self.new_tree.supports_symlinks():
1052
'Ignoring "%s" as symlinks are not '
1053
'supported on this filesystem.' % (change.path[0],))
1055
oldpath, newpath = change.path
1056
oldpath_encoded = get_encoded_path(change.path[0])
1057
newpath_encoded = get_encoded_path(change.path[1])
1058
old_present = (change.kind[0] is not None and change.versioned[0])
1059
new_present = (change.kind[1] is not None and change.versioned[1])
1060
executable = change.executable
1062
renamed = (change.parent_id[0], change.name[0]) != (change.parent_id[1], change.name[1])
994
1064
properties_changed = []
995
1065
properties_changed.extend(