93
272
oldtmpf.writelines(oldlines)
94
273
newtmpf.writelines(newlines)
280
if sys.platform == 'win32':
281
# Popen doesn't do the proper encoding for external commands
282
# Since we are dealing with an ANSI api, use mbcs encoding
283
old_label = old_label.encode('mbcs')
284
new_label = new_label.encode('mbcs')
101
285
diffcmd = ['diff',
102
286
'--label', old_label,
104
288
'--label', new_label,
107
# diff only allows one style to be specified; they don't override.
108
# note that some of these take optargs, and the optargs can be
109
# directly appended to the options.
110
# this is only an approximate parser; it doesn't properly understand
112
for s in ['-c', '-u', '-C', '-U',
117
'-y', '--side-by-side',
293
diff_opts = default_style_unified(diff_opts)
129
296
diffcmd.extend(diff_opts)
131
rc = os.spawnvp(os.P_WAIT, 'diff', diffcmd)
133
if rc != 0 and rc != 1:
298
pipe = _spawn_external_diff(diffcmd, capture_errors=True)
299
out, err = pipe.communicate()
302
# internal_diff() adds a trailing newline, add one here for consistency
305
# 'diff' gives retcode == 2 for all sorts of errors
306
# one of those is 'Binary files differ'.
307
# Bad options could also be the problem.
308
# 'Binary files' is not a real error, so we suppress that error.
311
# Since we got here, we want to make sure to give an i18n error
312
pipe = _spawn_external_diff(diffcmd, capture_errors=False)
313
out, err = pipe.communicate()
315
# Write out the new i18n diff response
316
to_file.write(out + b'\n')
317
if pipe.returncode != 2:
318
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,))
323
first_line = lang_c_out.split(b'\n', 1)[0]
324
# Starting with diffutils 2.8.4 the word "binary" was dropped.
325
m = re.match(b'^(binary )?files.*differ$', first_line, re.I)
327
raise errors.BzrError('external diff failed with exit code 2;'
328
' command: %r' % (diffcmd,))
330
# Binary files differ, just return
333
# If we got to here, we haven't written out the output of diff
134
337
# returns 1 if files differ; that's OK
136
339
msg = 'signal %d' % (-rc)
138
341
msg = 'exit code %d' % rc
140
raise BzrError('external diff failed with %s; command: %r' % (rc, diffcmd))
343
raise errors.BzrError('external diff failed with %s; command: %r'
142
347
oldtmpf.close() # and delete
147
def show_diff(b, revision, specific_files, external_diff_options=None,
148
revision2=None, output=None):
149
"""Shortcut for showing the diff to the working tree.
155
None for each, or otherwise the old revision to compare against.
157
The more general form is show_diff_trees(), where the caller
158
supplies any two trees.
351
# Warn in case the file couldn't be deleted (in case windows still
352
# holds the file open, but not if the files have already been
357
if e.errno not in (errno.ENOENT,):
358
warning('Failed to delete temporary file: %s %s', path, e)
364
def get_trees_and_branches_to_diff_locked(
365
path_list, revision_specs, old_url, new_url, exit_stack, apply_view=True):
366
"""Get the trees and specific files to diff given a list of paths.
368
This method works out the trees to be diff'ed and the files of
369
interest within those trees.
372
the list of arguments passed to the diff command
373
:param revision_specs:
374
Zero, one or two RevisionSpecs from the diff command line,
375
saying what revisions to compare.
377
The url of the old branch or tree. If None, the tree to use is
378
taken from the first path, if any, or the current working tree.
380
The url of the new branch or tree. If None, the tree to use is
381
taken from the first path, if any, or the current working tree.
383
an ExitStack object. get_trees_and_branches_to_diff
384
will register cleanups that must be run to unlock the trees, etc.
386
if True and a view is set, apply the view or check that the paths
389
a tuple of (old_tree, new_tree, old_branch, new_branch,
390
specific_files, extra_trees) where extra_trees is a sequence of
391
additional trees to search in for file-ids. The trees and branches
392
will be read-locked until the cleanups registered via the exit_stack
165
old_tree = b.basis_tree()
167
old_tree = b.revision_tree(revision.in_history(b).rev_id)
169
if revision2 is None:
170
new_tree = b.working_tree()
172
new_tree = b.revision_tree(revision2.in_history(b).rev_id)
174
show_diff_trees(old_tree, new_tree, output, specific_files,
175
external_diff_options)
395
# Get the old and new revision specs
396
old_revision_spec = None
397
new_revision_spec = None
398
if revision_specs is not None:
399
if len(revision_specs) > 0:
400
old_revision_spec = revision_specs[0]
402
old_url = old_revision_spec.get_branch()
403
if len(revision_specs) > 1:
404
new_revision_spec = revision_specs[1]
406
new_url = new_revision_spec.get_branch()
409
make_paths_wt_relative = True
410
consider_relpath = True
411
if path_list is None or len(path_list) == 0:
412
# If no path is given, the current working tree is used
413
default_location = u'.'
414
consider_relpath = False
415
elif old_url is not None and new_url is not None:
416
other_paths = path_list
417
make_paths_wt_relative = False
419
default_location = path_list[0]
420
other_paths = path_list[1:]
422
def lock_tree_or_branch(wt, br):
424
exit_stack.enter_context(wt.lock_read())
426
exit_stack.enter_context(br.lock_read())
428
# Get the old location
431
old_url = default_location
432
working_tree, branch, relpath = \
433
controldir.ControlDir.open_containing_tree_or_branch(old_url)
434
lock_tree_or_branch(working_tree, branch)
435
if consider_relpath and relpath != '':
436
if working_tree is not None and apply_view:
437
views.check_path_in_view(working_tree, relpath)
438
specific_files.append(relpath)
439
old_tree = _get_tree_to_diff(old_revision_spec, working_tree, branch)
442
# Get the new location
444
new_url = default_location
445
if new_url != old_url:
446
working_tree, branch, relpath = \
447
controldir.ControlDir.open_containing_tree_or_branch(new_url)
448
lock_tree_or_branch(working_tree, branch)
449
if consider_relpath and relpath != '':
450
if working_tree is not None and apply_view:
451
views.check_path_in_view(working_tree, relpath)
452
specific_files.append(relpath)
453
new_tree = _get_tree_to_diff(new_revision_spec, working_tree, branch,
454
basis_is_default=working_tree is None)
457
# Get the specific files (all files is None, no files is [])
458
if make_paths_wt_relative and working_tree is not None:
459
other_paths = working_tree.safe_relpath_files(
461
apply_view=apply_view)
462
specific_files.extend(other_paths)
463
if len(specific_files) == 0:
464
specific_files = None
465
if (working_tree is not None and working_tree.supports_views() and
467
view_files = working_tree.views.lookup_view()
469
specific_files = view_files
470
view_str = views.view_display_str(view_files)
471
note(gettext("*** Ignoring files outside view. View is %s") % view_str)
473
# Get extra trees that ought to be searched for file-ids
475
if working_tree is not None and working_tree not in (old_tree, new_tree):
476
extra_trees = (working_tree,)
477
return (old_tree, new_tree, old_branch, new_branch,
478
specific_files, extra_trees)
481
def _get_tree_to_diff(spec, tree=None, branch=None, basis_is_default=True):
482
if branch is None and tree is not None:
484
if spec is None or spec.spec is None:
487
return tree.basis_tree()
489
return branch.basis_tree()
492
return spec.as_tree(branch)
179
495
def show_diff_trees(old_tree, new_tree, to_file, specific_files=None,
180
external_diff_options=None):
496
external_diff_options=None,
497
old_label='a/', new_label='b/',
499
path_encoding='utf8',
502
context=DEFAULT_CONTEXT_AMOUNT):
181
503
"""Show in text form the changes from one tree to another.
184
If set, include only changes to these files.
186
external_diff_options
187
If set, use an external GNU diff and pass these options.
505
:param to_file: The output stream.
506
:param specific_files: Include only changes to these files - None for all
508
:param external_diff_options: If set, use an external GNU diff and pass
510
:param extra_trees: If set, more Trees to use for looking up file ids
511
:param path_encoding: If set, the path will be encoded as specified,
512
otherwise is supposed to be utf8
513
:param format_cls: Formatter class (DiffTree subclass)
190
# TODO: Options to control putting on a prefix or suffix, perhaps as a format string
194
DEVNULL = '/dev/null'
195
# Windows users, don't panic about this filename -- it is a
196
# special signal to GNU patch that the file should be created or
197
# deleted respectively.
199
# TODO: Generation of pseudo-diffs for added/deleted files could
200
# be usefully made into a much faster special case.
202
if external_diff_options:
203
assert isinstance(external_diff_options, basestring)
204
opts = external_diff_options.split()
205
def diff_file(olab, olines, nlab, nlines, to_file):
206
external_diff(olab, olines, nlab, nlines, to_file, opts)
516
context = DEFAULT_CONTEXT_AMOUNT
517
if format_cls is None:
518
format_cls = DiffTree
519
with contextlib.ExitStack() as exit_stack:
520
exit_stack.enter_context(old_tree.lock_read())
521
if extra_trees is not None:
522
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):
534
"""Returns a timestamp suitable for use in a patch header."""
536
mtime = tree.get_file_mtime(path)
537
except FileTimestampUnavailable:
539
return timestamp.format_patch_date(mtime)
542
def get_executable_change(old_is_x, new_is_x):
543
descr = {True: b"+x", False: b"-x", None: b"??"}
544
if old_is_x != new_is_x:
545
return [b"%s to %s" % (descr[old_is_x], descr[new_is_x],)]
208
diff_file = internal_diff
211
delta = compare_trees(old_tree, new_tree, want_unchanged=False,
212
specific_files=specific_files)
214
for path, file_id, kind in delta.removed:
215
print >>to_file, '=== removed %s %r' % (kind, path)
217
diff_file(old_label + path,
218
old_tree.get_file(file_id).readlines(),
223
for path, file_id, kind in delta.added:
224
print >>to_file, '=== added %s %r' % (kind, path)
229
new_tree.get_file(file_id).readlines(),
232
for old_path, new_path, file_id, kind, text_modified in delta.renamed:
233
print >>to_file, '=== renamed %s %r => %r' % (kind, old_path, new_path)
235
diff_file(old_label + old_path,
236
old_tree.get_file(file_id).readlines(),
237
new_label + new_path,
238
new_tree.get_file(file_id).readlines(),
241
for path, file_id, kind in delta.modified:
242
print >>to_file, '=== modified %s %r' % (kind, path)
244
diff_file(old_label + path,
245
old_tree.get_file(file_id).readlines(),
247
new_tree.get_file(file_id).readlines(),
550
class DiffPath(object):
551
"""Base type for command object that compare files"""
553
# The type or contents of the file were unsuitable for diffing
554
CANNOT_DIFF = 'CANNOT_DIFF'
555
# The file has changed in a semantic way
557
# The file content may have changed, but there is no semantic change
558
UNCHANGED = 'UNCHANGED'
560
def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8'):
563
:param old_tree: The tree to show as the old tree in the comparison
564
:param new_tree: The tree to show as new in the comparison
565
:param to_file: The file to write comparison data to
566
:param path_encoding: The character encoding to write paths in
568
self.old_tree = old_tree
569
self.new_tree = new_tree
570
self.to_file = to_file
571
self.path_encoding = path_encoding
577
def from_diff_tree(klass, diff_tree):
578
return klass(diff_tree.old_tree, diff_tree.new_tree,
579
diff_tree.to_file, diff_tree.path_encoding)
582
def _diff_many(differs, old_path, new_path, old_kind, new_kind):
583
for file_differ in differs:
584
result = file_differ.diff(old_path, new_path, old_kind, new_kind)
585
if result is not DiffPath.CANNOT_DIFF:
588
return DiffPath.CANNOT_DIFF
591
class DiffKindChange(object):
592
"""Special differ for file kind changes.
594
Represents kind change as deletion + creation. Uses the other differs
598
def __init__(self, differs):
599
self.differs = differs
605
def from_diff_tree(klass, diff_tree):
606
return klass(diff_tree.differs)
608
def diff(self, old_path, new_path, old_kind, new_kind):
609
"""Perform comparison
611
:param old_path: Path of the file in the old tree
612
:param new_path: Path of the file in the new tree
613
:param old_kind: Old file-kind of the file
614
:param new_kind: New file-kind of the file
616
if None in (old_kind, new_kind):
617
return DiffPath.CANNOT_DIFF
618
result = DiffPath._diff_many(
619
self.differs, old_path, new_path, old_kind, None)
620
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
641
class DiffDirectory(DiffPath):
643
def diff(self, old_path, new_path, old_kind, new_kind):
644
"""Perform comparison between two directories. (dummy)
647
if 'directory' not in (old_kind, new_kind):
648
return self.CANNOT_DIFF
649
if old_kind not in ('directory', None):
650
return self.CANNOT_DIFF
651
if new_kind not in ('directory', None):
652
return self.CANNOT_DIFF
656
class DiffSymlink(DiffPath):
658
def diff(self, old_path, new_path, old_kind, new_kind):
659
"""Perform comparison between two symlinks
661
:param old_path: Path of the file in the old tree
662
:param new_path: Path of the file in the new tree
663
:param old_kind: Old file-kind of the file
664
:param new_kind: New file-kind of the file
666
if 'symlink' not in (old_kind, new_kind):
667
return self.CANNOT_DIFF
668
if old_kind == 'symlink':
669
old_target = self.old_tree.get_symlink_target(old_path)
670
elif old_kind is None:
673
return self.CANNOT_DIFF
674
if new_kind == 'symlink':
675
new_target = self.new_tree.get_symlink_target(new_path)
676
elif new_kind is None:
679
return self.CANNOT_DIFF
680
return self.diff_symlink(old_target, new_target)
682
def diff_symlink(self, old_target, new_target):
683
if old_target is None:
684
self.to_file.write(b'=== target is \'%s\'\n' %
685
new_target.encode(self.path_encoding, 'replace'))
686
elif new_target is None:
687
self.to_file.write(b'=== target was \'%s\'\n' %
688
old_target.encode(self.path_encoding, 'replace'))
690
self.to_file.write(b'=== target changed \'%s\' => \'%s\'\n' %
691
(old_target.encode(self.path_encoding, 'replace'),
692
new_target.encode(self.path_encoding, 'replace')))
696
class DiffText(DiffPath):
698
# GNU Patch uses the epoch date to detect files that are being added
699
# or removed in a diff.
700
EPOCH_DATE = '1970-01-01 00:00:00 +0000'
702
def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8',
703
old_label='', new_label='', text_differ=internal_diff,
704
context_lines=DEFAULT_CONTEXT_AMOUNT):
705
DiffPath.__init__(self, old_tree, new_tree, to_file, path_encoding)
706
self.text_differ = text_differ
707
self.old_label = old_label
708
self.new_label = new_label
709
self.path_encoding = path_encoding
710
self.context_lines = context_lines
712
def diff(self, old_path, new_path, old_kind, new_kind):
713
"""Compare two files in unified diff format
715
:param old_path: Path of the file in the old tree
716
:param new_path: Path of the file in the new tree
717
:param old_kind: Old file-kind of the file
718
:param new_kind: New file-kind of the file
720
if 'file' not in (old_kind, new_kind):
721
return self.CANNOT_DIFF
722
if old_kind == 'file':
723
old_date = _patch_header_date(self.old_tree, old_path)
724
elif old_kind is None:
725
old_date = self.EPOCH_DATE
727
return self.CANNOT_DIFF
728
if new_kind == 'file':
729
new_date = _patch_header_date(self.new_tree, new_path)
730
elif new_kind is None:
731
new_date = self.EPOCH_DATE
733
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)
740
def diff_text(self, from_path, to_path, from_label, to_label):
741
"""Diff the content of given files in two trees
743
:param from_path: The path in the from tree. If None,
744
the file is not present in the from tree.
745
:param to_path: The path in the to tree. This may refer
746
to a different file from from_path. If None,
747
the file is not present in the to tree.
749
def _get_text(tree, path):
753
return tree.get_file_lines(path)
754
except errors.NoSuchFile:
757
from_text = _get_text(self.old_tree, from_path)
758
to_text = _get_text(self.new_tree, to_path)
759
self.text_differ(from_label, from_text, to_label, to_text,
760
self.to_file, path_encoding=self.path_encoding,
761
context_lines=self.context_lines)
762
except errors.BinaryFile:
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'))
771
class DiffFromTool(DiffPath):
773
def __init__(self, command_template, old_tree, new_tree, to_file,
774
path_encoding='utf-8'):
775
DiffPath.__init__(self, old_tree, new_tree, to_file, path_encoding)
776
self.command_template = command_template
777
self._root = osutils.mkdtemp(prefix='brz-diff-')
780
def from_string(klass, command_template, old_tree, new_tree, to_file,
781
path_encoding='utf-8'):
782
return klass(command_template, old_tree, new_tree, to_file,
786
def make_from_diff_tree(klass, command_string, external_diff_options=None):
787
def from_diff_tree(diff_tree):
788
full_command_string = [command_string]
789
if external_diff_options is not None:
790
full_command_string += ' ' + external_diff_options
791
return klass.from_string(full_command_string, diff_tree.old_tree,
792
diff_tree.new_tree, diff_tree.to_file)
793
return from_diff_tree
795
def _get_command(self, old_path, new_path):
796
my_map = {'old_path': old_path, 'new_path': new_path}
797
command = [t.format(**my_map) for t in
798
self.command_template]
799
if command == self.command_template:
800
command += [old_path, new_path]
801
if sys.platform == 'win32': # Popen doesn't accept unicode on win32
804
if isinstance(c, str):
805
command_encoded.append(c.encode('mbcs'))
807
command_encoded.append(c)
808
return command_encoded
812
def _execute(self, old_path, new_path):
813
command = self._get_command(old_path, new_path)
815
proc = subprocess.Popen(command, stdout=subprocess.PIPE,
818
if e.errno == errno.ENOENT:
819
raise errors.ExecutableMissing(command[0])
822
self.to_file.write(proc.stdout.read())
826
def _try_symlink_root(self, tree, prefix):
827
if (getattr(tree, 'abspath', None) is None or
828
not osutils.host_os_dereferences_symlinks()):
831
os.symlink(tree.abspath(''), osutils.pathjoin(self._root, prefix))
833
if e.errno != errno.EEXIST:
839
"""Returns safe encoding for passing file path to diff tool"""
840
if sys.platform == 'win32':
843
# Don't fallback to 'utf-8' because subprocess may not be able to
844
# handle utf-8 correctly when locale is not utf-8.
845
return sys.getfilesystemencoding() or 'ascii'
847
def _is_safepath(self, path):
848
"""Return true if `path` may be able to pass to subprocess."""
851
return path == path.encode(fenc).decode(fenc)
855
def _safe_filename(self, prefix, relpath):
856
"""Replace unsafe character in `relpath` then join `self._root`,
857
`prefix` and `relpath`."""
859
# encoded_str.replace('?', '_') may break multibyte char.
860
# So we should encode, decode, then replace(u'?', u'_')
861
relpath_tmp = relpath.encode(fenc, 'replace').decode(fenc, 'replace')
862
relpath_tmp = relpath_tmp.replace(u'?', u'_')
863
return osutils.pathjoin(self._root, prefix, relpath_tmp)
865
def _write_file(self, relpath, tree, prefix, force_temp=False,
867
if not force_temp and isinstance(tree, WorkingTree):
868
full_path = tree.abspath(relpath)
869
if self._is_safepath(full_path):
872
full_path = self._safe_filename(prefix, relpath)
873
if not force_temp and self._try_symlink_root(tree, prefix):
875
parent_dir = osutils.dirname(full_path)
877
os.makedirs(parent_dir)
879
if e.errno != errno.EEXIST:
881
with tree.get_file(relpath) as source, \
882
open(full_path, 'wb') as target:
883
osutils.pumpfile(source, target)
885
mtime = tree.get_file_mtime(relpath)
886
except FileTimestampUnavailable:
889
os.utime(full_path, (mtime, mtime))
891
osutils.make_readonly(full_path)
894
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)
901
return old_disk_path, new_disk_path
905
osutils.rmtree(self._root)
907
if e.errno != errno.ENOENT:
908
mutter("The temporary directory \"%s\" was not "
909
"cleanly removed: %s." % (self._root, e))
911
def diff(self, old_path, new_path, old_kind, new_kind):
912
if (old_kind, new_kind) != ('file', 'file'):
913
return DiffPath.CANNOT_DIFF
914
(old_disk_path, new_disk_path) = self._prepare_files(
916
self._execute(old_disk_path, new_disk_path)
918
def edit_file(self, old_path, new_path):
919
"""Use this tool to edit a file.
921
A temporary copy will be edited, and the new contents will be
924
:return: The new contents of the file.
926
old_abs_path, new_abs_path = self._prepare_files(
927
old_path, new_path, allow_write_new=True, force_temp=True)
928
command = self._get_command(old_abs_path, new_abs_path)
929
subprocess.call(command, cwd=self._root)
930
with open(new_abs_path, 'rb') as new_file:
931
return new_file.read()
934
class DiffTree(object):
935
"""Provides textual representations of the difference between two trees.
937
A DiffTree examines two trees and where a file-id has altered
938
between them, generates a textual representation of the difference.
939
DiffTree uses a sequence of DiffPath objects which are each
940
given the opportunity to handle a given altered fileid. The list
941
of DiffPath objects can be extended globally by appending to
942
DiffTree.diff_factories, or for a specific diff operation by
943
supplying the extra_factories option to the appropriate method.
946
# list of factories that can provide instances of DiffPath objects
947
# may be extended by plugins.
948
diff_factories = [DiffSymlink.from_diff_tree,
949
DiffDirectory.from_diff_tree,
950
DiffTreeReference.from_diff_tree]
952
def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8',
953
diff_text=None, extra_factories=None):
956
:param old_tree: Tree to show as old in the comparison
957
:param new_tree: Tree to show as new in the comparison
958
:param to_file: File to write comparision to
959
:param path_encoding: Character encoding to write paths in
960
:param diff_text: DiffPath-type object to use as a last resort for
962
:param extra_factories: Factories of DiffPaths to try before any other
964
if diff_text is None:
965
diff_text = DiffText(old_tree, new_tree, to_file, path_encoding,
966
'', '', internal_diff)
967
self.old_tree = old_tree
968
self.new_tree = new_tree
969
self.to_file = to_file
970
self.path_encoding = path_encoding
972
if extra_factories is not None:
973
self.differs.extend(f(self) for f in extra_factories)
974
self.differs.extend(f(self) for f in self.diff_factories)
975
self.differs.extend([diff_text, DiffKindChange.from_diff_tree(self)])
978
def from_trees_options(klass, old_tree, new_tree, to_file,
979
path_encoding, external_diff_options, old_label,
980
new_label, using, context_lines):
981
"""Factory for producing a DiffTree.
983
Designed to accept options used by show_diff_trees.
985
:param old_tree: The tree to show as old in the comparison
986
:param new_tree: The tree to show as new in the comparison
987
:param to_file: File to write comparisons to
988
:param path_encoding: Character encoding to use for writing paths
989
:param external_diff_options: If supplied, use the installed diff
990
binary to perform file comparison, using supplied options.
991
:param old_label: Prefix to use for old file labels
992
:param new_label: Prefix to use for new file labels
993
:param using: Commandline to use to invoke an external diff tool
995
if using is not None:
996
extra_factories = [DiffFromTool.make_from_diff_tree(
997
using, external_diff_options)]
1000
if external_diff_options:
1001
opts = external_diff_options.split()
1003
def diff_file(olab, olines, nlab, nlines, to_file, path_encoding=None, context_lines=None):
1004
""":param path_encoding: not used but required
1005
to match the signature of internal_diff.
1007
external_diff(olab, olines, nlab, nlines, to_file, opts)
1009
diff_file = internal_diff
1010
diff_text = DiffText(old_tree, new_tree, to_file, path_encoding,
1011
old_label, new_label, diff_file, context_lines=context_lines)
1012
return klass(old_tree, new_tree, to_file, path_encoding, diff_text,
1015
def show_diff(self, specific_files, extra_trees=None):
1016
"""Write tree diff to self.to_file
1018
:param specific_files: the specific files to compare (recursive)
1019
:param extra_trees: extra trees to use for mapping paths to file_ids
1022
return self._show_diff(specific_files, extra_trees)
1024
for differ in self.differs:
1027
def _show_diff(self, specific_files, extra_trees):
1028
# TODO: Generation of pseudo-diffs for added/deleted files could
1029
# be usefully made into a much faster special case.
1030
iterator = self.new_tree.iter_changes(self.old_tree,
1031
specific_files=specific_files,
1032
extra_trees=extra_trees,
1033
require_versioned=True)
1036
def changes_key(change):
1037
old_path, new_path = change.path
1043
def get_encoded_path(path):
1044
if path is not None:
1045
return path.encode(self.path_encoding, "replace")
1046
for change in sorted(iterator, key=changes_key):
1047
# The root does not get diffed, and items with no known kind (that
1048
# 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])
1065
properties_changed = []
1066
properties_changed.extend(
1067
get_executable_change(executable[0], executable[1]))
1069
if properties_changed:
1070
prop_str = b" (properties changed: %s)" % (
1071
b", ".join(properties_changed),)
1075
if (old_present, new_present) == (True, False):
1076
self.to_file.write(b"=== removed %s '%s'\n" %
1077
(kind[0].encode('ascii'), oldpath_encoded))
1078
elif (old_present, new_present) == (False, True):
1079
self.to_file.write(b"=== added %s '%s'\n" %
1080
(kind[1].encode('ascii'), 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))
1085
# if it was produced by iter_changes, it must be
1086
# 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])
1096
def diff(self, old_path, new_path):
1097
"""Perform a diff of a single file
1099
:param old_path: The path of the file in the old tree
1100
:param new_path: The path of the file in the new tree
1102
if old_path is None:
1105
old_kind = self.old_tree.kind(old_path)
1106
if new_path is None:
1109
new_kind = self.new_tree.kind(new_path)
1110
self._diff(old_path, 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)
1115
if result is DiffPath.CANNOT_DIFF:
1116
error_path = new_path
1117
if error_path is None:
1118
error_path = old_path
1119
raise errors.NoDiffFound(error_path)
1122
format_registry = Registry()
1123
format_registry.register('default', DiffTree)