82
86
# In the meantime we at least make sure the patch isn't
90
# Special workaround for Python2.3, where difflib fails if
91
# both sequences are empty.
92
if not oldlines and not newlines:
85
95
if allow_binary is False:
86
96
textfile.check_text_lines(oldlines)
87
97
textfile.check_text_lines(newlines)
89
99
if sequence_matcher is None:
90
100
sequence_matcher = patiencediff.PatienceSequenceMatcher
91
ud = unified_diff_bytes(
93
fromfile=old_label.encode(path_encoding, 'replace'),
94
tofile=new_label.encode(path_encoding, 'replace'),
95
n=context_lines, sequencematcher=sequence_matcher)
101
ud = patiencediff.unified_diff(oldlines, newlines,
102
fromfile=old_filename.encode(path_encoding, 'replace'),
103
tofile=new_filename.encode(path_encoding, 'replace'),
104
n=context_lines, sequencematcher=sequence_matcher)
98
if len(ud) == 0: # Identical contents, nothing to do
107
if len(ud) == 0: # Identical contents, nothing to do
100
109
# work-around for difflib being too smart for its own good
101
110
# if /dev/null is "1,0", patch won't recognize it as /dev/null
103
ud[2] = ud[2].replace(b'-1,0', b'-0,0')
112
ud[2] = ud[2].replace('-1,0', '-0,0')
104
113
elif not newlines:
105
ud[2] = ud[2].replace(b'+1,0', b'+0,0')
114
ud[2] = ud[2].replace('+1,0', '+0,0')
108
117
to_file.write(line)
109
if not line.endswith(b'\n'):
110
to_file.write(b"\n\\ No newline at end of file\n")
114
def unified_diff_bytes(a, b, fromfile=b'', tofile=b'', fromfiledate=b'',
115
tofiledate=b'', n=3, lineterm=b'\n', sequencematcher=None):
117
Compare two sequences of lines; generate the delta as a unified diff.
119
Unified diffs are a compact way of showing line changes and a few
120
lines of context. The number of context lines is set by 'n' which
123
By default, the diff control lines (those with ---, +++, or @@) are
124
created with a trailing newline. This is helpful so that inputs
125
created from file.readlines() result in diffs that are suitable for
126
file.writelines() since both the inputs and outputs have trailing
129
For inputs that do not have trailing newlines, set the lineterm
130
argument to "" so that the output will be uniformly newline free.
132
The unidiff format normally has a header for filenames and modification
133
times. Any or all of these may be specified using strings for
134
'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'. The modification
135
times are normally expressed in the format returned by time.ctime().
139
>>> for line in bytes_unified_diff(b'one two three four'.split(),
140
... b'zero one tree four'.split(), b'Original', b'Current',
141
... b'Sat Jan 26 23:30:50 1991', b'Fri Jun 06 10:20:52 2003',
144
--- Original Sat Jan 26 23:30:50 1991
145
+++ Current Fri Jun 06 10:20:52 2003
154
if sequencematcher is None:
155
sequencematcher = difflib.SequenceMatcher
158
fromfiledate = b'\t' + bytes(fromfiledate)
160
tofiledate = b'\t' + bytes(tofiledate)
163
for group in sequencematcher(None, a, b).get_grouped_opcodes(n):
165
yield b'--- %s%s%s' % (fromfile, fromfiledate, lineterm)
166
yield b'+++ %s%s%s' % (tofile, tofiledate, lineterm)
168
i1, i2, j1, j2 = group[0][1], group[-1][2], group[0][3], group[-1][4]
169
yield b"@@ -%d,%d +%d,%d @@%s" % (i1 + 1, i2 - i1, j1 + 1, j2 - j1, lineterm)
170
for tag, i1, i2, j1, j2 in group:
172
for line in a[i1:i2]:
175
if tag == 'replace' or tag == 'delete':
176
for line in a[i1:i2]:
178
if tag == 'replace' or tag == 'insert':
179
for line in b[j1:j2]:
118
if not line.endswith('\n'):
119
to_file.write("\n\\ No newline at end of file\n")
183
123
def _spawn_external_diff(diffcmd, capture_errors=True):
316
254
out, err = pipe.communicate()
318
256
# Write out the new i18n diff response
319
to_file.write(out + b'\n')
257
to_file.write(out+'\n')
320
258
if pipe.returncode != 2:
321
259
raise errors.BzrError(
322
'external diff failed with exit code 2'
323
' when run with LANG=C and LC_ALL=C,'
324
' but not when run natively: %r' % (diffcmd,))
260
'external diff failed with exit code 2'
261
' when run with LANG=C and LC_ALL=C,'
262
' but not when run natively: %r' % (diffcmd,))
326
first_line = lang_c_out.split(b'\n', 1)[0]
264
first_line = lang_c_out.split('\n', 1)[0]
327
265
# Starting with diffutils 2.8.4 the word "binary" was dropped.
328
m = re.match(b'^(binary )?files.*differ$', first_line, re.I)
266
m = re.match('^(binary )?files.*differ$', first_line, re.I)
330
268
raise errors.BzrError('external diff failed with exit code 2;'
331
269
' command: %r' % (diffcmd,))
519
460
context = DEFAULT_CONTEXT_AMOUNT
520
461
if format_cls is None:
521
462
format_cls = DiffTree
522
with cleanup.ExitStack() as exit_stack:
523
exit_stack.enter_context(old_tree.lock_read())
463
with old_tree.lock_read():
524
464
if extra_trees is not None:
525
465
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)
536
def _patch_header_date(tree, path):
469
differ = format_cls.from_trees_options(old_tree, new_tree, to_file,
471
external_diff_options,
472
old_label, new_label, using,
473
context_lines=context)
474
return differ.show_diff(specific_files, extra_trees)
477
if extra_trees is not None:
478
for tree in extra_trees:
482
def _patch_header_date(tree, file_id, path):
537
483
"""Returns a timestamp suitable for use in a patch header."""
539
mtime = tree.get_file_mtime(path)
485
mtime = tree.get_file_mtime(path, file_id)
540
486
except FileTimestampUnavailable:
542
488
return timestamp.format_patch_date(mtime)
545
491
def get_executable_change(old_is_x, new_is_x):
546
descr = {True: b"+x", False: b"-x", None: b"??"}
492
descr = { True:"+x", False:"-x", None:"??" }
547
493
if old_is_x != new_is_x:
548
return [b"%s to %s" % (descr[old_is_x], descr[new_is_x],)]
494
return ["%s to %s" % (descr[old_is_x], descr[new_is_x],)]
619
566
if None in (old_kind, new_kind):
620
567
return DiffPath.CANNOT_DIFF
621
result = DiffPath._diff_many(
622
self.differs, old_path, new_path, old_kind, None)
568
result = DiffPath._diff_many(self.differs, file_id, old_path,
569
new_path, old_kind, None)
623
570
if result is DiffPath.CANNOT_DIFF:
625
return DiffPath._diff_many(
626
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
572
return DiffPath._diff_many(self.differs, file_id, old_path, new_path,
644
576
class DiffDirectory(DiffPath):
646
def diff(self, old_path, new_path, old_kind, new_kind):
578
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
647
579
"""Perform comparison between two directories. (dummy)
723
654
if 'file' not in (old_kind, new_kind):
724
655
return self.CANNOT_DIFF
656
from_file_id = to_file_id = file_id
725
657
if old_kind == 'file':
726
old_date = _patch_header_date(self.old_tree, old_path)
658
old_date = _patch_header_date(self.old_tree, file_id, old_path)
727
659
elif old_kind is None:
728
660
old_date = self.EPOCH_DATE
730
663
return self.CANNOT_DIFF
731
664
if new_kind == 'file':
732
new_date = _patch_header_date(self.new_tree, new_path)
665
new_date = _patch_header_date(self.new_tree, file_id, new_path)
733
666
elif new_kind is None:
734
667
new_date = self.EPOCH_DATE
736
670
return self.CANNOT_DIFF
737
from_label = '%s%s\t%s' % (self.old_label, old_path,
739
to_label = '%s%s\t%s' % (self.new_label, new_path,
741
return self.diff_text(old_path, new_path, from_label, to_label)
671
from_label = '%s%s\t%s' % (self.old_label, old_path, old_date)
672
to_label = '%s%s\t%s' % (self.new_label, new_path, new_date)
673
return self.diff_text(old_path, new_path, from_label, to_label,
674
from_file_id, to_file_id)
743
def diff_text(self, from_path, to_path, from_label, to_label):
676
def diff_text(self, from_path, to_path, from_label, to_label,
677
from_file_id=None, to_file_id=None):
744
678
"""Diff the content of given files in two trees
746
680
:param from_path: The path in the from tree. If None,
748
682
:param to_path: The path in the to tree. This may refer
749
683
to a different file from from_path. If None,
750
684
the file is not present in the to tree.
685
:param from_file_id: The id of the file in the from tree or None if
687
:param to_file_id: The id of the file in the to tree or None if
752
def _get_text(tree, path):
756
return tree.get_file_lines(path)
757
except errors.NoSuchFile:
690
def _get_text(tree, file_id, path):
693
return tree.get_file_lines(path, file_id)
760
from_text = _get_text(self.old_tree, from_path)
761
to_text = _get_text(self.new_tree, to_path)
695
from_text = _get_text(self.old_tree, from_file_id, from_path)
696
to_text = _get_text(self.new_tree, to_file_id, to_path)
762
697
self.text_differ(from_label, from_text, to_label, to_text,
763
698
self.to_file, path_encoding=self.path_encoding,
764
699
context_lines=self.context_lines)
765
700
except errors.BinaryFile:
766
701
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'))
702
("Binary files %s and %s differ\n" %
703
(from_label, to_label)).encode(self.path_encoding, 'replace'))
769
704
return self.CHANGED
895
833
def _prepare_files(self, old_path, new_path, force_temp=False,
896
allow_write_new=False):
897
old_disk_path = self._write_file(
898
old_path, self.old_tree, 'old', force_temp)
899
new_disk_path = self._write_file(
900
new_path, self.new_tree, 'new', force_temp,
901
allow_write=allow_write_new)
834
allow_write_new=False, file_id=None):
835
old_disk_path = self._write_file(old_path, self.old_tree, 'old',
836
force_temp, file_id=file_id)
837
new_disk_path = self._write_file(new_path, self.new_tree, 'new',
838
force_temp, file_id=file_id,
839
allow_write=allow_write_new)
902
840
return old_disk_path, new_disk_path
904
842
def finish(self):
907
845
except OSError as e:
908
846
if e.errno != errno.ENOENT:
909
847
mutter("The temporary directory \"%s\" was not "
910
"cleanly removed: %s." % (self._root, e))
848
"cleanly removed: %s." % (self._root, e))
912
def diff(self, old_path, new_path, old_kind, new_kind):
850
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
913
851
if (old_kind, new_kind) != ('file', 'file'):
914
852
return DiffPath.CANNOT_DIFF
915
853
(old_disk_path, new_disk_path) = self._prepare_files(
854
old_path, new_path, file_id=file_id)
917
855
self._execute(old_disk_path, new_disk_path)
919
def edit_file(self, old_path, new_path):
857
def edit_file(self, old_path, new_path, file_id=None):
920
858
"""Use this tool to edit a file.
922
860
A temporary copy will be edited, and the new contents will be
863
:param file_id: The id of the file to edit.
925
864
:return: The new contents of the file.
927
866
old_abs_path, new_abs_path = self._prepare_files(
928
old_path, new_path, allow_write_new=True, force_temp=True)
867
old_path, new_path, allow_write_new=True, force_temp=True,
929
869
command = self._get_command(old_abs_path, new_abs_path)
930
870
subprocess.call(command, cwd=self._root)
931
871
with open(new_abs_path, 'rb') as new_file:
1029
966
# TODO: Generation of pseudo-diffs for added/deleted files could
1030
967
# be usefully made into a much faster special case.
1031
968
iterator = self.new_tree.iter_changes(self.old_tree,
1032
specific_files=specific_files,
1033
extra_trees=extra_trees,
1034
require_versioned=True)
969
specific_files=specific_files,
970
extra_trees=extra_trees,
971
require_versioned=True)
1037
973
def changes_key(change):
1038
old_path, new_path = change.path
974
old_path, new_path = change[1]
1040
976
if path is None:
1044
979
def get_encoded_path(path):
1045
980
if path is not None:
1046
981
return path.encode(self.path_encoding, "replace")
1047
for change in sorted(iterator, key=changes_key):
982
for (file_id, paths, changed_content, versioned, parent, name, kind,
983
executable) in sorted(iterator, key=changes_key):
1048
984
# The root does not get diffed, and items with no known kind (that
1049
985
# 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])
986
if parent == (None, None) or kind == (None, None):
988
oldpath, newpath = paths
989
oldpath_encoded = get_encoded_path(paths[0])
990
newpath_encoded = get_encoded_path(paths[1])
991
old_present = (kind[0] is not None and versioned[0])
992
new_present = (kind[1] is not None and versioned[1])
993
renamed = (parent[0], name[0]) != (parent[1], name[1])
1066
995
properties_changed = []
1067
properties_changed.extend(
1068
get_executable_change(executable[0], executable[1]))
996
properties_changed.extend(get_executable_change(executable[0], executable[1]))
1070
998
if properties_changed:
1071
prop_str = b" (properties changed: %s)" % (
1072
b", ".join(properties_changed),)
999
prop_str = " (properties changed: %s)" % (", ".join(properties_changed),)
1076
1003
if (old_present, new_present) == (True, False):
1077
self.to_file.write(b"=== removed %s '%s'\n" %
1078
(kind[0].encode('ascii'), oldpath_encoded))
1004
self.to_file.write("=== removed %s '%s'\n" %
1005
(kind[0], oldpath_encoded))
1079
1006
newpath = oldpath
1080
1007
elif (old_present, new_present) == (False, True):
1081
self.to_file.write(b"=== added %s '%s'\n" %
1082
(kind[1].encode('ascii'), newpath_encoded))
1008
self.to_file.write("=== added %s '%s'\n" %
1009
(kind[1], newpath_encoded))
1083
1010
oldpath = newpath
1085
self.to_file.write(b"=== renamed %s '%s' => '%s'%s\n" %
1086
(kind[0].encode('ascii'), oldpath_encoded, newpath_encoded, prop_str))
1012
self.to_file.write("=== renamed %s '%s' => '%s'%s\n" %
1013
(kind[0], oldpath_encoded, newpath_encoded, prop_str))
1088
1015
# if it was produced by iter_changes, it must be
1089
1016
# modified *somehow*, either content or execute bit.
1090
self.to_file.write(b"=== modified %s '%s'%s\n" % (kind[0].encode('ascii'),
1091
newpath_encoded, prop_str))
1092
if change.changed_content:
1093
self._diff(oldpath, newpath, kind[0], kind[1])
1017
self.to_file.write("=== modified %s '%s'%s\n" % (kind[0],
1018
newpath_encoded, prop_str))
1020
self._diff(oldpath, newpath, kind[0], kind[1], file_id=file_id)
1094
1021
has_changes = 1
1096
1023
has_changes = 1
1097
1024
return has_changes
1099
def diff(self, old_path, new_path):
1026
def diff(self, file_id, old_path, new_path):
1100
1027
"""Perform a diff of a single file
1029
:param file_id: file-id of the file
1102
1030
:param old_path: The path of the file in the old tree
1103
1031
:param new_path: The path of the file in the new tree
1105
1033
if old_path is None:
1106
1034
old_kind = None
1108
old_kind = self.old_tree.kind(old_path)
1036
old_kind = self.old_tree.kind(old_path, file_id)
1109
1037
if new_path is None:
1110
1038
new_kind = None
1112
new_kind = self.new_tree.kind(new_path)
1113
self._diff(old_path, new_path, old_kind, new_kind)
1040
new_kind = self.new_tree.kind(new_path, file_id)
1041
self._diff(old_path, new_path, old_kind, new_kind, file_id=file_id)
1115
def _diff(self, old_path, new_path, old_kind, new_kind):
1116
result = DiffPath._diff_many(
1117
self.differs, old_path, new_path, old_kind, new_kind)
1043
def _diff(self, old_path, new_path, old_kind, new_kind, file_id):
1044
result = DiffPath._diff_many(self.differs, file_id, old_path,
1045
new_path, old_kind, new_kind)
1118
1046
if result is DiffPath.CANNOT_DIFF:
1119
1047
error_path = new_path
1120
1048
if error_path is None: