263
@deprecated_function(zero_eight)
264
def show_diff(b, from_spec, specific_files, external_diff_options=None,
265
revision2=None, output=None, b2=None):
266
"""Shortcut for showing the diff to the working tree.
268
Please use show_diff_trees instead.
274
None for 'basis tree', or otherwise the old revision to compare against.
276
The more general form is show_diff_trees(), where the caller
277
supplies any two trees.
282
if from_spec is None:
283
old_tree = b.bzrdir.open_workingtree()
285
old_tree = old_tree = old_tree.basis_tree()
287
old_tree = b.repository.revision_tree(from_spec.in_history(b).rev_id)
289
if revision2 is None:
291
new_tree = b.bzrdir.open_workingtree()
293
new_tree = b2.bzrdir.open_workingtree()
295
new_tree = b.repository.revision_tree(revision2.in_history(b).rev_id)
297
return show_diff_trees(old_tree, new_tree, output, specific_files,
298
external_diff_options)
301
def diff_cmd_helper(tree, specific_files, external_diff_options,
302
old_revision_spec=None, new_revision_spec=None,
303
old_label='a/', new_label='b/'):
304
"""Helper for cmd_diff.
310
The specific files to compare, or None
312
external_diff_options
313
If non-None, run an external diff, and pass it these options
316
If None, use basis tree as old revision, otherwise use the tree for
317
the specified revision.
320
If None, use working tree as new revision, otherwise use the tree for
321
the specified revision.
323
The more general form is show_diff_trees(), where the caller
324
supplies any two trees.
328
revision = spec.in_store(tree.branch)
330
revision = spec.in_store(None)
331
revision_id = revision.rev_id
332
branch = revision.branch
333
return branch.repository.revision_tree(revision_id)
334
if old_revision_spec is None:
335
old_tree = tree.basis_tree()
337
old_tree = spec_tree(old_revision_spec)
339
if new_revision_spec is None:
342
new_tree = spec_tree(new_revision_spec)
343
if new_tree is not tree:
344
extra_trees = (tree,)
348
return show_diff_trees(old_tree, new_tree, sys.stdout, specific_files,
349
external_diff_options,
350
old_label=old_label, new_label=new_label,
351
extra_trees=extra_trees)
294
@deprecated_function(deprecated_in((2, 2, 0)))
295
def get_trees_and_branches_to_diff(path_list, revision_specs, old_url, new_url,
297
"""Get the trees and specific files to diff given a list of paths.
299
This method works out the trees to be diff'ed and the files of
300
interest within those trees.
303
the list of arguments passed to the diff command
304
:param revision_specs:
305
Zero, one or two RevisionSpecs from the diff command line,
306
saying what revisions to compare.
308
The url of the old branch or tree. If None, the tree to use is
309
taken from the first path, if any, or the current working tree.
311
The url of the new branch or tree. If None, the tree to use is
312
taken from the first path, if any, or the current working tree.
314
if True and a view is set, apply the view or check that the paths
317
a tuple of (old_tree, new_tree, old_branch, new_branch,
318
specific_files, extra_trees) where extra_trees is a sequence of
319
additional trees to search in for file-ids. The trees and branches
322
op = cleanup.OperationWithCleanups(get_trees_and_branches_to_diff_locked)
323
return op.run_simple(path_list, revision_specs, old_url, new_url,
324
op.add_cleanup, apply_view=apply_view)
327
def get_trees_and_branches_to_diff_locked(
328
path_list, revision_specs, old_url, new_url, add_cleanup, apply_view=True):
329
"""Get the trees and specific files to diff given a list of paths.
331
This method works out the trees to be diff'ed and the files of
332
interest within those trees.
335
the list of arguments passed to the diff command
336
:param revision_specs:
337
Zero, one or two RevisionSpecs from the diff command line,
338
saying what revisions to compare.
340
The url of the old branch or tree. If None, the tree to use is
341
taken from the first path, if any, or the current working tree.
343
The url of the new branch or tree. If None, the tree to use is
344
taken from the first path, if any, or the current working tree.
346
a callable like Command.add_cleanup. get_trees_and_branches_to_diff
347
will register cleanups that must be run to unlock the trees, etc.
349
if True and a view is set, apply the view or check that the paths
352
a tuple of (old_tree, new_tree, old_branch, new_branch,
353
specific_files, extra_trees) where extra_trees is a sequence of
354
additional trees to search in for file-ids. The trees and branches
355
will be read-locked until the cleanups registered via the add_cleanup
358
# Get the old and new revision specs
359
old_revision_spec = None
360
new_revision_spec = None
361
if revision_specs is not None:
362
if len(revision_specs) > 0:
363
old_revision_spec = revision_specs[0]
365
old_url = old_revision_spec.get_branch()
366
if len(revision_specs) > 1:
367
new_revision_spec = revision_specs[1]
369
new_url = new_revision_spec.get_branch()
372
make_paths_wt_relative = True
373
consider_relpath = True
374
if path_list is None or len(path_list) == 0:
375
# If no path is given, the current working tree is used
376
default_location = u'.'
377
consider_relpath = False
378
elif old_url is not None and new_url is not None:
379
other_paths = path_list
380
make_paths_wt_relative = False
382
default_location = path_list[0]
383
other_paths = path_list[1:]
385
def lock_tree_or_branch(wt, br):
388
add_cleanup(wt.unlock)
391
add_cleanup(br.unlock)
393
# Get the old location
396
old_url = default_location
397
working_tree, branch, relpath = \
398
bzrdir.BzrDir.open_containing_tree_or_branch(old_url)
399
lock_tree_or_branch(working_tree, branch)
400
if consider_relpath and relpath != '':
401
if working_tree is not None and apply_view:
402
views.check_path_in_view(working_tree, relpath)
403
specific_files.append(relpath)
404
old_tree = _get_tree_to_diff(old_revision_spec, working_tree, branch)
407
# Get the new location
409
new_url = default_location
410
if new_url != old_url:
411
working_tree, branch, relpath = \
412
bzrdir.BzrDir.open_containing_tree_or_branch(new_url)
413
lock_tree_or_branch(working_tree, branch)
414
if consider_relpath and relpath != '':
415
if working_tree is not None and apply_view:
416
views.check_path_in_view(working_tree, relpath)
417
specific_files.append(relpath)
418
new_tree = _get_tree_to_diff(new_revision_spec, working_tree, branch,
419
basis_is_default=working_tree is None)
422
# Get the specific files (all files is None, no files is [])
423
if make_paths_wt_relative and working_tree is not None:
425
from bzrlib.builtins import safe_relpath_files
426
other_paths = safe_relpath_files(working_tree, other_paths,
427
apply_view=apply_view)
428
except errors.FileInWrongBranch:
429
raise errors.BzrCommandError("Files are in different branches")
430
specific_files.extend(other_paths)
431
if len(specific_files) == 0:
432
specific_files = None
433
if (working_tree is not None and working_tree.supports_views()
435
view_files = working_tree.views.lookup_view()
437
specific_files = view_files
438
view_str = views.view_display_str(view_files)
439
note("*** Ignoring files outside view. View is %s" % view_str)
441
# Get extra trees that ought to be searched for file-ids
443
if working_tree is not None and working_tree not in (old_tree, new_tree):
444
extra_trees = (working_tree,)
445
return old_tree, new_tree, old_branch, new_branch, specific_files, extra_trees
448
def _get_tree_to_diff(spec, tree=None, branch=None, basis_is_default=True):
449
if branch is None and tree is not None:
451
if spec is None or spec.spec is None:
454
return tree.basis_tree()
456
return branch.basis_tree()
459
return spec.as_tree(branch)
354
462
def show_diff_trees(old_tree, new_tree, to_file, specific_files=None,
355
463
external_diff_options=None,
356
464
old_label='a/', new_label='b/',
466
path_encoding='utf8',
358
469
"""Show in text form the changes from one tree to another.
361
If set, include only changes to these files.
363
external_diff_options
364
If set, use an external GNU diff and pass these options.
367
If set, more Trees to use for looking up file ids
471
:param to_file: The output stream.
472
:param specific_files:Include only changes to these files - None for all
474
:param external_diff_options: If set, use an external GNU diff and pass
476
:param extra_trees: If set, more Trees to use for looking up file ids
477
:param path_encoding: If set, the path will be encoded as specified,
478
otherwise is supposed to be utf8
479
:param format_cls: Formatter class (DiffTree subclass)
481
if format_cls is None:
482
format_cls = DiffTree
369
483
old_tree.lock_read()
485
if extra_trees is not None:
486
for tree in extra_trees:
371
488
new_tree.lock_read()
373
return _show_diff_trees(old_tree, new_tree, to_file,
374
specific_files, external_diff_options,
375
old_label=old_label, new_label=new_label,
376
extra_trees=extra_trees)
490
differ = format_cls.from_trees_options(old_tree, new_tree, to_file,
492
external_diff_options,
493
old_label, new_label, using)
494
return differ.show_diff(specific_files, extra_trees)
378
496
new_tree.unlock()
497
if extra_trees is not None:
498
for tree in extra_trees:
380
501
old_tree.unlock()
383
def _show_diff_trees(old_tree, new_tree, to_file,
384
specific_files, external_diff_options,
385
old_label='a/', new_label='b/', extra_trees=None):
504
def _patch_header_date(tree, file_id, path):
505
"""Returns a timestamp suitable for use in a patch header."""
507
mtime = tree.get_file_mtime(file_id, path)
508
except errors.FileTimestampUnavailable:
510
return timestamp.format_patch_date(mtime)
513
def get_executable_change(old_is_x, new_is_x):
514
descr = { True:"+x", False:"-x", None:"??" }
515
if old_is_x != new_is_x:
516
return ["%s to %s" % (descr[old_is_x], descr[new_is_x],)]
521
class DiffPath(object):
522
"""Base type for command object that compare files"""
524
# The type or contents of the file were unsuitable for diffing
525
CANNOT_DIFF = 'CANNOT_DIFF'
526
# The file has changed in a semantic way
528
# The file content may have changed, but there is no semantic change
529
UNCHANGED = 'UNCHANGED'
531
def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8'):
534
:param old_tree: The tree to show as the old tree in the comparison
535
:param new_tree: The tree to show as new in the comparison
536
:param to_file: The file to write comparison data to
537
:param path_encoding: The character encoding to write paths in
539
self.old_tree = old_tree
540
self.new_tree = new_tree
541
self.to_file = to_file
542
self.path_encoding = path_encoding
548
def from_diff_tree(klass, diff_tree):
549
return klass(diff_tree.old_tree, diff_tree.new_tree,
550
diff_tree.to_file, diff_tree.path_encoding)
553
def _diff_many(differs, file_id, old_path, new_path, old_kind, new_kind):
554
for file_differ in differs:
555
result = file_differ.diff(file_id, old_path, new_path, old_kind,
557
if result is not DiffPath.CANNOT_DIFF:
560
return DiffPath.CANNOT_DIFF
563
class DiffKindChange(object):
564
"""Special differ for file kind changes.
566
Represents kind change as deletion + creation. Uses the other differs
569
def __init__(self, differs):
570
self.differs = differs
576
def from_diff_tree(klass, diff_tree):
577
return klass(diff_tree.differs)
579
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
580
"""Perform comparison
582
:param file_id: The file_id of the file to compare
583
:param old_path: Path of the file in the old tree
584
:param new_path: Path of the file in the new tree
585
:param old_kind: Old file-kind of the file
586
:param new_kind: New file-kind of the file
588
if None in (old_kind, new_kind):
589
return DiffPath.CANNOT_DIFF
590
result = DiffPath._diff_many(self.differs, file_id, old_path,
591
new_path, old_kind, None)
592
if result is DiffPath.CANNOT_DIFF:
594
return DiffPath._diff_many(self.differs, file_id, old_path, new_path,
598
class DiffDirectory(DiffPath):
600
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
601
"""Perform comparison between two directories. (dummy)
604
if 'directory' not in (old_kind, new_kind):
605
return self.CANNOT_DIFF
606
if old_kind not in ('directory', None):
607
return self.CANNOT_DIFF
608
if new_kind not in ('directory', None):
609
return self.CANNOT_DIFF
613
class DiffSymlink(DiffPath):
615
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
616
"""Perform comparison between two symlinks
618
:param file_id: The file_id of the file to compare
619
:param old_path: Path of the file in the old tree
620
:param new_path: Path of the file in the new tree
621
:param old_kind: Old file-kind of the file
622
:param new_kind: New file-kind of the file
624
if 'symlink' not in (old_kind, new_kind):
625
return self.CANNOT_DIFF
626
if old_kind == 'symlink':
627
old_target = self.old_tree.get_symlink_target(file_id)
628
elif old_kind is None:
631
return self.CANNOT_DIFF
632
if new_kind == 'symlink':
633
new_target = self.new_tree.get_symlink_target(file_id)
634
elif new_kind is None:
637
return self.CANNOT_DIFF
638
return self.diff_symlink(old_target, new_target)
640
def diff_symlink(self, old_target, new_target):
641
if old_target is None:
642
self.to_file.write('=== target is %r\n' % new_target)
643
elif new_target is None:
644
self.to_file.write('=== target was %r\n' % old_target)
646
self.to_file.write('=== target changed %r => %r\n' %
647
(old_target, new_target))
651
class DiffText(DiffPath):
387
653
# GNU Patch uses the epoch date to detect files that are being added
388
654
# or removed in a diff.
389
655
EPOCH_DATE = '1970-01-01 00:00:00 +0000'
391
# TODO: Generation of pseudo-diffs for added/deleted files could
392
# be usefully made into a much faster special case.
394
if external_diff_options:
395
assert isinstance(external_diff_options, basestring)
396
opts = external_diff_options.split()
397
def diff_file(olab, olines, nlab, nlines, to_file):
398
external_diff(olab, olines, nlab, nlines, to_file, opts)
400
diff_file = internal_diff
402
delta = new_tree.changes_from(old_tree,
403
specific_files=specific_files,
404
extra_trees=extra_trees, require_versioned=True)
407
for path, file_id, kind in delta.removed:
409
print >>to_file, '=== removed %s %r' % (kind, path.encode('utf8'))
410
old_name = '%s%s\t%s' % (old_label, path,
411
_patch_header_date(old_tree, file_id, path))
412
new_name = '%s%s\t%s' % (new_label, path, EPOCH_DATE)
413
old_tree.inventory[file_id].diff(diff_file, old_name, old_tree,
414
new_name, None, None, to_file)
415
for path, file_id, kind in delta.added:
417
print >>to_file, '=== added %s %r' % (kind, path.encode('utf8'))
418
old_name = '%s%s\t%s' % (old_label, path, EPOCH_DATE)
419
new_name = '%s%s\t%s' % (new_label, path,
420
_patch_header_date(new_tree, file_id, path))
421
new_tree.inventory[file_id].diff(diff_file, new_name, new_tree,
422
old_name, None, None, to_file,
424
for (old_path, new_path, file_id, kind,
425
text_modified, meta_modified) in delta.renamed:
427
prop_str = get_prop_change(meta_modified)
428
print >>to_file, '=== renamed %s %r => %r%s' % (
429
kind, old_path.encode('utf8'),
430
new_path.encode('utf8'), prop_str)
431
old_name = '%s%s\t%s' % (old_label, old_path,
432
_patch_header_date(old_tree, file_id,
434
new_name = '%s%s\t%s' % (new_label, new_path,
435
_patch_header_date(new_tree, file_id,
437
_maybe_diff_file_or_symlink(old_name, old_tree, file_id,
439
text_modified, kind, to_file, diff_file)
440
for path, file_id, kind, text_modified, meta_modified in delta.modified:
442
prop_str = get_prop_change(meta_modified)
443
print >>to_file, '=== modified %s %r%s' % (kind, path.encode('utf8'), prop_str)
444
old_name = '%s%s\t%s' % (old_label, path,
445
_patch_header_date(old_tree, file_id, path))
446
new_name = '%s%s\t%s' % (new_label, path,
447
_patch_header_date(new_tree, file_id, path))
449
_maybe_diff_file_or_symlink(old_name, old_tree, file_id,
451
True, kind, to_file, diff_file)
456
def _patch_header_date(tree, file_id, path):
457
"""Returns a timestamp suitable for use in a patch header."""
458
tm = time.gmtime(tree.get_file_mtime(file_id, path))
459
return time.strftime('%Y-%m-%d %H:%M:%S +0000', tm)
462
def _raise_if_nonexistent(paths, old_tree, new_tree):
463
"""Complain if paths are not in either inventory or tree.
465
It's OK with the files exist in either tree's inventory, or
466
if they exist in the tree but are not versioned.
468
This can be used by operations such as bzr status that can accept
469
unknown or ignored files.
657
def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8',
658
old_label='', new_label='', text_differ=internal_diff):
659
DiffPath.__init__(self, old_tree, new_tree, to_file, path_encoding)
660
self.text_differ = text_differ
661
self.old_label = old_label
662
self.new_label = new_label
663
self.path_encoding = path_encoding
665
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
666
"""Compare two files in unified diff format
668
:param file_id: The file_id of the file to compare
669
:param old_path: Path of the file in the old tree
670
:param new_path: Path of the file in the new tree
671
:param old_kind: Old file-kind of the file
672
:param new_kind: New file-kind of the file
674
if 'file' not in (old_kind, new_kind):
675
return self.CANNOT_DIFF
676
from_file_id = to_file_id = file_id
677
if old_kind == 'file':
678
old_date = _patch_header_date(self.old_tree, file_id, old_path)
679
elif old_kind is None:
680
old_date = self.EPOCH_DATE
683
return self.CANNOT_DIFF
684
if new_kind == 'file':
685
new_date = _patch_header_date(self.new_tree, file_id, new_path)
686
elif new_kind is None:
687
new_date = self.EPOCH_DATE
690
return self.CANNOT_DIFF
691
from_label = '%s%s\t%s' % (self.old_label, old_path, old_date)
692
to_label = '%s%s\t%s' % (self.new_label, new_path, new_date)
693
return self.diff_text(from_file_id, to_file_id, from_label, to_label,
696
def diff_text(self, from_file_id, to_file_id, from_label, to_label,
697
from_path=None, to_path=None):
698
"""Diff the content of given files in two trees
700
:param from_file_id: The id of the file in the from tree. If None,
701
the file is not present in the from tree.
702
:param to_file_id: The id of the file in the to tree. This may refer
703
to a different file from from_file_id. If None,
704
the file is not present in the to tree.
705
:param from_path: The path in the from tree or None if unknown.
706
:param to_path: The path in the to tree or None if unknown.
708
def _get_text(tree, file_id, path):
709
if file_id is not None:
710
return tree.get_file(file_id, path).readlines()
714
from_text = _get_text(self.old_tree, from_file_id, from_path)
715
to_text = _get_text(self.new_tree, to_file_id, to_path)
716
self.text_differ(from_label, from_text, to_label, to_text,
718
except errors.BinaryFile:
720
("Binary files %s and %s differ\n" %
721
(from_label, to_label)).encode(self.path_encoding))
725
class DiffFromTool(DiffPath):
727
def __init__(self, command_template, old_tree, new_tree, to_file,
728
path_encoding='utf-8'):
729
DiffPath.__init__(self, old_tree, new_tree, to_file, path_encoding)
730
self.command_template = command_template
731
self._root = osutils.mkdtemp(prefix='bzr-diff-')
734
def from_string(klass, command_string, old_tree, new_tree, to_file,
735
path_encoding='utf-8'):
736
command_template = cmdline.split(command_string)
737
if '@' not in command_string:
738
command_template.extend(['@old_path', '@new_path'])
739
return klass(command_template, old_tree, new_tree, to_file,
743
def make_from_diff_tree(klass, command_string):
744
def from_diff_tree(diff_tree):
745
return klass.from_string(command_string, diff_tree.old_tree,
746
diff_tree.new_tree, diff_tree.to_file)
747
return from_diff_tree
749
def _get_command(self, old_path, new_path):
750
my_map = {'old_path': old_path, 'new_path': new_path}
751
return [AtTemplate(t).substitute(my_map) for t in
752
self.command_template]
754
def _execute(self, old_path, new_path):
755
command = self._get_command(old_path, new_path)
757
proc = subprocess.Popen(command, stdout=subprocess.PIPE,
760
if e.errno == errno.ENOENT:
761
raise errors.ExecutableMissing(command[0])
764
self.to_file.write(proc.stdout.read())
767
def _try_symlink_root(self, tree, prefix):
768
if (getattr(tree, 'abspath', None) is None
769
or not osutils.host_os_dereferences_symlinks()):
772
os.symlink(tree.abspath(''), osutils.pathjoin(self._root, prefix))
774
if e.errno != errno.EEXIST:
778
def _write_file(self, file_id, tree, prefix, relpath, force_temp=False,
780
if not force_temp and isinstance(tree, WorkingTree):
781
return tree.abspath(tree.id2path(file_id))
783
full_path = osutils.pathjoin(self._root, prefix, relpath)
784
if not force_temp and self._try_symlink_root(tree, prefix):
786
parent_dir = osutils.dirname(full_path)
788
os.makedirs(parent_dir)
790
if e.errno != errno.EEXIST:
792
source = tree.get_file(file_id, relpath)
794
target = open(full_path, 'wb')
796
osutils.pumpfile(source, target)
802
mtime = tree.get_file_mtime(file_id)
803
except errors.FileTimestampUnavailable:
806
os.utime(full_path, (mtime, mtime))
808
osutils.make_readonly(full_path)
811
def _prepare_files(self, file_id, old_path, new_path, force_temp=False,
812
allow_write_new=False):
813
old_disk_path = self._write_file(file_id, self.old_tree, 'old',
814
old_path, force_temp)
815
new_disk_path = self._write_file(file_id, self.new_tree, 'new',
816
new_path, force_temp,
817
allow_write=allow_write_new)
818
return old_disk_path, new_disk_path
822
osutils.rmtree(self._root)
824
if e.errno != errno.ENOENT:
825
mutter("The temporary directory \"%s\" was not "
826
"cleanly removed: %s." % (self._root, e))
828
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
829
if (old_kind, new_kind) != ('file', 'file'):
830
return DiffPath.CANNOT_DIFF
831
(old_disk_path, new_disk_path) = self._prepare_files(
832
file_id, old_path, new_path)
833
self._execute(old_disk_path, new_disk_path)
835
def edit_file(self, file_id):
836
"""Use this tool to edit a file.
838
A temporary copy will be edited, and the new contents will be
841
:param file_id: The id of the file to edit.
842
:return: The new contents of the file.
844
old_path = self.old_tree.id2path(file_id)
845
new_path = self.new_tree.id2path(file_id)
846
new_abs_path = self._prepare_files(file_id, old_path, new_path,
847
allow_write_new=True,
849
command = self._get_command(osutils.pathjoin('old', old_path),
850
osutils.pathjoin('new', new_path))
851
subprocess.call(command, cwd=self._root)
852
new_file = open(new_abs_path, 'r')
854
return new_file.read()
859
class DiffTree(object):
860
"""Provides textual representations of the difference between two trees.
862
A DiffTree examines two trees and where a file-id has altered
863
between them, generates a textual representation of the difference.
864
DiffTree uses a sequence of DiffPath objects which are each
865
given the opportunity to handle a given altered fileid. The list
866
of DiffPath objects can be extended globally by appending to
867
DiffTree.diff_factories, or for a specific diff operation by
868
supplying the extra_factories option to the appropriate method.
471
mutter("check paths: %r", paths)
474
s = old_tree.filter_unversioned_files(paths)
475
s = new_tree.filter_unversioned_files(s)
476
s = [path for path in s if not new_tree.has_filename(path)]
478
raise errors.PathsDoNotExist(sorted(s))
481
def get_prop_change(meta_modified):
483
return " (properties changed)"
488
def _maybe_diff_file_or_symlink(old_path, old_tree, file_id,
489
new_path, new_tree, text_modified,
490
kind, to_file, diff_file):
492
new_entry = new_tree.inventory[file_id]
493
old_tree.inventory[file_id].diff(diff_file,
871
# list of factories that can provide instances of DiffPath objects
872
# may be extended by plugins.
873
diff_factories = [DiffSymlink.from_diff_tree,
874
DiffDirectory.from_diff_tree]
876
def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8',
877
diff_text=None, extra_factories=None):
880
:param old_tree: Tree to show as old in the comparison
881
:param new_tree: Tree to show as new in the comparison
882
:param to_file: File to write comparision to
883
:param path_encoding: Character encoding to write paths in
884
:param diff_text: DiffPath-type object to use as a last resort for
886
:param extra_factories: Factories of DiffPaths to try before any other
888
if diff_text is None:
889
diff_text = DiffText(old_tree, new_tree, to_file, path_encoding,
890
'', '', internal_diff)
891
self.old_tree = old_tree
892
self.new_tree = new_tree
893
self.to_file = to_file
894
self.path_encoding = path_encoding
896
if extra_factories is not None:
897
self.differs.extend(f(self) for f in extra_factories)
898
self.differs.extend(f(self) for f in self.diff_factories)
899
self.differs.extend([diff_text, DiffKindChange.from_diff_tree(self)])
902
def from_trees_options(klass, old_tree, new_tree, to_file,
903
path_encoding, external_diff_options, old_label,
905
"""Factory for producing a DiffTree.
907
Designed to accept options used by show_diff_trees.
908
:param old_tree: The tree to show as old in the comparison
909
:param new_tree: The tree to show as new in the comparison
910
:param to_file: File to write comparisons to
911
:param path_encoding: Character encoding to use for writing paths
912
:param external_diff_options: If supplied, use the installed diff
913
binary to perform file comparison, using supplied options.
914
:param old_label: Prefix to use for old file labels
915
:param new_label: Prefix to use for new file labels
916
:param using: Commandline to use to invoke an external diff tool
918
if using is not None:
919
extra_factories = [DiffFromTool.make_from_diff_tree(using)]
922
if external_diff_options:
923
opts = external_diff_options.split()
924
def diff_file(olab, olines, nlab, nlines, to_file):
925
external_diff(olab, olines, nlab, nlines, to_file, opts)
927
diff_file = internal_diff
928
diff_text = DiffText(old_tree, new_tree, to_file, path_encoding,
929
old_label, new_label, diff_file)
930
return klass(old_tree, new_tree, to_file, path_encoding, diff_text,
933
def show_diff(self, specific_files, extra_trees=None):
934
"""Write tree diff to self.to_file
936
:param specific_files: the specific files to compare (recursive)
937
:param extra_trees: extra trees to use for mapping paths to file_ids
940
return self._show_diff(specific_files, extra_trees)
942
for differ in self.differs:
945
def _show_diff(self, specific_files, extra_trees):
946
# TODO: Generation of pseudo-diffs for added/deleted files could
947
# be usefully made into a much faster special case.
948
iterator = self.new_tree.iter_changes(self.old_tree,
949
specific_files=specific_files,
950
extra_trees=extra_trees,
951
require_versioned=True)
953
def changes_key(change):
954
old_path, new_path = change[1]
959
def get_encoded_path(path):
961
return path.encode(self.path_encoding, "replace")
962
for (file_id, paths, changed_content, versioned, parent, name, kind,
963
executable) in sorted(iterator, key=changes_key):
964
# The root does not get diffed, and items with no known kind (that
965
# is, missing) in both trees are skipped as well.
966
if parent == (None, None) or kind == (None, None):
968
oldpath, newpath = paths
969
oldpath_encoded = get_encoded_path(paths[0])
970
newpath_encoded = get_encoded_path(paths[1])
971
old_present = (kind[0] is not None and versioned[0])
972
new_present = (kind[1] is not None and versioned[1])
973
renamed = (parent[0], name[0]) != (parent[1], name[1])
975
properties_changed = []
976
properties_changed.extend(get_executable_change(executable[0], executable[1]))
978
if properties_changed:
979
prop_str = " (properties changed: %s)" % (", ".join(properties_changed),)
983
if (old_present, new_present) == (True, False):
984
self.to_file.write("=== removed %s '%s'\n" %
985
(kind[0], oldpath_encoded))
987
elif (old_present, new_present) == (False, True):
988
self.to_file.write("=== added %s '%s'\n" %
989
(kind[1], newpath_encoded))
992
self.to_file.write("=== renamed %s '%s' => '%s'%s\n" %
993
(kind[0], oldpath_encoded, newpath_encoded, prop_str))
995
# if it was produced by iter_changes, it must be
996
# modified *somehow*, either content or execute bit.
997
self.to_file.write("=== modified %s '%s'%s\n" % (kind[0],
998
newpath_encoded, prop_str))
1000
self._diff(file_id, oldpath, newpath, kind[0], kind[1])
1006
def diff(self, file_id, old_path, new_path):
1007
"""Perform a diff of a single file
1009
:param file_id: file-id of the file
1010
:param old_path: The path of the file in the old tree
1011
:param new_path: The path of the file in the new tree
1014
old_kind = self.old_tree.kind(file_id)
1015
except (errors.NoSuchId, errors.NoSuchFile):
1018
new_kind = self.new_tree.kind(file_id)
1019
except (errors.NoSuchId, errors.NoSuchFile):
1021
self._diff(file_id, old_path, new_path, old_kind, new_kind)
1024
def _diff(self, file_id, old_path, new_path, old_kind, new_kind):
1025
result = DiffPath._diff_many(self.differs, file_id, old_path,
1026
new_path, old_kind, new_kind)
1027
if result is DiffPath.CANNOT_DIFF:
1028
error_path = new_path
1029
if error_path is None:
1030
error_path = old_path
1031
raise errors.NoDiffFound(error_path)
1034
format_registry = Registry()
1035
format_registry.register('default', DiffTree)