~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/diff.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2008-10-30 14:52:21 UTC
  • mfrom: (3805.2.3 jam-integration)
  • Revision ID: pqm@pqm.ubuntu.com-20081030145221-fog9qrw59bobguy0
(jam) A bit of cleanup for the default entries in the trace log.

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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
import difflib
18
18
import os
19
19
import re
20
20
import shutil
21
 
import string
22
21
import sys
23
22
 
24
23
from bzrlib.lazy_import import lazy_import
37
36
    patiencediff,
38
37
    textfile,
39
38
    timestamp,
40
 
    views,
41
39
    )
42
 
 
43
 
from bzrlib.workingtree import WorkingTree
44
40
""")
45
41
 
46
42
from bzrlib.symbol_versioning import (
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 = '@'
 
43
        deprecated_function,
 
44
        one_three
 
45
        )
 
46
from bzrlib.trace import warning
56
47
 
57
48
 
58
49
# TODO: Rather than building a changeset object, we should probably
87
78
    # both sequences are empty.
88
79
    if not oldlines and not newlines:
89
80
        return
90
 
 
 
81
    
91
82
    if allow_binary is False:
92
83
        textfile.check_text_lines(oldlines)
93
84
        textfile.check_text_lines(newlines)
108
99
        ud[2] = ud[2].replace('-1,0', '-0,0')
109
100
    elif not newlines:
110
101
        ud[2] = ud[2].replace('+1,0', '+0,0')
 
102
    # work around for difflib emitting random spaces after the label
 
103
    ud[0] = ud[0][:-2] + '\n'
 
104
    ud[1] = ud[1][:-2] + '\n'
111
105
 
112
106
    for line in ud:
113
107
        to_file.write(line)
180
174
 
181
175
        if not diff_opts:
182
176
            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')
188
177
        diffcmd = ['diff',
189
178
                   '--label', old_filename,
190
179
                   old_abspath,
213
202
            break
214
203
        else:
215
204
            diffcmd.append('-u')
216
 
 
 
205
                  
217
206
        if diff_opts:
218
207
            diffcmd.extend(diff_opts)
219
208
 
220
209
        pipe = _spawn_external_diff(diffcmd, capture_errors=True)
221
210
        out,err = pipe.communicate()
222
211
        rc = pipe.returncode
223
 
 
 
212
        
224
213
        # internal_diff() adds a trailing newline, add one here for consistency
225
214
        out += '\n'
226
215
        if rc == 2:
261
250
                msg = 'signal %d' % (-rc)
262
251
            else:
263
252
                msg = 'exit code %d' % rc
264
 
 
265
 
            raise errors.BzrError('external diff failed with %s; command: %r'
 
253
                
 
254
            raise errors.BzrError('external diff failed with %s; command: %r' 
266
255
                                  % (rc, diffcmd))
267
256
 
268
257
 
286
275
                        new_abspath, e)
287
276
 
288
277
 
289
 
def get_trees_and_branches_to_diff(path_list, revision_specs, old_url, new_url,
290
 
                                   apply_view=True):
 
278
def _get_trees_to_diff(path_list, revision_specs, old_url, new_url):
291
279
    """Get the trees and specific files to diff given a list of paths.
292
280
 
293
281
    This method works out the trees to be diff'ed and the files of
304
292
    :param new_url:
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.
307
 
    :param apply_view:
308
 
        if True and a view is set, apply the view or check that the paths
309
 
        are within it
310
295
    :returns:
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
 
298
        file-ids.
314
299
    """
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)
353
 
    old_branch = branch
354
336
 
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)
367
 
    new_branch = branch
368
347
 
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:
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")
 
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()
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)
387
354
 
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
393
360
 
394
361
 
395
362
def _get_tree_to_diff(spec, tree=None, branch=None, basis_is_default=True):
406
373
    return spec.as_tree(branch)
407
374
 
408
375
 
 
376
def _relative_paths_in_tree(tree, paths):
 
377
    """Get the relative paths within a working tree.
 
378
 
 
379
    Each path may be either an absolute path or a path relative to the
 
380
    current working directory.
 
381
    """
 
382
    result = []
 
383
    for filename in paths:
 
384
        try:
 
385
            result.append(tree.relpath(osutils.dereference_path(filename)))
 
386
        except errors.PathNotChild:
 
387
            raise errors.BzrCommandError("Files are in different branches")
 
388
    return result
 
389
 
 
390
 
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/',
453
435
 
454
436
def _patch_header_date(tree, file_id, path):
455
437
    """Returns a timestamp suitable for use in a patch header."""
456
 
    try:
457
 
        mtime = tree.get_file_mtime(file_id, path)
458
 
    except errors.FileTimestampUnavailable:
459
 
        mtime = 0
 
438
    mtime = tree.get_file_mtime(file_id, path)
460
439
    return timestamp.format_patch_date(mtime)
461
440
 
462
441
 
 
442
@deprecated_function(one_three)
 
443
def get_prop_change(meta_modified):
 
444
    if meta_modified:
 
445
        return " (properties changed)"
 
446
    else:
 
447
        return  ""
 
448
 
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,
644
 
            old_path, new_path)
 
629
        return self.diff_text(from_file_id, to_file_id, from_label, to_label)
645
630
 
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
649
633
 
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.
657
639
        """
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()
661
643
            else:
662
644
                return []
663
645
        try:
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,
667
649
                             self.to_file)
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,
690
671
                     path_encoding)
691
672
 
698
679
 
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]
703
683
 
704
684
    def _execute(self, old_path, new_path):
705
685
        command = self._get_command(old_path, new_path)
725
705
                raise
726
706
        return True
727
707
 
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
 
        
 
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):
735
711
            return full_path
736
712
        parent_dir = osutils.dirname(full_path)
737
713
        try:
748
724
                target.close()
749
725
        finally:
750
726
            source.close()
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
 
727
        osutils.make_readonly(full_path)
 
728
        mtime = tree.get_file_mtime(file_id)
757
729
        os.utime(full_path, (mtime, mtime))
758
730
        return full_path
759
731
 
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)
 
734
                                         old_path)
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)
 
736
                                         new_path)
767
737
        return old_disk_path, new_disk_path
768
738
 
769
739
    def finish(self):
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))
 
740
        osutils.rmtree(self._root)
776
741
 
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)
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
        self._prepare_files(file_id, old_path, new_path)
 
746
        self._execute(osutils.pathjoin('old', old_path),
 
747
                      osutils.pathjoin('new', new_path))
806
748
 
807
749
 
808
750
class DiffTree(object):
946
888
                self.to_file.write("=== modified %s '%s'%s\n" % (kind[0],
947
889
                                   newpath_encoded, prop_str))
948
890
            if changed_content:
949
 
                self._diff(file_id, oldpath, newpath, kind[0], kind[1])
 
891
                self.diff(file_id, oldpath, newpath)
950
892
                has_changes = 1
951
893
            if renamed:
952
894
                has_changes = 1
967
909
            new_kind = self.new_tree.kind(file_id)
968
910
        except (errors.NoSuchId, errors.NoSuchFile):
969
911
            new_kind = None
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):
 
912
 
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: