94
272
oldtmpf.writelines(oldlines)
95
273
newtmpf.writelines(newlines)
100
278
if not diff_opts:
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')
102
285
diffcmd = ['diff',
103
'--label', old_filename+'\t',
105
'--label', new_filename+'\t',
108
# diff only allows one style to be specified; they don't override.
109
# note that some of these take optargs, and the optargs can be
110
# directly appended to the options.
111
# this is only an approximate parser; it doesn't properly understand
113
for s in ['-c', '-u', '-C', '-U',
118
'-y', '--side-by-side',
286
'--label', old_label,
288
'--label', new_label,
293
diff_opts = default_style_unified(diff_opts)
130
296
diffcmd.extend(diff_opts)
132
rc = os.spawnvp(os.P_WAIT, 'diff', diffcmd)
134
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
135
337
# returns 1 if files differ; that's OK
137
339
msg = 'signal %d' % (-rc)
139
341
msg = 'exit code %d' % rc
141
raise BzrError('external diff failed with %s; command: %r' % (rc, diffcmd))
343
raise errors.BzrError('external diff failed with %s; command: %r'
143
347
oldtmpf.close() # and delete
147
@deprecated_function(zero_eight)
148
def show_diff(b, from_spec, specific_files, external_diff_options=None,
149
revision2=None, output=None, b2=None):
150
"""Shortcut for showing the diff to the working tree.
152
Please use show_diff_trees instead.
158
None for 'basis tree', or otherwise the old revision to compare against.
160
The more general form is show_diff_trees(), where the caller
161
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
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()
167
if from_spec is None:
168
old_tree = b.bzrdir.open_workingtree()
170
old_tree = old_tree = old_tree.basis_tree()
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
172
old_tree = b.repository.revision_tree(from_spec.in_history(b).rev_id)
174
if revision2 is None:
176
new_tree = b.bzrdir.open_workingtree()
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()
178
new_tree = b2.bzrdir.open_workingtree()
180
new_tree = b.repository.revision_tree(revision2.in_history(b).rev_id)
182
return show_diff_trees(old_tree, new_tree, output, specific_files,
183
external_diff_options)
186
def diff_cmd_helper(tree, specific_files, external_diff_options,
187
old_revision_spec=None, new_revision_spec=None):
188
"""Helper for cmd_diff.
194
The specific files to compare, or None
196
external_diff_options
197
If non-None, run an external diff, and pass it these options
200
If None, use basis tree as old revision, otherwise use the tree for
201
the specified revision.
204
If None, use working tree as new revision, otherwise use the tree for
205
the specified revision.
207
The more general form is show_diff_trees(), where the caller
208
supplies any two trees.
213
revision_id = spec.in_store(tree.branch).rev_id
214
return tree.branch.repository.revision_tree(revision_id)
215
if old_revision_spec is None:
216
old_tree = tree.basis_tree()
218
old_tree = spec_tree(old_revision_spec)
220
if new_revision_spec is None:
223
new_tree = spec_tree(new_revision_spec)
225
return show_diff_trees(old_tree, new_tree, sys.stdout, specific_files,
226
external_diff_options)
492
return spec.as_tree(branch)
229
495
def show_diff_trees(old_tree, new_tree, to_file, specific_files=None,
230
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):
231
503
"""Show in text form the changes from one tree to another.
234
If set, include only changes to these files.
236
external_diff_options
237
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)
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."""
243
return _show_diff_trees(old_tree, new_tree, to_file,
244
specific_files, external_diff_options)
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],)]
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' % (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)
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, self.new_label, to_path)).encode(self.path_encoding, 'replace'))
769
class DiffFromTool(DiffPath):
771
def __init__(self, command_template, old_tree, new_tree, to_file,
772
path_encoding='utf-8'):
773
DiffPath.__init__(self, old_tree, new_tree, to_file, path_encoding)
774
self.command_template = command_template
775
self._root = osutils.mkdtemp(prefix='brz-diff-')
778
def from_string(klass, command_template, old_tree, new_tree, to_file,
779
path_encoding='utf-8'):
780
return klass(command_template, old_tree, new_tree, to_file,
784
def make_from_diff_tree(klass, command_string, external_diff_options=None):
785
def from_diff_tree(diff_tree):
786
full_command_string = [command_string]
787
if external_diff_options is not None:
788
full_command_string += ' ' + external_diff_options
789
return klass.from_string(full_command_string, diff_tree.old_tree,
790
diff_tree.new_tree, diff_tree.to_file)
791
return from_diff_tree
793
def _get_command(self, old_path, new_path):
794
my_map = {'old_path': old_path, 'new_path': new_path}
795
command = [t.format(**my_map) for t in
796
self.command_template]
797
if command == self.command_template:
798
command += [old_path, new_path]
799
if sys.platform == 'win32': # Popen doesn't accept unicode on win32
802
if isinstance(c, str):
803
command_encoded.append(c.encode('mbcs'))
805
command_encoded.append(c)
806
return command_encoded
810
def _execute(self, old_path, new_path):
811
command = self._get_command(old_path, new_path)
813
proc = subprocess.Popen(command, stdout=subprocess.PIPE,
816
if e.errno == errno.ENOENT:
817
raise errors.ExecutableMissing(command[0])
820
self.to_file.write(proc.stdout.read())
824
def _try_symlink_root(self, tree, prefix):
825
if (getattr(tree, 'abspath', None) is None or
826
not osutils.host_os_dereferences_symlinks()):
829
os.symlink(tree.abspath(''), osutils.pathjoin(self._root, prefix))
831
if e.errno != errno.EEXIST:
837
"""Returns safe encoding for passing file path to diff tool"""
838
if sys.platform == 'win32':
841
# Don't fallback to 'utf-8' because subprocess may not be able to
842
# handle utf-8 correctly when locale is not utf-8.
843
return sys.getfilesystemencoding() or 'ascii'
845
def _is_safepath(self, path):
846
"""Return true if `path` may be able to pass to subprocess."""
849
return path == path.encode(fenc).decode(fenc)
853
def _safe_filename(self, prefix, relpath):
854
"""Replace unsafe character in `relpath` then join `self._root`,
855
`prefix` and `relpath`."""
857
# encoded_str.replace('?', '_') may break multibyte char.
858
# So we should encode, decode, then replace(u'?', u'_')
859
relpath_tmp = relpath.encode(fenc, 'replace').decode(fenc, 'replace')
860
relpath_tmp = relpath_tmp.replace(u'?', u'_')
861
return osutils.pathjoin(self._root, prefix, relpath_tmp)
863
def _write_file(self, relpath, tree, prefix, force_temp=False,
865
if not force_temp and isinstance(tree, WorkingTree):
866
full_path = tree.abspath(relpath)
867
if self._is_safepath(full_path):
870
full_path = self._safe_filename(prefix, relpath)
871
if not force_temp and self._try_symlink_root(tree, prefix):
873
parent_dir = osutils.dirname(full_path)
875
os.makedirs(parent_dir)
877
if e.errno != errno.EEXIST:
879
with tree.get_file(relpath) as source, \
880
open(full_path, 'wb') as target:
881
osutils.pumpfile(source, target)
883
mtime = tree.get_file_mtime(relpath)
884
except FileTimestampUnavailable:
887
os.utime(full_path, (mtime, mtime))
889
osutils.make_readonly(full_path)
892
def _prepare_files(self, old_path, new_path, force_temp=False,
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)
899
return old_disk_path, new_disk_path
903
osutils.rmtree(self._root)
905
if e.errno != errno.ENOENT:
906
mutter("The temporary directory \"%s\" was not "
907
"cleanly removed: %s." % (self._root, e))
909
def diff(self, old_path, new_path, old_kind, new_kind):
910
if (old_kind, new_kind) != ('file', 'file'):
911
return DiffPath.CANNOT_DIFF
912
(old_disk_path, new_disk_path) = self._prepare_files(
914
self._execute(old_disk_path, new_disk_path)
916
def edit_file(self, old_path, new_path):
917
"""Use this tool to edit a file.
919
A temporary copy will be edited, and the new contents will be
922
:return: The new contents of the file.
924
old_abs_path, new_abs_path = self._prepare_files(
925
old_path, new_path, allow_write_new=True, force_temp=True)
926
command = self._get_command(old_abs_path, new_abs_path)
927
subprocess.call(command, cwd=self._root)
928
with open(new_abs_path, 'rb') as new_file:
929
return new_file.read()
932
class DiffTree(object):
933
"""Provides textual representations of the difference between two trees.
935
A DiffTree examines two trees and where a file-id has altered
936
between them, generates a textual representation of the difference.
937
DiffTree uses a sequence of DiffPath objects which are each
938
given the opportunity to handle a given altered fileid. The list
939
of DiffPath objects can be extended globally by appending to
940
DiffTree.diff_factories, or for a specific diff operation by
941
supplying the extra_factories option to the appropriate method.
944
# list of factories that can provide instances of DiffPath objects
945
# may be extended by plugins.
946
diff_factories = [DiffSymlink.from_diff_tree,
947
DiffDirectory.from_diff_tree,
948
DiffTreeReference.from_diff_tree]
950
def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8',
951
diff_text=None, extra_factories=None):
954
:param old_tree: Tree to show as old in the comparison
955
:param new_tree: Tree to show as new in the comparison
956
:param to_file: File to write comparision to
957
:param path_encoding: Character encoding to write paths in
958
:param diff_text: DiffPath-type object to use as a last resort for
960
:param extra_factories: Factories of DiffPaths to try before any other
962
if diff_text is None:
963
diff_text = DiffText(old_tree, new_tree, to_file, path_encoding,
964
'', '', internal_diff)
965
self.old_tree = old_tree
966
self.new_tree = new_tree
967
self.to_file = to_file
968
self.path_encoding = path_encoding
970
if extra_factories is not None:
971
self.differs.extend(f(self) for f in extra_factories)
972
self.differs.extend(f(self) for f in self.diff_factories)
973
self.differs.extend([diff_text, DiffKindChange.from_diff_tree(self)])
976
def from_trees_options(klass, old_tree, new_tree, to_file,
977
path_encoding, external_diff_options, old_label,
978
new_label, using, context_lines):
979
"""Factory for producing a DiffTree.
981
Designed to accept options used by show_diff_trees.
983
:param old_tree: The tree to show as old in the comparison
984
:param new_tree: The tree to show as new in the comparison
985
:param to_file: File to write comparisons to
986
:param path_encoding: Character encoding to use for writing paths
987
:param external_diff_options: If supplied, use the installed diff
988
binary to perform file comparison, using supplied options.
989
:param old_label: Prefix to use for old file labels
990
:param new_label: Prefix to use for new file labels
991
:param using: Commandline to use to invoke an external diff tool
993
if using is not None:
994
extra_factories = [DiffFromTool.make_from_diff_tree(
995
using, external_diff_options)]
998
if external_diff_options:
999
opts = external_diff_options.split()
1001
def diff_file(olab, olines, nlab, nlines, to_file, path_encoding=None, context_lines=None):
1002
""":param path_encoding: not used but required
1003
to match the signature of internal_diff.
1005
external_diff(olab, olines, nlab, nlines, to_file, opts)
1007
diff_file = internal_diff
1008
diff_text = DiffText(old_tree, new_tree, to_file, path_encoding,
1009
old_label, new_label, diff_file, context_lines=context_lines)
1010
return klass(old_tree, new_tree, to_file, path_encoding, diff_text,
1013
def show_diff(self, specific_files, extra_trees=None):
1014
"""Write tree diff to self.to_file
1016
:param specific_files: the specific files to compare (recursive)
1017
:param extra_trees: extra trees to use for mapping paths to file_ids
1020
return self._show_diff(specific_files, extra_trees)
251
def _show_diff_trees(old_tree, new_tree, to_file,
252
specific_files, external_diff_options):
254
# TODO: Options to control putting on a prefix or suffix, perhaps
255
# as a format string?
259
DEVNULL = '/dev/null'
260
# Windows users, don't panic about this filename -- it is a
261
# special signal to GNU patch that the file should be created or
262
# deleted respectively.
264
# TODO: Generation of pseudo-diffs for added/deleted files could
265
# be usefully made into a much faster special case.
267
_raise_if_doubly_unversioned(specific_files, old_tree, new_tree)
269
if external_diff_options:
270
assert isinstance(external_diff_options, basestring)
271
opts = external_diff_options.split()
272
def diff_file(olab, olines, nlab, nlines, to_file):
273
external_diff(olab, olines, nlab, nlines, to_file, opts)
275
diff_file = internal_diff
277
delta = compare_trees(old_tree, new_tree, want_unchanged=False,
278
specific_files=specific_files)
281
for path, file_id, kind in delta.removed:
283
print >>to_file, '=== removed %s %r' % (kind, old_label + path)
284
old_tree.inventory[file_id].diff(diff_file, old_label + path, old_tree,
285
DEVNULL, None, None, to_file)
286
for path, file_id, kind in delta.added:
288
print >>to_file, '=== added %s %r' % (kind, new_label + path)
289
new_tree.inventory[file_id].diff(diff_file, new_label + path, new_tree,
290
DEVNULL, None, None, to_file,
292
for (old_path, new_path, file_id, kind,
293
text_modified, meta_modified) in delta.renamed:
295
prop_str = get_prop_change(meta_modified)
296
print >>to_file, '=== renamed %s %r => %r%s' % (
297
kind, old_label + old_path, new_label + new_path, prop_str)
298
_maybe_diff_file_or_symlink(old_label, old_path, old_tree, file_id,
299
new_label, new_path, new_tree,
300
text_modified, kind, to_file, diff_file)
301
for path, file_id, kind, text_modified, meta_modified in delta.modified:
303
prop_str = get_prop_change(meta_modified)
304
print >>to_file, '=== modified %s %r%s' % (kind, old_label + path,
307
_maybe_diff_file_or_symlink(old_label, path, old_tree, file_id,
308
new_label, path, new_tree,
309
True, kind, to_file, diff_file)
314
def _raise_if_doubly_unversioned(specific_files, old_tree, new_tree):
315
"""Complain if paths are not versioned in either tree."""
316
if not specific_files:
318
old_unversioned = old_tree.filter_unversioned_files(specific_files)
319
new_unversioned = new_tree.filter_unversioned_files(specific_files)
320
unversioned = old_unversioned.intersection(new_unversioned)
322
raise errors.PathsNotVersionedError(sorted(unversioned))
325
def get_prop_change(meta_modified):
327
return " (properties changed)"
332
def _maybe_diff_file_or_symlink(old_label, old_path, old_tree, file_id,
333
new_label, new_path, new_tree, text_modified,
334
kind, to_file, diff_file):
336
new_entry = new_tree.inventory[file_id]
337
old_tree.inventory[file_id].diff(diff_file,
338
old_label + old_path, old_tree,
339
new_label + new_path, new_entry,
1022
for differ in self.differs:
1025
def _show_diff(self, specific_files, extra_trees):
1026
# TODO: Generation of pseudo-diffs for added/deleted files could
1027
# be usefully made into a much faster special case.
1028
iterator = self.new_tree.iter_changes(self.old_tree,
1029
specific_files=specific_files,
1030
extra_trees=extra_trees,
1031
require_versioned=True)
1034
def changes_key(change):
1035
old_path, new_path = change.path
1041
def get_encoded_path(path):
1042
if path is not None:
1043
return path.encode(self.path_encoding, "replace")
1044
for change in sorted(iterator, key=changes_key):
1045
# The root does not get diffed, and items with no known kind (that
1046
# is, missing) in both trees are skipped as well.
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])
1063
properties_changed = []
1064
properties_changed.extend(
1065
get_executable_change(executable[0], executable[1]))
1067
if properties_changed:
1068
prop_str = b" (properties changed: %s)" % (
1069
b", ".join(properties_changed),)
1073
if (old_present, new_present) == (True, False):
1074
self.to_file.write(b"=== removed %s '%s'\n" %
1075
(kind[0].encode('ascii'), oldpath_encoded))
1077
elif (old_present, new_present) == (False, True):
1078
self.to_file.write(b"=== added %s '%s'\n" %
1079
(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)