215
126
diffcmd.append('-u')
218
129
diffcmd.extend(diff_opts)
220
pipe = _spawn_external_diff(diffcmd, capture_errors=True)
221
out,err = pipe.communicate()
224
# internal_diff() adds a trailing newline, add one here for consistency
227
# 'diff' gives retcode == 2 for all sorts of errors
228
# one of those is 'Binary files differ'.
229
# Bad options could also be the problem.
230
# 'Binary files' is not a real error, so we suppress that error.
233
# Since we got here, we want to make sure to give an i18n error
234
pipe = _spawn_external_diff(diffcmd, capture_errors=False)
235
out, err = pipe.communicate()
237
# Write out the new i18n diff response
238
to_file.write(out+'\n')
239
if pipe.returncode != 2:
240
raise errors.BzrError(
241
'external diff failed with exit code 2'
242
' when run with LANG=C and LC_ALL=C,'
243
' but not when run natively: %r' % (diffcmd,))
245
first_line = lang_c_out.split('\n', 1)[0]
246
# Starting with diffutils 2.8.4 the word "binary" was dropped.
247
m = re.match('^(binary )?files.*differ$', first_line, re.I)
249
raise errors.BzrError('external diff failed with exit code 2;'
250
' command: %r' % (diffcmd,))
252
# Binary files differ, just return
255
# 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:
259
134
# returns 1 if files differ; that's OK
261
136
msg = 'signal %d' % (-rc)
263
138
msg = 'exit code %d' % rc
265
raise errors.BzrError('external diff failed with %s; command: %r'
140
raise BzrError('external diff failed with %s; command: %r' % (rc, diffcmd))
270
142
oldtmpf.close() # and delete
272
# Clean up. Warn in case the files couldn't be deleted
273
# (in case windows still holds the file open, but not
274
# if the files have already been deleted)
276
os.remove(old_abspath)
278
if e.errno not in (errno.ENOENT,):
279
warning('Failed to delete temporary file: %s %s',
282
os.remove(new_abspath)
284
if e.errno not in (errno.ENOENT,):
285
warning('Failed to delete temporary file: %s %s',
289
def get_trees_and_branches_to_diff(path_list, revision_specs, old_url, new_url,
291
"""Get the trees and specific files to diff given a list of paths.
293
This method works out the trees to be diff'ed and the files of
294
interest within those trees.
297
the list of arguments passed to the diff command
298
:param revision_specs:
299
Zero, one or two RevisionSpecs from the diff command line,
300
saying what revisions to compare.
302
The url of the old branch or tree. If None, the tree to use is
303
taken from the first path, if any, or the current working tree.
305
The url of the new branch or tree. If None, the tree to use is
306
taken from the first path, if any, or the current working tree.
308
if True and a view is set, apply the view or check that the paths
311
a tuple of (old_tree, new_tree, old_branch, new_branch,
312
specific_files, extra_trees) where extra_trees is a sequence of
313
additional trees to search in for file-ids.
146
@deprecated_function(zero_eight)
147
def show_diff(b, from_spec, specific_files, external_diff_options=None,
148
revision2=None, output=None, b2=None):
149
"""Shortcut for showing the diff to the working tree.
151
Please use show_diff_trees instead.
157
None for 'basis tree', or otherwise the old revision to compare against.
159
The more general form is show_diff_trees(), where the caller
160
supplies any two trees.
315
# Get the old and new revision specs
316
old_revision_spec = None
317
new_revision_spec = None
318
if revision_specs is not None:
319
if len(revision_specs) > 0:
320
old_revision_spec = revision_specs[0]
322
old_url = old_revision_spec.get_branch()
323
if len(revision_specs) > 1:
324
new_revision_spec = revision_specs[1]
326
new_url = new_revision_spec.get_branch()
329
make_paths_wt_relative = True
330
consider_relpath = True
331
if path_list is None or len(path_list) == 0:
332
# If no path is given, the current working tree is used
333
default_location = u'.'
334
consider_relpath = False
335
elif old_url is not None and new_url is not None:
336
other_paths = path_list
337
make_paths_wt_relative = False
166
if from_spec is None:
167
old_tree = b.bzrdir.open_workingtree()
169
old_tree = old_tree = old_tree.basis_tree()
339
default_location = path_list[0]
340
other_paths = path_list[1:]
342
# Get the old location
345
old_url = default_location
346
working_tree, branch, relpath = \
347
bzrdir.BzrDir.open_containing_tree_or_branch(old_url)
348
if consider_relpath and relpath != '':
349
if working_tree is not None and apply_view:
350
views.check_path_in_view(working_tree, relpath)
351
specific_files.append(relpath)
352
old_tree = _get_tree_to_diff(old_revision_spec, working_tree, branch)
355
# Get the new location
357
new_url = default_location
358
if new_url != old_url:
359
working_tree, branch, relpath = \
360
bzrdir.BzrDir.open_containing_tree_or_branch(new_url)
361
if consider_relpath and relpath != '':
362
if working_tree is not None and apply_view:
363
views.check_path_in_view(working_tree, relpath)
364
specific_files.append(relpath)
365
new_tree = _get_tree_to_diff(new_revision_spec, working_tree, branch,
366
basis_is_default=working_tree is None)
369
# Get the specific files (all files is None, no files is [])
370
if make_paths_wt_relative and working_tree is not None:
372
from bzrlib.builtins import safe_relpath_files
373
other_paths = safe_relpath_files(working_tree, other_paths,
374
apply_view=apply_view)
375
except errors.FileInWrongBranch:
376
raise errors.BzrCommandError("Files are in different branches")
377
specific_files.extend(other_paths)
378
if len(specific_files) == 0:
379
specific_files = None
380
if (working_tree is not None and working_tree.supports_views()
382
view_files = working_tree.views.lookup_view()
384
specific_files = view_files
385
view_str = views.view_display_str(view_files)
386
note("*** Ignoring files outside view. View is %s" % view_str)
388
# Get extra trees that ought to be searched for file-ids
390
if working_tree is not None and working_tree not in (old_tree, new_tree):
391
extra_trees = (working_tree,)
392
return old_tree, new_tree, old_branch, new_branch, specific_files, extra_trees
395
def _get_tree_to_diff(spec, tree=None, branch=None, basis_is_default=True):
396
if branch is None and tree is not None:
398
if spec is None or spec.spec is None:
401
return tree.basis_tree()
403
return branch.basis_tree()
171
old_tree = b.repository.revision_tree(from_spec.in_history(b).rev_id)
173
if revision2 is None:
175
new_tree = b.bzrdir.open_workingtree()
406
return spec.as_tree(branch)
177
new_tree = b2.bzrdir.open_workingtree()
179
new_tree = b.repository.revision_tree(revision2.in_history(b).rev_id)
181
return show_diff_trees(old_tree, new_tree, output, specific_files,
182
external_diff_options)
185
def diff_cmd_helper(tree, specific_files, external_diff_options,
186
old_revision_spec=None, new_revision_spec=None):
187
"""Helper for cmd_diff.
193
The specific files to compare, or None
195
external_diff_options
196
If non-None, run an external diff, and pass it these options
199
If None, use basis tree as old revision, otherwise use the tree for
200
the specified revision.
203
If None, use working tree as new revision, otherwise use the tree for
204
the specified revision.
206
The more general form is show_diff_trees(), where the caller
207
supplies any two trees.
212
revision_id = spec.in_store(tree.branch).rev_id
213
return tree.branch.repository.revision_tree(revision_id)
214
if old_revision_spec is None:
215
old_tree = tree.basis_tree()
217
old_tree = spec_tree(old_revision_spec)
219
if new_revision_spec is None:
222
new_tree = spec_tree(new_revision_spec)
224
return show_diff_trees(old_tree, new_tree, sys.stdout, specific_files,
225
external_diff_options)
409
228
def show_diff_trees(old_tree, new_tree, to_file, specific_files=None,
410
external_diff_options=None,
411
old_label='a/', new_label='b/',
413
path_encoding='utf8',
229
external_diff_options=None):
415
230
"""Show in text form the changes from one tree to another.
421
Include only changes to these files - None for all changes.
233
If set, include only changes to these files.
423
235
external_diff_options
424
236
If set, use an external GNU diff and pass these options.
427
If set, more Trees to use for looking up file ids
430
If set, the path will be encoded as specified, otherwise is supposed
433
239
old_tree.lock_read()
435
if extra_trees is not None:
436
for tree in extra_trees:
438
241
new_tree.lock_read()
440
differ = DiffTree.from_trees_options(old_tree, new_tree, to_file,
442
external_diff_options,
443
old_label, new_label, using)
444
return differ.show_diff(specific_files, extra_trees)
243
return _show_diff_trees(old_tree, new_tree, to_file,
244
specific_files, external_diff_options)
446
246
new_tree.unlock()
447
if extra_trees is not None:
448
for tree in extra_trees:
451
248
old_tree.unlock()
454
def _patch_header_date(tree, file_id, path):
455
"""Returns a timestamp suitable for use in a patch header."""
456
mtime = tree.get_file_mtime(file_id, path)
457
return timestamp.format_patch_date(mtime)
460
def get_executable_change(old_is_x, new_is_x):
461
descr = { True:"+x", False:"-x", None:"??" }
462
if old_is_x != new_is_x:
463
return ["%s to %s" % (descr[old_is_x], descr[new_is_x],)]
468
class DiffPath(object):
469
"""Base type for command object that compare files"""
471
# The type or contents of the file were unsuitable for diffing
472
CANNOT_DIFF = 'CANNOT_DIFF'
473
# The file has changed in a semantic way
475
# The file content may have changed, but there is no semantic change
476
UNCHANGED = 'UNCHANGED'
478
def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8'):
481
:param old_tree: The tree to show as the old tree in the comparison
482
:param new_tree: The tree to show as new in the comparison
483
:param to_file: The file to write comparison data to
484
:param path_encoding: The character encoding to write paths in
486
self.old_tree = old_tree
487
self.new_tree = new_tree
488
self.to_file = to_file
489
self.path_encoding = path_encoding
495
def from_diff_tree(klass, diff_tree):
496
return klass(diff_tree.old_tree, diff_tree.new_tree,
497
diff_tree.to_file, diff_tree.path_encoding)
500
def _diff_many(differs, file_id, old_path, new_path, old_kind, new_kind):
501
for file_differ in differs:
502
result = file_differ.diff(file_id, old_path, new_path, old_kind,
504
if result is not DiffPath.CANNOT_DIFF:
507
return DiffPath.CANNOT_DIFF
510
class DiffKindChange(object):
511
"""Special differ for file kind changes.
513
Represents kind change as deletion + creation. Uses the other differs
516
def __init__(self, differs):
517
self.differs = differs
523
def from_diff_tree(klass, diff_tree):
524
return klass(diff_tree.differs)
526
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
527
"""Perform comparison
529
:param file_id: The file_id of the file to compare
530
:param old_path: Path of the file in the old tree
531
:param new_path: Path of the file in the new tree
532
:param old_kind: Old file-kind of the file
533
:param new_kind: New file-kind of the file
535
if None in (old_kind, new_kind):
536
return DiffPath.CANNOT_DIFF
537
result = DiffPath._diff_many(self.differs, file_id, old_path,
538
new_path, old_kind, None)
539
if result is DiffPath.CANNOT_DIFF:
541
return DiffPath._diff_many(self.differs, file_id, old_path, new_path,
545
class DiffDirectory(DiffPath):
547
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
548
"""Perform comparison between two directories. (dummy)
551
if 'directory' not in (old_kind, new_kind):
552
return self.CANNOT_DIFF
553
if old_kind not in ('directory', None):
554
return self.CANNOT_DIFF
555
if new_kind not in ('directory', None):
556
return self.CANNOT_DIFF
560
class DiffSymlink(DiffPath):
562
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
563
"""Perform comparison between two symlinks
565
:param file_id: The file_id of the file to compare
566
:param old_path: Path of the file in the old tree
567
:param new_path: Path of the file in the new tree
568
:param old_kind: Old file-kind of the file
569
:param new_kind: New file-kind of the file
571
if 'symlink' not in (old_kind, new_kind):
572
return self.CANNOT_DIFF
573
if old_kind == 'symlink':
574
old_target = self.old_tree.get_symlink_target(file_id)
575
elif old_kind is None:
578
return self.CANNOT_DIFF
579
if new_kind == 'symlink':
580
new_target = self.new_tree.get_symlink_target(file_id)
581
elif new_kind is None:
584
return self.CANNOT_DIFF
585
return self.diff_symlink(old_target, new_target)
587
def diff_symlink(self, old_target, new_target):
588
if old_target is None:
589
self.to_file.write('=== target is %r\n' % new_target)
590
elif new_target is None:
591
self.to_file.write('=== target was %r\n' % old_target)
593
self.to_file.write('=== target changed %r => %r\n' %
594
(old_target, new_target))
598
class DiffText(DiffPath):
600
# GNU Patch uses the epoch date to detect files that are being added
601
# or removed in a diff.
602
EPOCH_DATE = '1970-01-01 00:00:00 +0000'
604
def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8',
605
old_label='', new_label='', text_differ=internal_diff):
606
DiffPath.__init__(self, old_tree, new_tree, to_file, path_encoding)
607
self.text_differ = text_differ
608
self.old_label = old_label
609
self.new_label = new_label
610
self.path_encoding = path_encoding
612
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
613
"""Compare two files in unified diff format
615
:param file_id: The file_id of the file to compare
616
:param old_path: Path of the file in the old tree
617
:param new_path: Path of the file in the new tree
618
:param old_kind: Old file-kind of the file
619
:param new_kind: New file-kind of the file
621
if 'file' not in (old_kind, new_kind):
622
return self.CANNOT_DIFF
623
from_file_id = to_file_id = file_id
624
if old_kind == 'file':
625
old_date = _patch_header_date(self.old_tree, file_id, old_path)
626
elif old_kind is None:
627
old_date = self.EPOCH_DATE
630
return self.CANNOT_DIFF
631
if new_kind == 'file':
632
new_date = _patch_header_date(self.new_tree, file_id, new_path)
633
elif new_kind is None:
634
new_date = self.EPOCH_DATE
637
return self.CANNOT_DIFF
638
from_label = '%s%s\t%s' % (self.old_label, old_path, old_date)
639
to_label = '%s%s\t%s' % (self.new_label, new_path, new_date)
640
return self.diff_text(from_file_id, to_file_id, from_label, to_label,
643
def diff_text(self, from_file_id, to_file_id, from_label, to_label,
644
from_path=None, to_path=None):
645
"""Diff the content of given files in two trees
647
:param from_file_id: The id of the file in the from tree. If None,
648
the file is not present in the from tree.
649
:param to_file_id: The id of the file in the to tree. This may refer
650
to a different file from from_file_id. If None,
651
the file is not present in the to tree.
652
:param from_path: The path in the from tree or None if unknown.
653
:param to_path: The path in the to tree or None if unknown.
655
def _get_text(tree, file_id, path):
656
if file_id is not None:
657
return tree.get_file(file_id, path).readlines()
661
from_text = _get_text(self.old_tree, from_file_id, from_path)
662
to_text = _get_text(self.new_tree, to_file_id, to_path)
663
self.text_differ(from_label, from_text, to_label, to_text,
665
except errors.BinaryFile:
667
("Binary files %s and %s differ\n" %
668
(from_label, to_label)).encode(self.path_encoding))
672
class DiffFromTool(DiffPath):
674
def __init__(self, command_template, old_tree, new_tree, to_file,
675
path_encoding='utf-8'):
676
DiffPath.__init__(self, old_tree, new_tree, to_file, path_encoding)
677
self.command_template = command_template
678
self._root = osutils.mkdtemp(prefix='bzr-diff-')
681
def from_string(klass, command_string, old_tree, new_tree, to_file,
682
path_encoding='utf-8'):
683
command_template = commands.shlex_split_unicode(command_string)
684
if '@' not in command_string:
685
command_template.extend(['@old_path', '@new_path'])
686
return klass(command_template, old_tree, new_tree, to_file,
690
def make_from_diff_tree(klass, command_string):
691
def from_diff_tree(diff_tree):
692
return klass.from_string(command_string, diff_tree.old_tree,
693
diff_tree.new_tree, diff_tree.to_file)
694
return from_diff_tree
696
def _get_command(self, old_path, new_path):
697
my_map = {'old_path': old_path, 'new_path': new_path}
698
return [AtTemplate(t).substitute(my_map) for t in
699
self.command_template]
701
def _execute(self, old_path, new_path):
702
command = self._get_command(old_path, new_path)
704
proc = subprocess.Popen(command, stdout=subprocess.PIPE,
707
if e.errno == errno.ENOENT:
708
raise errors.ExecutableMissing(command[0])
711
self.to_file.write(proc.stdout.read())
714
def _try_symlink_root(self, tree, prefix):
715
if (getattr(tree, 'abspath', None) is None
716
or not osutils.host_os_dereferences_symlinks()):
719
os.symlink(tree.abspath(''), osutils.pathjoin(self._root, prefix))
721
if e.errno != errno.EEXIST:
725
def _write_file(self, file_id, tree, prefix, relpath, force_temp=False,
727
if not force_temp and isinstance(tree, WorkingTree):
728
return tree.abspath(tree.id2path(file_id))
730
full_path = osutils.pathjoin(self._root, prefix, relpath)
731
if not force_temp and self._try_symlink_root(tree, prefix):
733
parent_dir = osutils.dirname(full_path)
735
os.makedirs(parent_dir)
737
if e.errno != errno.EEXIST:
739
source = tree.get_file(file_id, relpath)
741
target = open(full_path, 'wb')
743
osutils.pumpfile(source, target)
749
osutils.make_readonly(full_path)
750
mtime = tree.get_file_mtime(file_id)
751
os.utime(full_path, (mtime, mtime))
754
def _prepare_files(self, file_id, old_path, new_path, force_temp=False,
755
allow_write_new=False):
756
old_disk_path = self._write_file(file_id, self.old_tree, 'old',
757
old_path, force_temp)
758
new_disk_path = self._write_file(file_id, self.new_tree, 'new',
759
new_path, force_temp,
760
allow_write=allow_write_new)
761
return old_disk_path, new_disk_path
765
osutils.rmtree(self._root)
767
if e.errno != errno.ENOENT:
768
mutter("The temporary directory \"%s\" was not "
769
"cleanly removed: %s." % (self._root, e))
771
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
772
if (old_kind, new_kind) != ('file', 'file'):
773
return DiffPath.CANNOT_DIFF
774
(old_disk_path, new_disk_path) = self._prepare_files(
775
file_id, old_path, new_path)
776
self._execute(old_disk_path, new_disk_path)
778
def edit_file(self, file_id):
779
"""Use this tool to edit a file.
781
A temporary copy will be edited, and the new contents will be
784
:param file_id: The id of the file to edit.
785
:return: The new contents of the file.
787
old_path = self.old_tree.id2path(file_id)
788
new_path = self.new_tree.id2path(file_id)
789
new_abs_path = self._prepare_files(file_id, old_path, new_path,
790
allow_write_new=True,
792
command = self._get_command(osutils.pathjoin('old', old_path),
793
osutils.pathjoin('new', new_path))
794
subprocess.call(command, cwd=self._root)
795
new_file = open(new_abs_path, 'r')
797
return new_file.read()
802
class DiffTree(object):
803
"""Provides textual representations of the difference between two trees.
805
A DiffTree examines two trees and where a file-id has altered
806
between them, generates a textual representation of the difference.
807
DiffTree uses a sequence of DiffPath objects which are each
808
given the opportunity to handle a given altered fileid. The list
809
of DiffPath objects can be extended globally by appending to
810
DiffTree.diff_factories, or for a specific diff operation by
811
supplying the extra_factories option to the appropriate method.
814
# list of factories that can provide instances of DiffPath objects
815
# may be extended by plugins.
816
diff_factories = [DiffSymlink.from_diff_tree,
817
DiffDirectory.from_diff_tree]
819
def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8',
820
diff_text=None, extra_factories=None):
823
:param old_tree: Tree to show as old in the comparison
824
:param new_tree: Tree to show as new in the comparison
825
:param to_file: File to write comparision to
826
:param path_encoding: Character encoding to write paths in
827
:param diff_text: DiffPath-type object to use as a last resort for
829
:param extra_factories: Factories of DiffPaths to try before any other
831
if diff_text is None:
832
diff_text = DiffText(old_tree, new_tree, to_file, path_encoding,
833
'', '', internal_diff)
834
self.old_tree = old_tree
835
self.new_tree = new_tree
836
self.to_file = to_file
837
self.path_encoding = path_encoding
839
if extra_factories is not None:
840
self.differs.extend(f(self) for f in extra_factories)
841
self.differs.extend(f(self) for f in self.diff_factories)
842
self.differs.extend([diff_text, DiffKindChange.from_diff_tree(self)])
845
def from_trees_options(klass, old_tree, new_tree, to_file,
846
path_encoding, external_diff_options, old_label,
848
"""Factory for producing a DiffTree.
850
Designed to accept options used by show_diff_trees.
851
:param old_tree: The tree to show as old in the comparison
852
:param new_tree: The tree to show as new in the comparison
853
:param to_file: File to write comparisons to
854
:param path_encoding: Character encoding to use for writing paths
855
:param external_diff_options: If supplied, use the installed diff
856
binary to perform file comparison, using supplied options.
857
:param old_label: Prefix to use for old file labels
858
:param new_label: Prefix to use for new file labels
859
:param using: Commandline to use to invoke an external diff tool
861
if using is not None:
862
extra_factories = [DiffFromTool.make_from_diff_tree(using)]
865
if external_diff_options:
866
opts = external_diff_options.split()
867
def diff_file(olab, olines, nlab, nlines, to_file):
868
external_diff(olab, olines, nlab, nlines, to_file, opts)
870
diff_file = internal_diff
871
diff_text = DiffText(old_tree, new_tree, to_file, path_encoding,
872
old_label, new_label, diff_file)
873
return klass(old_tree, new_tree, to_file, path_encoding, diff_text,
876
def show_diff(self, specific_files, extra_trees=None):
877
"""Write tree diff to self.to_file
879
:param sepecific_files: the specific files to compare (recursive)
880
:param extra_trees: extra trees to use for mapping paths to file_ids
883
return self._show_diff(specific_files, extra_trees)
885
for differ in self.differs:
888
def _show_diff(self, specific_files, extra_trees):
889
# TODO: Generation of pseudo-diffs for added/deleted files could
890
# be usefully made into a much faster special case.
891
iterator = self.new_tree.iter_changes(self.old_tree,
892
specific_files=specific_files,
893
extra_trees=extra_trees,
894
require_versioned=True)
896
def changes_key(change):
897
old_path, new_path = change[1]
902
def get_encoded_path(path):
904
return path.encode(self.path_encoding, "replace")
905
for (file_id, paths, changed_content, versioned, parent, name, kind,
906
executable) in sorted(iterator, key=changes_key):
907
# The root does not get diffed, and items with no known kind (that
908
# is, missing) in both trees are skipped as well.
909
if parent == (None, None) or kind == (None, None):
911
oldpath, newpath = paths
912
oldpath_encoded = get_encoded_path(paths[0])
913
newpath_encoded = get_encoded_path(paths[1])
914
old_present = (kind[0] is not None and versioned[0])
915
new_present = (kind[1] is not None and versioned[1])
916
renamed = (parent[0], name[0]) != (parent[1], name[1])
918
properties_changed = []
919
properties_changed.extend(get_executable_change(executable[0], executable[1]))
921
if properties_changed:
922
prop_str = " (properties changed: %s)" % (", ".join(properties_changed),)
926
if (old_present, new_present) == (True, False):
927
self.to_file.write("=== removed %s '%s'\n" %
928
(kind[0], oldpath_encoded))
930
elif (old_present, new_present) == (False, True):
931
self.to_file.write("=== added %s '%s'\n" %
932
(kind[1], newpath_encoded))
935
self.to_file.write("=== renamed %s '%s' => '%s'%s\n" %
936
(kind[0], oldpath_encoded, newpath_encoded, prop_str))
938
# if it was produced by iter_changes, it must be
939
# modified *somehow*, either content or execute bit.
940
self.to_file.write("=== modified %s '%s'%s\n" % (kind[0],
941
newpath_encoded, prop_str))
943
self._diff(file_id, oldpath, newpath, kind[0], kind[1])
949
def diff(self, file_id, old_path, new_path):
950
"""Perform a diff of a single file
952
:param file_id: file-id of the file
953
:param old_path: The path of the file in the old tree
954
:param new_path: The path of the file in the new tree
957
old_kind = self.old_tree.kind(file_id)
958
except (errors.NoSuchId, errors.NoSuchFile):
961
new_kind = self.new_tree.kind(file_id)
962
except (errors.NoSuchId, errors.NoSuchFile):
964
self._diff(file_id, old_path, new_path, old_kind, new_kind)
967
def _diff(self, file_id, old_path, new_path, old_kind, new_kind):
968
result = DiffPath._diff_many(self.differs, file_id, old_path,
969
new_path, old_kind, new_kind)
970
if result is DiffPath.CANNOT_DIFF:
971
error_path = new_path
972
if error_path is None:
973
error_path = old_path
974
raise errors.NoDiffFound(error_path)
251
def _show_diff_trees(old_tree, new_tree, to_file,
252
specific_files, external_diff_options):
254
# TODO: Options to control putting on a prefix or suffix, perhaps
255
# as a format string?
259
DEVNULL = '/dev/null'
260
# Windows users, don't panic about this filename -- it is a
261
# special signal to GNU patch that the file should be created or
262
# deleted respectively.
264
# TODO: Generation of pseudo-diffs for added/deleted files could
265
# be usefully made into a much faster special case.
267
if external_diff_options:
268
assert isinstance(external_diff_options, basestring)
269
opts = external_diff_options.split()
270
def diff_file(olab, olines, nlab, nlines, to_file):
271
external_diff(olab, olines, nlab, nlines, to_file, opts)
273
diff_file = internal_diff
276
delta = compare_trees(old_tree, new_tree, want_unchanged=False,
277
specific_files=specific_files)
280
for path, file_id, kind in delta.removed:
282
print >>to_file, '=== removed %s %r' % (kind, old_label + path)
283
old_tree.inventory[file_id].diff(diff_file, old_label + path, old_tree,
284
DEVNULL, None, None, to_file)
285
for path, file_id, kind in delta.added:
287
print >>to_file, '=== added %s %r' % (kind, new_label + path)
288
new_tree.inventory[file_id].diff(diff_file, new_label + path, new_tree,
289
DEVNULL, None, None, to_file,
291
for (old_path, new_path, file_id, kind,
292
text_modified, meta_modified) in delta.renamed:
294
prop_str = get_prop_change(meta_modified)
295
print >>to_file, '=== renamed %s %r => %r%s' % (
296
kind, old_label + old_path, new_label + new_path, prop_str)
297
_maybe_diff_file_or_symlink(old_label, old_path, old_tree, file_id,
298
new_label, new_path, new_tree,
299
text_modified, kind, to_file, diff_file)
300
for path, file_id, kind, text_modified, meta_modified in delta.modified:
302
prop_str = get_prop_change(meta_modified)
303
print >>to_file, '=== modified %s %r%s' % (kind, old_label + path,
306
_maybe_diff_file_or_symlink(old_label, path, old_tree, file_id,
307
new_label, path, new_tree,
308
True, kind, to_file, diff_file)
312
def get_prop_change(meta_modified):
314
return " (properties changed)"
319
def _maybe_diff_file_or_symlink(old_label, old_path, old_tree, file_id,
320
new_label, new_path, new_tree, text_modified,
321
kind, to_file, diff_file):
323
new_entry = new_tree.inventory[file_id]
324
old_tree.inventory[file_id].diff(diff_file,
325
old_label + old_path, old_tree,
326
new_label + new_path, new_entry,