126
220
diffcmd.append('-u')
129
223
diffcmd.extend(diff_opts)
131
rc = os.spawnvp(os.P_WAIT, 'diff', diffcmd)
133
if rc != 0 and rc != 1:
225
pipe = _spawn_external_diff(diffcmd, capture_errors=True)
226
out,err = pipe.communicate()
229
# internal_diff() adds a trailing newline, add one here for consistency
232
# 'diff' gives retcode == 2 for all sorts of errors
233
# one of those is 'Binary files differ'.
234
# Bad options could also be the problem.
235
# 'Binary files' is not a real error, so we suppress that error.
238
# Since we got here, we want to make sure to give an i18n error
239
pipe = _spawn_external_diff(diffcmd, capture_errors=False)
240
out, err = pipe.communicate()
242
# Write out the new i18n diff response
243
to_file.write(out+'\n')
244
if pipe.returncode != 2:
245
raise errors.BzrError(
246
'external diff failed with exit code 2'
247
' when run with LANG=C and LC_ALL=C,'
248
' but not when run natively: %r' % (diffcmd,))
250
first_line = lang_c_out.split('\n', 1)[0]
251
# Starting with diffutils 2.8.4 the word "binary" was dropped.
252
m = re.match('^(binary )?files.*differ$', first_line, re.I)
254
raise errors.BzrError('external diff failed with exit code 2;'
255
' command: %r' % (diffcmd,))
257
# Binary files differ, just return
260
# If we got to here, we haven't written out the output of diff
134
264
# returns 1 if files differ; that's OK
136
266
msg = 'signal %d' % (-rc)
138
268
msg = 'exit code %d' % rc
140
raise BzrError('external diff failed with %s; command: %r' % (rc, diffcmd))
270
raise errors.BzrError('external diff failed with %s; command: %r'
142
275
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.
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)
277
# Clean up. Warn in case the files couldn't be deleted
278
# (in case windows still holds the file open, but not
279
# if the files have already been deleted)
281
os.remove(old_abspath)
283
if e.errno not in (errno.ENOENT,):
284
warning('Failed to delete temporary file: %s %s',
287
os.remove(new_abspath)
289
if e.errno not in (errno.ENOENT,):
290
warning('Failed to delete temporary file: %s %s',
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)
179
462
def show_diff_trees(old_tree, new_tree, to_file, specific_files=None,
180
external_diff_options=None):
463
external_diff_options=None,
464
old_label='a/', new_label='b/',
466
path_encoding='utf8',
181
469
"""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.
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)
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)
481
if format_cls is None:
482
format_cls = DiffTree
485
if extra_trees is not None:
486
for tree in 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)
497
if extra_trees is not None:
498
for tree in extra_trees:
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],)]
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(),
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):
653
# GNU Patch uses the epoch date to detect files that are being added
654
# or removed in a diff.
655
EPOCH_DATE = '1970-01-01 00:00:00 +0000'
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
osutils.make_readonly(full_path)
804
mtime = tree.get_file_mtime(file_id)
805
except errors.FileTimestampUnavailable:
807
os.utime(full_path, (mtime, mtime))
810
def _prepare_files(self, file_id, old_path, new_path, force_temp=False,
811
allow_write_new=False):
812
old_disk_path = self._write_file(file_id, self.old_tree, 'old',
813
old_path, force_temp)
814
new_disk_path = self._write_file(file_id, self.new_tree, 'new',
815
new_path, force_temp,
816
allow_write=allow_write_new)
817
return old_disk_path, new_disk_path
821
osutils.rmtree(self._root)
823
if e.errno != errno.ENOENT:
824
mutter("The temporary directory \"%s\" was not "
825
"cleanly removed: %s." % (self._root, e))
827
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
828
if (old_kind, new_kind) != ('file', 'file'):
829
return DiffPath.CANNOT_DIFF
830
(old_disk_path, new_disk_path) = self._prepare_files(
831
file_id, old_path, new_path)
832
self._execute(old_disk_path, new_disk_path)
834
def edit_file(self, file_id):
835
"""Use this tool to edit a file.
837
A temporary copy will be edited, and the new contents will be
840
:param file_id: The id of the file to edit.
841
:return: The new contents of the file.
843
old_path = self.old_tree.id2path(file_id)
844
new_path = self.new_tree.id2path(file_id)
845
new_abs_path = self._prepare_files(file_id, old_path, new_path,
846
allow_write_new=True,
848
command = self._get_command(osutils.pathjoin('old', old_path),
849
osutils.pathjoin('new', new_path))
850
subprocess.call(command, cwd=self._root)
851
new_file = open(new_abs_path, 'r')
853
return new_file.read()
858
class DiffTree(object):
859
"""Provides textual representations of the difference between two trees.
861
A DiffTree examines two trees and where a file-id has altered
862
between them, generates a textual representation of the difference.
863
DiffTree uses a sequence of DiffPath objects which are each
864
given the opportunity to handle a given altered fileid. The list
865
of DiffPath objects can be extended globally by appending to
866
DiffTree.diff_factories, or for a specific diff operation by
867
supplying the extra_factories option to the appropriate method.
870
# list of factories that can provide instances of DiffPath objects
871
# may be extended by plugins.
872
diff_factories = [DiffSymlink.from_diff_tree,
873
DiffDirectory.from_diff_tree]
875
def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8',
876
diff_text=None, extra_factories=None):
879
:param old_tree: Tree to show as old in the comparison
880
:param new_tree: Tree to show as new in the comparison
881
:param to_file: File to write comparision to
882
:param path_encoding: Character encoding to write paths in
883
:param diff_text: DiffPath-type object to use as a last resort for
885
:param extra_factories: Factories of DiffPaths to try before any other
887
if diff_text is None:
888
diff_text = DiffText(old_tree, new_tree, to_file, path_encoding,
889
'', '', internal_diff)
890
self.old_tree = old_tree
891
self.new_tree = new_tree
892
self.to_file = to_file
893
self.path_encoding = path_encoding
895
if extra_factories is not None:
896
self.differs.extend(f(self) for f in extra_factories)
897
self.differs.extend(f(self) for f in self.diff_factories)
898
self.differs.extend([diff_text, DiffKindChange.from_diff_tree(self)])
901
def from_trees_options(klass, old_tree, new_tree, to_file,
902
path_encoding, external_diff_options, old_label,
904
"""Factory for producing a DiffTree.
906
Designed to accept options used by show_diff_trees.
907
:param old_tree: The tree to show as old in the comparison
908
:param new_tree: The tree to show as new in the comparison
909
:param to_file: File to write comparisons to
910
:param path_encoding: Character encoding to use for writing paths
911
:param external_diff_options: If supplied, use the installed diff
912
binary to perform file comparison, using supplied options.
913
:param old_label: Prefix to use for old file labels
914
:param new_label: Prefix to use for new file labels
915
:param using: Commandline to use to invoke an external diff tool
917
if using is not None:
918
extra_factories = [DiffFromTool.make_from_diff_tree(using)]
921
if external_diff_options:
922
opts = external_diff_options.split()
923
def diff_file(olab, olines, nlab, nlines, to_file):
924
external_diff(olab, olines, nlab, nlines, to_file, opts)
926
diff_file = internal_diff
927
diff_text = DiffText(old_tree, new_tree, to_file, path_encoding,
928
old_label, new_label, diff_file)
929
return klass(old_tree, new_tree, to_file, path_encoding, diff_text,
932
def show_diff(self, specific_files, extra_trees=None):
933
"""Write tree diff to self.to_file
935
:param specific_files: the specific files to compare (recursive)
936
:param extra_trees: extra trees to use for mapping paths to file_ids
939
return self._show_diff(specific_files, extra_trees)
941
for differ in self.differs:
944
def _show_diff(self, specific_files, extra_trees):
945
# TODO: Generation of pseudo-diffs for added/deleted files could
946
# be usefully made into a much faster special case.
947
iterator = self.new_tree.iter_changes(self.old_tree,
948
specific_files=specific_files,
949
extra_trees=extra_trees,
950
require_versioned=True)
952
def changes_key(change):
953
old_path, new_path = change[1]
958
def get_encoded_path(path):
960
return path.encode(self.path_encoding, "replace")
961
for (file_id, paths, changed_content, versioned, parent, name, kind,
962
executable) in sorted(iterator, key=changes_key):
963
# The root does not get diffed, and items with no known kind (that
964
# is, missing) in both trees are skipped as well.
965
if parent == (None, None) or kind == (None, None):
967
oldpath, newpath = paths
968
oldpath_encoded = get_encoded_path(paths[0])
969
newpath_encoded = get_encoded_path(paths[1])
970
old_present = (kind[0] is not None and versioned[0])
971
new_present = (kind[1] is not None and versioned[1])
972
renamed = (parent[0], name[0]) != (parent[1], name[1])
974
properties_changed = []
975
properties_changed.extend(get_executable_change(executable[0], executable[1]))
977
if properties_changed:
978
prop_str = " (properties changed: %s)" % (", ".join(properties_changed),)
982
if (old_present, new_present) == (True, False):
983
self.to_file.write("=== removed %s '%s'\n" %
984
(kind[0], oldpath_encoded))
986
elif (old_present, new_present) == (False, True):
987
self.to_file.write("=== added %s '%s'\n" %
988
(kind[1], newpath_encoded))
991
self.to_file.write("=== renamed %s '%s' => '%s'%s\n" %
992
(kind[0], oldpath_encoded, newpath_encoded, prop_str))
994
# if it was produced by iter_changes, it must be
995
# modified *somehow*, either content or execute bit.
996
self.to_file.write("=== modified %s '%s'%s\n" % (kind[0],
997
newpath_encoded, prop_str))
999
self._diff(file_id, oldpath, newpath, kind[0], kind[1])
1005
def diff(self, file_id, old_path, new_path):
1006
"""Perform a diff of a single file
1008
:param file_id: file-id of the file
1009
:param old_path: The path of the file in the old tree
1010
:param new_path: The path of the file in the new tree
1013
old_kind = self.old_tree.kind(file_id)
1014
except (errors.NoSuchId, errors.NoSuchFile):
1017
new_kind = self.new_tree.kind(file_id)
1018
except (errors.NoSuchId, errors.NoSuchFile):
1020
self._diff(file_id, old_path, new_path, old_kind, new_kind)
1023
def _diff(self, file_id, old_path, new_path, old_kind, new_kind):
1024
result = DiffPath._diff_many(self.differs, file_id, old_path,
1025
new_path, old_kind, new_kind)
1026
if result is DiffPath.CANNOT_DIFF:
1027
error_path = new_path
1028
if error_path is None:
1029
error_path = old_path
1030
raise errors.NoDiffFound(error_path)
1033
format_registry = Registry()
1034
format_registry.register('default', DiffTree)