86
79
# 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:
95
82
if allow_binary is False:
96
83
textfile.check_text_lines(oldlines)
97
84
textfile.check_text_lines(newlines)
99
86
if sequence_matcher is None:
100
87
sequence_matcher = patiencediff.PatienceSequenceMatcher
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)
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)
107
if len(ud) == 0: # Identical contents, nothing to do
95
if len(ud) == 0: # Identical contents, nothing to do
109
97
# work-around for difflib being too smart for its own good
110
98
# if /dev/null is "1,0", patch won't recognize it as /dev/null
112
ud[2] = ud[2].replace('-1,0', '-0,0')
100
ud[2] = ud[2].replace(b'-1,0', b'-0,0')
113
101
elif not newlines:
114
ud[2] = ud[2].replace('+1,0', '+0,0')
102
ud[2] = ud[2].replace(b'+1,0', b'+0,0')
117
105
to_file.write(line)
118
if not line.endswith('\n'):
119
to_file.write("\n\\ No newline at end of file\n")
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]:
123
180
def _spawn_external_diff(diffcmd, capture_errors=True):
254
313
out, err = pipe.communicate()
256
315
# Write out the new i18n diff response
257
to_file.write(out+'\n')
316
to_file.write(out + b'\n')
258
317
if pipe.returncode != 2:
259
318
raise errors.BzrError(
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,))
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,))
264
first_line = lang_c_out.split('\n', 1)[0]
323
first_line = lang_c_out.split(b'\n', 1)[0]
265
324
# Starting with diffutils 2.8.4 the word "binary" was dropped.
266
m = re.match('^(binary )?files.*differ$', first_line, re.I)
325
m = re.match(b'^(binary )?files.*differ$', first_line, re.I)
268
327
raise errors.BzrError('external diff failed with exit code 2;'
269
328
' command: %r' % (diffcmd,))
460
516
context = DEFAULT_CONTEXT_AMOUNT
461
517
if format_cls is None:
462
518
format_cls = DiffTree
463
with old_tree.lock_read():
519
with contextlib.ExitStack() as exit_stack:
520
exit_stack.enter_context(old_tree.lock_read())
464
521
if extra_trees is not None:
465
522
for tree in extra_trees:
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):
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):
483
534
"""Returns a timestamp suitable for use in a patch header."""
485
mtime = tree.get_file_mtime(path, file_id)
536
mtime = tree.get_file_mtime(path)
486
537
except FileTimestampUnavailable:
488
539
return timestamp.format_patch_date(mtime)
491
542
def get_executable_change(old_is_x, new_is_x):
492
descr = { True:"+x", False:"-x", None:"??" }
543
descr = {True: b"+x", False: b"-x", None: b"??"}
493
544
if old_is_x != new_is_x:
494
return ["%s to %s" % (descr[old_is_x], descr[new_is_x],)]
545
return [b"%s to %s" % (descr[old_is_x], descr[new_is_x],)]
566
616
if None in (old_kind, new_kind):
567
617
return DiffPath.CANNOT_DIFF
568
result = DiffPath._diff_many(self.differs, file_id, old_path,
569
new_path, old_kind, None)
618
result = DiffPath._diff_many(
619
self.differs, old_path, new_path, old_kind, None)
570
620
if result is DiffPath.CANNOT_DIFF:
572
return DiffPath._diff_many(self.differs, file_id, old_path, new_path,
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
576
641
class DiffDirectory(DiffPath):
578
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
643
def diff(self, old_path, new_path, old_kind, new_kind):
579
644
"""Perform comparison between two directories. (dummy)
654
720
if 'file' not in (old_kind, new_kind):
655
721
return self.CANNOT_DIFF
656
from_file_id = to_file_id = file_id
657
722
if old_kind == 'file':
658
old_date = _patch_header_date(self.old_tree, file_id, old_path)
723
old_date = _patch_header_date(self.old_tree, old_path)
659
724
elif old_kind is None:
660
725
old_date = self.EPOCH_DATE
663
727
return self.CANNOT_DIFF
664
728
if new_kind == 'file':
665
new_date = _patch_header_date(self.new_tree, file_id, new_path)
729
new_date = _patch_header_date(self.new_tree, new_path)
666
730
elif new_kind is None:
667
731
new_date = self.EPOCH_DATE
670
733
return self.CANNOT_DIFF
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)
734
from_label = '%s%s\t%s' % (self.old_label, old_path,
736
to_label = '%s%s\t%s' % (self.new_label, new_path,
738
return self.diff_text(old_path, new_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):
740
def diff_text(self, from_path, to_path, from_label, to_label):
678
741
"""Diff the content of given files in two trees
680
743
:param from_path: The path in the from tree. If None,
682
745
:param to_path: The path in the to tree. This may refer
683
746
to a different file from from_path. If None,
684
747
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
690
def _get_text(tree, file_id, path):
693
return tree.get_file_lines(path, file_id)
749
def _get_text(tree, path):
753
return tree.get_file_lines(path)
754
except errors.NoSuchFile:
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)
757
from_text = _get_text(self.old_tree, from_path)
758
to_text = _get_text(self.new_tree, to_path)
697
759
self.text_differ(from_label, from_text, to_label, to_text,
698
760
self.to_file, path_encoding=self.path_encoding,
699
761
context_lines=self.context_lines)
700
762
except errors.BinaryFile:
701
763
self.to_file.write(
702
("Binary files %s and %s differ\n" %
703
(from_label, to_label)).encode(self.path_encoding, 'replace'))
764
("Binary files %s%s and %s%s differ\n" %
765
(self.old_label, from_path, self.new_label, to_path)).encode(self.path_encoding, 'replace'))
704
766
return self.CHANGED
836
892
def _prepare_files(self, old_path, new_path, force_temp=False,
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)
893
allow_write_new=False):
894
old_disk_path = self._write_file(
895
old_path, self.old_tree, 'old', force_temp)
896
new_disk_path = self._write_file(
897
new_path, self.new_tree, 'new', force_temp,
898
allow_write=allow_write_new)
843
899
return old_disk_path, new_disk_path
845
901
def finish(self):
848
904
except OSError as e:
849
905
if e.errno != errno.ENOENT:
850
906
mutter("The temporary directory \"%s\" was not "
851
"cleanly removed: %s." % (self._root, e))
907
"cleanly removed: %s." % (self._root, e))
853
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
909
def diff(self, old_path, new_path, old_kind, new_kind):
854
910
if (old_kind, new_kind) != ('file', 'file'):
855
911
return DiffPath.CANNOT_DIFF
856
912
(old_disk_path, new_disk_path) = self._prepare_files(
857
old_path, new_path, file_id=file_id)
858
914
self._execute(old_disk_path, new_disk_path)
860
def edit_file(self, old_path, new_path, file_id=None):
916
def edit_file(self, old_path, new_path):
861
917
"""Use this tool to edit a file.
863
919
A temporary copy will be edited, and the new contents will be
866
:param file_id: The id of the file to edit.
867
922
:return: The new contents of the file.
869
924
old_abs_path, new_abs_path = self._prepare_files(
870
old_path, new_path, allow_write_new=True, force_temp=True,
925
old_path, new_path, allow_write_new=True, force_temp=True)
872
926
command = self._get_command(old_abs_path, new_abs_path)
873
927
subprocess.call(command, cwd=self._root)
874
new_file = open(new_abs_path, 'rb')
928
with open(new_abs_path, 'rb') as new_file:
876
929
return new_file.read()
881
932
class DiffTree(object):
972
1026
# TODO: Generation of pseudo-diffs for added/deleted files could
973
1027
# be usefully made into a much faster special case.
974
1028
iterator = self.new_tree.iter_changes(self.old_tree,
975
specific_files=specific_files,
976
extra_trees=extra_trees,
977
require_versioned=True)
1029
specific_files=specific_files,
1030
extra_trees=extra_trees,
1031
require_versioned=True)
979
1034
def changes_key(change):
980
old_path, new_path = change[1]
1035
old_path, new_path = change.path
982
1037
if path is None:
985
1041
def get_encoded_path(path):
986
1042
if path is not None:
987
1043
return path.encode(self.path_encoding, "replace")
988
for (file_id, paths, changed_content, versioned, parent, name, kind,
989
executable) in sorted(iterator, key=changes_key):
1044
for change in sorted(iterator, key=changes_key):
990
1045
# The root does not get diffed, and items with no known kind (that
991
1046
# is, missing) in both trees are skipped as well.
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])
1047
if change.parent_id == (None, None) or change.kind == (None, None):
1049
if change.kind[0] == 'symlink' and not self.new_tree.supports_symlinks():
1051
'Ignoring "%s" as symlinks are not '
1052
'supported on this filesystem.' % (change.path[0],))
1054
oldpath, newpath = change.path
1055
oldpath_encoded = get_encoded_path(change.path[0])
1056
newpath_encoded = get_encoded_path(change.path[1])
1057
old_present = (change.kind[0] is not None and change.versioned[0])
1058
new_present = (change.kind[1] is not None and change.versioned[1])
1059
executable = change.executable
1061
renamed = (change.parent_id[0], change.name[0]) != (change.parent_id[1], change.name[1])
1001
1063
properties_changed = []
1002
properties_changed.extend(get_executable_change(executable[0], executable[1]))
1064
properties_changed.extend(
1065
get_executable_change(executable[0], executable[1]))
1004
1067
if properties_changed:
1005
prop_str = " (properties changed: %s)" % (", ".join(properties_changed),)
1068
prop_str = b" (properties changed: %s)" % (
1069
b", ".join(properties_changed),)
1009
1073
if (old_present, new_present) == (True, False):
1010
self.to_file.write("=== removed %s '%s'\n" %
1011
(kind[0], oldpath_encoded))
1074
self.to_file.write(b"=== removed %s '%s'\n" %
1075
(kind[0].encode('ascii'), oldpath_encoded))
1012
1076
newpath = oldpath
1013
1077
elif (old_present, new_present) == (False, True):
1014
self.to_file.write("=== added %s '%s'\n" %
1015
(kind[1], newpath_encoded))
1078
self.to_file.write(b"=== added %s '%s'\n" %
1079
(kind[1].encode('ascii'), newpath_encoded))
1016
1080
oldpath = newpath
1018
self.to_file.write("=== renamed %s '%s' => '%s'%s\n" %
1019
(kind[0], oldpath_encoded, newpath_encoded, prop_str))
1082
self.to_file.write(b"=== renamed %s '%s' => '%s'%s\n" %
1083
(kind[0].encode('ascii'), oldpath_encoded, newpath_encoded, prop_str))
1021
1085
# if it was produced by iter_changes, it must be
1022
1086
# modified *somehow*, either content or execute bit.
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)
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])
1027
1091
has_changes = 1
1029
1093
has_changes = 1
1030
1094
return has_changes
1032
def diff(self, file_id, old_path, new_path):
1096
def diff(self, old_path, new_path):
1033
1097
"""Perform a diff of a single file
1035
:param file_id: file-id of the file
1036
1099
:param old_path: The path of the file in the old tree
1037
1100
:param new_path: The path of the file in the new tree
1039
1102
if old_path is None:
1040
1103
old_kind = None
1042
old_kind = self.old_tree.kind(old_path, file_id)
1105
old_kind = self.old_tree.kind(old_path)
1043
1106
if new_path is None:
1044
1107
new_kind = None
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)
1109
new_kind = self.new_tree.kind(new_path)
1110
self._diff(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)
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)
1052
1115
if result is DiffPath.CANNOT_DIFF:
1053
1116
error_path = new_path
1054
1117
if error_path is None: