~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/diff.py

  • Committer: Vincent Ladeuil
  • Date: 2010-02-10 15:46:03 UTC
  • mfrom: (4985.3.21 update)
  • mto: This revision was merged to the branch mainline in revision 5021.
  • Revision ID: v.ladeuil+lp@free.fr-20100210154603-k4no1gvfuqpzrw7p
Update performs two merges in a more logical order but stop on 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/',
432
453
 
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)
 
456
    try:
 
457
        mtime = tree.get_file_mtime(file_id, path)
 
458
    except errors.FileTimestampUnavailable:
 
459
        mtime = 0
436
460
    return timestamp.format_patch_date(mtime)
437
461
 
438
462
 
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
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,
 
644
            old_path, new_path)
627
645
 
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
630
649
 
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.
636
657
        """
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()
640
661
            else:
641
662
                return []
642
663
        try:
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,
646
667
                             self.to_file)
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,
668
690
                     path_encoding)
669
691
 
676
698
 
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]
680
703
 
681
704
    def _execute(self, old_path, new_path):
682
705
        command = self._get_command(old_path, new_path)
702
725
                raise
703
726
        return True
704
727
 
705
 
    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
        
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):
708
735
            return full_path
709
736
        parent_dir = osutils.dirname(full_path)
710
737
        try:
721
748
                target.close()
722
749
        finally:
723
750
            source.close()
724
 
        osutils.make_readonly(full_path)
725
 
        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
726
757
        os.utime(full_path, (mtime, mtime))
727
758
        return full_path
728
759
 
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',
731
 
                                         old_path)
 
763
                                         old_path, force_temp)
732
764
        new_disk_path = self._write_file(file_id, self.new_tree, 'new',
733
 
                                         new_path)
 
765
                                         new_path, force_temp,
 
766
                                         allow_write=allow_write_new)
734
767
        return old_disk_path, new_disk_path
735
768
 
736
769
    def finish(self):
737
 
        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))
738
776
 
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)
 
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()
745
806
 
746
807
 
747
808
class DiffTree(object):
885
946
                self.to_file.write("=== modified %s '%s'%s\n" % (kind[0],
886
947
                                   newpath_encoded, prop_str))
887
948
            if changed_content:
888
 
                self.diff(file_id, oldpath, newpath)
 
949
                self._diff(file_id, oldpath, newpath, kind[0], kind[1])
889
950
                has_changes = 1
890
951
            if renamed:
891
952
                has_changes = 1
906
967
            new_kind = self.new_tree.kind(file_id)
907
968
        except (errors.NoSuchId, errors.NoSuchFile):
908
969
            new_kind = None
909
 
 
 
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):
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: