220
133
diffcmd.append('-u')
223
136
diffcmd.extend(diff_opts)
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
138
rc = os.spawnvp(os.P_WAIT, 'diff', diffcmd)
140
if rc != 0 and rc != 1:
264
141
# returns 1 if files differ; that's OK
266
143
msg = 'signal %d' % (-rc)
268
145
msg = 'exit code %d' % rc
270
raise errors.BzrError('external diff failed with %s; command: %r'
147
raise BzrError('external diff failed with %s; command: %r' % (rc, diffcmd))
275
149
oldtmpf.close() # and delete
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)
153
@deprecated_function(zero_eight)
154
def show_diff(b, from_spec, specific_files, external_diff_options=None,
155
revision2=None, output=None, b2=None):
156
"""Shortcut for showing the diff to the working tree.
158
Please use show_diff_trees instead.
164
None for 'basis tree', or otherwise the old revision to compare against.
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
166
The more general form is show_diff_trees(), where the caller
167
supplies any two trees.
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
173
if from_spec is None:
174
old_tree = b.bzrdir.open_workingtree()
176
old_tree = old_tree = old_tree.basis_tree()
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()
178
old_tree = b.repository.revision_tree(from_spec.in_history(b).rev_id)
180
if revision2 is None:
182
new_tree = b.bzrdir.open_workingtree()
459
return spec.as_tree(branch)
184
new_tree = b2.bzrdir.open_workingtree()
186
new_tree = b.repository.revision_tree(revision2.in_history(b).rev_id)
188
return show_diff_trees(old_tree, new_tree, output, specific_files,
189
external_diff_options)
192
def diff_cmd_helper(tree, specific_files, external_diff_options,
193
old_revision_spec=None, new_revision_spec=None,
194
old_label='a/', new_label='b/'):
195
"""Helper for cmd_diff.
201
The specific files to compare, or None
203
external_diff_options
204
If non-None, run an external diff, and pass it these options
207
If None, use basis tree as old revision, otherwise use the tree for
208
the specified revision.
211
If None, use working tree as new revision, otherwise use the tree for
212
the specified revision.
214
The more general form is show_diff_trees(), where the caller
215
supplies any two trees.
220
revision_id = spec.in_store(tree.branch).rev_id
221
return tree.branch.repository.revision_tree(revision_id)
222
if old_revision_spec is None:
223
old_tree = tree.basis_tree()
225
old_tree = spec_tree(old_revision_spec)
227
if new_revision_spec is None:
230
new_tree = spec_tree(new_revision_spec)
232
return show_diff_trees(old_tree, new_tree, sys.stdout, specific_files,
233
external_diff_options,
234
old_label=old_label, new_label=new_label)
462
237
def show_diff_trees(old_tree, new_tree, to_file, specific_files=None,
463
238
external_diff_options=None,
464
old_label='a/', new_label='b/',
466
path_encoding='utf8',
239
old_label='a/', new_label='b/'):
469
240
"""Show in text form the changes from one tree to another.
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)
243
If set, include only changes to these files.
245
external_diff_options
246
If set, use an external GNU diff and pass these options.
481
if format_cls is None:
482
format_cls = DiffTree
483
248
old_tree.lock_read()
485
if extra_trees is not None:
486
for tree in extra_trees:
488
250
new_tree.lock_read()
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)
252
return _show_diff_trees(old_tree, new_tree, to_file,
253
specific_files, external_diff_options,
254
old_label=old_label, new_label=new_label)
496
256
new_tree.unlock()
497
if extra_trees is not None:
498
for tree in extra_trees:
501
258
old_tree.unlock()
504
def _patch_header_date(tree, file_id, path):
505
"""Returns a timestamp suitable for use in a patch header."""
507
mtime = tree.get_file_mtime(file_id, path)
508
except errors.FileTimestampUnavailable:
510
return timestamp.format_patch_date(mtime)
513
def get_executable_change(old_is_x, new_is_x):
514
descr = { True:"+x", False:"-x", None:"??" }
515
if old_is_x != new_is_x:
516
return ["%s to %s" % (descr[old_is_x], descr[new_is_x],)]
521
class DiffPath(object):
522
"""Base type for command object that compare files"""
524
# The type or contents of the file were unsuitable for diffing
525
CANNOT_DIFF = 'CANNOT_DIFF'
526
# The file has changed in a semantic way
528
# The file content may have changed, but there is no semantic change
529
UNCHANGED = 'UNCHANGED'
531
def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8'):
534
:param old_tree: The tree to show as the old tree in the comparison
535
:param new_tree: The tree to show as new in the comparison
536
:param to_file: The file to write comparison data to
537
:param path_encoding: The character encoding to write paths in
539
self.old_tree = old_tree
540
self.new_tree = new_tree
541
self.to_file = to_file
542
self.path_encoding = path_encoding
548
def from_diff_tree(klass, diff_tree):
549
return klass(diff_tree.old_tree, diff_tree.new_tree,
550
diff_tree.to_file, diff_tree.path_encoding)
553
def _diff_many(differs, file_id, old_path, new_path, old_kind, new_kind):
554
for file_differ in differs:
555
result = file_differ.diff(file_id, old_path, new_path, old_kind,
557
if result is not DiffPath.CANNOT_DIFF:
560
return DiffPath.CANNOT_DIFF
563
class DiffKindChange(object):
564
"""Special differ for file kind changes.
566
Represents kind change as deletion + creation. Uses the other differs
569
def __init__(self, differs):
570
self.differs = differs
576
def from_diff_tree(klass, diff_tree):
577
return klass(diff_tree.differs)
579
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
580
"""Perform comparison
582
:param file_id: The file_id of the file to compare
583
:param old_path: Path of the file in the old tree
584
:param new_path: Path of the file in the new tree
585
:param old_kind: Old file-kind of the file
586
:param new_kind: New file-kind of the file
588
if None in (old_kind, new_kind):
589
return DiffPath.CANNOT_DIFF
590
result = DiffPath._diff_many(self.differs, file_id, old_path,
591
new_path, old_kind, None)
592
if result is DiffPath.CANNOT_DIFF:
594
return DiffPath._diff_many(self.differs, file_id, old_path, new_path,
598
class DiffDirectory(DiffPath):
600
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
601
"""Perform comparison between two directories. (dummy)
604
if 'directory' not in (old_kind, new_kind):
605
return self.CANNOT_DIFF
606
if old_kind not in ('directory', None):
607
return self.CANNOT_DIFF
608
if new_kind not in ('directory', None):
609
return self.CANNOT_DIFF
613
class DiffSymlink(DiffPath):
615
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
616
"""Perform comparison between two symlinks
618
:param file_id: The file_id of the file to compare
619
:param old_path: Path of the file in the old tree
620
:param new_path: Path of the file in the new tree
621
:param old_kind: Old file-kind of the file
622
:param new_kind: New file-kind of the file
624
if 'symlink' not in (old_kind, new_kind):
625
return self.CANNOT_DIFF
626
if old_kind == 'symlink':
627
old_target = self.old_tree.get_symlink_target(file_id)
628
elif old_kind is None:
631
return self.CANNOT_DIFF
632
if new_kind == 'symlink':
633
new_target = self.new_tree.get_symlink_target(file_id)
634
elif new_kind is None:
637
return self.CANNOT_DIFF
638
return self.diff_symlink(old_target, new_target)
640
def diff_symlink(self, old_target, new_target):
641
if old_target is None:
642
self.to_file.write('=== target is %r\n' % new_target)
643
elif new_target is None:
644
self.to_file.write('=== target was %r\n' % old_target)
646
self.to_file.write('=== target changed %r => %r\n' %
647
(old_target, new_target))
651
class DiffText(DiffPath):
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)
261
def _show_diff_trees(old_tree, new_tree, to_file,
262
specific_files, external_diff_options,
263
old_label='a/', new_label='b/' ):
265
DEVNULL = '/dev/null'
266
# Windows users, don't panic about this filename -- it is a
267
# special signal to GNU patch that the file should be created or
268
# deleted respectively.
270
# TODO: Generation of pseudo-diffs for added/deleted files could
271
# be usefully made into a much faster special case.
273
_raise_if_doubly_unversioned(specific_files, old_tree, new_tree)
275
if external_diff_options:
276
assert isinstance(external_diff_options, basestring)
277
opts = external_diff_options.split()
278
def diff_file(olab, olines, nlab, nlines, to_file):
279
external_diff(olab, olines, nlab, nlines, to_file, opts)
281
diff_file = internal_diff
283
delta = compare_trees(old_tree, new_tree, want_unchanged=False,
284
specific_files=specific_files)
287
for path, file_id, kind in delta.removed:
289
print >>to_file, '=== removed %s %r' % (kind, path)
290
old_tree.inventory[file_id].diff(diff_file, old_label + path, old_tree,
291
DEVNULL, None, None, to_file)
292
for path, file_id, kind in delta.added:
294
print >>to_file, '=== added %s %r' % (kind, path)
295
new_tree.inventory[file_id].diff(diff_file, new_label + path, new_tree,
296
DEVNULL, None, None, to_file,
298
for (old_path, new_path, file_id, kind,
299
text_modified, meta_modified) in delta.renamed:
301
prop_str = get_prop_change(meta_modified)
302
print >>to_file, '=== renamed %s %r => %r%s' % (
303
kind, old_path, new_path, prop_str)
304
_maybe_diff_file_or_symlink(old_label, old_path, old_tree, file_id,
305
new_label, new_path, new_tree,
306
text_modified, kind, to_file, diff_file)
307
for path, file_id, kind, text_modified, meta_modified in delta.modified:
309
prop_str = get_prop_change(meta_modified)
310
print >>to_file, '=== modified %s %r%s' % (kind, path, prop_str)
312
_maybe_diff_file_or_symlink(old_label, path, old_tree, file_id,
313
new_label, path, new_tree,
314
True, kind, to_file, diff_file)
319
def _raise_if_doubly_unversioned(specific_files, old_tree, new_tree):
320
"""Complain if paths are not versioned in either tree."""
321
if not specific_files:
323
old_unversioned = old_tree.filter_unversioned_files(specific_files)
324
new_unversioned = new_tree.filter_unversioned_files(specific_files)
325
unversioned = old_unversioned.intersection(new_unversioned)
327
raise errors.PathsNotVersionedError(sorted(unversioned))
330
def _raise_if_nonexistent(paths, old_tree, new_tree):
331
"""Complain if paths are not in either inventory or tree.
333
It's OK with the files exist in either tree's inventory, or
334
if they exist in the tree but are not versioned.
336
This can be used by operations such as bzr status that can accept
337
unknown or ignored files.
339
mutter("check paths: %r", paths)
342
s = old_tree.filter_unversioned_files(paths)
343
s = new_tree.filter_unversioned_files(s)
344
s = [path for path in s if not new_tree.has_filename(path)]
346
raise errors.PathsDoNotExist(sorted(s))
349
def get_prop_change(meta_modified):
351
return " (properties changed)"
356
def _maybe_diff_file_or_symlink(old_label, old_path, old_tree, file_id,
357
new_label, new_path, new_tree, text_modified,
358
kind, to_file, diff_file):
360
new_entry = new_tree.inventory[file_id]
361
old_tree.inventory[file_id].diff(diff_file,
362
old_label + old_path, old_tree,
363
new_label + new_path, new_entry,