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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24
23
from bzrlib.lazy_import import lazy_import
43
from bzrlib.workingtree import WorkingTree
46
42
from bzrlib.symbol_versioning import (
49
from bzrlib.trace import mutter, note, warning
52
class AtTemplate(string.Template):
53
"""Templating class that uses @ instead of $."""
46
from bzrlib.trace import warning
58
49
# TODO: Rather than building a changeset object, we should probably
305
293
The url of the new branch or tree. If None, the tree to use is
306
294
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
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.
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
315
300
# Get the old and new revision specs
316
301
old_revision_spec = None
346
331
working_tree, branch, relpath = \
347
332
bzrdir.BzrDir.open_containing_tree_or_branch(old_url)
348
333
if consider_relpath and relpath != '':
349
if working_tree is not None and apply_view:
350
views.check_path_in_view(working_tree, relpath)
351
334
specific_files.append(relpath)
352
335
old_tree = _get_tree_to_diff(old_revision_spec, working_tree, branch)
355
337
# Get the new location
356
338
if new_url is None:
359
341
working_tree, branch, relpath = \
360
342
bzrdir.BzrDir.open_containing_tree_or_branch(new_url)
361
343
if consider_relpath and relpath != '':
362
if working_tree is not None and apply_view:
363
views.check_path_in_view(working_tree, relpath)
364
344
specific_files.append(relpath)
365
345
new_tree = _get_tree_to_diff(new_revision_spec, working_tree, branch,
366
346
basis_is_default=working_tree is None)
369
348
# Get the specific files (all files is None, no files is [])
370
349
if make_paths_wt_relative and working_tree is not None:
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")
350
other_paths = _relative_paths_in_tree(working_tree, other_paths)
377
351
specific_files.extend(other_paths)
378
352
if len(specific_files) == 0:
379
353
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)
388
355
# Get extra trees that ought to be searched for file-ids
389
356
extra_trees = None
390
357
if working_tree is not None and working_tree not in (old_tree, new_tree):
391
358
extra_trees = (working_tree,)
392
return old_tree, new_tree, old_branch, new_branch, specific_files, extra_trees
359
return old_tree, new_tree, specific_files, extra_trees
395
362
def _get_tree_to_diff(spec, tree=None, branch=None, basis_is_default=True):
406
373
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")
409
391
def show_diff_trees(old_tree, new_tree, to_file, specific_files=None,
410
392
external_diff_options=None,
411
393
old_label='a/', new_label='b/',
454
436
def _patch_header_date(tree, file_id, path):
455
437
"""Returns a timestamp suitable for use in a patch header."""
457
mtime = tree.get_file_mtime(file_id, path)
458
except errors.FileTimestampUnavailable:
438
mtime = tree.get_file_mtime(file_id, path)
460
439
return timestamp.format_patch_date(mtime)
442
@deprecated_function(one_three)
443
def get_prop_change(meta_modified):
445
return " (properties changed)"
463
449
def get_executable_change(old_is_x, new_is_x):
464
450
descr = { True:"+x", False:"-x", None:"??" }
465
451
if old_is_x != new_is_x:
640
626
return self.CANNOT_DIFF
641
627
from_label = '%s%s\t%s' % (self.old_label, old_path, old_date)
642
628
to_label = '%s%s\t%s' % (self.new_label, new_path, new_date)
643
return self.diff_text(from_file_id, to_file_id, from_label, to_label,
629
return self.diff_text(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):
631
def diff_text(self, from_file_id, to_file_id, from_label, to_label):
648
632
"""Diff the content of given files in two trees
650
634
:param from_file_id: The id of the file in the from tree. If None,
652
636
:param to_file_id: The id of the file in the to tree. This may refer
653
637
to a different file from from_file_id. If None,
654
638
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.
658
def _get_text(tree, file_id, path):
640
def _get_text(tree, file_id):
659
641
if file_id is not None:
660
return tree.get_file(file_id, path).readlines()
642
return tree.get_file(file_id).readlines()
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)
646
from_text = _get_text(self.old_tree, from_file_id)
647
to_text = _get_text(self.new_tree, to_file_id)
666
648
self.text_differ(from_label, from_text, to_label, to_text,
668
650
except errors.BinaryFile:
684
666
def from_string(klass, command_string, old_tree, new_tree, to_file,
685
667
path_encoding='utf-8'):
686
668
command_template = commands.shlex_split_unicode(command_string)
687
if '@' not in command_string:
688
command_template.extend(['@old_path', '@new_path'])
669
command_template.extend(['%(old_path)s', '%(new_path)s'])
689
670
return klass(command_template, old_tree, new_tree, to_file,
699
680
def _get_command(self, old_path, new_path):
700
681
my_map = {'old_path': old_path, 'new_path': new_path}
701
return [AtTemplate(t).substitute(my_map) for t in
702
self.command_template]
682
return [t % my_map for t in self.command_template]
704
684
def _execute(self, old_path, new_path):
705
685
command = self._get_command(old_path, new_path)
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))
708
def _write_file(self, file_id, tree, prefix, relpath):
733
709
full_path = osutils.pathjoin(self._root, prefix, relpath)
734
if not force_temp and self._try_symlink_root(tree, prefix):
710
if self._try_symlink_root(tree, prefix):
736
712
parent_dir = osutils.dirname(full_path)
752
osutils.make_readonly(full_path)
754
mtime = tree.get_file_mtime(file_id)
755
except errors.FileTimestampUnavailable:
727
osutils.make_readonly(full_path)
728
mtime = tree.get_file_mtime(file_id)
757
729
os.utime(full_path, (mtime, mtime))
760
def _prepare_files(self, file_id, old_path, new_path, force_temp=False,
761
allow_write_new=False):
732
def _prepare_files(self, file_id, old_path, new_path):
762
733
old_disk_path = self._write_file(file_id, self.old_tree, 'old',
763
old_path, force_temp)
764
735
new_disk_path = self._write_file(file_id, self.new_tree, 'new',
765
new_path, force_temp,
766
allow_write=allow_write_new)
767
737
return old_disk_path, new_disk_path
769
739
def finish(self):
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))
740
osutils.rmtree(self._root)
777
742
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
778
743
if (old_kind, new_kind) != ('file', 'file'):
779
744
return DiffPath.CANNOT_DIFF
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()
745
self._prepare_files(file_id, old_path, new_path)
746
self._execute(osutils.pathjoin('old', old_path),
747
osutils.pathjoin('new', new_path))
808
750
class DiffTree(object):
967
909
new_kind = self.new_tree.kind(file_id)
968
910
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):
974
913
result = DiffPath._diff_many(self.differs, file_id, old_path,
975
914
new_path, old_kind, new_kind)
976
915
if result is DiffPath.CANNOT_DIFF: