87
82
# In the meantime we at least make sure the patch isn't
86
# Special workaround for Python2.3, where difflib fails if
87
# both sequences are empty.
88
if not oldlines and not newlines:
90
91
if allow_binary is False:
91
92
textfile.check_text_lines(oldlines)
92
93
textfile.check_text_lines(newlines)
94
95
if sequence_matcher is None:
95
96
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)
97
ud = patiencediff.unified_diff(oldlines, newlines,
98
fromfile=old_filename.encode(path_encoding),
99
tofile=new_filename.encode(path_encoding),
100
sequencematcher=sequence_matcher)
104
if len(ud) == 0: # Identical contents, nothing to do
103
if len(ud) == 0: # Identical contents, nothing to do
106
105
# work-around for difflib being too smart for its own good
107
106
# 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')
108
ud[2] = ud[2].replace('-1,0', '-0,0')
110
109
elif not newlines:
111
ud[2] = ud[2].replace(b'+1,0', b'+0,0')
110
ud[2] = ud[2].replace('+1,0', '+0,0')
114
113
to_file.write(line)
115
if not line.endswith(b'\n'):
116
to_file.write(b"\n\\ No newline at end of file\n")
114
if not line.endswith('\n'):
115
to_file.write("\n\\ No newline at end of file\n")
120
119
def _spawn_external_diff(diffcmd, capture_errors=True):
121
"""Spawn the external diff process, and return the child handle.
120
"""Spawn the externall diff process, and return the child handle.
123
122
:param diffcmd: The command list to spawn
124
123
: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,
156
def external_diff(old_filename, oldlines, new_filename, newlines, to_file,
195
158
"""Display a diff by calling out to the external diff program."""
196
159
# 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-')
162
oldtmp_fd, old_abspath = tempfile.mkstemp(prefix='bzr-diff-old-')
163
newtmp_fd, new_abspath = tempfile.mkstemp(prefix='bzr-diff-new-')
201
164
oldtmpf = os.fdopen(oldtmp_fd, 'wb')
202
165
newtmpf = os.fdopen(newtmp_fd, 'wb')
386
357
new_url = default_location
387
358
if new_url != old_url:
388
359
working_tree, branch, relpath = \
389
controldir.ControlDir.open_containing_tree_or_branch(new_url)
390
lock_tree_or_branch(working_tree, branch)
360
bzrdir.BzrDir.open_containing_tree_or_branch(new_url)
391
361
if consider_relpath and relpath != '':
392
362
if working_tree is not None and apply_view:
393
363
views.check_path_in_view(working_tree, relpath)
394
364
specific_files.append(relpath)
395
365
new_tree = _get_tree_to_diff(new_revision_spec, working_tree, branch,
396
basis_is_default=working_tree is None)
366
basis_is_default=working_tree is None)
397
367
new_branch = branch
399
369
# Get the specific files (all files is None, no files is [])
400
370
if make_paths_wt_relative and working_tree is not None:
401
other_paths = working_tree.safe_relpath_files(
372
from bzrlib.builtins import safe_relpath_files
373
other_paths = safe_relpath_files(working_tree, other_paths,
403
374
apply_view=apply_view)
375
except errors.FileInWrongBranch:
376
raise errors.BzrCommandError("Files are in different branches")
404
377
specific_files.extend(other_paths)
405
378
if len(specific_files) == 0:
406
379
specific_files = None
407
if (working_tree is not None and working_tree.supports_views() and
380
if (working_tree is not None and working_tree.supports_views()
409
382
view_files = working_tree.views.lookup_view()
411
384
specific_files = view_files
412
385
view_str = views.view_display_str(view_files)
413
note(gettext("*** Ignoring files outside view. View is %s") % view_str)
386
note("*** Ignoring files outside view. View is %s" % view_str)
415
388
# Get extra trees that ought to be searched for file-ids
416
389
extra_trees = None
417
390
if working_tree is not None and working_tree not in (old_tree, new_tree):
418
391
extra_trees = (working_tree,)
419
return (old_tree, new_tree, old_branch, new_branch,
420
specific_files, extra_trees)
392
return old_tree, new_tree, old_branch, new_branch, specific_files, extra_trees
423
395
def _get_tree_to_diff(spec, tree=None, branch=None, basis_is_default=True):
439
411
old_label='a/', new_label='b/',
440
412
extra_trees=None,
441
413
path_encoding='utf8',
444
context=DEFAULT_CONTEXT_AMOUNT):
445
415
"""Show in text form the changes from one tree to another.
447
:param to_file: The output stream.
448
:param specific_files: Include only changes to these files - None for all
450
:param external_diff_options: If set, use an external GNU diff and pass
452
:param extra_trees: If set, more Trees to use for looking up file ids
453
:param path_encoding: If set, the path will be encoded as specified,
454
otherwise is supposed to be utf8
455
:param format_cls: Formatter class (DiffTree subclass)
421
Include only changes to these files - None for all changes.
423
external_diff_options
424
If set, use an external GNU diff and pass these options.
427
If set, more Trees to use for looking up file ids
430
If set, the path will be encoded as specified, otherwise is supposed
458
context = DEFAULT_CONTEXT_AMOUNT
459
if format_cls is None:
460
format_cls = DiffTree
461
with old_tree.lock_read():
462
435
if extra_trees is not None:
463
436
for tree in extra_trees:
465
438
new_tree.lock_read()
467
differ = format_cls.from_trees_options(old_tree, new_tree, to_file,
469
external_diff_options,
470
old_label, new_label, using,
471
context_lines=context)
440
differ = DiffTree.from_trees_options(old_tree, new_tree, to_file,
442
external_diff_options,
443
old_label, new_label, using)
472
444
return differ.show_diff(specific_files, extra_trees)
474
446
new_tree.unlock()
475
447
if extra_trees is not None:
476
448
for tree in extra_trees:
480
454
def _patch_header_date(tree, file_id, path):
481
455
"""Returns a timestamp suitable for use in a patch header."""
483
mtime = tree.get_file_mtime(path)
484
except FileTimestampUnavailable:
456
mtime = tree.get_file_mtime(file_id, path)
486
457
return timestamp.format_patch_date(mtime)
489
460
def get_executable_change(old_is_x, new_is_x):
490
descr = {True: b"+x", False: b"-x", None: b"??"}
461
descr = { True:"+x", False:"-x", None:"??" }
491
462
if old_is_x != new_is_x:
492
return [b"%s to %s" % (descr[old_is_x], descr[new_is_x],)]
463
return ["%s to %s" % (descr[old_is_x], descr[new_is_x],)]
670
635
to_file_id = None
672
637
return self.CANNOT_DIFF
673
from_label = '%s%s\t%s' % (self.old_label, old_path,
675
to_label = '%s%s\t%s' % (self.new_label, new_path,
677
return self.diff_text(old_path, new_path, from_label, to_label,
678
from_file_id, to_file_id)
638
from_label = '%s%s\t%s' % (self.old_label, old_path, old_date)
639
to_label = '%s%s\t%s' % (self.new_label, new_path, new_date)
640
return self.diff_text(from_file_id, to_file_id, from_label, to_label,
680
def diff_text(self, from_path, to_path, from_label, to_label,
681
from_file_id=None, to_file_id=None):
643
def diff_text(self, from_file_id, to_file_id, from_label, to_label,
644
from_path=None, to_path=None):
682
645
"""Diff the content of given files in two trees
684
:param from_path: The path in the from tree. If None,
647
:param from_file_id: The id of the file in the from tree. If None,
685
648
the file is not present in the from tree.
686
:param to_path: The path in the to tree. This may refer
687
to a different file from from_path. If None,
649
:param to_file_id: The id of the file in the to tree. This may refer
650
to a different file from from_file_id. If None,
688
651
the file is not present in the to tree.
689
:param from_file_id: The id of the file in the from tree or None if
691
:param to_file_id: The id of the file in the to tree or None if
652
:param from_path: The path in the from tree or None if unknown.
653
:param to_path: The path in the to tree or None if unknown.
694
655
def _get_text(tree, file_id, path):
656
if file_id is not None:
657
return tree.get_file(file_id, path).readlines()
697
return tree.get_file_lines(path)
699
661
from_text = _get_text(self.old_tree, from_file_id, from_path)
700
662
to_text = _get_text(self.new_tree, to_file_id, to_path)
701
663
self.text_differ(from_label, from_text, to_label, to_text,
702
self.to_file, path_encoding=self.path_encoding,
703
context_lines=self.context_lines)
704
665
except errors.BinaryFile:
705
666
self.to_file.write(
706
("Binary files %s and %s differ\n" %
707
(from_label, to_label)).encode(self.path_encoding, 'replace'))
667
("Binary files %s and %s differ\n" %
668
(from_label, to_label)).encode(self.path_encoding))
708
669
return self.CHANGED
714
675
path_encoding='utf-8'):
715
676
DiffPath.__init__(self, old_tree, new_tree, to_file, path_encoding)
716
677
self.command_template = command_template
717
self._root = osutils.mkdtemp(prefix='brz-diff-')
678
self._root = osutils.mkdtemp(prefix='bzr-diff-')
720
681
def from_string(klass, command_string, old_tree, new_tree, to_file,
721
682
path_encoding='utf-8'):
722
command_template = cmdline.split(command_string)
683
command_template = commands.shlex_split_unicode(command_string)
723
684
if '@' not in command_string:
724
685
command_template.extend(['@old_path', '@new_path'])
725
686
return klass(command_template, old_tree, new_tree, to_file,
729
def make_from_diff_tree(klass, command_string, external_diff_options=None):
690
def make_from_diff_tree(klass, command_string):
730
691
def from_diff_tree(diff_tree):
731
full_command_string = [command_string]
732
if external_diff_options is not None:
733
full_command_string += ' ' + external_diff_options
734
return klass.from_string(full_command_string, diff_tree.old_tree,
692
return klass.from_string(command_string, diff_tree.old_tree,
735
693
diff_tree.new_tree, diff_tree.to_file)
736
694
return from_diff_tree
738
696
def _get_command(self, old_path, new_path):
739
697
my_map = {'old_path': old_path, 'new_path': new_path}
740
command = [AtTemplate(t).substitute(my_map) for t in
741
self.command_template]
742
if sys.platform == 'win32': # Popen doesn't accept unicode on win32
745
if isinstance(c, text_type):
746
command_encoded.append(c.encode('mbcs'))
748
command_encoded.append(c)
749
return command_encoded
698
return [AtTemplate(t).substitute(my_map) for t in
699
self.command_template]
753
701
def _execute(self, old_path, new_path):
754
702
command = self._get_command(old_path, new_path)
756
704
proc = subprocess.Popen(command, stdout=subprocess.PIPE,
759
707
if e.errno == errno.ENOENT:
760
708
raise errors.ExecutableMissing(command[0])
763
711
self.to_file.write(proc.stdout.read())
765
712
return proc.wait()
767
714
def _try_symlink_root(self, tree, prefix):
768
if (getattr(tree, 'abspath', None) is None or
769
not osutils.host_os_dereferences_symlinks()):
715
if (getattr(tree, 'abspath', None) is None
716
or not osutils.host_os_dereferences_symlinks()):
772
719
os.symlink(tree.abspath(''), osutils.pathjoin(self._root, prefix))
774
721
if e.errno != errno.EEXIST:
780
"""Returns safe encoding for passing file path to diff tool"""
781
if sys.platform == 'win32':
784
# Don't fallback to 'utf-8' because subprocess may not be able to
785
# handle utf-8 correctly when locale is not utf-8.
786
return sys.getfilesystemencoding() or 'ascii'
788
def _is_safepath(self, path):
789
"""Return true if `path` may be able to pass to subprocess."""
792
return path == path.encode(fenc).decode(fenc)
796
def _safe_filename(self, prefix, relpath):
797
"""Replace unsafe character in `relpath` then join `self._root`,
798
`prefix` and `relpath`."""
800
# encoded_str.replace('?', '_') may break multibyte char.
801
# So we should encode, decode, then replace(u'?', u'_')
802
relpath_tmp = relpath.encode(fenc, 'replace').decode(fenc, 'replace')
803
relpath_tmp = relpath_tmp.replace(u'?', u'_')
804
return osutils.pathjoin(self._root, prefix, relpath_tmp)
806
def _write_file(self, relpath, tree, prefix, force_temp=False,
807
allow_write=False, file_id=None):
725
def _write_file(self, file_id, tree, prefix, relpath, force_temp=False,
808
727
if not force_temp and isinstance(tree, WorkingTree):
809
full_path = tree.abspath(relpath)
810
if self._is_safepath(full_path):
813
full_path = self._safe_filename(prefix, relpath)
728
return tree.abspath(tree.id2path(file_id))
730
full_path = osutils.pathjoin(self._root, prefix, relpath)
814
731
if not force_temp and self._try_symlink_root(tree, prefix):
816
733
parent_dir = osutils.dirname(full_path)
818
735
os.makedirs(parent_dir)
820
737
if e.errno != errno.EEXIST:
822
source = tree.get_file(relpath)
739
source = tree.get_file(file_id, relpath)
824
with open(full_path, 'wb') as target:
741
target = open(full_path, 'wb')
825
743
osutils.pumpfile(source, target)
829
mtime = tree.get_file_mtime(relpath)
830
except FileTimestampUnavailable:
833
os.utime(full_path, (mtime, mtime))
834
748
if not allow_write:
835
749
osutils.make_readonly(full_path)
750
mtime = tree.get_file_mtime(file_id)
751
os.utime(full_path, (mtime, mtime))
838
def _prepare_files(self, old_path, new_path, force_temp=False,
839
allow_write_new=False, file_id=None):
840
old_disk_path = self._write_file(old_path, self.old_tree, 'old',
841
force_temp, file_id=file_id)
842
new_disk_path = self._write_file(new_path, self.new_tree, 'new',
843
force_temp, file_id=file_id,
754
def _prepare_files(self, file_id, old_path, new_path, force_temp=False,
755
allow_write_new=False):
756
old_disk_path = self._write_file(file_id, self.old_tree, 'old',
757
old_path, force_temp)
758
new_disk_path = self._write_file(file_id, self.new_tree, 'new',
759
new_path, force_temp,
844
760
allow_write=allow_write_new)
845
761
return old_disk_path, new_disk_path
847
763
def finish(self):
849
765
osutils.rmtree(self._root)
851
767
if e.errno != errno.ENOENT:
852
768
mutter("The temporary directory \"%s\" was not "
853
"cleanly removed: %s." % (self._root, e))
769
"cleanly removed: %s." % (self._root, e))
855
771
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
856
772
if (old_kind, new_kind) != ('file', 'file'):
857
773
return DiffPath.CANNOT_DIFF
858
774
(old_disk_path, new_disk_path) = self._prepare_files(
859
old_path, new_path, file_id=file_id)
775
file_id, old_path, new_path)
860
776
self._execute(old_disk_path, new_disk_path)
862
def edit_file(self, old_path, new_path, file_id=None):
778
def edit_file(self, file_id):
863
779
"""Use this tool to edit a file.
865
781
A temporary copy will be edited, and the new contents will be
938
859
:param using: Commandline to use to invoke an external diff tool
940
861
if using is not None:
941
extra_factories = [DiffFromTool.make_from_diff_tree(
942
using, external_diff_options)]
862
extra_factories = [DiffFromTool.make_from_diff_tree(using)]
944
864
extra_factories = []
945
865
if external_diff_options:
946
866
opts = external_diff_options.split()
948
def diff_file(olab, olines, nlab, nlines, to_file, path_encoding=None, context_lines=None):
949
""":param path_encoding: not used but required
950
to match the signature of internal_diff.
867
def diff_file(olab, olines, nlab, nlines, to_file):
952
868
external_diff(olab, olines, nlab, nlines, to_file, opts)
954
870
diff_file = internal_diff
955
871
diff_text = DiffText(old_tree, new_tree, to_file, path_encoding,
956
old_label, new_label, diff_file, context_lines=context_lines)
872
old_label, new_label, diff_file)
957
873
return klass(old_tree, new_tree, to_file, path_encoding, diff_text,
960
876
def show_diff(self, specific_files, extra_trees=None):
961
877
"""Write tree diff to self.to_file
963
:param specific_files: the specific files to compare (recursive)
879
:param sepecific_files: the specific files to compare (recursive)
964
880
:param extra_trees: extra trees to use for mapping paths to file_ids
1002
916
renamed = (parent[0], name[0]) != (parent[1], name[1])
1004
918
properties_changed = []
1005
properties_changed.extend(
1006
get_executable_change(executable[0], executable[1]))
919
properties_changed.extend(get_executable_change(executable[0], executable[1]))
1008
921
if properties_changed:
1009
prop_str = b" (properties changed: %s)" % (
1010
b", ".join(properties_changed),)
922
prop_str = " (properties changed: %s)" % (", ".join(properties_changed),)
1014
926
if (old_present, new_present) == (True, False):
1015
self.to_file.write(b"=== removed %s '%s'\n" %
1016
(kind[0].encode('ascii'), oldpath_encoded))
927
self.to_file.write("=== removed %s '%s'\n" %
928
(kind[0], oldpath_encoded))
1017
929
newpath = oldpath
1018
930
elif (old_present, new_present) == (False, True):
1019
self.to_file.write(b"=== added %s '%s'\n" %
1020
(kind[1].encode('ascii'), newpath_encoded))
931
self.to_file.write("=== added %s '%s'\n" %
932
(kind[1], newpath_encoded))
1021
933
oldpath = newpath
1023
self.to_file.write(b"=== renamed %s '%s' => '%s'%s\n" %
1024
(kind[0].encode('ascii'), oldpath_encoded, newpath_encoded, prop_str))
935
self.to_file.write("=== renamed %s '%s' => '%s'%s\n" %
936
(kind[0], oldpath_encoded, newpath_encoded, prop_str))
1026
938
# if it was produced by iter_changes, it must be
1027
939
# modified *somehow*, either content or execute bit.
1028
self.to_file.write(b"=== modified %s '%s'%s\n" % (kind[0].encode('ascii'),
1029
newpath_encoded, prop_str))
940
self.to_file.write("=== modified %s '%s'%s\n" % (kind[0],
941
newpath_encoded, prop_str))
1030
942
if changed_content:
1031
self._diff(oldpath, newpath, kind[0], kind[1], file_id=file_id)
943
self._diff(file_id, oldpath, newpath, kind[0], kind[1])
1041
953
:param old_path: The path of the file in the old tree
1042
954
:param new_path: The path of the file in the new tree
1044
if old_path is None:
957
old_kind = self.old_tree.kind(file_id)
958
except (errors.NoSuchId, errors.NoSuchFile):
1047
old_kind = self.old_tree.kind(old_path)
1048
if new_path is None:
961
new_kind = self.new_tree.kind(file_id)
962
except (errors.NoSuchId, errors.NoSuchFile):
1051
new_kind = self.new_tree.kind(new_path)
1052
self._diff(old_path, new_path, old_kind, new_kind, file_id=file_id)
1054
def _diff(self, old_path, new_path, old_kind, new_kind, file_id):
964
self._diff(file_id, old_path, new_path, old_kind, new_kind)
967
def _diff(self, file_id, old_path, new_path, old_kind, new_kind):
1055
968
result = DiffPath._diff_many(self.differs, file_id, old_path,
1056
new_path, old_kind, new_kind)
969
new_path, old_kind, new_kind)
1057
970
if result is DiffPath.CANNOT_DIFF:
1058
971
error_path = new_path
1059
972
if error_path is None:
1060
973
error_path = old_path
1061
974
raise errors.NoDiffFound(error_path)
1064
format_registry = Registry()
1065
format_registry.register('default', DiffTree)