13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23
24
from bzrlib.lazy_import import lazy_import
30
31
from bzrlib import (
31
32
branch as _mod_branch,
43
from bzrlib.workingtree import WorkingTree
46
from bzrlib.registry import (
42
49
from bzrlib.symbol_versioning import (
46
from bzrlib.trace import warning
52
from bzrlib.trace import mutter, note, warning
55
class AtTemplate(string.Template):
56
"""Templating class that uses @ instead of $."""
49
61
# TODO: Rather than building a changeset object, we should probably
293
308
The url of the new branch or tree. If None, the tree to use is
294
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
296
a tuple of (old_tree, new_tree, specific_files, extra_trees) where
297
extra_trees is a sequence of additional trees to search in for
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.
300
318
# Get the old and new revision specs
301
319
old_revision_spec = None
331
349
working_tree, branch, relpath = \
332
350
bzrdir.BzrDir.open_containing_tree_or_branch(old_url)
333
351
if consider_relpath and relpath != '':
352
if working_tree is not None and apply_view:
353
views.check_path_in_view(working_tree, relpath)
334
354
specific_files.append(relpath)
335
355
old_tree = _get_tree_to_diff(old_revision_spec, working_tree, branch)
337
358
# Get the new location
338
359
if new_url is None:
341
362
working_tree, branch, relpath = \
342
363
bzrdir.BzrDir.open_containing_tree_or_branch(new_url)
343
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)
344
367
specific_files.append(relpath)
345
368
new_tree = _get_tree_to_diff(new_revision_spec, working_tree, branch,
346
369
basis_is_default=working_tree is None)
348
372
# Get the specific files (all files is None, no files is [])
349
373
if make_paths_wt_relative and working_tree is not None:
350
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")
351
380
specific_files.extend(other_paths)
352
381
if len(specific_files) == 0:
353
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)
355
391
# Get extra trees that ought to be searched for file-ids
356
392
extra_trees = None
357
393
if working_tree is not None and working_tree not in (old_tree, new_tree):
358
394
extra_trees = (working_tree,)
359
return old_tree, new_tree, specific_files, extra_trees
395
return old_tree, new_tree, old_branch, new_branch, specific_files, extra_trees
362
398
def _get_tree_to_diff(spec, tree=None, branch=None, basis_is_default=True):
373
409
return spec.as_tree(branch)
376
def _relative_paths_in_tree(tree, paths):
377
"""Get the relative paths within a working tree.
379
Each path may be either an absolute path or a path relative to the
380
current working directory.
383
for filename in paths:
385
result.append(tree.relpath(osutils.dereference_path(filename)))
386
except errors.PathNotChild:
387
raise errors.BzrCommandError("Files are in different branches")
391
412
def show_diff_trees(old_tree, new_tree, to_file, specific_files=None,
392
413
external_diff_options=None,
393
414
old_label='a/', new_label='b/',
394
415
extra_trees=None,
395
416
path_encoding='utf8',
397
419
"""Show in text form the changes from one tree to another.
403
Include only changes to these files - None for all changes.
405
external_diff_options
406
If set, use an external GNU diff and pass these options.
409
If set, more Trees to use for looking up file ids
412
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
415
433
old_tree.lock_read()
417
435
if extra_trees is not None:
420
438
new_tree.lock_read()
422
differ = DiffTree.from_trees_options(old_tree, new_tree, to_file,
424
external_diff_options,
425
old_label, new_label, using)
440
differ = format_cls.from_trees_options(old_tree, new_tree, to_file,
442
external_diff_options,
443
old_label, new_label, using)
426
444
return differ.show_diff(specific_files, extra_trees)
428
446
new_tree.unlock()
436
454
def _patch_header_date(tree, file_id, path):
437
455
"""Returns a timestamp suitable for use in a patch header."""
438
mtime = tree.get_file_mtime(file_id, path)
457
mtime = tree.get_file_mtime(file_id, path)
458
except errors.FileTimestampUnavailable:
439
460
return timestamp.format_patch_date(mtime)
442
@deprecated_function(one_three)
443
def get_prop_change(meta_modified):
445
return " (properties changed)"
449
463
def get_executable_change(old_is_x, new_is_x):
450
464
descr = { True:"+x", False:"-x", None:"??" }
451
465
if old_is_x != new_is_x:
626
640
return self.CANNOT_DIFF
627
641
from_label = '%s%s\t%s' % (self.old_label, old_path, old_date)
628
642
to_label = '%s%s\t%s' % (self.new_label, new_path, new_date)
629
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,
631
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):
632
648
"""Diff the content of given files in two trees
634
650
:param from_file_id: The id of the file in the from tree. If None,
636
652
:param to_file_id: The id of the file in the to tree. This may refer
637
653
to a different file from from_file_id. If None,
638
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.
640
def _get_text(tree, file_id):
658
def _get_text(tree, file_id, path):
641
659
if file_id is not None:
642
return tree.get_file(file_id).readlines()
660
return tree.get_file(file_id, path).readlines()
646
from_text = _get_text(self.old_tree, from_file_id)
647
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)
648
666
self.text_differ(from_label, from_text, to_label, to_text,
650
668
except errors.BinaryFile:
666
684
def from_string(klass, command_string, old_tree, new_tree, to_file,
667
685
path_encoding='utf-8'):
668
command_template = commands.shlex_split_unicode(command_string)
669
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'])
670
689
return klass(command_template, old_tree, new_tree, to_file,
680
699
def _get_command(self, old_path, new_path):
681
700
my_map = {'old_path': old_path, 'new_path': new_path}
682
return [t % my_map for t in self.command_template]
701
return [AtTemplate(t).substitute(my_map) for t in
702
self.command_template]
684
704
def _execute(self, old_path, new_path):
685
705
command = self._get_command(old_path, new_path)
708
def _write_file(self, file_id, tree, prefix, relpath):
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))
709
733
full_path = osutils.pathjoin(self._root, prefix, relpath)
710
if self._try_symlink_root(tree, prefix):
734
if not force_temp and self._try_symlink_root(tree, prefix):
712
736
parent_dir = osutils.dirname(full_path)
727
osutils.make_readonly(full_path)
728
mtime = tree.get_file_mtime(file_id)
752
osutils.make_readonly(full_path)
754
mtime = tree.get_file_mtime(file_id)
755
except errors.FileTimestampUnavailable:
729
757
os.utime(full_path, (mtime, mtime))
732
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):
733
762
old_disk_path = self._write_file(file_id, self.old_tree, 'old',
763
old_path, force_temp)
735
764
new_disk_path = self._write_file(file_id, self.new_tree, 'new',
765
new_path, force_temp,
766
allow_write=allow_write_new)
737
767
return old_disk_path, new_disk_path
739
769
def finish(self):
740
osutils.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))
742
777
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
743
778
if (old_kind, new_kind) != ('file', 'file'):
744
779
return DiffPath.CANNOT_DIFF
745
self._prepare_files(file_id, old_path, new_path)
746
self._execute(osutils.pathjoin('old', old_path),
747
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()
750
808
class DiffTree(object):
909
967
new_kind = self.new_tree.kind(file_id)
910
968
except (errors.NoSuchId, errors.NoSuchFile):
970
self._diff(file_id, old_path, new_path, old_kind, new_kind)
973
def _diff(self, file_id, old_path, new_path, old_kind, new_kind):
913
974
result = DiffPath._diff_many(self.differs, file_id, old_path,
914
975
new_path, old_kind, new_kind)
915
976
if result is DiffPath.CANNOT_DIFF: