~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/diff.py

  • Committer: Gordon Tyler
  • Date: 2010-02-02 06:30:43 UTC
  • mto: (5037.3.1 integration)
  • mto: This revision was merged to the branch mainline in revision 5046.
  • Revision ID: gordon@doxxx.net-20100202063043-3ygr1114d25m3f7m
Added cmdline.split function, which replaces commands.shlex_split_unicode.

Show diffs side-by-side

added added

removed removed

Lines of Context:
18
18
import os
19
19
import re
20
20
import shutil
 
21
import string
21
22
import sys
22
23
 
23
24
from bzrlib.lazy_import import lazy_import
30
31
from bzrlib import (
31
32
    branch as _mod_branch,
32
33
    bzrdir,
33
 
    commands,
 
34
    cmdline,
34
35
    errors,
35
36
    osutils,
36
37
    patiencediff,
38
39
    timestamp,
39
40
    views,
40
41
    )
 
42
 
 
43
from bzrlib.workingtree import WorkingTree
41
44
""")
42
45
 
43
46
from bzrlib.symbol_versioning import (
46
49
from bzrlib.trace import mutter, note, warning
47
50
 
48
51
 
 
52
class AtTemplate(string.Template):
 
53
    """Templating class that uses @ instead of $."""
 
54
 
 
55
    delimiter = '@'
 
56
 
 
57
 
49
58
# TODO: Rather than building a changeset object, we should probably
50
59
# invoke callbacks on an object.  That object can either accumulate a
51
60
# list, write them out directly, etc etc.
171
180
 
172
181
        if not diff_opts:
173
182
            diff_opts = []
 
183
        if sys.platform == 'win32':
 
184
            # Popen doesn't do the proper encoding for external commands
 
185
            # Since we are dealing with an ANSI api, use mbcs encoding
 
186
            old_filename = old_filename.encode('mbcs')
 
187
            new_filename = new_filename.encode('mbcs')
174
188
        diffcmd = ['diff',
175
189
                   '--label', old_filename,
176
190
                   old_abspath,
272
286
                        new_abspath, e)
273
287
 
274
288
 
275
 
def _get_trees_to_diff(path_list, revision_specs, old_url, new_url,
276
 
    apply_view=True):
 
289
def get_trees_and_branches_to_diff(path_list, revision_specs, old_url, new_url,
 
290
                                   apply_view=True):
277
291
    """Get the trees and specific files to diff given a list of paths.
278
292
 
279
293
    This method works out the trees to be diff'ed and the files of
294
308
        if True and a view is set, apply the view or check that the paths
295
309
        are within it
296
310
    :returns:
297
 
        a tuple of (old_tree, new_tree, specific_files, extra_trees) where
298
 
        extra_trees is a sequence of additional trees to search in for
299
 
        file-ids.
 
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.
300
314
    """
301
315
    # Get the old and new revision specs
302
316
    old_revision_spec = None
336
350
            views.check_path_in_view(working_tree, relpath)
337
351
        specific_files.append(relpath)
338
352
    old_tree = _get_tree_to_diff(old_revision_spec, working_tree, branch)
 
353
    old_branch = branch
339
354
 
340
355
    # Get the new location
341
356
    if new_url is None:
349
364
            specific_files.append(relpath)
350
365
    new_tree = _get_tree_to_diff(new_revision_spec, working_tree, branch,
351
366
        basis_is_default=working_tree is None)
 
367
    new_branch = branch
352
368
 
353
369
    # Get the specific files (all files is None, no files is [])
354
370
    if make_paths_wt_relative and working_tree is not None:
367
383
            if view_files:
368
384
                specific_files = view_files
369
385
                view_str = views.view_display_str(view_files)
370
 
                note("*** ignoring files outside view: %s" % view_str)
 
386
                note("*** Ignoring files outside view. View is %s" % view_str)
371
387
 
372
388
    # Get extra trees that ought to be searched for file-ids
373
389
    extra_trees = None
374
390
    if working_tree is not None and working_tree not in (old_tree, new_tree):
375
391
        extra_trees = (working_tree,)
376
 
    return old_tree, new_tree, specific_files, extra_trees
 
392
    return old_tree, new_tree, old_branch, new_branch, specific_files, extra_trees
 
393
 
377
394
 
378
395
def _get_tree_to_diff(spec, tree=None, branch=None, basis_is_default=True):
379
396
    if branch is None and tree is not None:
436
453
 
437
454
def _patch_header_date(tree, file_id, path):
438
455
    """Returns a timestamp suitable for use in a patch header."""
439
 
    mtime = tree.get_file_mtime(file_id, path)
 
456
    try:
 
457
        mtime = tree.get_file_mtime(file_id, path)
 
458
    except errors.FileTimestampUnavailable:
 
459
        mtime = 0
440
460
    return timestamp.format_patch_date(mtime)
441
461
 
442
462
 
620
640
            return self.CANNOT_DIFF
621
641
        from_label = '%s%s\t%s' % (self.old_label, old_path, old_date)
622
642
        to_label = '%s%s\t%s' % (self.new_label, new_path, new_date)
623
 
        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,
 
644
            old_path, new_path)
624
645
 
625
 
    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):
626
648
        """Diff the content of given files in two trees
627
649
 
628
650
        :param from_file_id: The id of the file in the from tree.  If None,
630
652
        :param to_file_id: The id of the file in the to tree.  This may refer
631
653
            to a different file from from_file_id.  If None,
632
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.
633
657
        """
634
 
        def _get_text(tree, file_id):
 
658
        def _get_text(tree, file_id, path):
635
659
            if file_id is not None:
636
 
                return tree.get_file(file_id).readlines()
 
660
                return tree.get_file(file_id, path).readlines()
637
661
            else:
638
662
                return []
639
663
        try:
640
 
            from_text = _get_text(self.old_tree, from_file_id)
641
 
            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)
642
666
            self.text_differ(from_label, from_text, to_label, to_text,
643
667
                             self.to_file)
644
668
        except errors.BinaryFile:
659
683
    @classmethod
660
684
    def from_string(klass, command_string, old_tree, new_tree, to_file,
661
685
                    path_encoding='utf-8'):
662
 
        command_template = commands.shlex_split_unicode(command_string)
663
 
        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'])
664
689
        return klass(command_template, old_tree, new_tree, to_file,
665
690
                     path_encoding)
666
691
 
673
698
 
674
699
    def _get_command(self, old_path, new_path):
675
700
        my_map = {'old_path': old_path, 'new_path': new_path}
676
 
        return [t % my_map for t in self.command_template]
 
701
        return [AtTemplate(t).substitute(my_map) for t in
 
702
                self.command_template]
677
703
 
678
704
    def _execute(self, old_path, new_path):
679
705
        command = self._get_command(old_path, new_path)
699
725
                raise
700
726
        return True
701
727
 
702
 
    def _write_file(self, file_id, tree, prefix, relpath):
 
728
    def _write_file(self, file_id, tree, prefix, relpath, force_temp=False,
 
729
                    allow_write=False):
 
730
        if not force_temp and isinstance(tree, WorkingTree):
 
731
            return tree.abspath(tree.id2path(file_id))
 
732
        
703
733
        full_path = osutils.pathjoin(self._root, prefix, relpath)
704
 
        if self._try_symlink_root(tree, prefix):
 
734
        if not force_temp and self._try_symlink_root(tree, prefix):
705
735
            return full_path
706
736
        parent_dir = osutils.dirname(full_path)
707
737
        try:
718
748
                target.close()
719
749
        finally:
720
750
            source.close()
721
 
        osutils.make_readonly(full_path)
722
 
        mtime = tree.get_file_mtime(file_id)
 
751
        if not allow_write:
 
752
            osutils.make_readonly(full_path)
 
753
        try:
 
754
            mtime = tree.get_file_mtime(file_id)
 
755
        except errors.FileTimestampUnavailable:
 
756
            mtime = 0
723
757
        os.utime(full_path, (mtime, mtime))
724
758
        return full_path
725
759
 
726
 
    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):
727
762
        old_disk_path = self._write_file(file_id, self.old_tree, 'old',
728
 
                                         old_path)
 
763
                                         old_path, force_temp)
729
764
        new_disk_path = self._write_file(file_id, self.new_tree, 'new',
730
 
                                         new_path)
 
765
                                         new_path, force_temp,
 
766
                                         allow_write=allow_write_new)
731
767
        return old_disk_path, new_disk_path
732
768
 
733
769
    def finish(self):
734
 
        osutils.rmtree(self._root)
 
770
        try:
 
771
            osutils.rmtree(self._root)
 
772
        except OSError, e:
 
773
            if e.errno != errno.ENOENT:
 
774
                mutter("The temporary directory \"%s\" was not "
 
775
                        "cleanly removed: %s." % (self._root, e))
735
776
 
736
777
    def diff(self, file_id, old_path, new_path, old_kind, new_kind):
737
778
        if (old_kind, new_kind) != ('file', 'file'):
738
779
            return DiffPath.CANNOT_DIFF
739
 
        self._prepare_files(file_id, old_path, new_path)
740
 
        self._execute(osutils.pathjoin('old', old_path),
741
 
                      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)
 
783
 
 
784
    def edit_file(self, file_id):
 
785
        """Use this tool to edit a file.
 
786
 
 
787
        A temporary copy will be edited, and the new contents will be
 
788
        returned.
 
789
 
 
790
        :param file_id: The id of the file to edit.
 
791
        :return: The new contents of the file.
 
792
        """
 
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,
 
797
                                           force_temp=True)[1]
 
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')
 
802
        try:
 
803
            return new_file.read()
 
804
        finally:
 
805
            new_file.close()
742
806
 
743
807
 
744
808
class DiffTree(object):
882
946
                self.to_file.write("=== modified %s '%s'%s\n" % (kind[0],
883
947
                                   newpath_encoded, prop_str))
884
948
            if changed_content:
885
 
                self.diff(file_id, oldpath, newpath)
 
949
                self._diff(file_id, oldpath, newpath, kind[0], kind[1])
886
950
                has_changes = 1
887
951
            if renamed:
888
952
                has_changes = 1
903
967
            new_kind = self.new_tree.kind(file_id)
904
968
        except (errors.NoSuchId, errors.NoSuchFile):
905
969
            new_kind = None
906
 
 
 
970
        self._diff(file_id, old_path, new_path, old_kind, new_kind)
 
971
 
 
972
 
 
973
    def _diff(self, file_id, old_path, new_path, old_kind, new_kind):
907
974
        result = DiffPath._diff_many(self.differs, file_id, old_path,
908
975
                                       new_path, old_kind, new_kind)
909
976
        if result is DiffPath.CANNOT_DIFF: