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
833
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)
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)
901
840
return old_disk_path, new_disk_path
903
842
def finish(self):
906
845
except OSError as e:
907
846
if e.errno != errno.ENOENT:
908
847
mutter("The temporary directory \"%s\" was not "
909
"cleanly removed: %s." % (self._root, e))
848
"cleanly removed: %s." % (self._root, e))
911
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):
912
851
if (old_kind, new_kind) != ('file', 'file'):
913
852
return DiffPath.CANNOT_DIFF
914
853
(old_disk_path, new_disk_path) = self._prepare_files(
854
old_path, new_path, file_id=file_id)
916
855
self._execute(old_disk_path, new_disk_path)
918
def edit_file(self, old_path, new_path):
857
def edit_file(self, old_path, new_path, file_id=None):
919
858
"""Use this tool to edit a file.
921
860
A temporary copy will be edited, and the new contents will be
863
:param file_id: The id of the file to edit.
924
864
:return: The new contents of the file.
926
866
old_abs_path, new_abs_path = self._prepare_files(
927
old_path, new_path, allow_write_new=True, force_temp=True)
867
old_path, new_path, allow_write_new=True, force_temp=True,
928
869
command = self._get_command(old_abs_path, new_abs_path)
929
870
subprocess.call(command, cwd=self._root)
930
871
with open(new_abs_path, 'rb') as new_file:
1028
966
# TODO: Generation of pseudo-diffs for added/deleted files could
1029
967
# be usefully made into a much faster special case.
1030
968
iterator = self.new_tree.iter_changes(self.old_tree,
1031
specific_files=specific_files,
1032
extra_trees=extra_trees,
1033
require_versioned=True)
969
specific_files=specific_files,
970
extra_trees=extra_trees,
971
require_versioned=True)
1036
973
def changes_key(change):
1037
old_path, new_path = change.path
974
old_path, new_path = change[1]
1039
976
if path is None:
1043
979
def get_encoded_path(path):
1044
980
if path is not None:
1045
981
return path.encode(self.path_encoding, "replace")
1046
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):
1047
984
# The root does not get diffed, and items with no known kind (that
1048
985
# 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])
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])
1065
995
properties_changed = []
1066
properties_changed.extend(
1067
get_executable_change(executable[0], executable[1]))
996
properties_changed.extend(get_executable_change(executable[0], executable[1]))
1069
998
if properties_changed:
1070
prop_str = b" (properties changed: %s)" % (
1071
b", ".join(properties_changed),)
999
prop_str = " (properties changed: %s)" % (", ".join(properties_changed),)
1075
1003
if (old_present, new_present) == (True, False):
1076
self.to_file.write(b"=== removed %s '%s'\n" %
1077
(kind[0].encode('ascii'), oldpath_encoded))
1004
self.to_file.write("=== removed %s '%s'\n" %
1005
(kind[0], oldpath_encoded))
1078
1007
elif (old_present, new_present) == (False, True):
1079
self.to_file.write(b"=== added %s '%s'\n" %
1080
(kind[1].encode('ascii'), newpath_encoded))
1008
self.to_file.write("=== added %s '%s'\n" %
1009
(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))
1012
self.to_file.write("=== renamed %s '%s' => '%s'%s\n" %
1013
(kind[0], oldpath_encoded, newpath_encoded, prop_str))
1085
1015
# if it was produced by iter_changes, it must be
1086
1016
# 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])
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)
1091
1021
has_changes = 1
1093
1023
has_changes = 1
1094
1024
return has_changes
1096
def diff(self, old_path, new_path):
1026
def diff(self, file_id, old_path, new_path):
1097
1027
"""Perform a diff of a single file
1029
:param file_id: file-id of the file
1099
1030
:param old_path: The path of the file in the old tree
1100
1031
:param new_path: The path of the file in the new tree
1102
1033
if old_path is None:
1103
1034
old_kind = None
1105
old_kind = self.old_tree.kind(old_path)
1036
old_kind = self.old_tree.kind(old_path, file_id)
1106
1037
if new_path is None:
1107
1038
new_kind = None
1109
new_kind = self.new_tree.kind(new_path)
1110
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)
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)
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)
1115
1046
if result is DiffPath.CANNOT_DIFF:
1116
1047
error_path = new_path
1117
1048
if error_path is None: