289
def get_trees_and_branches_to_diff(path_list, revision_specs, old_url, new_url,
291
"""Get the trees and specific files to diff given a list of paths.
293
This method works out the trees to be diff'ed and the files of
294
interest within those trees.
297
the list of arguments passed to the diff command
298
:param revision_specs:
299
Zero, one or two RevisionSpecs from the diff command line,
300
saying what revisions to compare.
302
The url of the old branch or tree. If None, the tree to use is
303
taken from the first path, if any, or the current working tree.
305
The url of the new branch or tree. If None, the tree to use is
306
taken from the first path, if any, or the current working tree.
308
if True and a view is set, apply the view or check that the paths
311
a tuple of (old_tree, new_tree, old_branch, new_branch,
312
specific_files, extra_trees) where extra_trees is a sequence of
313
additional trees to search in for file-ids.
271
def diff_cmd_helper(tree, specific_files, external_diff_options,
272
old_revision_spec=None, new_revision_spec=None,
274
old_label='a/', new_label='b/'):
275
"""Helper for cmd_diff.
280
:param specific_files:
281
The specific files to compare, or None
283
:param external_diff_options:
284
If non-None, run an external diff, and pass it these options
286
:param old_revision_spec:
287
If None, use basis tree as old revision, otherwise use the tree for
288
the specified revision.
290
:param new_revision_spec:
291
If None, use working tree as new revision, otherwise use the tree for
292
the specified revision.
294
:param revision_specs:
295
Zero, one or two RevisionSpecs from the command line, saying what revisions
296
to compare. This can be passed as an alternative to the old_revision_spec
297
and new_revision_spec parameters.
299
The more general form is show_diff_trees(), where the caller
300
supplies any two trees.
315
# Get the old and new revision specs
316
old_revision_spec = None
317
new_revision_spec = None
303
# TODO: perhaps remove the old parameters old_revision_spec and
304
# new_revision_spec, since this is only really for use from cmd_diff and
305
# it now always passes through a sequence of revision_specs -- mbp
310
revision = spec.in_store(tree.branch)
312
revision = spec.in_store(None)
313
revision_id = revision.rev_id
314
branch = revision.branch
315
return branch.repository.revision_tree(revision_id)
318
317
if revision_specs is not None:
318
assert (old_revision_spec is None
319
and new_revision_spec is None)
319
320
if len(revision_specs) > 0:
320
321
old_revision_spec = revision_specs[0]
322
old_url = old_revision_spec.get_branch()
323
322
if len(revision_specs) > 1:
324
323
new_revision_spec = revision_specs[1]
326
new_url = new_revision_spec.get_branch()
329
make_paths_wt_relative = True
330
consider_relpath = True
331
if path_list is None or len(path_list) == 0:
332
# If no path is given, the current working tree is used
333
default_location = u'.'
334
consider_relpath = False
335
elif old_url is not None and new_url is not None:
336
other_paths = path_list
337
make_paths_wt_relative = False
339
default_location = path_list[0]
340
other_paths = path_list[1:]
342
# Get the old location
345
old_url = default_location
346
working_tree, branch, relpath = \
347
bzrdir.BzrDir.open_containing_tree_or_branch(old_url)
348
if consider_relpath and relpath != '':
349
if working_tree is not None and apply_view:
350
views.check_path_in_view(working_tree, relpath)
351
specific_files.append(relpath)
352
old_tree = _get_tree_to_diff(old_revision_spec, working_tree, branch)
355
# Get the new location
357
new_url = default_location
358
if new_url != old_url:
359
working_tree, branch, relpath = \
360
bzrdir.BzrDir.open_containing_tree_or_branch(new_url)
361
if consider_relpath and relpath != '':
362
if working_tree is not None and apply_view:
363
views.check_path_in_view(working_tree, relpath)
364
specific_files.append(relpath)
365
new_tree = _get_tree_to_diff(new_revision_spec, working_tree, branch,
366
basis_is_default=working_tree is None)
369
# Get the specific files (all files is None, no files is [])
370
if make_paths_wt_relative and working_tree is not None:
372
from bzrlib.builtins import safe_relpath_files
373
other_paths = safe_relpath_files(working_tree, other_paths,
374
apply_view=apply_view)
375
except errors.FileInWrongBranch:
376
raise errors.BzrCommandError("Files are in different branches")
377
specific_files.extend(other_paths)
378
if len(specific_files) == 0:
379
specific_files = None
380
if (working_tree is not None and working_tree.supports_views()
382
view_files = working_tree.views.lookup_view()
384
specific_files = view_files
385
view_str = views.view_display_str(view_files)
386
note("*** Ignoring files outside view. View is %s" % view_str)
388
# Get extra trees that ought to be searched for file-ids
390
if working_tree is not None and working_tree not in (old_tree, new_tree):
391
extra_trees = (working_tree,)
392
return old_tree, new_tree, old_branch, new_branch, specific_files, extra_trees
395
def _get_tree_to_diff(spec, tree=None, branch=None, basis_is_default=True):
396
if branch is None and tree is not None:
398
if spec is None or spec.spec is None:
401
return tree.basis_tree()
403
return branch.basis_tree()
406
return spec.as_tree(branch)
325
if old_revision_spec is None:
326
old_tree = tree.basis_tree()
328
old_tree = spec_tree(old_revision_spec)
330
if (new_revision_spec is None
331
or new_revision_spec.spec is None):
334
new_tree = spec_tree(new_revision_spec)
336
if new_tree is not tree:
337
extra_trees = (tree,)
341
return show_diff_trees(old_tree, new_tree, sys.stdout, specific_files,
342
external_diff_options,
343
old_label=old_label, new_label=new_label,
344
extra_trees=extra_trees)
409
347
def show_diff_trees(old_tree, new_tree, to_file, specific_files=None,
410
348
external_diff_options=None,
411
349
old_label='a/', new_label='b/',
412
350
extra_trees=None,
413
path_encoding='utf8',
351
path_encoding='utf8'):
415
352
"""Show in text form the changes from one tree to another.
421
Include only changes to these files - None for all changes.
355
If set, include only changes to these files.
423
357
external_diff_options
424
358
If set, use an external GNU diff and pass these options.
451
385
old_tree.unlock()
388
def _show_diff_trees(old_tree, new_tree, to_file,
389
specific_files, external_diff_options, path_encoding,
390
old_label='a/', new_label='b/', extra_trees=None):
392
# GNU Patch uses the epoch date to detect files that are being added
393
# or removed in a diff.
394
EPOCH_DATE = '1970-01-01 00:00:00 +0000'
396
# TODO: Generation of pseudo-diffs for added/deleted files could
397
# be usefully made into a much faster special case.
399
if external_diff_options:
400
assert isinstance(external_diff_options, basestring)
401
opts = external_diff_options.split()
402
def diff_file(olab, olines, nlab, nlines, to_file):
403
external_diff(olab, olines, nlab, nlines, to_file, opts)
405
diff_file = internal_diff
407
delta = new_tree.changes_from(old_tree,
408
specific_files=specific_files,
409
extra_trees=extra_trees, require_versioned=True)
412
for path, file_id, kind in delta.removed:
414
path_encoded = path.encode(path_encoding, "replace")
415
to_file.write("=== removed %s '%s'\n" % (kind, path_encoded))
416
old_name = '%s%s\t%s' % (old_label, path,
417
_patch_header_date(old_tree, file_id, path))
418
new_name = '%s%s\t%s' % (new_label, path, EPOCH_DATE)
419
old_tree.inventory[file_id].diff(diff_file, old_name, old_tree,
420
new_name, None, None, to_file)
421
for path, file_id, kind in delta.added:
423
path_encoded = path.encode(path_encoding, "replace")
424
to_file.write("=== added %s '%s'\n" % (kind, path_encoded))
425
old_name = '%s%s\t%s' % (old_label, path, EPOCH_DATE)
426
new_name = '%s%s\t%s' % (new_label, path,
427
_patch_header_date(new_tree, file_id, path))
428
new_tree.inventory[file_id].diff(diff_file, new_name, new_tree,
429
old_name, None, None, to_file,
431
for (old_path, new_path, file_id, kind,
432
text_modified, meta_modified) in delta.renamed:
434
prop_str = get_prop_change(meta_modified)
435
oldpath_encoded = old_path.encode(path_encoding, "replace")
436
newpath_encoded = new_path.encode(path_encoding, "replace")
437
to_file.write("=== renamed %s '%s' => '%s'%s\n" % (kind,
438
oldpath_encoded, newpath_encoded, prop_str))
439
old_name = '%s%s\t%s' % (old_label, old_path,
440
_patch_header_date(old_tree, file_id,
442
new_name = '%s%s\t%s' % (new_label, new_path,
443
_patch_header_date(new_tree, file_id,
445
_maybe_diff_file_or_symlink(old_name, old_tree, file_id,
447
text_modified, kind, to_file, diff_file)
448
for path, file_id, kind, text_modified, meta_modified in delta.modified:
450
prop_str = get_prop_change(meta_modified)
451
path_encoded = path.encode(path_encoding, "replace")
452
to_file.write("=== modified %s '%s'%s\n" % (kind,
453
path_encoded, prop_str))
454
# The file may be in a different location in the old tree (because
455
# the containing dir was renamed, but the file itself was not)
456
old_path = old_tree.id2path(file_id)
457
old_name = '%s%s\t%s' % (old_label, old_path,
458
_patch_header_date(old_tree, file_id, old_path))
459
new_name = '%s%s\t%s' % (new_label, path,
460
_patch_header_date(new_tree, file_id, path))
462
_maybe_diff_file_or_symlink(old_name, old_tree, file_id,
464
True, kind, to_file, diff_file)
454
469
def _patch_header_date(tree, file_id, path):
455
470
"""Returns a timestamp suitable for use in a patch header."""
456
471
mtime = tree.get_file_mtime(file_id, path)
472
assert mtime is not None, \
473
"got an mtime of None for file-id %s, path %s in tree %s" % (
457
475
return timestamp.format_patch_date(mtime)
460
def get_executable_change(old_is_x, new_is_x):
461
descr = { True:"+x", False:"-x", None:"??" }
462
if old_is_x != new_is_x:
463
return ["%s to %s" % (descr[old_is_x], descr[new_is_x],)]
478
def _raise_if_nonexistent(paths, old_tree, new_tree):
479
"""Complain if paths are not in either inventory or tree.
481
It's OK with the files exist in either tree's inventory, or
482
if they exist in the tree but are not versioned.
484
This can be used by operations such as bzr status that can accept
485
unknown or ignored files.
487
mutter("check paths: %r", paths)
490
s = old_tree.filter_unversioned_files(paths)
491
s = new_tree.filter_unversioned_files(s)
492
s = [path for path in s if not new_tree.has_filename(path)]
494
raise errors.PathsDoNotExist(sorted(s))
497
def get_prop_change(meta_modified):
499
return " (properties changed)"
468
class DiffPath(object):
469
"""Base type for command object that compare files"""
471
# The type or contents of the file were unsuitable for diffing
472
CANNOT_DIFF = 'CANNOT_DIFF'
473
# The file has changed in a semantic way
475
# The file content may have changed, but there is no semantic change
476
UNCHANGED = 'UNCHANGED'
478
def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8'):
481
:param old_tree: The tree to show as the old tree in the comparison
482
:param new_tree: The tree to show as new in the comparison
483
:param to_file: The file to write comparison data to
484
:param path_encoding: The character encoding to write paths in
486
self.old_tree = old_tree
487
self.new_tree = new_tree
488
self.to_file = to_file
489
self.path_encoding = path_encoding
495
def from_diff_tree(klass, diff_tree):
496
return klass(diff_tree.old_tree, diff_tree.new_tree,
497
diff_tree.to_file, diff_tree.path_encoding)
500
def _diff_many(differs, file_id, old_path, new_path, old_kind, new_kind):
501
for file_differ in differs:
502
result = file_differ.diff(file_id, old_path, new_path, old_kind,
504
if result is not DiffPath.CANNOT_DIFF:
507
return DiffPath.CANNOT_DIFF
510
class DiffKindChange(object):
511
"""Special differ for file kind changes.
513
Represents kind change as deletion + creation. Uses the other differs
516
def __init__(self, differs):
517
self.differs = differs
523
def from_diff_tree(klass, diff_tree):
524
return klass(diff_tree.differs)
526
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
527
"""Perform comparison
529
:param file_id: The file_id of the file to compare
530
:param old_path: Path of the file in the old tree
531
:param new_path: Path of the file in the new tree
532
:param old_kind: Old file-kind of the file
533
:param new_kind: New file-kind of the file
535
if None in (old_kind, new_kind):
536
return DiffPath.CANNOT_DIFF
537
result = DiffPath._diff_many(self.differs, file_id, old_path,
538
new_path, old_kind, None)
539
if result is DiffPath.CANNOT_DIFF:
541
return DiffPath._diff_many(self.differs, file_id, old_path, new_path,
545
class DiffDirectory(DiffPath):
547
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
548
"""Perform comparison between two directories. (dummy)
551
if 'directory' not in (old_kind, new_kind):
552
return self.CANNOT_DIFF
553
if old_kind not in ('directory', None):
554
return self.CANNOT_DIFF
555
if new_kind not in ('directory', None):
556
return self.CANNOT_DIFF
560
class DiffSymlink(DiffPath):
562
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
563
"""Perform comparison between two symlinks
565
:param file_id: The file_id of the file to compare
566
:param old_path: Path of the file in the old tree
567
:param new_path: Path of the file in the new tree
568
:param old_kind: Old file-kind of the file
569
:param new_kind: New file-kind of the file
571
if 'symlink' not in (old_kind, new_kind):
572
return self.CANNOT_DIFF
573
if old_kind == 'symlink':
574
old_target = self.old_tree.get_symlink_target(file_id)
575
elif old_kind is None:
578
return self.CANNOT_DIFF
579
if new_kind == 'symlink':
580
new_target = self.new_tree.get_symlink_target(file_id)
581
elif new_kind is None:
584
return self.CANNOT_DIFF
585
return self.diff_symlink(old_target, new_target)
587
def diff_symlink(self, old_target, new_target):
588
if old_target is None:
589
self.to_file.write('=== target is %r\n' % new_target)
590
elif new_target is None:
591
self.to_file.write('=== target was %r\n' % old_target)
593
self.to_file.write('=== target changed %r => %r\n' %
594
(old_target, new_target))
598
class DiffText(DiffPath):
600
# GNU Patch uses the epoch date to detect files that are being added
601
# or removed in a diff.
602
EPOCH_DATE = '1970-01-01 00:00:00 +0000'
604
def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8',
605
old_label='', new_label='', text_differ=internal_diff):
606
DiffPath.__init__(self, old_tree, new_tree, to_file, path_encoding)
607
self.text_differ = text_differ
608
self.old_label = old_label
609
self.new_label = new_label
610
self.path_encoding = path_encoding
612
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
613
"""Compare two files in unified diff format
615
:param file_id: The file_id of the file to compare
616
:param old_path: Path of the file in the old tree
617
:param new_path: Path of the file in the new tree
618
:param old_kind: Old file-kind of the file
619
:param new_kind: New file-kind of the file
621
if 'file' not in (old_kind, new_kind):
622
return self.CANNOT_DIFF
623
from_file_id = to_file_id = file_id
624
if old_kind == 'file':
625
old_date = _patch_header_date(self.old_tree, file_id, old_path)
626
elif old_kind is None:
627
old_date = self.EPOCH_DATE
630
return self.CANNOT_DIFF
631
if new_kind == 'file':
632
new_date = _patch_header_date(self.new_tree, file_id, new_path)
633
elif new_kind is None:
634
new_date = self.EPOCH_DATE
637
return self.CANNOT_DIFF
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,
643
def diff_text(self, from_file_id, to_file_id, from_label, to_label,
644
from_path=None, to_path=None):
645
"""Diff the content of given files in two trees
647
:param from_file_id: The id of the file in the from tree. If None,
648
the file is not present in the from tree.
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,
651
the file is not present in the to tree.
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.
655
def _get_text(tree, file_id, path):
656
if file_id is not None:
657
return tree.get_file(file_id, path).readlines()
661
from_text = _get_text(self.old_tree, from_file_id, from_path)
662
to_text = _get_text(self.new_tree, to_file_id, to_path)
663
self.text_differ(from_label, from_text, to_label, to_text,
665
except errors.BinaryFile:
667
("Binary files %s and %s differ\n" %
668
(from_label, to_label)).encode(self.path_encoding))
672
class DiffFromTool(DiffPath):
674
def __init__(self, command_template, old_tree, new_tree, to_file,
675
path_encoding='utf-8'):
676
DiffPath.__init__(self, old_tree, new_tree, to_file, path_encoding)
677
self.command_template = command_template
678
self._root = osutils.mkdtemp(prefix='bzr-diff-')
681
def from_string(klass, command_string, old_tree, new_tree, to_file,
682
path_encoding='utf-8'):
683
command_template = commands.shlex_split_unicode(command_string)
684
if '@' not in command_string:
685
command_template.extend(['@old_path', '@new_path'])
686
return klass(command_template, old_tree, new_tree, to_file,
690
def make_from_diff_tree(klass, command_string):
691
def from_diff_tree(diff_tree):
692
return klass.from_string(command_string, diff_tree.old_tree,
693
diff_tree.new_tree, diff_tree.to_file)
694
return from_diff_tree
696
def _get_command(self, old_path, new_path):
697
my_map = {'old_path': old_path, 'new_path': new_path}
698
return [AtTemplate(t).substitute(my_map) for t in
699
self.command_template]
701
def _execute(self, old_path, new_path):
702
command = self._get_command(old_path, new_path)
704
proc = subprocess.Popen(command, stdout=subprocess.PIPE,
707
if e.errno == errno.ENOENT:
708
raise errors.ExecutableMissing(command[0])
711
self.to_file.write(proc.stdout.read())
714
def _try_symlink_root(self, tree, prefix):
715
if (getattr(tree, 'abspath', None) is None
716
or not osutils.host_os_dereferences_symlinks()):
719
os.symlink(tree.abspath(''), osutils.pathjoin(self._root, prefix))
721
if e.errno != errno.EEXIST:
725
def _write_file(self, file_id, tree, prefix, relpath, force_temp=False,
727
if not force_temp and isinstance(tree, WorkingTree):
728
return tree.abspath(tree.id2path(file_id))
730
full_path = osutils.pathjoin(self._root, prefix, relpath)
731
if not force_temp and self._try_symlink_root(tree, prefix):
733
parent_dir = osutils.dirname(full_path)
735
os.makedirs(parent_dir)
737
if e.errno != errno.EEXIST:
739
source = tree.get_file(file_id, relpath)
741
target = open(full_path, 'wb')
743
osutils.pumpfile(source, target)
749
osutils.make_readonly(full_path)
750
mtime = tree.get_file_mtime(file_id)
751
os.utime(full_path, (mtime, mtime))
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,
760
allow_write=allow_write_new)
761
return old_disk_path, new_disk_path
765
osutils.rmtree(self._root)
767
if e.errno != errno.ENOENT:
768
mutter("The temporary directory \"%s\" was not "
769
"cleanly removed: %s." % (self._root, e))
771
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
772
if (old_kind, new_kind) != ('file', 'file'):
773
return DiffPath.CANNOT_DIFF
774
(old_disk_path, new_disk_path) = self._prepare_files(
775
file_id, old_path, new_path)
776
self._execute(old_disk_path, new_disk_path)
778
def edit_file(self, file_id):
779
"""Use this tool to edit a file.
781
A temporary copy will be edited, and the new contents will be
784
:param file_id: The id of the file to edit.
785
:return: The new contents of the file.
787
old_path = self.old_tree.id2path(file_id)
788
new_path = self.new_tree.id2path(file_id)
789
new_abs_path = self._prepare_files(file_id, old_path, new_path,
790
allow_write_new=True,
792
command = self._get_command(osutils.pathjoin('old', old_path),
793
osutils.pathjoin('new', new_path))
794
subprocess.call(command, cwd=self._root)
795
new_file = open(new_abs_path, 'r')
797
return new_file.read()
802
class DiffTree(object):
803
"""Provides textual representations of the difference between two trees.
805
A DiffTree examines two trees and where a file-id has altered
806
between them, generates a textual representation of the difference.
807
DiffTree uses a sequence of DiffPath objects which are each
808
given the opportunity to handle a given altered fileid. The list
809
of DiffPath objects can be extended globally by appending to
810
DiffTree.diff_factories, or for a specific diff operation by
811
supplying the extra_factories option to the appropriate method.
814
# list of factories that can provide instances of DiffPath objects
815
# may be extended by plugins.
816
diff_factories = [DiffSymlink.from_diff_tree,
817
DiffDirectory.from_diff_tree]
819
def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8',
820
diff_text=None, extra_factories=None):
823
:param old_tree: Tree to show as old in the comparison
824
:param new_tree: Tree to show as new in the comparison
825
:param to_file: File to write comparision to
826
:param path_encoding: Character encoding to write paths in
827
:param diff_text: DiffPath-type object to use as a last resort for
829
:param extra_factories: Factories of DiffPaths to try before any other
831
if diff_text is None:
832
diff_text = DiffText(old_tree, new_tree, to_file, path_encoding,
833
'', '', internal_diff)
834
self.old_tree = old_tree
835
self.new_tree = new_tree
836
self.to_file = to_file
837
self.path_encoding = path_encoding
839
if extra_factories is not None:
840
self.differs.extend(f(self) for f in extra_factories)
841
self.differs.extend(f(self) for f in self.diff_factories)
842
self.differs.extend([diff_text, DiffKindChange.from_diff_tree(self)])
845
def from_trees_options(klass, old_tree, new_tree, to_file,
846
path_encoding, external_diff_options, old_label,
848
"""Factory for producing a DiffTree.
850
Designed to accept options used by show_diff_trees.
851
:param old_tree: The tree to show as old in the comparison
852
:param new_tree: The tree to show as new in the comparison
853
:param to_file: File to write comparisons to
854
:param path_encoding: Character encoding to use for writing paths
855
:param external_diff_options: If supplied, use the installed diff
856
binary to perform file comparison, using supplied options.
857
:param old_label: Prefix to use for old file labels
858
:param new_label: Prefix to use for new file labels
859
:param using: Commandline to use to invoke an external diff tool
861
if using is not None:
862
extra_factories = [DiffFromTool.make_from_diff_tree(using)]
865
if external_diff_options:
866
opts = external_diff_options.split()
867
def diff_file(olab, olines, nlab, nlines, to_file):
868
external_diff(olab, olines, nlab, nlines, to_file, opts)
870
diff_file = internal_diff
871
diff_text = DiffText(old_tree, new_tree, to_file, path_encoding,
872
old_label, new_label, diff_file)
873
return klass(old_tree, new_tree, to_file, path_encoding, diff_text,
876
def show_diff(self, specific_files, extra_trees=None):
877
"""Write tree diff to self.to_file
879
:param sepecific_files: the specific files to compare (recursive)
880
:param extra_trees: extra trees to use for mapping paths to file_ids
883
return self._show_diff(specific_files, extra_trees)
885
for differ in self.differs:
888
def _show_diff(self, specific_files, extra_trees):
889
# TODO: Generation of pseudo-diffs for added/deleted files could
890
# be usefully made into a much faster special case.
891
iterator = self.new_tree.iter_changes(self.old_tree,
892
specific_files=specific_files,
893
extra_trees=extra_trees,
894
require_versioned=True)
896
def changes_key(change):
897
old_path, new_path = change[1]
902
def get_encoded_path(path):
904
return path.encode(self.path_encoding, "replace")
905
for (file_id, paths, changed_content, versioned, parent, name, kind,
906
executable) in sorted(iterator, key=changes_key):
907
# The root does not get diffed, and items with no known kind (that
908
# is, missing) in both trees are skipped as well.
909
if parent == (None, None) or kind == (None, None):
911
oldpath, newpath = paths
912
oldpath_encoded = get_encoded_path(paths[0])
913
newpath_encoded = get_encoded_path(paths[1])
914
old_present = (kind[0] is not None and versioned[0])
915
new_present = (kind[1] is not None and versioned[1])
916
renamed = (parent[0], name[0]) != (parent[1], name[1])
918
properties_changed = []
919
properties_changed.extend(get_executable_change(executable[0], executable[1]))
921
if properties_changed:
922
prop_str = " (properties changed: %s)" % (", ".join(properties_changed),)
926
if (old_present, new_present) == (True, False):
927
self.to_file.write("=== removed %s '%s'\n" %
928
(kind[0], oldpath_encoded))
930
elif (old_present, new_present) == (False, True):
931
self.to_file.write("=== added %s '%s'\n" %
932
(kind[1], newpath_encoded))
935
self.to_file.write("=== renamed %s '%s' => '%s'%s\n" %
936
(kind[0], oldpath_encoded, newpath_encoded, prop_str))
938
# if it was produced by iter_changes, it must be
939
# modified *somehow*, either content or execute bit.
940
self.to_file.write("=== modified %s '%s'%s\n" % (kind[0],
941
newpath_encoded, prop_str))
943
self._diff(file_id, oldpath, newpath, kind[0], kind[1])
949
def diff(self, file_id, old_path, new_path):
950
"""Perform a diff of a single file
952
:param file_id: file-id of the file
953
:param old_path: The path of the file in the old tree
954
:param new_path: The path of the file in the new tree
957
old_kind = self.old_tree.kind(file_id)
958
except (errors.NoSuchId, errors.NoSuchFile):
961
new_kind = self.new_tree.kind(file_id)
962
except (errors.NoSuchId, errors.NoSuchFile):
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):
968
result = DiffPath._diff_many(self.differs, file_id, old_path,
969
new_path, old_kind, new_kind)
970
if result is DiffPath.CANNOT_DIFF:
971
error_path = new_path
972
if error_path is None:
973
error_path = old_path
974
raise errors.NoDiffFound(error_path)
504
def _maybe_diff_file_or_symlink(old_path, old_tree, file_id,
505
new_path, new_tree, text_modified,
506
kind, to_file, diff_file):
508
new_entry = new_tree.inventory[file_id]
509
old_tree.inventory[file_id].diff(diff_file,