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
276
def _get_trees_to_diff(path_list, revision_specs, old_url, new_url,
278
"""Get the trees and specific files to diff given a list of paths.
280
This method works out the trees to be diff'ed and the files of
281
interest within those trees.
284
the list of arguments passed to the diff command
285
:param revision_specs:
286
Zero, one or two RevisionSpecs from the diff command line,
287
saying what revisions to compare.
289
The url of the old branch or tree. If None, the tree to use is
290
taken from the first path, if any, or the current working tree.
292
The url of the new branch or tree. If None, the tree to use is
293
taken from the first path, if any, or the current working tree.
295
if True and a view is set, apply the view or check that the paths
298
a tuple of (old_tree, new_tree, specific_files, extra_trees) where
299
extra_trees is a sequence of additional trees to search in for
355
302
# Get the old and new revision specs
356
303
old_revision_spec = None
379
326
default_location = path_list[0]
380
327
other_paths = path_list[1:]
382
def lock_tree_or_branch(wt, br):
385
add_cleanup(wt.unlock)
388
add_cleanup(br.unlock)
390
329
# Get the old location
391
330
specific_files = []
392
331
if old_url is None:
393
332
old_url = default_location
394
333
working_tree, branch, relpath = \
395
334
bzrdir.BzrDir.open_containing_tree_or_branch(old_url)
396
lock_tree_or_branch(working_tree, branch)
397
335
if consider_relpath and relpath != '':
398
336
if working_tree is not None and apply_view:
399
337
views.check_path_in_view(working_tree, relpath)
400
338
specific_files.append(relpath)
401
339
old_tree = _get_tree_to_diff(old_revision_spec, working_tree, branch)
404
341
# Get the new location
405
342
if new_url is None:
407
344
if new_url != old_url:
408
345
working_tree, branch, relpath = \
409
346
bzrdir.BzrDir.open_containing_tree_or_branch(new_url)
410
lock_tree_or_branch(working_tree, branch)
411
347
if consider_relpath and relpath != '':
412
348
if working_tree is not None and apply_view:
413
349
views.check_path_in_view(working_tree, relpath)
414
350
specific_files.append(relpath)
415
351
new_tree = _get_tree_to_diff(new_revision_spec, working_tree, branch,
416
352
basis_is_default=working_tree is None)
419
354
# Get the specific files (all files is None, no files is [])
420
355
if make_paths_wt_relative and working_tree is not None:
421
other_paths = working_tree.safe_relpath_files(
357
from bzrlib.builtins import safe_relpath_files
358
other_paths = safe_relpath_files(working_tree, other_paths,
423
359
apply_view=apply_view)
360
except errors.FileInWrongBranch:
361
raise errors.BzrCommandError("Files are in different branches")
424
362
specific_files.extend(other_paths)
425
363
if len(specific_files) == 0:
426
364
specific_files = None
431
369
specific_files = view_files
432
370
view_str = views.view_display_str(view_files)
433
note("*** Ignoring files outside view. View is %s" % view_str)
371
note("*** ignoring files outside view: %s" % view_str)
435
373
# Get extra trees that ought to be searched for file-ids
436
374
extra_trees = None
437
375
if working_tree is not None and working_tree not in (old_tree, new_tree):
438
376
extra_trees = (working_tree,)
439
return old_tree, new_tree, old_branch, new_branch, specific_files, extra_trees
377
return old_tree, new_tree, specific_files, extra_trees
442
379
def _get_tree_to_diff(spec, tree=None, branch=None, basis_is_default=True):
443
380
if branch is None and tree is not None:
458
395
old_label='a/', new_label='b/',
459
396
extra_trees=None,
460
397
path_encoding='utf8',
463
399
"""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)
405
Include only changes to these files - None for all changes.
407
external_diff_options
408
If set, use an external GNU diff and pass these options.
411
If set, more Trees to use for looking up file ids
414
If set, the path will be encoded as specified, otherwise is supposed
475
if format_cls is None:
476
format_cls = DiffTree
477
417
old_tree.lock_read()
479
419
if extra_trees is not None:
498
438
def _patch_header_date(tree, file_id, path):
499
439
"""Returns a timestamp suitable for use in a patch header."""
501
mtime = tree.get_file_mtime(file_id, path)
502
except errors.FileTimestampUnavailable:
440
mtime = tree.get_file_mtime(file_id, path)
504
441
return timestamp.format_patch_date(mtime)
444
@deprecated_function(one_three)
445
def get_prop_change(meta_modified):
447
return " (properties changed)"
507
451
def get_executable_change(old_is_x, new_is_x):
508
452
descr = { True:"+x", False:"-x", None:"??" }
509
453
if old_is_x != new_is_x:
684
628
return self.CANNOT_DIFF
685
629
from_label = '%s%s\t%s' % (self.old_label, old_path, old_date)
686
630
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,
631
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):
633
def diff_text(self, from_file_id, to_file_id, from_label, to_label):
692
634
"""Diff the content of given files in two trees
694
636
:param from_file_id: The id of the file in the from tree. If None,
696
638
:param to_file_id: The id of the file in the to tree. This may refer
697
639
to a different file from from_file_id. If None,
698
640
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):
642
def _get_text(tree, file_id):
703
643
if file_id is not None:
704
return tree.get_file_lines(file_id, path)
644
return tree.get_file(file_id).readlines()
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)
648
from_text = _get_text(self.old_tree, from_file_id)
649
to_text = _get_text(self.new_tree, to_file_id)
710
650
self.text_differ(from_label, from_text, to_label, to_text,
711
self.to_file, path_encoding=self.path_encoding)
712
652
except errors.BinaryFile:
713
653
self.to_file.write(
714
654
("Binary files %s and %s differ\n" %
715
(from_label, to_label)).encode(self.path_encoding,'replace'))
655
(from_label, to_label)).encode(self.path_encoding))
716
656
return self.CHANGED
728
668
def from_string(klass, command_string, old_tree, new_tree, to_file,
729
669
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'])
670
command_template = commands.shlex_split_unicode(command_string)
671
command_template.extend(['%(old_path)s', '%(new_path)s'])
733
672
return klass(command_template, old_tree, new_tree, to_file,
737
def make_from_diff_tree(klass, command_string, external_diff_options=None):
676
def make_from_diff_tree(klass, command_string):
738
677
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,
678
return klass.from_string(command_string, diff_tree.old_tree,
743
679
diff_tree.new_tree, diff_tree.to_file)
744
680
return from_diff_tree
746
682
def _get_command(self, old_path, new_path):
747
683
my_map = {'old_path': old_path, 'new_path': new_path}
748
return [AtTemplate(t).substitute(my_map) for t in
749
self.command_template]
684
return [t % my_map for t in self.command_template]
751
686
def _execute(self, old_path, new_path):
752
687
command = self._get_command(old_path, new_path)
775
def _write_file(self, file_id, tree, prefix, relpath, force_temp=False,
777
if not force_temp and isinstance(tree, WorkingTree):
778
return tree.abspath(tree.id2path(file_id))
710
def _write_file(self, file_id, tree, prefix, relpath):
780
711
full_path = osutils.pathjoin(self._root, prefix, relpath)
781
if not force_temp and self._try_symlink_root(tree, prefix):
712
if self._try_symlink_root(tree, prefix):
783
714
parent_dir = osutils.dirname(full_path)
799
mtime = tree.get_file_mtime(file_id)
800
except errors.FileTimestampUnavailable:
803
os.utime(full_path, (mtime, mtime))
805
osutils.make_readonly(full_path)
729
osutils.make_readonly(full_path)
730
mtime = tree.get_file_mtime(file_id)
731
os.utime(full_path, (mtime, mtime))
808
def _prepare_files(self, file_id, old_path, new_path, force_temp=False,
809
allow_write_new=False):
734
def _prepare_files(self, file_id, old_path, new_path):
810
735
old_disk_path = self._write_file(file_id, self.old_tree, 'old',
811
old_path, force_temp)
812
737
new_disk_path = self._write_file(file_id, self.new_tree, 'new',
813
new_path, force_temp,
814
allow_write=allow_write_new)
815
739
return old_disk_path, new_disk_path
817
741
def finish(self):
819
osutils.rmtree(self._root)
821
if e.errno != errno.ENOENT:
822
mutter("The temporary directory \"%s\" was not "
823
"cleanly removed: %s." % (self._root, e))
742
osutils.rmtree(self._root)
825
744
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
826
745
if (old_kind, new_kind) != ('file', 'file'):
827
746
return DiffPath.CANNOT_DIFF
828
(old_disk_path, new_disk_path) = self._prepare_files(
829
file_id, old_path, new_path)
830
self._execute(old_disk_path, new_disk_path)
832
def edit_file(self, file_id):
833
"""Use this tool to edit a file.
835
A temporary copy will be edited, and the new contents will be
838
:param file_id: The id of the file to edit.
839
:return: The new contents of the file.
841
old_path = self.old_tree.id2path(file_id)
842
new_path = self.new_tree.id2path(file_id)
843
new_abs_path = self._prepare_files(file_id, old_path, new_path,
844
allow_write_new=True,
846
command = self._get_command(osutils.pathjoin('old', old_path),
847
osutils.pathjoin('new', new_path))
848
subprocess.call(command, cwd=self._root)
849
new_file = open(new_abs_path, 'r')
851
return new_file.read()
747
self._prepare_files(file_id, old_path, new_path)
748
self._execute(osutils.pathjoin('old', old_path),
749
osutils.pathjoin('new', new_path))
856
752
class DiffTree(object):
913
809
:param using: Commandline to use to invoke an external diff tool
915
811
if using is not None:
916
extra_factories = [DiffFromTool.make_from_diff_tree(using, external_diff_options)]
812
extra_factories = [DiffFromTool.make_from_diff_tree(using)]
918
814
extra_factories = []
919
815
if external_diff_options:
920
816
opts = external_diff_options.split()
921
def diff_file(olab, olines, nlab, nlines, to_file, path_encoding=None):
922
""":param path_encoding: not used but required
923
to match the signature of internal_diff.
817
def diff_file(olab, olines, nlab, nlines, to_file):
925
818
external_diff(olab, olines, nlab, nlines, to_file, opts)
927
820
diff_file = internal_diff
1018
911
new_kind = self.new_tree.kind(file_id)
1019
912
except (errors.NoSuchId, errors.NoSuchFile):
1021
self._diff(file_id, old_path, new_path, old_kind, new_kind)
1024
def _diff(self, file_id, old_path, new_path, old_kind, new_kind):
1025
915
result = DiffPath._diff_many(self.differs, file_id, old_path,
1026
916
new_path, old_kind, new_kind)
1027
917
if result is DiffPath.CANNOT_DIFF: