~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/diff.py

  • Committer: Andrew Bennetts
  • Date: 2010-01-12 03:53:21 UTC
  • mfrom: (4948 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4964.
  • Revision ID: andrew.bennetts@canonical.com-20100112035321-hofpz5p10224ryj3
Merge lp:bzr, resolving conflicts.

Show diffs side-by-side

added added

removed removed

Lines of Context:
12
12
#
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
16
16
 
17
17
import difflib
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
36
37
    patiencediff,
37
38
    textfile,
38
39
    timestamp,
 
40
    views,
39
41
    )
 
42
 
 
43
from bzrlib.workingtree import WorkingTree
40
44
""")
41
45
 
42
46
from bzrlib.symbol_versioning import (
43
 
        deprecated_function,
44
 
        one_three
45
 
        )
46
 
from bzrlib.trace import warning
 
47
    deprecated_function,
 
48
    )
 
49
from bzrlib.trace import mutter, note, warning
 
50
 
 
51
 
 
52
class AtTemplate(string.Template):
 
53
    """Templating class that uses @ instead of $."""
 
54
 
 
55
    delimiter = '@'
47
56
 
48
57
 
49
58
# TODO: Rather than building a changeset object, we should probably
78
87
    # both sequences are empty.
79
88
    if not oldlines and not newlines:
80
89
        return
81
 
    
 
90
 
82
91
    if allow_binary is False:
83
92
        textfile.check_text_lines(oldlines)
84
93
        textfile.check_text_lines(newlines)
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,
199
213
            break
200
214
        else:
201
215
            diffcmd.append('-u')
202
 
                  
 
216
 
203
217
        if diff_opts:
204
218
            diffcmd.extend(diff_opts)
205
219
 
206
220
        pipe = _spawn_external_diff(diffcmd, capture_errors=True)
207
221
        out,err = pipe.communicate()
208
222
        rc = pipe.returncode
209
 
        
 
223
 
210
224
        # internal_diff() adds a trailing newline, add one here for consistency
211
225
        out += '\n'
212
226
        if rc == 2:
247
261
                msg = 'signal %d' % (-rc)
248
262
            else:
249
263
                msg = 'exit code %d' % rc
250
 
                
251
 
            raise errors.BzrError('external diff failed with %s; command: %r' 
 
264
 
 
265
            raise errors.BzrError('external diff failed with %s; command: %r'
252
266
                                  % (rc, diffcmd))
253
267
 
254
268
 
272
286
                        new_abspath, e)
273
287
 
274
288
 
275
 
def _get_trees_to_diff(path_list, revision_specs, old_url, new_url):
 
289
def get_trees_and_branches_to_diff(path_list, revision_specs, old_url, new_url,
 
290
                                   apply_view=True):
276
291
    """Get the trees and specific files to diff given a list of paths.
277
292
 
278
293
    This method works out the trees to be diff'ed and the files of
289
304
    :param new_url:
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.
 
307
    :param apply_view:
 
308
        if True and a view is set, apply the view or check that the paths
 
309
        are within it
292
310
    :returns:
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
295
 
        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.
296
314
    """
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)
 
353
    old_branch = branch
333
354
 
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)
 
367
    new_branch = branch
344
368
 
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)
 
371
        try:
 
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()
 
381
            and apply_view):
 
382
            view_files = working_tree.views.lookup_view()
 
383
            if view_files:
 
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)
351
387
 
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
357
393
 
358
394
 
359
395
def _get_tree_to_diff(spec, tree=None, branch=None, basis_is_default=True):
370
406
    return spec.as_tree(branch)
371
407
 
372
408
 
373
 
def _relative_paths_in_tree(tree, paths):
374
 
    """Get the relative paths within a working tree.
375
 
 
376
 
    Each path may be either an absolute path or a path relative to the
377
 
    current working directory.
378
 
    """
379
 
    result = []
380
 
    for filename in paths:
381
 
        try:
382
 
            result.append(tree.relpath(osutils.dereference_path(filename)))
383
 
        except errors.PathNotChild:
384
 
            raise errors.BzrCommandError("Files are in different branches")
385
 
    return result
386
 
 
387
 
 
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/',
436
457
    return timestamp.format_patch_date(mtime)
437
458
 
438
459
 
439
 
@deprecated_function(one_three)
440
 
def get_prop_change(meta_modified):
441
 
    if meta_modified:
442
 
        return " (properties changed)"
443
 
    else:
444
 
        return  ""
445
 
 
446
460
def get_executable_change(old_is_x, new_is_x):
447
461
    descr = { True:"+x", False:"-x", None:"??" }
448
462
    if old_is_x != new_is_x:
623
637
            return self.CANNOT_DIFF
624
638
        from_label = '%s%s\t%s' % (self.old_label, old_path, old_date)
625
639
        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)
 
640
        return self.diff_text(from_file_id, to_file_id, from_label, to_label,
 
641
            old_path, new_path)
627
642
 
628
 
    def diff_text(self, from_file_id, to_file_id, from_label, to_label):
 
643
    def diff_text(self, from_file_id, to_file_id, from_label, to_label,
 
644
        from_path=None, to_path=None):
629
645
        """Diff the content of given files in two trees
630
646
 
631
647
        :param from_file_id: The id of the file in the from tree.  If None,
633
649
        :param to_file_id: The id of the file in the to tree.  This may refer
634
650
            to a different file from from_file_id.  If None,
635
651
            the file is not present in the to tree.
 
652
        :param from_path: The path in the from tree or None if unknown.
 
653
        :param to_path: The path in the to tree or None if unknown.
636
654
        """
637
 
        def _get_text(tree, file_id):
 
655
        def _get_text(tree, file_id, path):
638
656
            if file_id is not None:
639
 
                return tree.get_file(file_id).readlines()
 
657
                return tree.get_file(file_id, path).readlines()
640
658
            else:
641
659
                return []
642
660
        try:
643
 
            from_text = _get_text(self.old_tree, from_file_id)
644
 
            to_text = _get_text(self.new_tree, to_file_id)
 
661
            from_text = _get_text(self.old_tree, from_file_id, from_path)
 
662
            to_text = _get_text(self.new_tree, to_file_id, to_path)
645
663
            self.text_differ(from_label, from_text, to_label, to_text,
646
664
                             self.to_file)
647
665
        except errors.BinaryFile:
663
681
    def from_string(klass, command_string, old_tree, new_tree, to_file,
664
682
                    path_encoding='utf-8'):
665
683
        command_template = commands.shlex_split_unicode(command_string)
666
 
        command_template.extend(['%(old_path)s', '%(new_path)s'])
 
684
        if '@' not in command_string:
 
685
            command_template.extend(['@old_path', '@new_path'])
667
686
        return klass(command_template, old_tree, new_tree, to_file,
668
687
                     path_encoding)
669
688
 
676
695
 
677
696
    def _get_command(self, old_path, new_path):
678
697
        my_map = {'old_path': old_path, 'new_path': new_path}
679
 
        return [t % my_map for t in self.command_template]
 
698
        return [AtTemplate(t).substitute(my_map) for t in
 
699
                self.command_template]
680
700
 
681
701
    def _execute(self, old_path, new_path):
682
702
        command = self._get_command(old_path, new_path)
702
722
                raise
703
723
        return True
704
724
 
705
 
    def _write_file(self, file_id, tree, prefix, relpath):
 
725
    def _write_file(self, file_id, tree, prefix, relpath, force_temp=False,
 
726
                    allow_write=False):
 
727
        if not force_temp and isinstance(tree, WorkingTree):
 
728
            return tree.abspath(tree.id2path(file_id))
 
729
        
706
730
        full_path = osutils.pathjoin(self._root, prefix, relpath)
707
 
        if self._try_symlink_root(tree, prefix):
 
731
        if not force_temp and self._try_symlink_root(tree, prefix):
708
732
            return full_path
709
733
        parent_dir = osutils.dirname(full_path)
710
734
        try:
721
745
                target.close()
722
746
        finally:
723
747
            source.close()
724
 
        osutils.make_readonly(full_path)
 
748
        if not allow_write:
 
749
            osutils.make_readonly(full_path)
725
750
        mtime = tree.get_file_mtime(file_id)
726
751
        os.utime(full_path, (mtime, mtime))
727
752
        return full_path
728
753
 
729
 
    def _prepare_files(self, file_id, old_path, new_path):
 
754
    def _prepare_files(self, file_id, old_path, new_path, force_temp=False,
 
755
                       allow_write_new=False):
730
756
        old_disk_path = self._write_file(file_id, self.old_tree, 'old',
731
 
                                         old_path)
 
757
                                         old_path, force_temp)
732
758
        new_disk_path = self._write_file(file_id, self.new_tree, 'new',
733
 
                                         new_path)
 
759
                                         new_path, force_temp,
 
760
                                         allow_write=allow_write_new)
734
761
        return old_disk_path, new_disk_path
735
762
 
736
763
    def finish(self):
737
 
        osutils.rmtree(self._root)
 
764
        try:
 
765
            osutils.rmtree(self._root)
 
766
        except OSError, e:
 
767
            if e.errno != errno.ENOENT:
 
768
                mutter("The temporary directory \"%s\" was not "
 
769
                        "cleanly removed: %s." % (self._root, e))
738
770
 
739
771
    def diff(self, file_id, old_path, new_path, old_kind, new_kind):
740
772
        if (old_kind, new_kind) != ('file', 'file'):
741
773
            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))
 
774
        (old_disk_path, new_disk_path) = self._prepare_files(
 
775
                                                file_id, old_path, new_path)
 
776
        self._execute(old_disk_path, new_disk_path)
 
777
 
 
778
    def edit_file(self, file_id):
 
779
        """Use this tool to edit a file.
 
780
 
 
781
        A temporary copy will be edited, and the new contents will be
 
782
        returned.
 
783
 
 
784
        :param file_id: The id of the file to edit.
 
785
        :return: The new contents of the file.
 
786
        """
 
787
        old_path = self.old_tree.id2path(file_id)
 
788
        new_path = self.new_tree.id2path(file_id)
 
789
        new_abs_path = self._prepare_files(file_id, old_path, new_path,
 
790
                                           allow_write_new=True,
 
791
                                           force_temp=True)[1]
 
792
        command = self._get_command(osutils.pathjoin('old', old_path),
 
793
                                    osutils.pathjoin('new', new_path))
 
794
        subprocess.call(command, cwd=self._root)
 
795
        new_file = open(new_abs_path, 'r')
 
796
        try:
 
797
            return new_file.read()
 
798
        finally:
 
799
            new_file.close()
745
800
 
746
801
 
747
802
class DiffTree(object):
885
940
                self.to_file.write("=== modified %s '%s'%s\n" % (kind[0],
886
941
                                   newpath_encoded, prop_str))
887
942
            if changed_content:
888
 
                self.diff(file_id, oldpath, newpath)
 
943
                self._diff(file_id, oldpath, newpath, kind[0], kind[1])
889
944
                has_changes = 1
890
945
            if renamed:
891
946
                has_changes = 1
906
961
            new_kind = self.new_tree.kind(file_id)
907
962
        except (errors.NoSuchId, errors.NoSuchFile):
908
963
            new_kind = None
909
 
 
 
964
        self._diff(file_id, old_path, new_path, old_kind, new_kind)
 
965
 
 
966
 
 
967
    def _diff(self, file_id, old_path, new_path, old_kind, new_kind):
910
968
        result = DiffPath._diff_many(self.differs, file_id, old_path,
911
969
                                       new_path, old_kind, new_kind)
912
970
        if result is DiffPath.CANNOT_DIFF: