87
85
# In the meantime we at least make sure the patch isn't
89
# Special workaround for Python2.3, where difflib fails if
90
# both sequences are empty.
91
if not oldlines and not newlines:
90
94
if allow_binary is False:
91
95
textfile.check_text_lines(oldlines)
92
96
textfile.check_text_lines(newlines)
94
98
if sequence_matcher is None:
95
99
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)
100
ud = patiencediff.unified_diff(oldlines, newlines,
101
fromfile=old_filename.encode(path_encoding),
102
tofile=new_filename.encode(path_encoding),
103
sequencematcher=sequence_matcher)
104
if len(ud) == 0: # Identical contents, nothing to do
106
if len(ud) == 0: # Identical contents, nothing to do
106
108
# work-around for difflib being too smart for its own good
107
109
# if /dev/null is "1,0", patch won't recognize it as /dev/null
109
ud[2] = ud[2].replace(b'-1,0', b'-0,0')
111
ud[2] = ud[2].replace('-1,0', '-0,0')
110
112
elif not newlines:
111
ud[2] = ud[2].replace(b'+1,0', b'+0,0')
113
ud[2] = ud[2].replace('+1,0', '+0,0')
114
116
to_file.write(line)
115
if not line.endswith(b'\n'):
116
to_file.write(b"\n\\ No newline at end of file\n")
117
if not line.endswith('\n'):
118
to_file.write("\n\\ No newline at end of file\n")
120
122
def _spawn_external_diff(diffcmd, capture_errors=True):
121
"""Spawn the external diff process, and return the child handle.
123
"""Spawn the externall diff process, and return the child handle.
123
125
:param diffcmd: The command list to spawn
124
126
:param capture_errors: Capture stderr as well as setting LANG=C
157
# diff style options as of GNU diff v3.2
158
style_option_list = ['-c', '-C', '--context',
160
'-f', '--forward-ed',
164
'-u', '-U', '--unified',
165
'-y', '--side-by-side',
169
def default_style_unified(diff_opts):
170
"""Default to unified diff style if alternative not specified in diff_opts.
172
diff only allows one style to be specified; they don't override.
173
Note that some of these take optargs, and the optargs can be
174
directly appended to the options.
175
This is only an approximate parser; it doesn't properly understand
178
:param diff_opts: List of options for external (GNU) diff.
179
:return: List of options with default style=='unified'.
181
for s in style_option_list:
189
diff_opts.append('-u')
193
def external_diff(old_label, oldlines, new_label, newlines, to_file,
159
def external_diff(old_filename, oldlines, new_filename, newlines, to_file,
195
161
"""Display a diff by calling out to the external diff program."""
196
162
# make sure our own output is properly ordered before the diff
199
oldtmp_fd, old_abspath = tempfile.mkstemp(prefix='brz-diff-old-')
200
newtmp_fd, new_abspath = tempfile.mkstemp(prefix='brz-diff-new-')
165
oldtmp_fd, old_abspath = tempfile.mkstemp(prefix='bzr-diff-old-')
166
newtmp_fd, new_abspath = tempfile.mkstemp(prefix='bzr-diff-new-')
201
167
oldtmpf = os.fdopen(oldtmp_fd, 'wb')
202
168
newtmpf = os.fdopen(newtmp_fd, 'wb')
386
360
new_url = default_location
387
361
if new_url != old_url:
388
362
working_tree, branch, relpath = \
389
controldir.ControlDir.open_containing_tree_or_branch(new_url)
390
lock_tree_or_branch(working_tree, branch)
363
bzrdir.BzrDir.open_containing_tree_or_branch(new_url)
391
364
if consider_relpath and relpath != '':
392
365
if working_tree is not None and apply_view:
393
366
views.check_path_in_view(working_tree, relpath)
394
367
specific_files.append(relpath)
395
368
new_tree = _get_tree_to_diff(new_revision_spec, working_tree, branch,
396
basis_is_default=working_tree is None)
369
basis_is_default=working_tree is None)
397
370
new_branch = branch
399
372
# Get the specific files (all files is None, no files is [])
400
373
if make_paths_wt_relative and working_tree is not None:
401
other_paths = working_tree.safe_relpath_files(
375
from bzrlib.builtins import safe_relpath_files
376
other_paths = safe_relpath_files(working_tree, other_paths,
403
377
apply_view=apply_view)
378
except errors.FileInWrongBranch:
379
raise errors.BzrCommandError("Files are in different branches")
404
380
specific_files.extend(other_paths)
405
381
if len(specific_files) == 0:
406
382
specific_files = None
407
if (working_tree is not None and working_tree.supports_views() and
383
if (working_tree is not None and working_tree.supports_views()
409
385
view_files = working_tree.views.lookup_view()
411
387
specific_files = view_files
412
388
view_str = views.view_display_str(view_files)
413
note(gettext("*** Ignoring files outside view. View is %s") % view_str)
389
note("*** Ignoring files outside view. View is %s" % view_str)
415
391
# Get extra trees that ought to be searched for file-ids
416
392
extra_trees = None
417
393
if working_tree is not None and working_tree not in (old_tree, new_tree):
418
394
extra_trees = (working_tree,)
419
return (old_tree, new_tree, old_branch, new_branch,
420
specific_files, extra_trees)
395
return old_tree, new_tree, old_branch, new_branch, specific_files, extra_trees
423
398
def _get_tree_to_diff(spec, tree=None, branch=None, basis_is_default=True):
652
624
if 'file' not in (old_kind, new_kind):
653
625
return self.CANNOT_DIFF
626
from_file_id = to_file_id = file_id
654
627
if old_kind == 'file':
655
old_date = _patch_header_date(self.old_tree, old_path)
628
old_date = _patch_header_date(self.old_tree, file_id, old_path)
656
629
elif old_kind is None:
657
630
old_date = self.EPOCH_DATE
659
633
return self.CANNOT_DIFF
660
634
if new_kind == 'file':
661
new_date = _patch_header_date(self.new_tree, new_path)
635
new_date = _patch_header_date(self.new_tree, file_id, new_path)
662
636
elif new_kind is None:
663
637
new_date = self.EPOCH_DATE
665
640
return self.CANNOT_DIFF
666
from_label = '%s%s\t%s' % (self.old_label, old_path,
668
to_label = '%s%s\t%s' % (self.new_label, new_path,
670
return self.diff_text(old_path, new_path, from_label, to_label)
641
from_label = '%s%s\t%s' % (self.old_label, old_path, old_date)
642
to_label = '%s%s\t%s' % (self.new_label, new_path, new_date)
643
return self.diff_text(from_file_id, to_file_id, from_label, to_label,
672
def diff_text(self, from_path, to_path, from_label, to_label):
646
def diff_text(self, from_file_id, to_file_id, from_label, to_label,
647
from_path=None, to_path=None):
673
648
"""Diff the content of given files in two trees
675
:param from_path: The path in the from tree. If None,
650
:param from_file_id: The id of the file in the from tree. If None,
676
651
the file is not present in the from tree.
677
:param to_path: The path in the to tree. This may refer
678
to a different file from from_path. If None,
652
:param to_file_id: The id of the file in the to tree. This may refer
653
to a different file from from_file_id. If None,
679
654
the file is not present in the to tree.
655
:param from_path: The path in the from tree or None if unknown.
656
:param to_path: The path in the to tree or None if unknown.
681
def _get_text(tree, path):
685
return tree.get_file_lines(path)
686
except errors.NoSuchFile:
658
def _get_text(tree, file_id, path):
659
if file_id is not None:
660
return tree.get_file(file_id, path).readlines()
689
from_text = _get_text(self.old_tree, from_path)
690
to_text = _get_text(self.new_tree, to_path)
664
from_text = _get_text(self.old_tree, from_file_id, from_path)
665
to_text = _get_text(self.new_tree, to_file_id, to_path)
691
666
self.text_differ(from_label, from_text, to_label, to_text,
692
self.to_file, path_encoding=self.path_encoding,
693
context_lines=self.context_lines)
694
668
except errors.BinaryFile:
695
669
self.to_file.write(
696
("Binary files %s and %s differ\n" %
697
(from_label, to_label)).encode(self.path_encoding, 'replace'))
670
("Binary files %s and %s differ\n" %
671
(from_label, to_label)).encode(self.path_encoding))
698
672
return self.CHANGED
719
def make_from_diff_tree(klass, command_string, external_diff_options=None):
693
def make_from_diff_tree(klass, command_string):
720
694
def from_diff_tree(diff_tree):
721
full_command_string = [command_string]
722
if external_diff_options is not None:
723
full_command_string += ' ' + external_diff_options
724
return klass.from_string(full_command_string, diff_tree.old_tree,
695
return klass.from_string(command_string, diff_tree.old_tree,
725
696
diff_tree.new_tree, diff_tree.to_file)
726
697
return from_diff_tree
728
699
def _get_command(self, old_path, new_path):
729
700
my_map = {'old_path': old_path, 'new_path': new_path}
730
command = [AtTemplate(t).substitute(my_map) for t in
731
self.command_template]
732
if sys.platform == 'win32': # Popen doesn't accept unicode on win32
735
if isinstance(c, text_type):
736
command_encoded.append(c.encode('mbcs'))
738
command_encoded.append(c)
739
return command_encoded
701
return [AtTemplate(t).substitute(my_map) for t in
702
self.command_template]
743
704
def _execute(self, old_path, new_path):
744
705
command = self._get_command(old_path, new_path)
746
707
proc = subprocess.Popen(command, stdout=subprocess.PIPE,
749
710
if e.errno == errno.ENOENT:
750
711
raise errors.ExecutableMissing(command[0])
753
714
self.to_file.write(proc.stdout.read())
755
715
return proc.wait()
757
717
def _try_symlink_root(self, tree, prefix):
758
if (getattr(tree, 'abspath', None) is None or
759
not osutils.host_os_dereferences_symlinks()):
718
if (getattr(tree, 'abspath', None) is None
719
or not osutils.host_os_dereferences_symlinks()):
762
722
os.symlink(tree.abspath(''), osutils.pathjoin(self._root, prefix))
764
724
if e.errno != errno.EEXIST:
770
"""Returns safe encoding for passing file path to diff tool"""
771
if sys.platform == 'win32':
774
# Don't fallback to 'utf-8' because subprocess may not be able to
775
# handle utf-8 correctly when locale is not utf-8.
776
return sys.getfilesystemencoding() or 'ascii'
778
def _is_safepath(self, path):
779
"""Return true if `path` may be able to pass to subprocess."""
782
return path == path.encode(fenc).decode(fenc)
786
def _safe_filename(self, prefix, relpath):
787
"""Replace unsafe character in `relpath` then join `self._root`,
788
`prefix` and `relpath`."""
790
# encoded_str.replace('?', '_') may break multibyte char.
791
# So we should encode, decode, then replace(u'?', u'_')
792
relpath_tmp = relpath.encode(fenc, 'replace').decode(fenc, 'replace')
793
relpath_tmp = relpath_tmp.replace(u'?', u'_')
794
return osutils.pathjoin(self._root, prefix, relpath_tmp)
796
def _write_file(self, relpath, tree, prefix, force_temp=False,
728
def _write_file(self, file_id, tree, prefix, relpath, force_temp=False,
797
729
allow_write=False):
798
730
if not force_temp and isinstance(tree, WorkingTree):
799
full_path = tree.abspath(relpath)
800
if self._is_safepath(full_path):
803
full_path = self._safe_filename(prefix, relpath)
731
return tree.abspath(tree.id2path(file_id))
733
full_path = osutils.pathjoin(self._root, prefix, relpath)
804
734
if not force_temp and self._try_symlink_root(tree, prefix):
806
736
parent_dir = osutils.dirname(full_path)
808
738
os.makedirs(parent_dir)
810
740
if e.errno != errno.EEXIST:
812
source = tree.get_file(relpath)
742
source = tree.get_file(file_id, relpath)
814
with open(full_path, 'wb') as target:
744
target = open(full_path, 'wb')
815
746
osutils.pumpfile(source, target)
819
mtime = tree.get_file_mtime(relpath)
820
except FileTimestampUnavailable:
823
os.utime(full_path, (mtime, mtime))
752
mtime = tree.get_file_mtime(file_id)
753
except errors.FileTimestampUnavailable:
754
# GZ 2010-04-13: Zero is a bad 'unavailable' time as it predates
755
# the earliest allowable date on FAT filesystems
757
os.utime(full_path, (mtime, mtime))
824
758
if not allow_write:
825
759
osutils.make_readonly(full_path)
828
def _prepare_files(self, old_path, new_path, force_temp=False,
762
def _prepare_files(self, file_id, old_path, new_path, force_temp=False,
829
763
allow_write_new=False):
830
old_disk_path = self._write_file(
831
old_path, self.old_tree, 'old', force_temp)
832
new_disk_path = self._write_file(
833
new_path, self.new_tree, 'new', force_temp,
834
allow_write=allow_write_new)
764
old_disk_path = self._write_file(file_id, self.old_tree, 'old',
765
old_path, force_temp)
766
new_disk_path = self._write_file(file_id, self.new_tree, 'new',
767
new_path, force_temp,
768
allow_write=allow_write_new)
835
769
return old_disk_path, new_disk_path
837
771
def finish(self):
839
773
osutils.rmtree(self._root)
841
775
if e.errno != errno.ENOENT:
842
776
mutter("The temporary directory \"%s\" was not "
843
"cleanly removed: %s." % (self._root, e))
777
"cleanly removed: %s." % (self._root, e))
845
def diff(self, old_path, new_path, old_kind, new_kind):
779
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
846
780
if (old_kind, new_kind) != ('file', 'file'):
847
781
return DiffPath.CANNOT_DIFF
848
782
(old_disk_path, new_disk_path) = self._prepare_files(
783
file_id, old_path, new_path)
850
784
self._execute(old_disk_path, new_disk_path)
852
def edit_file(self, old_path, new_path):
786
def edit_file(self, file_id):
853
787
"""Use this tool to edit a file.
855
789
A temporary copy will be edited, and the new contents will be
792
:param file_id: The id of the file to edit.
858
793
:return: The new contents of the file.
860
old_abs_path, new_abs_path = self._prepare_files(
861
old_path, new_path, allow_write_new=True, force_temp=True)
862
command = self._get_command(old_abs_path, new_abs_path)
795
old_path = self.old_tree.id2path(file_id)
796
new_path = self.new_tree.id2path(file_id)
797
new_abs_path = self._prepare_files(file_id, old_path, new_path,
798
allow_write_new=True,
800
command = self._get_command(osutils.pathjoin('old', old_path),
801
osutils.pathjoin('new', new_path))
863
802
subprocess.call(command, cwd=self._root)
864
with open(new_abs_path, 'rb') as new_file:
803
new_file = open(new_abs_path, 'r')
865
805
return new_file.read()
868
810
class DiffTree(object):
990
924
renamed = (parent[0], name[0]) != (parent[1], name[1])
992
926
properties_changed = []
993
properties_changed.extend(
994
get_executable_change(executable[0], executable[1]))
927
properties_changed.extend(get_executable_change(executable[0], executable[1]))
996
929
if properties_changed:
997
prop_str = b" (properties changed: %s)" % (
998
b", ".join(properties_changed),)
930
prop_str = " (properties changed: %s)" % (", ".join(properties_changed),)
1002
934
if (old_present, new_present) == (True, False):
1003
self.to_file.write(b"=== removed %s '%s'\n" %
1004
(kind[0].encode('ascii'), oldpath_encoded))
935
self.to_file.write("=== removed %s '%s'\n" %
936
(kind[0], oldpath_encoded))
1005
937
newpath = oldpath
1006
938
elif (old_present, new_present) == (False, True):
1007
self.to_file.write(b"=== added %s '%s'\n" %
1008
(kind[1].encode('ascii'), newpath_encoded))
939
self.to_file.write("=== added %s '%s'\n" %
940
(kind[1], newpath_encoded))
1009
941
oldpath = newpath
1011
self.to_file.write(b"=== renamed %s '%s' => '%s'%s\n" %
1012
(kind[0].encode('ascii'), oldpath_encoded, newpath_encoded, prop_str))
943
self.to_file.write("=== renamed %s '%s' => '%s'%s\n" %
944
(kind[0], oldpath_encoded, newpath_encoded, prop_str))
1014
946
# if it was produced by iter_changes, it must be
1015
947
# modified *somehow*, either content or execute bit.
1016
self.to_file.write(b"=== modified %s '%s'%s\n" % (kind[0].encode('ascii'),
1017
newpath_encoded, prop_str))
948
self.to_file.write("=== modified %s '%s'%s\n" % (kind[0],
949
newpath_encoded, prop_str))
1018
950
if changed_content:
1019
self._diff(oldpath, newpath, kind[0], kind[1])
951
self._diff(file_id, oldpath, newpath, kind[0], kind[1])
1023
955
return has_changes
1025
def diff(self, old_path, new_path):
957
def diff(self, file_id, old_path, new_path):
1026
958
"""Perform a diff of a single file
960
:param file_id: file-id of the file
1028
961
:param old_path: The path of the file in the old tree
1029
962
:param new_path: The path of the file in the new tree
1031
if old_path is None:
965
old_kind = self.old_tree.kind(file_id)
966
except (errors.NoSuchId, errors.NoSuchFile):
1034
old_kind = self.old_tree.kind(old_path)
1035
if new_path is None:
969
new_kind = self.new_tree.kind(file_id)
970
except (errors.NoSuchId, errors.NoSuchFile):
1038
new_kind = self.new_tree.kind(new_path)
1039
self._diff(old_path, new_path, old_kind, new_kind)
1041
def _diff(self, old_path, new_path, old_kind, new_kind):
1042
result = DiffPath._diff_many(
1043
self.differs, old_path, new_path, old_kind, new_kind)
972
self._diff(file_id, old_path, new_path, old_kind, new_kind)
975
def _diff(self, file_id, old_path, new_path, old_kind, new_kind):
976
result = DiffPath._diff_many(self.differs, file_id, old_path,
977
new_path, old_kind, new_kind)
1044
978
if result is DiffPath.CANNOT_DIFF:
1045
979
error_path = new_path
1046
980
if error_path is None: