217
126
diffcmd.append('-u')
220
129
diffcmd.extend(diff_opts)
222
pipe = _spawn_external_diff(diffcmd, capture_errors=True)
223
out,err = pipe.communicate()
226
# internal_diff() adds a trailing newline, add one here for consistency
229
# 'diff' gives retcode == 2 for all sorts of errors
230
# one of those is 'Binary files differ'.
231
# Bad options could also be the problem.
232
# 'Binary files' is not a real error, so we suppress that error.
235
# Since we got here, we want to make sure to give an i18n error
236
pipe = _spawn_external_diff(diffcmd, capture_errors=False)
237
out, err = pipe.communicate()
239
# Write out the new i18n diff response
240
to_file.write(out+'\n')
241
if pipe.returncode != 2:
242
raise errors.BzrError(
243
'external diff failed with exit code 2'
244
' when run with LANG=C and LC_ALL=C,'
245
' but not when run natively: %r' % (diffcmd,))
247
first_line = lang_c_out.split('\n', 1)[0]
248
# Starting with diffutils 2.8.4 the word "binary" was dropped.
249
m = re.match('^(binary )?files.*differ$', first_line, re.I)
251
raise errors.BzrError('external diff failed with exit code 2;'
252
' command: %r' % (diffcmd,))
254
# Binary files differ, just return
257
# If we got to here, we haven't written out the output of diff
131
rc = os.spawnvp(os.P_WAIT, 'diff', diffcmd)
133
if rc != 0 and rc != 1:
261
134
# returns 1 if files differ; that's OK
263
136
msg = 'signal %d' % (-rc)
265
138
msg = 'exit code %d' % rc
267
raise errors.BzrError('external diff failed with %s; command: %r'
140
raise BzrError('external diff failed with %s; command: %r' % (rc, diffcmd))
272
142
oldtmpf.close() # and delete
274
# Clean up. Warn in case the files couldn't be deleted
275
# (in case windows still holds the file open, but not
276
# if the files have already been deleted)
278
os.remove(old_abspath)
280
if e.errno not in (errno.ENOENT,):
281
warning('Failed to delete temporary file: %s %s',
284
os.remove(new_abspath)
286
if e.errno not in (errno.ENOENT,):
287
warning('Failed to delete temporary file: %s %s',
291
@deprecated_function(deprecated_in((2, 2, 0)))
292
def get_trees_and_branches_to_diff(path_list, revision_specs, old_url, new_url,
294
"""Get the trees and specific files to diff given a list of paths.
296
This method works out the trees to be diff'ed and the files of
297
interest within those trees.
300
the list of arguments passed to the diff command
301
:param revision_specs:
302
Zero, one or two RevisionSpecs from the diff command line,
303
saying what revisions to compare.
305
The url of the old branch or tree. If None, the tree to use is
306
taken from the first path, if any, or the current working tree.
308
The url of the new branch or tree. If None, the tree to use is
309
taken from the first path, if any, or the current working tree.
311
if True and a view is set, apply the view or check that the paths
314
a tuple of (old_tree, new_tree, old_branch, new_branch,
315
specific_files, extra_trees) where extra_trees is a sequence of
316
additional trees to search in for file-ids. The trees and branches
319
op = cleanup.OperationWithCleanups(get_trees_and_branches_to_diff_locked)
320
return op.run_simple(path_list, revision_specs, old_url, new_url,
321
op.add_cleanup, apply_view=apply_view)
324
def get_trees_and_branches_to_diff_locked(
325
path_list, revision_specs, old_url, new_url, add_cleanup, apply_view=True):
326
"""Get the trees and specific files to diff given a list of paths.
328
This method works out the trees to be diff'ed and the files of
329
interest within those trees.
332
the list of arguments passed to the diff command
333
:param revision_specs:
334
Zero, one or two RevisionSpecs from the diff command line,
335
saying what revisions to compare.
337
The url of the old branch or tree. If None, the tree to use is
338
taken from the first path, if any, or the current working tree.
340
The url of the new branch or tree. If None, the tree to use is
341
taken from the first path, if any, or the current working tree.
343
a callable like Command.add_cleanup. get_trees_and_branches_to_diff
344
will register cleanups that must be run to unlock the trees, etc.
346
if True and a view is set, apply the view or check that the paths
349
a tuple of (old_tree, new_tree, old_branch, new_branch,
350
specific_files, extra_trees) where extra_trees is a sequence of
351
additional trees to search in for file-ids. The trees and branches
352
will be read-locked until the cleanups registered via the add_cleanup
355
# Get the old and new revision specs
356
old_revision_spec = None
357
new_revision_spec = None
358
if revision_specs is not None:
359
if len(revision_specs) > 0:
360
old_revision_spec = revision_specs[0]
362
old_url = old_revision_spec.get_branch()
363
if len(revision_specs) > 1:
364
new_revision_spec = revision_specs[1]
366
new_url = new_revision_spec.get_branch()
369
make_paths_wt_relative = True
370
consider_relpath = True
371
if path_list is None or len(path_list) == 0:
372
# If no path is given, the current working tree is used
373
default_location = u'.'
374
consider_relpath = False
375
elif old_url is not None and new_url is not None:
376
other_paths = path_list
377
make_paths_wt_relative = False
379
default_location = path_list[0]
380
other_paths = path_list[1:]
382
def lock_tree_or_branch(wt, br):
385
add_cleanup(wt.unlock)
388
add_cleanup(br.unlock)
390
# Get the old location
393
old_url = default_location
394
working_tree, branch, relpath = \
395
bzrdir.BzrDir.open_containing_tree_or_branch(old_url)
396
lock_tree_or_branch(working_tree, branch)
397
if consider_relpath and relpath != '':
398
if working_tree is not None and apply_view:
399
views.check_path_in_view(working_tree, relpath)
400
specific_files.append(relpath)
401
old_tree = _get_tree_to_diff(old_revision_spec, working_tree, branch)
404
# Get the new location
406
new_url = default_location
407
if new_url != old_url:
408
working_tree, branch, relpath = \
409
bzrdir.BzrDir.open_containing_tree_or_branch(new_url)
410
lock_tree_or_branch(working_tree, branch)
411
if consider_relpath and relpath != '':
412
if working_tree is not None and apply_view:
413
views.check_path_in_view(working_tree, relpath)
414
specific_files.append(relpath)
415
new_tree = _get_tree_to_diff(new_revision_spec, working_tree, branch,
416
basis_is_default=working_tree is None)
419
# Get the specific files (all files is None, no files is [])
420
if make_paths_wt_relative and working_tree is not None:
421
other_paths = working_tree.safe_relpath_files(
423
apply_view=apply_view)
424
specific_files.extend(other_paths)
425
if len(specific_files) == 0:
426
specific_files = None
427
if (working_tree is not None and working_tree.supports_views()
429
view_files = working_tree.views.lookup_view()
431
specific_files = view_files
432
view_str = views.view_display_str(view_files)
433
note("*** Ignoring files outside view. View is %s" % view_str)
435
# Get extra trees that ought to be searched for file-ids
437
if working_tree is not None and working_tree not in (old_tree, new_tree):
438
extra_trees = (working_tree,)
439
return old_tree, new_tree, old_branch, new_branch, specific_files, extra_trees
442
def _get_tree_to_diff(spec, tree=None, branch=None, basis_is_default=True):
443
if branch is None and tree is not None:
445
if spec is None or spec.spec is None:
448
return tree.basis_tree()
450
return branch.basis_tree()
453
return spec.as_tree(branch)
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_branch(b).rev_id)
174
show_diff_trees(old_tree, new_tree, output, specific_files,
175
external_diff_options)
456
179
def show_diff_trees(old_tree, new_tree, to_file, specific_files=None,
457
external_diff_options=None,
458
old_label='a/', new_label='b/',
460
path_encoding='utf8',
180
external_diff_options=None):
463
181
"""Show in text form the changes from one tree to another.
465
:param to_file: The output stream.
466
:param specific_files: Include only changes to these files - None for all
468
:param external_diff_options: If set, use an external GNU diff and pass
470
:param extra_trees: If set, more Trees to use for looking up file ids
471
:param path_encoding: If set, the path will be encoded as specified,
472
otherwise is supposed to be utf8
473
:param format_cls: Formatter class (DiffTree subclass)
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.
475
if format_cls is None:
476
format_cls = DiffTree
479
if extra_trees is not None:
480
for tree in extra_trees:
484
differ = format_cls.from_trees_options(old_tree, new_tree, to_file,
486
external_diff_options,
487
old_label, new_label, using)
488
return differ.show_diff(specific_files, extra_trees)
491
if extra_trees is not None:
492
for tree in extra_trees:
498
def _patch_header_date(tree, file_id, path):
499
"""Returns a timestamp suitable for use in a patch header."""
501
mtime = tree.get_file_mtime(file_id, path)
502
except errors.FileTimestampUnavailable:
504
return timestamp.format_patch_date(mtime)
507
def get_executable_change(old_is_x, new_is_x):
508
descr = { True:"+x", False:"-x", None:"??" }
509
if old_is_x != new_is_x:
510
return ["%s to %s" % (descr[old_is_x], descr[new_is_x],)]
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)
515
class DiffPath(object):
516
"""Base type for command object that compare files"""
518
# The type or contents of the file were unsuitable for diffing
519
CANNOT_DIFF = 'CANNOT_DIFF'
520
# The file has changed in a semantic way
522
# The file content may have changed, but there is no semantic change
523
UNCHANGED = 'UNCHANGED'
525
def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8'):
528
:param old_tree: The tree to show as the old tree in the comparison
529
:param new_tree: The tree to show as new in the comparison
530
:param to_file: The file to write comparison data to
531
:param path_encoding: The character encoding to write paths in
533
self.old_tree = old_tree
534
self.new_tree = new_tree
535
self.to_file = to_file
536
self.path_encoding = path_encoding
542
def from_diff_tree(klass, diff_tree):
543
return klass(diff_tree.old_tree, diff_tree.new_tree,
544
diff_tree.to_file, diff_tree.path_encoding)
547
def _diff_many(differs, file_id, old_path, new_path, old_kind, new_kind):
548
for file_differ in differs:
549
result = file_differ.diff(file_id, old_path, new_path, old_kind,
551
if result is not DiffPath.CANNOT_DIFF:
554
return DiffPath.CANNOT_DIFF
557
class DiffKindChange(object):
558
"""Special differ for file kind changes.
560
Represents kind change as deletion + creation. Uses the other differs
563
def __init__(self, differs):
564
self.differs = differs
570
def from_diff_tree(klass, diff_tree):
571
return klass(diff_tree.differs)
573
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
574
"""Perform comparison
576
:param file_id: The file_id of the file to compare
577
:param old_path: Path of the file in the old tree
578
:param new_path: Path of the file in the new tree
579
:param old_kind: Old file-kind of the file
580
:param new_kind: New file-kind of the file
582
if None in (old_kind, new_kind):
583
return DiffPath.CANNOT_DIFF
584
result = DiffPath._diff_many(self.differs, file_id, old_path,
585
new_path, old_kind, None)
586
if result is DiffPath.CANNOT_DIFF:
588
return DiffPath._diff_many(self.differs, file_id, old_path, new_path,
592
class DiffDirectory(DiffPath):
594
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
595
"""Perform comparison between two directories. (dummy)
598
if 'directory' not in (old_kind, new_kind):
599
return self.CANNOT_DIFF
600
if old_kind not in ('directory', None):
601
return self.CANNOT_DIFF
602
if new_kind not in ('directory', None):
603
return self.CANNOT_DIFF
607
class DiffSymlink(DiffPath):
609
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
610
"""Perform comparison between two symlinks
612
:param file_id: The file_id of the file to compare
613
:param old_path: Path of the file in the old tree
614
:param new_path: Path of the file in the new tree
615
:param old_kind: Old file-kind of the file
616
:param new_kind: New file-kind of the file
618
if 'symlink' not in (old_kind, new_kind):
619
return self.CANNOT_DIFF
620
if old_kind == 'symlink':
621
old_target = self.old_tree.get_symlink_target(file_id)
622
elif old_kind is None:
625
return self.CANNOT_DIFF
626
if new_kind == 'symlink':
627
new_target = self.new_tree.get_symlink_target(file_id)
628
elif new_kind is None:
631
return self.CANNOT_DIFF
632
return self.diff_symlink(old_target, new_target)
634
def diff_symlink(self, old_target, new_target):
635
if old_target is None:
636
self.to_file.write('=== target is %r\n' % new_target)
637
elif new_target is None:
638
self.to_file.write('=== target was %r\n' % old_target)
640
self.to_file.write('=== target changed %r => %r\n' %
641
(old_target, new_target))
645
class DiffText(DiffPath):
647
# GNU Patch uses the epoch date to detect files that are being added
648
# or removed in a diff.
649
EPOCH_DATE = '1970-01-01 00:00:00 +0000'
651
def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8',
652
old_label='', new_label='', text_differ=internal_diff):
653
DiffPath.__init__(self, old_tree, new_tree, to_file, path_encoding)
654
self.text_differ = text_differ
655
self.old_label = old_label
656
self.new_label = new_label
657
self.path_encoding = path_encoding
659
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
660
"""Compare two files in unified diff format
662
:param file_id: The file_id of the file to compare
663
:param old_path: Path of the file in the old tree
664
:param new_path: Path of the file in the new tree
665
:param old_kind: Old file-kind of the file
666
:param new_kind: New file-kind of the file
668
if 'file' not in (old_kind, new_kind):
669
return self.CANNOT_DIFF
670
from_file_id = to_file_id = file_id
671
if old_kind == 'file':
672
old_date = _patch_header_date(self.old_tree, file_id, old_path)
673
elif old_kind is None:
674
old_date = self.EPOCH_DATE
677
return self.CANNOT_DIFF
678
if new_kind == 'file':
679
new_date = _patch_header_date(self.new_tree, file_id, new_path)
680
elif new_kind is None:
681
new_date = self.EPOCH_DATE
684
return self.CANNOT_DIFF
685
from_label = '%s%s\t%s' % (self.old_label, old_path, old_date)
686
to_label = '%s%s\t%s' % (self.new_label, new_path, new_date)
687
return self.diff_text(from_file_id, to_file_id, from_label, to_label,
690
def diff_text(self, from_file_id, to_file_id, from_label, to_label,
691
from_path=None, to_path=None):
692
"""Diff the content of given files in two trees
694
:param from_file_id: The id of the file in the from tree. If None,
695
the file is not present in the from tree.
696
:param to_file_id: The id of the file in the to tree. This may refer
697
to a different file from from_file_id. If None,
698
the file is not present in the to tree.
699
:param from_path: The path in the from tree or None if unknown.
700
:param to_path: The path in the to tree or None if unknown.
702
def _get_text(tree, file_id, path):
703
if file_id is not None:
704
return tree.get_file_lines(file_id, path)
708
from_text = _get_text(self.old_tree, from_file_id, from_path)
709
to_text = _get_text(self.new_tree, to_file_id, to_path)
710
self.text_differ(from_label, from_text, to_label, to_text,
711
self.to_file, path_encoding=self.path_encoding)
712
except errors.BinaryFile:
714
("Binary files %s and %s differ\n" %
715
(from_label, to_label)).encode(self.path_encoding,'replace'))
719
class DiffFromTool(DiffPath):
721
def __init__(self, command_template, old_tree, new_tree, to_file,
722
path_encoding='utf-8'):
723
DiffPath.__init__(self, old_tree, new_tree, to_file, path_encoding)
724
self.command_template = command_template
725
self._root = osutils.mkdtemp(prefix='bzr-diff-')
728
def from_string(klass, command_string, old_tree, new_tree, to_file,
729
path_encoding='utf-8'):
730
command_template = cmdline.split(command_string)
731
if '@' not in command_string:
732
command_template.extend(['@old_path', '@new_path'])
733
return klass(command_template, old_tree, new_tree, to_file,
737
def make_from_diff_tree(klass, command_string, external_diff_options=None):
738
def from_diff_tree(diff_tree):
739
full_command_string = [command_string]
740
if external_diff_options is not None:
741
full_command_string += ' ' + external_diff_options
742
return klass.from_string(full_command_string, diff_tree.old_tree,
743
diff_tree.new_tree, diff_tree.to_file)
744
return from_diff_tree
746
def _get_command(self, old_path, new_path):
747
my_map = {'old_path': old_path, 'new_path': new_path}
748
command = [AtTemplate(t).substitute(my_map) for t in
749
self.command_template]
750
if sys.platform == 'win32': # Popen doesn't accept unicode on win32
753
if isinstance(c, unicode):
754
command_encoded.append(c.encode('mbcs'))
756
command_encoded.append(c)
757
return command_encoded
761
def _execute(self, old_path, new_path):
762
command = self._get_command(old_path, new_path)
764
proc = subprocess.Popen(command, stdout=subprocess.PIPE,
767
if e.errno == errno.ENOENT:
768
raise errors.ExecutableMissing(command[0])
771
self.to_file.write(proc.stdout.read())
774
def _try_symlink_root(self, tree, prefix):
775
if (getattr(tree, 'abspath', None) is None
776
or not osutils.host_os_dereferences_symlinks()):
779
os.symlink(tree.abspath(''), osutils.pathjoin(self._root, prefix))
781
if e.errno != errno.EEXIST:
787
"""Returns safe encoding for passing file path to diff tool"""
788
if sys.platform == 'win32':
791
# Don't fallback to 'utf-8' because subprocess may not be able to
792
# handle utf-8 correctly when locale is not utf-8.
793
return sys.getfilesystemencoding() or 'ascii'
795
def _is_safepath(self, path):
796
"""Return true if `path` may be able to pass to subprocess."""
799
return path == path.encode(fenc).decode(fenc)
803
def _safe_filename(self, prefix, relpath):
804
"""Replace unsafe character in `relpath` then join `self._root`,
805
`prefix` and `relpath`."""
807
# encoded_str.replace('?', '_') may break multibyte char.
808
# So we should encode, decode, then replace(u'?', u'_')
809
relpath_tmp = relpath.encode(fenc, 'replace').decode(fenc, 'replace')
810
relpath_tmp = relpath_tmp.replace(u'?', u'_')
811
return osutils.pathjoin(self._root, prefix, relpath_tmp)
813
def _write_file(self, file_id, tree, prefix, relpath, force_temp=False,
815
if not force_temp and isinstance(tree, WorkingTree):
816
full_path = tree.abspath(tree.id2path(file_id))
817
if self._is_safepath(full_path):
820
full_path = self._safe_filename(prefix, relpath)
821
if not force_temp and self._try_symlink_root(tree, prefix):
823
parent_dir = osutils.dirname(full_path)
825
os.makedirs(parent_dir)
827
if e.errno != errno.EEXIST:
829
source = tree.get_file(file_id, relpath)
831
target = open(full_path, 'wb')
833
osutils.pumpfile(source, target)
839
mtime = tree.get_file_mtime(file_id)
840
except errors.FileTimestampUnavailable:
843
os.utime(full_path, (mtime, mtime))
845
osutils.make_readonly(full_path)
848
def _prepare_files(self, file_id, old_path, new_path, force_temp=False,
849
allow_write_new=False):
850
old_disk_path = self._write_file(file_id, self.old_tree, 'old',
851
old_path, force_temp)
852
new_disk_path = self._write_file(file_id, self.new_tree, 'new',
853
new_path, force_temp,
854
allow_write=allow_write_new)
855
return old_disk_path, new_disk_path
859
osutils.rmtree(self._root)
861
if e.errno != errno.ENOENT:
862
mutter("The temporary directory \"%s\" was not "
863
"cleanly removed: %s." % (self._root, e))
865
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
866
if (old_kind, new_kind) != ('file', 'file'):
867
return DiffPath.CANNOT_DIFF
868
(old_disk_path, new_disk_path) = self._prepare_files(
869
file_id, old_path, new_path)
870
self._execute(old_disk_path, new_disk_path)
872
def edit_file(self, file_id):
873
"""Use this tool to edit a file.
875
A temporary copy will be edited, and the new contents will be
878
:param file_id: The id of the file to edit.
879
:return: The new contents of the file.
881
old_path = self.old_tree.id2path(file_id)
882
new_path = self.new_tree.id2path(file_id)
883
old_abs_path, new_abs_path = self._prepare_files(
884
file_id, old_path, new_path,
885
allow_write_new=True,
887
command = self._get_command(old_abs_path, new_abs_path)
888
subprocess.call(command, cwd=self._root)
889
new_file = open(new_abs_path, 'rb')
891
return new_file.read()
896
class DiffTree(object):
897
"""Provides textual representations of the difference between two trees.
899
A DiffTree examines two trees and where a file-id has altered
900
between them, generates a textual representation of the difference.
901
DiffTree uses a sequence of DiffPath objects which are each
902
given the opportunity to handle a given altered fileid. The list
903
of DiffPath objects can be extended globally by appending to
904
DiffTree.diff_factories, or for a specific diff operation by
905
supplying the extra_factories option to the appropriate method.
908
# list of factories that can provide instances of DiffPath objects
909
# may be extended by plugins.
910
diff_factories = [DiffSymlink.from_diff_tree,
911
DiffDirectory.from_diff_tree]
913
def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8',
914
diff_text=None, extra_factories=None):
917
:param old_tree: Tree to show as old in the comparison
918
:param new_tree: Tree to show as new in the comparison
919
:param to_file: File to write comparision to
920
:param path_encoding: Character encoding to write paths in
921
:param diff_text: DiffPath-type object to use as a last resort for
923
:param extra_factories: Factories of DiffPaths to try before any other
925
if diff_text is None:
926
diff_text = DiffText(old_tree, new_tree, to_file, path_encoding,
927
'', '', internal_diff)
928
self.old_tree = old_tree
929
self.new_tree = new_tree
930
self.to_file = to_file
931
self.path_encoding = path_encoding
933
if extra_factories is not None:
934
self.differs.extend(f(self) for f in extra_factories)
935
self.differs.extend(f(self) for f in self.diff_factories)
936
self.differs.extend([diff_text, DiffKindChange.from_diff_tree(self)])
939
def from_trees_options(klass, old_tree, new_tree, to_file,
940
path_encoding, external_diff_options, old_label,
942
"""Factory for producing a DiffTree.
944
Designed to accept options used by show_diff_trees.
946
:param old_tree: The tree to show as old in the comparison
947
:param new_tree: The tree to show as new in the comparison
948
:param to_file: File to write comparisons to
949
:param path_encoding: Character encoding to use for writing paths
950
:param external_diff_options: If supplied, use the installed diff
951
binary to perform file comparison, using supplied options.
952
:param old_label: Prefix to use for old file labels
953
:param new_label: Prefix to use for new file labels
954
:param using: Commandline to use to invoke an external diff tool
956
if using is not None:
957
extra_factories = [DiffFromTool.make_from_diff_tree(using, external_diff_options)]
960
if external_diff_options:
961
opts = external_diff_options.split()
962
def diff_file(olab, olines, nlab, nlines, to_file, path_encoding=None):
963
""":param path_encoding: not used but required
964
to match the signature of internal_diff.
966
external_diff(olab, olines, nlab, nlines, to_file, opts)
968
diff_file = internal_diff
969
diff_text = DiffText(old_tree, new_tree, to_file, path_encoding,
970
old_label, new_label, diff_file)
971
return klass(old_tree, new_tree, to_file, path_encoding, diff_text,
974
def show_diff(self, specific_files, extra_trees=None):
975
"""Write tree diff to self.to_file
977
:param specific_files: the specific files to compare (recursive)
978
:param extra_trees: extra trees to use for mapping paths to file_ids
981
return self._show_diff(specific_files, extra_trees)
983
for differ in self.differs:
986
def _show_diff(self, specific_files, extra_trees):
987
# TODO: Generation of pseudo-diffs for added/deleted files could
988
# be usefully made into a much faster special case.
989
iterator = self.new_tree.iter_changes(self.old_tree,
990
specific_files=specific_files,
991
extra_trees=extra_trees,
992
require_versioned=True)
994
def changes_key(change):
995
old_path, new_path = change[1]
1000
def get_encoded_path(path):
1001
if path is not None:
1002
return path.encode(self.path_encoding, "replace")
1003
for (file_id, paths, changed_content, versioned, parent, name, kind,
1004
executable) in sorted(iterator, key=changes_key):
1005
# The root does not get diffed, and items with no known kind (that
1006
# is, missing) in both trees are skipped as well.
1007
if parent == (None, None) or kind == (None, None):
1009
oldpath, newpath = paths
1010
oldpath_encoded = get_encoded_path(paths[0])
1011
newpath_encoded = get_encoded_path(paths[1])
1012
old_present = (kind[0] is not None and versioned[0])
1013
new_present = (kind[1] is not None and versioned[1])
1014
renamed = (parent[0], name[0]) != (parent[1], name[1])
1016
properties_changed = []
1017
properties_changed.extend(get_executable_change(executable[0], executable[1]))
1019
if properties_changed:
1020
prop_str = " (properties changed: %s)" % (", ".join(properties_changed),)
1024
if (old_present, new_present) == (True, False):
1025
self.to_file.write("=== removed %s '%s'\n" %
1026
(kind[0], oldpath_encoded))
1028
elif (old_present, new_present) == (False, True):
1029
self.to_file.write("=== added %s '%s'\n" %
1030
(kind[1], newpath_encoded))
1033
self.to_file.write("=== renamed %s '%s' => '%s'%s\n" %
1034
(kind[0], oldpath_encoded, newpath_encoded, prop_str))
1036
# if it was produced by iter_changes, it must be
1037
# modified *somehow*, either content or execute bit.
1038
self.to_file.write("=== modified %s '%s'%s\n" % (kind[0],
1039
newpath_encoded, prop_str))
1041
self._diff(file_id, oldpath, newpath, kind[0], kind[1])
1047
def diff(self, file_id, old_path, new_path):
1048
"""Perform a diff of a single file
1050
:param file_id: file-id of the file
1051
:param old_path: The path of the file in the old tree
1052
:param new_path: The path of the file in the new tree
1055
old_kind = self.old_tree.kind(file_id)
1056
except (errors.NoSuchId, errors.NoSuchFile):
1059
new_kind = self.new_tree.kind(file_id)
1060
except (errors.NoSuchId, errors.NoSuchFile):
1062
self._diff(file_id, old_path, new_path, old_kind, new_kind)
1065
def _diff(self, file_id, old_path, new_path, old_kind, new_kind):
1066
result = DiffPath._diff_many(self.differs, file_id, old_path,
1067
new_path, old_kind, new_kind)
1068
if result is DiffPath.CANNOT_DIFF:
1069
error_path = new_path
1070
if error_path is None:
1071
error_path = old_path
1072
raise errors.NoDiffFound(error_path)
1075
format_registry = Registry()
1076
format_registry.register('default', DiffTree)
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(),