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
43
from bzrlib.workingtree import WorkingTree
42
46
from bzrlib.symbol_versioning import (
46
from bzrlib.trace import warning
49
from bzrlib.trace import mutter, note, warning
52
class AtTemplate(string.Template):
53
"""Templating class that uses @ instead of $."""
49
58
# TODO: Rather than building a changeset object, we should probably
290
305
The url of the new branch or tree. If None, the tree to use is
291
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
293
a tuple of (old_tree, new_tree, specific_files, extra_trees) where
294
extra_trees is a sequence of additional trees to search in for
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.
297
315
# Get the old and new revision specs
298
316
old_revision_spec = None
328
346
working_tree, branch, relpath = \
329
347
bzrdir.BzrDir.open_containing_tree_or_branch(old_url)
330
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)
331
351
specific_files.append(relpath)
332
352
old_tree = _get_tree_to_diff(old_revision_spec, working_tree, branch)
334
355
# Get the new location
335
356
if new_url is None:
338
359
working_tree, branch, relpath = \
339
360
bzrdir.BzrDir.open_containing_tree_or_branch(new_url)
340
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)
341
364
specific_files.append(relpath)
342
365
new_tree = _get_tree_to_diff(new_revision_spec, working_tree, branch,
343
366
basis_is_default=working_tree is None)
345
369
# Get the specific files (all files is None, no files is [])
346
370
if make_paths_wt_relative and working_tree is not None:
347
other_paths = _relative_paths_in_tree(working_tree, other_paths)
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")
348
377
specific_files.extend(other_paths)
349
378
if len(specific_files) == 0:
350
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)
352
388
# Get extra trees that ought to be searched for file-ids
353
389
extra_trees = None
354
390
if working_tree is not None and working_tree not in (old_tree, new_tree):
355
391
extra_trees = (working_tree,)
356
return old_tree, new_tree, specific_files, extra_trees
392
return old_tree, new_tree, old_branch, new_branch, specific_files, extra_trees
359
395
def _get_tree_to_diff(spec, tree=None, branch=None, basis_is_default=True):
370
406
return spec.as_tree(branch)
373
def _relative_paths_in_tree(tree, paths):
374
"""Get the relative paths within a working tree.
376
Each path may be either an absolute path or a path relative to the
377
current working directory.
380
for filename in paths:
382
result.append(tree.relpath(osutils.dereference_path(filename)))
383
except errors.PathNotChild:
384
raise errors.BzrCommandError("Files are in different branches")
388
409
def show_diff_trees(old_tree, new_tree, to_file, specific_files=None,
389
410
external_diff_options=None,
390
411
old_label='a/', new_label='b/',
433
454
def _patch_header_date(tree, file_id, path):
434
455
"""Returns a timestamp suitable for use in a patch header."""
435
mtime = tree.get_file_mtime(file_id, path)
457
mtime = tree.get_file_mtime(file_id, path)
458
except errors.FileTimestampUnavailable:
436
460
return timestamp.format_patch_date(mtime)
439
@deprecated_function(one_three)
440
def get_prop_change(meta_modified):
442
return " (properties changed)"
446
463
def get_executable_change(old_is_x, new_is_x):
447
464
descr = { True:"+x", False:"-x", None:"??" }
448
465
if old_is_x != new_is_x:
623
640
return self.CANNOT_DIFF
624
641
from_label = '%s%s\t%s' % (self.old_label, old_path, old_date)
625
642
to_label = '%s%s\t%s' % (self.new_label, new_path, new_date)
626
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,
628
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):
629
648
"""Diff the content of given files in two trees
631
650
:param from_file_id: The id of the file in the from tree. If None,
633
652
:param to_file_id: The id of the file in the to tree. This may refer
634
653
to a different file from from_file_id. If None,
635
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.
637
def _get_text(tree, file_id):
658
def _get_text(tree, file_id, path):
638
659
if file_id is not None:
639
return tree.get_file(file_id).readlines()
660
return tree.get_file(file_id, path).readlines()
643
from_text = _get_text(self.old_tree, from_file_id)
644
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)
645
666
self.text_differ(from_label, from_text, to_label, to_text,
647
668
except errors.BinaryFile:
663
684
def from_string(klass, command_string, old_tree, new_tree, to_file,
664
685
path_encoding='utf-8'):
665
686
command_template = commands.shlex_split_unicode(command_string)
666
command_template.extend(['%(old_path)s', '%(new_path)s'])
687
if '@' not in command_string:
688
command_template.extend(['@old_path', '@new_path'])
667
689
return klass(command_template, old_tree, new_tree, to_file,
677
699
def _get_command(self, old_path, new_path):
678
700
my_map = {'old_path': old_path, 'new_path': new_path}
679
return [t % my_map for t in self.command_template]
701
return [AtTemplate(t).substitute(my_map) for t in
702
self.command_template]
681
704
def _execute(self, old_path, new_path):
682
705
command = self._get_command(old_path, new_path)
705
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))
706
733
full_path = osutils.pathjoin(self._root, prefix, relpath)
707
if self._try_symlink_root(tree, prefix):
734
if not force_temp and self._try_symlink_root(tree, prefix):
709
736
parent_dir = osutils.dirname(full_path)
724
osutils.make_readonly(full_path)
725
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:
726
757
os.utime(full_path, (mtime, mtime))
729
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):
730
762
old_disk_path = self._write_file(file_id, self.old_tree, 'old',
763
old_path, force_temp)
732
764
new_disk_path = self._write_file(file_id, self.new_tree, 'new',
765
new_path, force_temp,
766
allow_write=allow_write_new)
734
767
return old_disk_path, new_disk_path
736
769
def finish(self):
737
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))
739
777
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
740
778
if (old_kind, new_kind) != ('file', 'file'):
741
779
return DiffPath.CANNOT_DIFF
742
self._prepare_files(file_id, old_path, new_path)
743
self._execute(osutils.pathjoin('old', old_path),
744
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()
747
808
class DiffTree(object):
906
967
new_kind = self.new_tree.kind(file_id)
907
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):
910
974
result = DiffPath._diff_many(self.differs, file_id, old_path,
911
975
new_path, old_kind, new_kind)
912
976
if result is DiffPath.CANNOT_DIFF: