277
@deprecated_function(one_zero)
278
def diff_cmd_helper(tree, specific_files, external_diff_options,
279
old_revision_spec=None, new_revision_spec=None,
281
old_label='a/', new_label='b/'):
282
"""Helper for cmd_diff.
287
:param specific_files:
288
The specific files to compare, or None
290
:param external_diff_options:
291
If non-None, run an external diff, and pass it these options
293
:param old_revision_spec:
294
If None, use basis tree as old revision, otherwise use the tree for
295
the specified revision.
297
:param new_revision_spec:
298
If None, use working tree as new revision, otherwise use the tree for
299
the specified revision.
301
:param revision_specs:
302
Zero, one or two RevisionSpecs from the command line, saying what revisions
303
to compare. This can be passed as an alternative to the old_revision_spec
304
and new_revision_spec parameters.
306
The more general form is show_diff_trees(), where the caller
307
supplies any two trees.
310
# TODO: perhaps remove the old parameters old_revision_spec and
311
# new_revision_spec, since this is only really for use from cmd_diff and
312
# it now always passes through a sequence of revision_specs -- mbp
317
revision = spec.in_store(tree.branch)
319
revision = spec.in_store(None)
320
revision_id = revision.rev_id
321
branch = revision.branch
322
return branch.repository.revision_tree(revision_id)
324
if revision_specs is not None:
325
assert (old_revision_spec is None
326
and new_revision_spec is None)
327
if len(revision_specs) > 0:
328
old_revision_spec = revision_specs[0]
329
if len(revision_specs) > 1:
330
new_revision_spec = revision_specs[1]
332
if old_revision_spec is None:
333
old_tree = tree.basis_tree()
335
old_tree = spec_tree(old_revision_spec)
337
if (new_revision_spec is None
338
or new_revision_spec.spec is None):
341
new_tree = spec_tree(new_revision_spec)
343
if new_tree is not tree:
344
extra_trees = (tree,)
348
return show_diff_trees(old_tree, new_tree, sys.stdout, specific_files,
349
external_diff_options,
350
old_label=old_label, new_label=new_label,
351
extra_trees=extra_trees)
354
def _get_trees_to_diff(path_list, revision_specs, old_url, new_url):
292
def get_trees_and_branches_to_diff(path_list, revision_specs, old_url, new_url,
355
294
"""Get the trees and specific files to diff given a list of paths.
357
296
This method works out the trees to be diff'ed and the files of
414
361
if new_url != old_url:
415
362
working_tree, branch, relpath = \
416
363
bzrdir.BzrDir.open_containing_tree_or_branch(new_url)
364
if consider_relpath and relpath != '':
365
if working_tree is not None and apply_view:
366
views.check_path_in_view(working_tree, relpath)
418
367
specific_files.append(relpath)
419
368
new_tree = _get_tree_to_diff(new_revision_spec, working_tree, branch,
420
369
basis_is_default=working_tree is None)
422
372
# Get the specific files (all files is None, no files is [])
423
373
if make_paths_wt_relative and working_tree is not None:
424
other_paths = _relative_paths_in_tree(working_tree, other_paths)
375
from bzrlib.builtins import safe_relpath_files
376
other_paths = safe_relpath_files(working_tree, other_paths,
377
apply_view=apply_view)
378
except errors.FileInWrongBranch:
379
raise errors.BzrCommandError("Files are in different branches")
425
380
specific_files.extend(other_paths)
426
381
if len(specific_files) == 0:
427
382
specific_files = None
383
if (working_tree is not None and working_tree.supports_views()
385
view_files = working_tree.views.lookup_view()
387
specific_files = view_files
388
view_str = views.view_display_str(view_files)
389
note("*** Ignoring files outside view. View is %s" % view_str)
429
391
# Get extra trees that ought to be searched for file-ids
430
392
extra_trees = None
431
393
if working_tree is not None and working_tree not in (old_tree, new_tree):
432
394
extra_trees = (working_tree,)
433
return old_tree, new_tree, specific_files, extra_trees
395
return old_tree, new_tree, old_branch, new_branch, specific_files, extra_trees
436
398
def _get_tree_to_diff(spec, tree=None, branch=None, basis_is_default=True):
470
414
old_label='a/', new_label='b/',
471
415
extra_trees=None,
472
416
path_encoding='utf8',
474
419
"""Show in text form the changes from one tree to another.
480
Include only changes to these files - None for all changes.
482
external_diff_options
483
If set, use an external GNU diff and pass these options.
486
If set, more Trees to use for looking up file ids
489
If set, the path will be encoded as specified, otherwise is supposed
421
:param to_file: The output stream.
422
:param specific_files:Include only changes to these files - None for all
424
:param external_diff_options: If set, use an external GNU diff and pass
426
:param extra_trees: If set, more Trees to use for looking up file ids
427
:param path_encoding: If set, the path will be encoded as specified,
428
otherwise is supposed to be utf8
429
:param format_cls: Formatter class (DiffTree subclass)
431
if format_cls is None:
432
format_cls = DiffTree
492
433
old_tree.lock_read()
494
435
if extra_trees is not None:
513
454
def _patch_header_date(tree, file_id, path):
514
455
"""Returns a timestamp suitable for use in a patch header."""
515
mtime = tree.get_file_mtime(file_id, path)
516
assert mtime is not None, \
517
"got an mtime of None for file-id %s, path %s in tree %s" % (
457
mtime = tree.get_file_mtime(file_id, path)
458
except errors.FileTimestampUnavailable:
519
460
return timestamp.format_patch_date(mtime)
522
def _raise_if_nonexistent(paths, old_tree, new_tree):
523
"""Complain if paths are not in either inventory or tree.
525
It's OK with the files exist in either tree's inventory, or
526
if they exist in the tree but are not versioned.
528
This can be used by operations such as bzr status that can accept
529
unknown or ignored files.
531
mutter("check paths: %r", paths)
534
s = old_tree.filter_unversioned_files(paths)
535
s = new_tree.filter_unversioned_files(s)
536
s = [path for path in s if not new_tree.has_filename(path)]
538
raise errors.PathsDoNotExist(sorted(s))
541
def get_prop_change(meta_modified):
543
return " (properties changed)"
463
def get_executable_change(old_is_x, new_is_x):
464
descr = { True:"+x", False:"-x", None:"??" }
465
if old_is_x != new_is_x:
466
return ["%s to %s" % (descr[old_is_x], descr[new_is_x],)]
548
471
class DiffPath(object):
717
640
return self.CANNOT_DIFF
718
641
from_label = '%s%s\t%s' % (self.old_label, old_path, old_date)
719
642
to_label = '%s%s\t%s' % (self.new_label, new_path, new_date)
720
return self.diff_text(from_file_id, to_file_id, from_label, to_label)
643
return self.diff_text(from_file_id, to_file_id, from_label, to_label,
722
def diff_text(self, from_file_id, to_file_id, from_label, to_label):
646
def diff_text(self, from_file_id, to_file_id, from_label, to_label,
647
from_path=None, to_path=None):
723
648
"""Diff the content of given files in two trees
725
650
:param from_file_id: The id of the file in the from tree. If None,
727
652
:param to_file_id: The id of the file in the to tree. This may refer
728
653
to a different file from from_file_id. If None,
729
654
the file is not present in the to tree.
655
:param from_path: The path in the from tree or None if unknown.
656
:param to_path: The path in the to tree or None if unknown.
731
def _get_text(tree, file_id):
658
def _get_text(tree, file_id, path):
732
659
if file_id is not None:
733
return tree.get_file(file_id).readlines()
660
return tree.get_file(file_id, path).readlines()
737
from_text = _get_text(self.old_tree, from_file_id)
738
to_text = _get_text(self.new_tree, to_file_id)
664
from_text = _get_text(self.old_tree, from_file_id, from_path)
665
to_text = _get_text(self.new_tree, to_file_id, to_path)
739
666
self.text_differ(from_label, from_text, to_label, to_text,
741
668
except errors.BinaryFile:
751
678
path_encoding='utf-8'):
752
679
DiffPath.__init__(self, old_tree, new_tree, to_file, path_encoding)
753
680
self.command_template = command_template
754
self._root = tempfile.mkdtemp(prefix='bzr-diff-')
681
self._root = osutils.mkdtemp(prefix='bzr-diff-')
757
684
def from_string(klass, command_string, old_tree, new_tree, to_file,
758
685
path_encoding='utf-8'):
759
command_template = commands.shlex_split_unicode(command_string)
760
command_template.extend(['%(old_path)s', '%(new_path)s'])
686
command_template = cmdline.split(command_string)
687
if '@' not in command_string:
688
command_template.extend(['@old_path', '@new_path'])
761
689
return klass(command_template, old_tree, new_tree, to_file,
771
699
def _get_command(self, old_path, new_path):
772
700
my_map = {'old_path': old_path, 'new_path': new_path}
773
return [t % my_map for t in self.command_template]
701
return [AtTemplate(t).substitute(my_map) for t in
702
self.command_template]
775
704
def _execute(self, old_path, new_path):
776
proc = subprocess.Popen(self._get_command(old_path, new_path),
777
stdout=subprocess.PIPE, cwd=self._root)
705
command = self._get_command(old_path, new_path)
707
proc = subprocess.Popen(command, stdout=subprocess.PIPE,
710
if e.errno == errno.ENOENT:
711
raise errors.ExecutableMissing(command[0])
778
714
self.to_file.write(proc.stdout.read())
779
715
return proc.wait()
781
def _write_file(self, file_id, tree, prefix, old_path):
782
full_old_path = osutils.pathjoin(self._root, prefix, old_path)
783
parent_dir = osutils.dirname(full_old_path)
717
def _try_symlink_root(self, tree, prefix):
718
if (getattr(tree, 'abspath', None) is None
719
or not osutils.host_os_dereferences_symlinks()):
722
os.symlink(tree.abspath(''), osutils.pathjoin(self._root, prefix))
724
if e.errno != errno.EEXIST:
728
def _write_file(self, file_id, tree, prefix, relpath, force_temp=False,
730
if not force_temp and isinstance(tree, WorkingTree):
731
return tree.abspath(tree.id2path(file_id))
733
full_path = osutils.pathjoin(self._root, prefix, relpath)
734
if not force_temp and self._try_symlink_root(tree, prefix):
736
parent_dir = osutils.dirname(full_path)
785
738
os.makedirs(parent_dir)
786
739
except OSError, e:
787
740
if e.errno != errno.EEXIST:
789
source = tree.get_file(file_id)
742
source = tree.get_file(file_id, relpath)
791
target = open(full_old_path, 'wb')
744
target = open(full_path, 'wb')
793
746
osutils.pumpfile(source, target)
752
osutils.make_readonly(full_path)
754
mtime = tree.get_file_mtime(file_id)
755
except errors.FileTimestampUnavailable:
757
os.utime(full_path, (mtime, mtime))
800
def _prepare_files(self, file_id, old_path, new_path):
760
def _prepare_files(self, file_id, old_path, new_path, force_temp=False,
761
allow_write_new=False):
801
762
old_disk_path = self._write_file(file_id, self.old_tree, 'old',
763
old_path, force_temp)
803
764
new_disk_path = self._write_file(file_id, self.new_tree, 'new',
765
new_path, force_temp,
766
allow_write=allow_write_new)
805
767
return old_disk_path, new_disk_path
807
769
def finish(self):
808
shutil.rmtree(self._root)
771
osutils.rmtree(self._root)
773
if e.errno != errno.ENOENT:
774
mutter("The temporary directory \"%s\" was not "
775
"cleanly removed: %s." % (self._root, e))
810
777
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
811
778
if (old_kind, new_kind) != ('file', 'file'):
812
779
return DiffPath.CANNOT_DIFF
813
self._prepare_files(file_id, old_path, new_path)
814
self._execute(osutils.pathjoin('old', old_path),
815
osutils.pathjoin('new', new_path))
780
(old_disk_path, new_disk_path) = self._prepare_files(
781
file_id, old_path, new_path)
782
self._execute(old_disk_path, new_disk_path)
784
def edit_file(self, file_id):
785
"""Use this tool to edit a file.
787
A temporary copy will be edited, and the new contents will be
790
:param file_id: The id of the file to edit.
791
:return: The new contents of the file.
793
old_path = self.old_tree.id2path(file_id)
794
new_path = self.new_tree.id2path(file_id)
795
new_abs_path = self._prepare_files(file_id, old_path, new_path,
796
allow_write_new=True,
798
command = self._get_command(osutils.pathjoin('old', old_path),
799
osutils.pathjoin('new', new_path))
800
subprocess.call(command, cwd=self._root)
801
new_file = open(new_abs_path, 'r')
803
return new_file.read()
818
808
class DiffTree(object):