127
207
diffcmd.extend(diff_opts)
129
rc = os.spawnvp(os.P_WAIT, 'diff', diffcmd)
209
pipe = _spawn_external_diff(diffcmd, capture_errors=True)
210
out,err = pipe.communicate()
131
if rc != 0 and rc != 1:
213
# internal_diff() adds a trailing newline, add one here for consistency
216
# 'diff' gives retcode == 2 for all sorts of errors
217
# one of those is 'Binary files differ'.
218
# Bad options could also be the problem.
219
# 'Binary files' is not a real error, so we suppress that error.
222
# Since we got here, we want to make sure to give an i18n error
223
pipe = _spawn_external_diff(diffcmd, capture_errors=False)
224
out, err = pipe.communicate()
226
# Write out the new i18n diff response
227
to_file.write(out+'\n')
228
if pipe.returncode != 2:
229
raise errors.BzrError(
230
'external diff failed with exit code 2'
231
' when run with LANG=C and LC_ALL=C,'
232
' but not when run natively: %r' % (diffcmd,))
234
first_line = lang_c_out.split('\n', 1)[0]
235
# Starting with diffutils 2.8.4 the word "binary" was dropped.
236
m = re.match('^(binary )?files.*differ$', first_line, re.I)
238
raise errors.BzrError('external diff failed with exit code 2;'
239
' command: %r' % (diffcmd,))
241
# Binary files differ, just return
244
# If we got to here, we haven't written out the output of diff
132
248
# returns 1 if files differ; that's OK
134
250
msg = 'signal %d' % (-rc)
136
252
msg = 'exit code %d' % rc
138
raise BzrError('external diff failed with %s; command: %r' % (rc, diffcmd))
254
raise errors.BzrError('external diff failed with %s; command: %r'
140
259
oldtmpf.close() # and delete
143
def show_diff(b, revision, specific_files, external_diff_options=None,
144
revision2=None, output=None):
145
"""Shortcut for showing the diff to the working tree.
151
None for each, or otherwise the old revision to compare against.
261
# Clean up. Warn in case the files couldn't be deleted
262
# (in case windows still holds the file open, but not
263
# if the files have already been deleted)
265
os.remove(old_abspath)
267
if e.errno not in (errno.ENOENT,):
268
warning('Failed to delete temporary file: %s %s',
271
os.remove(new_abspath)
273
if e.errno not in (errno.ENOENT,):
274
warning('Failed to delete temporary file: %s %s',
278
@deprecated_function(one_zero)
279
def diff_cmd_helper(tree, specific_files, external_diff_options,
280
old_revision_spec=None, new_revision_spec=None,
282
old_label='a/', new_label='b/'):
283
"""Helper for cmd_diff.
288
:param specific_files:
289
The specific files to compare, or None
291
:param external_diff_options:
292
If non-None, run an external diff, and pass it these options
294
:param old_revision_spec:
295
If None, use basis tree as old revision, otherwise use the tree for
296
the specified revision.
298
:param new_revision_spec:
299
If None, use working tree as new revision, otherwise use the tree for
300
the specified revision.
302
:param revision_specs:
303
Zero, one or two RevisionSpecs from the command line, saying what revisions
304
to compare. This can be passed as an alternative to the old_revision_spec
305
and new_revision_spec parameters.
153
307
The more general form is show_diff_trees(), where the caller
154
308
supplies any two trees.
161
old_tree = b.basis_tree()
163
old_tree = b.revision_tree(revision.in_history(b).rev_id)
165
if revision2 is None:
166
new_tree = b.working_tree()
168
new_tree = b.revision_tree(revision2.in_history(b).rev_id)
170
show_diff_trees(old_tree, new_tree, output, specific_files,
171
external_diff_options)
311
# TODO: perhaps remove the old parameters old_revision_spec and
312
# new_revision_spec, since this is only really for use from cmd_diff and
313
# it now always passes through a sequence of revision_specs -- mbp
318
revision = spec.in_store(tree.branch)
320
revision = spec.in_store(None)
321
revision_id = revision.rev_id
322
branch = revision.branch
323
return branch.repository.revision_tree(revision_id)
325
if revision_specs is not None:
326
assert (old_revision_spec is None
327
and new_revision_spec is None)
328
if len(revision_specs) > 0:
329
old_revision_spec = revision_specs[0]
330
if len(revision_specs) > 1:
331
new_revision_spec = revision_specs[1]
333
if old_revision_spec is None:
334
old_tree = tree.basis_tree()
336
old_tree = spec_tree(old_revision_spec)
338
if (new_revision_spec is None
339
or new_revision_spec.spec is None):
342
new_tree = spec_tree(new_revision_spec)
344
if new_tree is not tree:
345
extra_trees = (tree,)
349
return show_diff_trees(old_tree, new_tree, sys.stdout, specific_files,
350
external_diff_options,
351
old_label=old_label, new_label=new_label,
352
extra_trees=extra_trees)
355
def _get_trees_to_diff(path_list, revision_specs, old_url, new_url):
356
"""Get the trees and specific files to diff given a list of paths.
358
This method works out the trees to be diff'ed and the files of
359
interest within those trees.
362
the list of arguments passed to the diff command
363
:param revision_specs:
364
Zero, one or two RevisionSpecs from the diff command line,
365
saying what revisions to compare.
367
The url of the old branch or tree. If None, the tree to use is
368
taken from the first path, if any, or the current working tree.
370
The url of the new branch or tree. If None, the tree to use is
371
taken from the first path, if any, or the current working tree.
373
a tuple of (old_tree, new_tree, specific_files, extra_trees) where
374
extra_trees is a sequence of additional trees to search in for
377
# Get the old and new revision specs
378
old_revision_spec = None
379
new_revision_spec = None
380
if revision_specs is not None:
381
if len(revision_specs) > 0:
382
old_revision_spec = revision_specs[0]
384
old_url = old_revision_spec.get_branch()
385
if len(revision_specs) > 1:
386
new_revision_spec = revision_specs[1]
388
new_url = new_revision_spec.get_branch()
391
make_paths_wt_relative = True
392
consider_relpath = True
393
if path_list is None or len(path_list) == 0:
394
# If no path is given, the current working tree is used
395
default_location = u'.'
396
consider_relpath = False
397
elif old_url is not None and new_url is not None:
398
other_paths = path_list
399
make_paths_wt_relative = False
401
default_location = path_list[0]
402
other_paths = path_list[1:]
404
# Get the old location
407
old_url = default_location
408
working_tree, branch, relpath = \
409
bzrdir.BzrDir.open_containing_tree_or_branch(old_url)
410
if consider_relpath and relpath != '':
411
specific_files.append(relpath)
412
old_tree = _get_tree_to_diff(old_revision_spec, working_tree, branch)
414
# Get the new location
416
new_url = default_location
417
if new_url != old_url:
418
working_tree, branch, relpath = \
419
bzrdir.BzrDir.open_containing_tree_or_branch(new_url)
420
if consider_relpath and relpath != '':
421
specific_files.append(relpath)
422
new_tree = _get_tree_to_diff(new_revision_spec, working_tree, branch,
423
basis_is_default=working_tree is None)
425
# Get the specific files (all files is None, no files is [])
426
if make_paths_wt_relative and working_tree is not None:
427
other_paths = _relative_paths_in_tree(working_tree, other_paths)
428
specific_files.extend(other_paths)
429
if len(specific_files) == 0:
430
specific_files = None
432
# Get extra trees that ought to be searched for file-ids
434
if working_tree is not None and working_tree not in (old_tree, new_tree):
435
extra_trees = (working_tree,)
436
return old_tree, new_tree, specific_files, extra_trees
439
def _get_tree_to_diff(spec, tree=None, branch=None, basis_is_default=True):
440
if branch is None and tree is not None:
442
if spec is None or spec.spec is None:
445
return tree.basis_tree()
447
return branch.basis_tree()
450
revision = spec.in_store(branch)
451
revision_id = revision.rev_id
452
rev_branch = revision.branch
453
return rev_branch.repository.revision_tree(revision_id)
456
def _relative_paths_in_tree(tree, paths):
457
"""Get the relative paths within a working tree.
459
Each path may be either an absolute path or a path relative to the
460
current working directory.
463
for filename in paths:
465
result.append(tree.relpath(osutils.dereference_path(filename)))
466
except errors.PathNotChild:
467
raise errors.BzrCommandError("Files are in different branches")
175
471
def show_diff_trees(old_tree, new_tree, to_file, specific_files=None,
176
external_diff_options=None):
472
external_diff_options=None,
473
old_label='a/', new_label='b/',
475
path_encoding='utf8',
177
477
"""Show in text form the changes from one tree to another.
180
If set, include only changes to these files.
483
Include only changes to these files - None for all changes.
182
485
external_diff_options
183
486
If set, use an external GNU diff and pass these options.
186
# TODO: Options to control putting on a prefix or suffix, perhaps as a format string
190
DEVNULL = '/dev/null'
191
# Windows users, don't panic about this filename -- it is a
192
# special signal to GNU patch that the file should be created or
193
# deleted respectively.
195
# TODO: Generation of pseudo-diffs for added/deleted files could
196
# be usefully made into a much faster special case.
198
if external_diff_options:
199
assert isinstance(external_diff_options, basestring)
200
opts = external_diff_options.split()
201
def diff_file(olab, olines, nlab, nlines, to_file):
202
external_diff(olab, olines, nlab, nlines, to_file, opts)
204
diff_file = internal_diff
207
delta = compare_trees(old_tree, new_tree, want_unchanged=False,
208
specific_files=specific_files)
210
for path, file_id, kind in delta.removed:
211
print >>to_file, '=== removed %s %r' % (kind, path)
212
old_tree.inventory[file_id].diff(diff_file, old_label + path, old_tree,
213
DEVNULL, None, None, to_file)
214
for path, file_id, kind in delta.added:
215
print >>to_file, '=== added %s %r' % (kind, path)
216
new_tree.inventory[file_id].diff(diff_file, new_label + path, new_tree,
217
DEVNULL, None, None, to_file,
219
for (old_path, new_path, file_id, kind,
220
text_modified, meta_modified) in delta.renamed:
221
prop_str = get_prop_change(meta_modified)
222
print >>to_file, '=== renamed %s %r => %r%s' % (
223
kind, old_path, new_path, prop_str)
224
_maybe_diff_file_or_symlink(old_label, old_path, old_tree, file_id,
225
new_label, new_path, new_tree,
226
text_modified, kind, to_file, diff_file)
227
for path, file_id, kind, text_modified, meta_modified in delta.modified:
228
prop_str = get_prop_change(meta_modified)
229
print >>to_file, '=== modified %s %r%s' % (kind, path, prop_str)
231
_maybe_diff_file_or_symlink(old_label, path, old_tree, file_id,
232
new_label, path, new_tree,
233
True, kind, to_file, diff_file)
489
If set, more Trees to use for looking up file ids
492
If set, the path will be encoded as specified, otherwise is supposed
497
if extra_trees is not None:
498
for tree in extra_trees:
502
differ = DiffTree.from_trees_options(old_tree, new_tree, to_file,
504
external_diff_options,
505
old_label, new_label, using)
506
return differ.show_diff(specific_files, extra_trees)
509
if extra_trees is not None:
510
for tree in extra_trees:
516
def _patch_header_date(tree, file_id, path):
517
"""Returns a timestamp suitable for use in a patch header."""
518
mtime = tree.get_file_mtime(file_id, path)
519
assert mtime is not None, \
520
"got an mtime of None for file-id %s, path %s in tree %s" % (
522
return timestamp.format_patch_date(mtime)
525
def _raise_if_nonexistent(paths, old_tree, new_tree):
526
"""Complain if paths are not in either inventory or tree.
528
It's OK with the files exist in either tree's inventory, or
529
if they exist in the tree but are not versioned.
531
This can be used by operations such as bzr status that can accept
532
unknown or ignored files.
534
mutter("check paths: %r", paths)
537
s = old_tree.filter_unversioned_files(paths)
538
s = new_tree.filter_unversioned_files(s)
539
s = [path for path in s if not new_tree.has_filename(path)]
541
raise errors.PathsDoNotExist(sorted(s))
544
@deprecated_function(one_three)
236
545
def get_prop_change(meta_modified):
237
546
if meta_modified:
238
547
return " (properties changed)"
243
def _maybe_diff_file_or_symlink(old_label, old_path, old_tree, file_id,
244
new_label, new_path, new_tree, text_modified,
245
kind, to_file, diff_file):
247
new_entry = new_tree.inventory[file_id]
248
old_tree.inventory[file_id].diff(diff_file,
249
old_label + old_path, old_tree,
250
new_label + new_path, new_entry,
551
def get_executable_change(old_is_x, new_is_x):
552
descr = { True:"+x", False:"-x", None:"??" }
553
if old_is_x != new_is_x:
554
return ["%s to %s" % (descr[old_is_x], descr[new_is_x],)]
559
class DiffPath(object):
560
"""Base type for command object that compare files"""
562
# The type or contents of the file were unsuitable for diffing
563
CANNOT_DIFF = 'CANNOT_DIFF'
564
# The file has changed in a semantic way
566
# The file content may have changed, but there is no semantic change
567
UNCHANGED = 'UNCHANGED'
569
def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8'):
572
:param old_tree: The tree to show as the old tree in the comparison
573
:param new_tree: The tree to show as new in the comparison
574
:param to_file: The file to write comparison data to
575
:param path_encoding: The character encoding to write paths in
577
self.old_tree = old_tree
578
self.new_tree = new_tree
579
self.to_file = to_file
580
self.path_encoding = path_encoding
586
def from_diff_tree(klass, diff_tree):
587
return klass(diff_tree.old_tree, diff_tree.new_tree,
588
diff_tree.to_file, diff_tree.path_encoding)
591
def _diff_many(differs, file_id, old_path, new_path, old_kind, new_kind):
592
for file_differ in differs:
593
result = file_differ.diff(file_id, old_path, new_path, old_kind,
595
if result is not DiffPath.CANNOT_DIFF:
598
return DiffPath.CANNOT_DIFF
601
class DiffKindChange(object):
602
"""Special differ for file kind changes.
604
Represents kind change as deletion + creation. Uses the other differs
607
def __init__(self, differs):
608
self.differs = differs
614
def from_diff_tree(klass, diff_tree):
615
return klass(diff_tree.differs)
617
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
618
"""Perform comparison
620
:param file_id: The file_id of the file to compare
621
:param old_path: Path of the file in the old tree
622
:param new_path: Path of the file in the new tree
623
:param old_kind: Old file-kind of the file
624
:param new_kind: New file-kind of the file
626
if None in (old_kind, new_kind):
627
return DiffPath.CANNOT_DIFF
628
result = DiffPath._diff_many(self.differs, file_id, old_path,
629
new_path, old_kind, None)
630
if result is DiffPath.CANNOT_DIFF:
632
return DiffPath._diff_many(self.differs, file_id, old_path, new_path,
636
class DiffDirectory(DiffPath):
638
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
639
"""Perform comparison between two directories. (dummy)
642
if 'directory' not in (old_kind, new_kind):
643
return self.CANNOT_DIFF
644
if old_kind not in ('directory', None):
645
return self.CANNOT_DIFF
646
if new_kind not in ('directory', None):
647
return self.CANNOT_DIFF
651
class DiffSymlink(DiffPath):
653
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
654
"""Perform comparison between two symlinks
656
:param file_id: The file_id of the file to compare
657
:param old_path: Path of the file in the old tree
658
:param new_path: Path of the file in the new tree
659
:param old_kind: Old file-kind of the file
660
:param new_kind: New file-kind of the file
662
if 'symlink' not in (old_kind, new_kind):
663
return self.CANNOT_DIFF
664
if old_kind == 'symlink':
665
old_target = self.old_tree.get_symlink_target(file_id)
666
elif old_kind is None:
669
return self.CANNOT_DIFF
670
if new_kind == 'symlink':
671
new_target = self.new_tree.get_symlink_target(file_id)
672
elif new_kind is None:
675
return self.CANNOT_DIFF
676
return self.diff_symlink(old_target, new_target)
678
def diff_symlink(self, old_target, new_target):
679
if old_target is None:
680
self.to_file.write('=== target is %r\n' % new_target)
681
elif new_target is None:
682
self.to_file.write('=== target was %r\n' % old_target)
684
self.to_file.write('=== target changed %r => %r\n' %
685
(old_target, new_target))
689
class DiffText(DiffPath):
691
# GNU Patch uses the epoch date to detect files that are being added
692
# or removed in a diff.
693
EPOCH_DATE = '1970-01-01 00:00:00 +0000'
695
def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8',
696
old_label='', new_label='', text_differ=internal_diff):
697
DiffPath.__init__(self, old_tree, new_tree, to_file, path_encoding)
698
self.text_differ = text_differ
699
self.old_label = old_label
700
self.new_label = new_label
701
self.path_encoding = path_encoding
703
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
704
"""Compare two files in unified diff format
706
:param file_id: The file_id of the file to compare
707
:param old_path: Path of the file in the old tree
708
:param new_path: Path of the file in the new tree
709
:param old_kind: Old file-kind of the file
710
:param new_kind: New file-kind of the file
712
if 'file' not in (old_kind, new_kind):
713
return self.CANNOT_DIFF
714
from_file_id = to_file_id = file_id
715
if old_kind == 'file':
716
old_date = _patch_header_date(self.old_tree, file_id, old_path)
717
elif old_kind is None:
718
old_date = self.EPOCH_DATE
721
return self.CANNOT_DIFF
722
if new_kind == 'file':
723
new_date = _patch_header_date(self.new_tree, file_id, new_path)
724
elif new_kind is None:
725
new_date = self.EPOCH_DATE
728
return self.CANNOT_DIFF
729
from_label = '%s%s\t%s' % (self.old_label, old_path, old_date)
730
to_label = '%s%s\t%s' % (self.new_label, new_path, new_date)
731
return self.diff_text(from_file_id, to_file_id, from_label, to_label)
733
def diff_text(self, from_file_id, to_file_id, from_label, to_label):
734
"""Diff the content of given files in two trees
736
:param from_file_id: The id of the file in the from tree. If None,
737
the file is not present in the from tree.
738
:param to_file_id: The id of the file in the to tree. This may refer
739
to a different file from from_file_id. If None,
740
the file is not present in the to tree.
742
def _get_text(tree, file_id):
743
if file_id is not None:
744
return tree.get_file(file_id).readlines()
748
from_text = _get_text(self.old_tree, from_file_id)
749
to_text = _get_text(self.new_tree, to_file_id)
750
self.text_differ(from_label, from_text, to_label, to_text,
752
except errors.BinaryFile:
754
("Binary files %s and %s differ\n" %
755
(from_label, to_label)).encode(self.path_encoding))
759
class DiffFromTool(DiffPath):
761
def __init__(self, command_template, old_tree, new_tree, to_file,
762
path_encoding='utf-8'):
763
DiffPath.__init__(self, old_tree, new_tree, to_file, path_encoding)
764
self.command_template = command_template
765
self._root = tempfile.mkdtemp(prefix='bzr-diff-')
768
def from_string(klass, command_string, old_tree, new_tree, to_file,
769
path_encoding='utf-8'):
770
command_template = commands.shlex_split_unicode(command_string)
771
command_template.extend(['%(old_path)s', '%(new_path)s'])
772
return klass(command_template, old_tree, new_tree, to_file,
776
def make_from_diff_tree(klass, command_string):
777
def from_diff_tree(diff_tree):
778
return klass.from_string(command_string, diff_tree.old_tree,
779
diff_tree.new_tree, diff_tree.to_file)
780
return from_diff_tree
782
def _get_command(self, old_path, new_path):
783
my_map = {'old_path': old_path, 'new_path': new_path}
784
return [t % my_map for t in self.command_template]
786
def _execute(self, old_path, new_path):
787
command = self._get_command(old_path, new_path)
789
proc = subprocess.Popen(command, stdout=subprocess.PIPE,
792
if e.errno == errno.ENOENT:
793
raise errors.ExecutableMissing(command[0])
796
self.to_file.write(proc.stdout.read())
799
def _try_symlink_root(self, tree, prefix):
800
if not (getattr(tree, 'abspath', None) is not None
801
and osutils.has_symlinks()):
804
os.symlink(tree.abspath(''), osutils.pathjoin(self._root, prefix))
806
if e.errno != errno.EEXIST:
810
def _write_file(self, file_id, tree, prefix, relpath):
811
full_path = osutils.pathjoin(self._root, prefix, relpath)
812
if self._try_symlink_root(tree, prefix):
814
parent_dir = osutils.dirname(full_path)
816
os.makedirs(parent_dir)
818
if e.errno != errno.EEXIST:
820
source = tree.get_file(file_id, relpath)
822
target = open(full_path, 'wb')
824
osutils.pumpfile(source, target)
829
osutils.make_readonly(full_path)
830
mtime = tree.get_file_mtime(file_id)
831
os.utime(full_path, (mtime, mtime))
834
def _prepare_files(self, file_id, old_path, new_path):
835
old_disk_path = self._write_file(file_id, self.old_tree, 'old',
837
new_disk_path = self._write_file(file_id, self.new_tree, 'new',
839
return old_disk_path, new_disk_path
842
osutils.rmtree(self._root)
844
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
845
if (old_kind, new_kind) != ('file', 'file'):
846
return DiffPath.CANNOT_DIFF
847
self._prepare_files(file_id, old_path, new_path)
848
self._execute(osutils.pathjoin('old', old_path),
849
osutils.pathjoin('new', new_path))
852
class DiffTree(object):
853
"""Provides textual representations of the difference between two trees.
855
A DiffTree examines two trees and where a file-id has altered
856
between them, generates a textual representation of the difference.
857
DiffTree uses a sequence of DiffPath objects which are each
858
given the opportunity to handle a given altered fileid. The list
859
of DiffPath objects can be extended globally by appending to
860
DiffTree.diff_factories, or for a specific diff operation by
861
supplying the extra_factories option to the appropriate method.
864
# list of factories that can provide instances of DiffPath objects
865
# may be extended by plugins.
866
diff_factories = [DiffSymlink.from_diff_tree,
867
DiffDirectory.from_diff_tree]
869
def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8',
870
diff_text=None, extra_factories=None):
873
:param old_tree: Tree to show as old in the comparison
874
:param new_tree: Tree to show as new in the comparison
875
:param to_file: File to write comparision to
876
:param path_encoding: Character encoding to write paths in
877
:param diff_text: DiffPath-type object to use as a last resort for
879
:param extra_factories: Factories of DiffPaths to try before any other
881
if diff_text is None:
882
diff_text = DiffText(old_tree, new_tree, to_file, path_encoding,
883
'', '', internal_diff)
884
self.old_tree = old_tree
885
self.new_tree = new_tree
886
self.to_file = to_file
887
self.path_encoding = path_encoding
889
if extra_factories is not None:
890
self.differs.extend(f(self) for f in extra_factories)
891
self.differs.extend(f(self) for f in self.diff_factories)
892
self.differs.extend([diff_text, DiffKindChange.from_diff_tree(self)])
895
def from_trees_options(klass, old_tree, new_tree, to_file,
896
path_encoding, external_diff_options, old_label,
898
"""Factory for producing a DiffTree.
900
Designed to accept options used by show_diff_trees.
901
:param old_tree: The tree to show as old in the comparison
902
:param new_tree: The tree to show as new in the comparison
903
:param to_file: File to write comparisons to
904
:param path_encoding: Character encoding to use for writing paths
905
:param external_diff_options: If supplied, use the installed diff
906
binary to perform file comparison, using supplied options.
907
:param old_label: Prefix to use for old file labels
908
:param new_label: Prefix to use for new file labels
909
:param using: Commandline to use to invoke an external diff tool
911
if using is not None:
912
extra_factories = [DiffFromTool.make_from_diff_tree(using)]
915
if external_diff_options:
916
assert isinstance(external_diff_options, basestring)
917
opts = external_diff_options.split()
918
def diff_file(olab, olines, nlab, nlines, to_file):
919
external_diff(olab, olines, nlab, nlines, to_file, opts)
921
diff_file = internal_diff
922
diff_text = DiffText(old_tree, new_tree, to_file, path_encoding,
923
old_label, new_label, diff_file)
924
return klass(old_tree, new_tree, to_file, path_encoding, diff_text,
927
def show_diff(self, specific_files, extra_trees=None):
928
"""Write tree diff to self.to_file
930
:param sepecific_files: the specific files to compare (recursive)
931
:param extra_trees: extra trees to use for mapping paths to file_ids
934
return self._show_diff(specific_files, extra_trees)
936
for differ in self.differs:
939
def _show_diff(self, specific_files, extra_trees):
940
# TODO: Generation of pseudo-diffs for added/deleted files could
941
# be usefully made into a much faster special case.
942
iterator = self.new_tree.iter_changes(self.old_tree,
943
specific_files=specific_files,
944
extra_trees=extra_trees,
945
require_versioned=True)
947
def changes_key(change):
948
old_path, new_path = change[1]
953
def get_encoded_path(path):
955
return path.encode(self.path_encoding, "replace")
956
for (file_id, paths, changed_content, versioned, parent, name, kind,
957
executable) in sorted(iterator, key=changes_key):
958
if parent == (None, None):
960
oldpath, newpath = paths
961
oldpath_encoded = get_encoded_path(paths[0])
962
newpath_encoded = get_encoded_path(paths[1])
963
old_present = (kind[0] is not None and versioned[0])
964
new_present = (kind[1] is not None and versioned[1])
965
renamed = (parent[0], name[0]) != (parent[1], name[1])
967
properties_changed = []
968
properties_changed.extend(get_executable_change(executable[0], executable[1]))
970
if properties_changed:
971
prop_str = " (properties changed: %s)" % (", ".join(properties_changed),)
975
if (old_present, new_present) == (True, False):
976
self.to_file.write("=== removed %s '%s'\n" %
977
(kind[0], oldpath_encoded))
979
elif (old_present, new_present) == (False, True):
980
self.to_file.write("=== added %s '%s'\n" %
981
(kind[1], newpath_encoded))
984
self.to_file.write("=== renamed %s '%s' => '%s'%s\n" %
985
(kind[0], oldpath_encoded, newpath_encoded, prop_str))
987
# if it was produced by iter_changes, it must be
988
# modified *somehow*, either content or execute bit.
989
self.to_file.write("=== modified %s '%s'%s\n" % (kind[0],
990
newpath_encoded, prop_str))
992
self.diff(file_id, oldpath, newpath)
998
def diff(self, file_id, old_path, new_path):
999
"""Perform a diff of a single file
1001
:param file_id: file-id of the file
1002
:param old_path: The path of the file in the old tree
1003
:param new_path: The path of the file in the new tree
1006
old_kind = self.old_tree.kind(file_id)
1007
except (errors.NoSuchId, errors.NoSuchFile):
1010
new_kind = self.new_tree.kind(file_id)
1011
except (errors.NoSuchId, errors.NoSuchFile):
1014
result = DiffPath._diff_many(self.differs, file_id, old_path,
1015
new_path, old_kind, new_kind)
1016
if result is DiffPath.CANNOT_DIFF:
1017
error_path = new_path
1018
if error_path is None:
1019
error_path = old_path
1020
raise errors.NoDiffFound(error_path)