79
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:
82
95
if allow_binary is False:
83
96
textfile.check_text_lines(oldlines)
84
97
textfile.check_text_lines(newlines)
86
99
if sequence_matcher is None:
87
100
sequence_matcher = patiencediff.PatienceSequenceMatcher
88
ud = unified_diff_bytes(
90
fromfile=old_label.encode(path_encoding, 'replace'),
91
tofile=new_label.encode(path_encoding, 'replace'),
92
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)
95
if len(ud) == 0: # Identical contents, nothing to do
107
if len(ud) == 0: # Identical contents, nothing to do
97
109
# work-around for difflib being too smart for its own good
98
110
# if /dev/null is "1,0", patch won't recognize it as /dev/null
100
ud[2] = ud[2].replace(b'-1,0', b'-0,0')
112
ud[2] = ud[2].replace('-1,0', '-0,0')
101
113
elif not newlines:
102
ud[2] = ud[2].replace(b'+1,0', b'+0,0')
114
ud[2] = ud[2].replace('+1,0', '+0,0')
105
117
to_file.write(line)
106
if not line.endswith(b'\n'):
107
to_file.write(b"\n\\ No newline at end of file\n")
111
def unified_diff_bytes(a, b, fromfile=b'', tofile=b'', fromfiledate=b'',
112
tofiledate=b'', n=3, lineterm=b'\n', sequencematcher=None):
114
Compare two sequences of lines; generate the delta as a unified diff.
116
Unified diffs are a compact way of showing line changes and a few
117
lines of context. The number of context lines is set by 'n' which
120
By default, the diff control lines (those with ---, +++, or @@) are
121
created with a trailing newline. This is helpful so that inputs
122
created from file.readlines() result in diffs that are suitable for
123
file.writelines() since both the inputs and outputs have trailing
126
For inputs that do not have trailing newlines, set the lineterm
127
argument to "" so that the output will be uniformly newline free.
129
The unidiff format normally has a header for filenames and modification
130
times. Any or all of these may be specified using strings for
131
'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'. The modification
132
times are normally expressed in the format returned by time.ctime().
136
>>> for line in bytes_unified_diff(b'one two three four'.split(),
137
... b'zero one tree four'.split(), b'Original', b'Current',
138
... b'Sat Jan 26 23:30:50 1991', b'Fri Jun 06 10:20:52 2003',
141
--- Original Sat Jan 26 23:30:50 1991
142
+++ Current Fri Jun 06 10:20:52 2003
151
if sequencematcher is None:
152
sequencematcher = difflib.SequenceMatcher
155
fromfiledate = b'\t' + bytes(fromfiledate)
157
tofiledate = b'\t' + bytes(tofiledate)
160
for group in sequencematcher(None, a, b).get_grouped_opcodes(n):
162
yield b'--- %s%s%s' % (fromfile, fromfiledate, lineterm)
163
yield b'+++ %s%s%s' % (tofile, tofiledate, lineterm)
165
i1, i2, j1, j2 = group[0][1], group[-1][2], group[0][3], group[-1][4]
166
yield b"@@ -%d,%d +%d,%d @@%s" % (i1 + 1, i2 - i1, j1 + 1, j2 - j1, lineterm)
167
for tag, i1, i2, j1, j2 in group:
169
for line in a[i1:i2]:
172
if tag == 'replace' or tag == 'delete':
173
for line in a[i1:i2]:
175
if tag == 'replace' or tag == 'insert':
176
for line in b[j1:j2]:
118
if not line.endswith('\n'):
119
to_file.write("\n\\ No newline at end of file\n")
180
123
def _spawn_external_diff(diffcmd, capture_errors=True):
313
254
out, err = pipe.communicate()
315
256
# Write out the new i18n diff response
316
to_file.write(out + b'\n')
257
to_file.write(out+'\n')
317
258
if pipe.returncode != 2:
318
259
raise errors.BzrError(
319
'external diff failed with exit code 2'
320
' when run with LANG=C and LC_ALL=C,'
321
' 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,))
323
first_line = lang_c_out.split(b'\n', 1)[0]
264
first_line = lang_c_out.split('\n', 1)[0]
324
265
# Starting with diffutils 2.8.4 the word "binary" was dropped.
325
m = re.match(b'^(binary )?files.*differ$', first_line, re.I)
266
m = re.match('^(binary )?files.*differ$', first_line, re.I)
327
268
raise errors.BzrError('external diff failed with exit code 2;'
328
269
' command: %r' % (diffcmd,))
516
460
context = DEFAULT_CONTEXT_AMOUNT
517
461
if format_cls is None:
518
462
format_cls = DiffTree
519
with contextlib.ExitStack() as exit_stack:
520
exit_stack.enter_context(old_tree.lock_read())
463
with old_tree.lock_read():
521
464
if extra_trees is not None:
522
465
for tree in extra_trees:
523
exit_stack.enter_context(tree.lock_read())
524
exit_stack.enter_context(new_tree.lock_read())
525
differ = format_cls.from_trees_options(old_tree, new_tree, to_file,
527
external_diff_options,
528
old_label, new_label, using,
529
context_lines=context)
530
return differ.show_diff(specific_files, extra_trees)
533
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):
534
483
"""Returns a timestamp suitable for use in a patch header."""
536
mtime = tree.get_file_mtime(path)
485
mtime = tree.get_file_mtime(path, file_id)
537
486
except FileTimestampUnavailable:
539
488
return timestamp.format_patch_date(mtime)
542
491
def get_executable_change(old_is_x, new_is_x):
543
descr = {True: b"+x", False: b"-x", None: b"??"}
492
descr = { True:"+x", False:"-x", None:"??" }
544
493
if old_is_x != new_is_x:
545
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],)]
616
566
if None in (old_kind, new_kind):
617
567
return DiffPath.CANNOT_DIFF
618
result = DiffPath._diff_many(
619
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)
620
570
if result is DiffPath.CANNOT_DIFF:
622
return DiffPath._diff_many(
623
self.differs, old_path, new_path, None, new_kind)
626
class DiffTreeReference(DiffPath):
628
def diff(self, old_path, new_path, old_kind, new_kind):
629
"""Perform comparison between two tree references. (dummy)
632
if 'tree-reference' not in (old_kind, new_kind):
633
return self.CANNOT_DIFF
634
if old_kind not in ('tree-reference', None):
635
return self.CANNOT_DIFF
636
if new_kind not in ('tree-reference', None):
637
return self.CANNOT_DIFF
572
return DiffPath._diff_many(self.differs, file_id, old_path, new_path,
641
576
class DiffDirectory(DiffPath):
643
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):
644
579
"""Perform comparison between two directories. (dummy)
720
654
if 'file' not in (old_kind, new_kind):
721
655
return self.CANNOT_DIFF
656
from_file_id = to_file_id = file_id
722
657
if old_kind == 'file':
723
old_date = _patch_header_date(self.old_tree, old_path)
658
old_date = _patch_header_date(self.old_tree, file_id, old_path)
724
659
elif old_kind is None:
725
660
old_date = self.EPOCH_DATE
727
663
return self.CANNOT_DIFF
728
664
if new_kind == 'file':
729
new_date = _patch_header_date(self.new_tree, new_path)
665
new_date = _patch_header_date(self.new_tree, file_id, new_path)
730
666
elif new_kind is None:
731
667
new_date = self.EPOCH_DATE
733
670
return self.CANNOT_DIFF
734
from_label = '%s%s\t%s' % (
735
self.old_label, old_path or new_path, old_date)
736
to_label = '%s%s\t%s' % (
737
self.new_label, new_path or old_path, new_date)
738
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)
740
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):
741
678
"""Diff the content of given files in two trees
743
680
:param from_path: The path in the from tree. If None,
745
682
:param to_path: The path in the to tree. This may refer
746
683
to a different file from from_path. If None,
747
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
749
def _get_text(tree, path):
753
return tree.get_file_lines(path)
754
except errors.NoSuchFile:
690
def _get_text(tree, file_id, path):
693
return tree.get_file_lines(path, file_id)
757
from_text = _get_text(self.old_tree, from_path)
758
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)
759
697
self.text_differ(from_label, from_text, to_label, to_text,
760
698
self.to_file, path_encoding=self.path_encoding,
761
699
context_lines=self.context_lines)
762
700
except errors.BinaryFile:
763
701
self.to_file.write(
764
("Binary files %s%s and %s%s differ\n" %
765
(self.old_label, from_path or to_path,
766
self.new_label, to_path or from_path)
767
).encode(self.path_encoding, 'replace'))
702
("Binary files %s and %s differ\n" %
703
(from_label, to_label)).encode(self.path_encoding, 'replace'))
768
704
return self.CHANGED
894
836
def _prepare_files(self, old_path, new_path, force_temp=False,
895
allow_write_new=False):
896
old_disk_path = self._write_file(
897
old_path, self.old_tree, 'old', force_temp)
898
new_disk_path = self._write_file(
899
new_path, self.new_tree, 'new', force_temp,
900
allow_write=allow_write_new)
837
allow_write_new=False, file_id=None):
838
old_disk_path = self._write_file(old_path, self.old_tree, 'old',
839
force_temp, file_id=file_id)
840
new_disk_path = self._write_file(new_path, self.new_tree, 'new',
841
force_temp, file_id=file_id,
842
allow_write=allow_write_new)
901
843
return old_disk_path, new_disk_path
903
845
def finish(self):
906
848
except OSError as e:
907
849
if e.errno != errno.ENOENT:
908
850
mutter("The temporary directory \"%s\" was not "
909
"cleanly removed: %s." % (self._root, e))
851
"cleanly removed: %s." % (self._root, e))
911
def diff(self, old_path, new_path, old_kind, new_kind):
853
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
912
854
if (old_kind, new_kind) != ('file', 'file'):
913
855
return DiffPath.CANNOT_DIFF
914
856
(old_disk_path, new_disk_path) = self._prepare_files(
857
old_path, new_path, file_id=file_id)
916
858
self._execute(old_disk_path, new_disk_path)
918
def edit_file(self, old_path, new_path):
860
def edit_file(self, old_path, new_path, file_id=None):
919
861
"""Use this tool to edit a file.
921
863
A temporary copy will be edited, and the new contents will be
866
:param file_id: The id of the file to edit.
924
867
:return: The new contents of the file.
926
869
old_abs_path, new_abs_path = self._prepare_files(
927
old_path, new_path, allow_write_new=True, force_temp=True)
870
old_path, new_path, allow_write_new=True, force_temp=True,
928
872
command = self._get_command(old_abs_path, new_abs_path)
929
873
subprocess.call(command, cwd=self._root)
930
with open(new_abs_path, 'rb') as new_file:
874
new_file = open(new_abs_path, 'rb')
931
876
return new_file.read()
934
881
class DiffTree(object):
1028
972
# TODO: Generation of pseudo-diffs for added/deleted files could
1029
973
# be usefully made into a much faster special case.
1030
974
iterator = self.new_tree.iter_changes(self.old_tree,
1031
specific_files=specific_files,
1032
extra_trees=extra_trees,
1033
require_versioned=True)
975
specific_files=specific_files,
976
extra_trees=extra_trees,
977
require_versioned=True)
1036
979
def changes_key(change):
1037
old_path, new_path = change.path
980
old_path, new_path = change[1]
1039
982
if path is None:
1043
985
def get_encoded_path(path):
1044
986
if path is not None:
1045
987
return path.encode(self.path_encoding, "replace")
1046
for change in sorted(iterator, key=changes_key):
988
for (file_id, paths, changed_content, versioned, parent, name, kind,
989
executable) in sorted(iterator, key=changes_key):
1047
990
# The root does not get diffed, and items with no known kind (that
1048
991
# is, missing) in both trees are skipped as well.
1049
if change.parent_id == (None, None) or change.kind == (None, None):
1051
if change.kind[0] == 'symlink' and not self.new_tree.supports_symlinks():
1053
'Ignoring "%s" as symlinks are not '
1054
'supported on this filesystem.' % (change.path[0],))
1056
oldpath, newpath = change.path
1057
oldpath_encoded = get_encoded_path(oldpath)
1058
newpath_encoded = get_encoded_path(newpath)
1059
old_present = (change.kind[0] is not None and change.versioned[0])
1060
new_present = (change.kind[1] is not None and change.versioned[1])
1061
executable = change.executable
1063
renamed = (change.parent_id[0], change.name[0]) != (change.parent_id[1], change.name[1])
992
if parent == (None, None) or kind == (None, None):
994
oldpath, newpath = paths
995
oldpath_encoded = get_encoded_path(paths[0])
996
newpath_encoded = get_encoded_path(paths[1])
997
old_present = (kind[0] is not None and versioned[0])
998
new_present = (kind[1] is not None and versioned[1])
999
renamed = (parent[0], name[0]) != (parent[1], name[1])
1065
1001
properties_changed = []
1066
properties_changed.extend(
1067
get_executable_change(executable[0], executable[1]))
1002
properties_changed.extend(get_executable_change(executable[0], executable[1]))
1069
1004
if properties_changed:
1070
prop_str = b" (properties changed: %s)" % (
1071
b", ".join(properties_changed),)
1005
prop_str = " (properties changed: %s)" % (", ".join(properties_changed),)
1075
1009
if (old_present, new_present) == (True, False):
1076
self.to_file.write(b"=== removed %s '%s'\n" %
1077
(kind[0].encode('ascii'), oldpath_encoded))
1010
self.to_file.write("=== removed %s '%s'\n" %
1011
(kind[0], oldpath_encoded))
1078
1013
elif (old_present, new_present) == (False, True):
1079
self.to_file.write(b"=== added %s '%s'\n" %
1080
(kind[1].encode('ascii'), newpath_encoded))
1014
self.to_file.write("=== added %s '%s'\n" %
1015
(kind[1], newpath_encoded))
1082
self.to_file.write(b"=== renamed %s '%s' => '%s'%s\n" %
1083
(kind[0].encode('ascii'), oldpath_encoded, newpath_encoded, prop_str))
1018
self.to_file.write("=== renamed %s '%s' => '%s'%s\n" %
1019
(kind[0], oldpath_encoded, newpath_encoded, prop_str))
1085
1021
# if it was produced by iter_changes, it must be
1086
1022
# modified *somehow*, either content or execute bit.
1087
self.to_file.write(b"=== modified %s '%s'%s\n" % (kind[0].encode('ascii'),
1088
newpath_encoded, prop_str))
1089
if change.changed_content:
1090
self._diff(oldpath, newpath, kind[0], kind[1])
1023
self.to_file.write("=== modified %s '%s'%s\n" % (kind[0],
1024
newpath_encoded, prop_str))
1026
self._diff(oldpath, newpath, kind[0], kind[1], file_id=file_id)
1091
1027
has_changes = 1
1093
1029
has_changes = 1
1094
1030
return has_changes
1096
def diff(self, old_path, new_path):
1032
def diff(self, file_id, old_path, new_path):
1097
1033
"""Perform a diff of a single file
1035
:param file_id: file-id of the file
1099
1036
:param old_path: The path of the file in the old tree
1100
1037
:param new_path: The path of the file in the new tree
1102
1039
if old_path is None:
1103
1040
old_kind = None
1105
old_kind = self.old_tree.kind(old_path)
1042
old_kind = self.old_tree.kind(old_path, file_id)
1106
1043
if new_path is None:
1107
1044
new_kind = None
1109
new_kind = self.new_tree.kind(new_path)
1110
self._diff(old_path, new_path, old_kind, new_kind)
1046
new_kind = self.new_tree.kind(new_path, file_id)
1047
self._diff(old_path, new_path, old_kind, new_kind, file_id=file_id)
1112
def _diff(self, old_path, new_path, old_kind, new_kind):
1113
result = DiffPath._diff_many(
1114
self.differs, old_path, new_path, old_kind, new_kind)
1049
def _diff(self, old_path, new_path, old_kind, new_kind, file_id):
1050
result = DiffPath._diff_many(self.differs, file_id, old_path,
1051
new_path, old_kind, new_kind)
1115
1052
if result is DiffPath.CANNOT_DIFF:
1116
1053
error_path = new_path
1117
1054
if error_path is None: