~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/diff.py

  • Committer: John Arbash Meinel
  • Date: 2008-09-09 15:09:12 UTC
  • mto: This revision was merged to the branch mainline in revision 3699.
  • Revision ID: john@arbash-meinel.com-20080909150912-wyttm8he1zsls2ck
Use the right timing function on win32

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 mutter, 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):
403
370
                return branch.basis_tree()
404
371
        else:
405
372
            return tree
406
 
    return spec.as_tree(branch)
 
373
    if not spec.needs_branch():
 
374
        branch = _mod_branch.Branch.open(spec.get_branch())
 
375
    revision_id = spec.as_revision_id(branch)
 
376
    return branch.repository.revision_tree(revision_id)
 
377
 
 
378
 
 
379
def _relative_paths_in_tree(tree, paths):
 
380
    """Get the relative paths within a working tree.
 
381
 
 
382
    Each path may be either an absolute path or a path relative to the
 
383
    current working directory.
 
384
    """
 
385
    result = []
 
386
    for filename in paths:
 
387
        try:
 
388
            result.append(tree.relpath(osutils.dereference_path(filename)))
 
389
        except errors.PathNotChild:
 
390
            raise errors.BzrCommandError("Files are in different branches")
 
391
    return result
407
392
 
408
393
 
409
394
def show_diff_trees(old_tree, new_tree, to_file, specific_files=None,
453
438
 
454
439
def _patch_header_date(tree, file_id, path):
455
440
    """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
 
441
    mtime = tree.get_file_mtime(file_id, path)
460
442
    return timestamp.format_patch_date(mtime)
461
443
 
462
444
 
 
445
def _raise_if_nonexistent(paths, old_tree, new_tree):
 
446
    """Complain if paths are not in either inventory or tree.
 
447
 
 
448
    It's OK with the files exist in either tree's inventory, or 
 
449
    if they exist in the tree but are not versioned.
 
450
    
 
451
    This can be used by operations such as bzr status that can accept
 
452
    unknown or ignored files.
 
453
    """
 
454
    mutter("check paths: %r", paths)
 
455
    if not paths:
 
456
        return
 
457
    s = old_tree.filter_unversioned_files(paths)
 
458
    s = new_tree.filter_unversioned_files(s)
 
459
    s = [path for path in s if not new_tree.has_filename(path)]
 
460
    if s:
 
461
        raise errors.PathsDoNotExist(sorted(s))
 
462
 
 
463
 
 
464
@deprecated_function(one_three)
 
465
def get_prop_change(meta_modified):
 
466
    if meta_modified:
 
467
        return " (properties changed)"
 
468
    else:
 
469
        return  ""
 
470
 
463
471
def get_executable_change(old_is_x, new_is_x):
464
472
    descr = { True:"+x", False:"-x", None:"??" }
465
473
    if old_is_x != new_is_x:
640
648
            return self.CANNOT_DIFF
641
649
        from_label = '%s%s\t%s' % (self.old_label, old_path, old_date)
642
650
        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)
 
651
        return self.diff_text(from_file_id, to_file_id, from_label, to_label)
645
652
 
646
 
    def diff_text(self, from_file_id, to_file_id, from_label, to_label,
647
 
        from_path=None, to_path=None):
 
653
    def diff_text(self, from_file_id, to_file_id, from_label, to_label):
648
654
        """Diff the content of given files in two trees
649
655
 
650
656
        :param from_file_id: The id of the file in the from tree.  If None,
652
658
        :param to_file_id: The id of the file in the to tree.  This may refer
653
659
            to a different file from from_file_id.  If None,
654
660
            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
661
        """
658
 
        def _get_text(tree, file_id, path):
 
662
        def _get_text(tree, file_id):
659
663
            if file_id is not None:
660
 
                return tree.get_file(file_id, path).readlines()
 
664
                return tree.get_file(file_id).readlines()
661
665
            else:
662
666
                return []
663
667
        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)
 
668
            from_text = _get_text(self.old_tree, from_file_id)
 
669
            to_text = _get_text(self.new_tree, to_file_id)
666
670
            self.text_differ(from_label, from_text, to_label, to_text,
667
671
                             self.to_file)
668
672
        except errors.BinaryFile:
684
688
    def from_string(klass, command_string, old_tree, new_tree, to_file,
685
689
                    path_encoding='utf-8'):
686
690
        command_template = commands.shlex_split_unicode(command_string)
687
 
        if '@' not in command_string:
688
 
            command_template.extend(['@old_path', '@new_path'])
 
691
        command_template.extend(['%(old_path)s', '%(new_path)s'])
689
692
        return klass(command_template, old_tree, new_tree, to_file,
690
693
                     path_encoding)
691
694
 
698
701
 
699
702
    def _get_command(self, old_path, new_path):
700
703
        my_map = {'old_path': old_path, 'new_path': new_path}
701
 
        return [AtTemplate(t).substitute(my_map) for t in
702
 
                self.command_template]
 
704
        return [t % my_map for t in self.command_template]
703
705
 
704
706
    def _execute(self, old_path, new_path):
705
707
        command = self._get_command(old_path, new_path)
725
727
                raise
726
728
        return True
727
729
 
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
 
        
 
730
    def _write_file(self, file_id, tree, prefix, relpath):
733
731
        full_path = osutils.pathjoin(self._root, prefix, relpath)
734
 
        if not force_temp and self._try_symlink_root(tree, prefix):
 
732
        if self._try_symlink_root(tree, prefix):
735
733
            return full_path
736
734
        parent_dir = osutils.dirname(full_path)
737
735
        try:
748
746
                target.close()
749
747
        finally:
750
748
            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
 
749
        osutils.make_readonly(full_path)
 
750
        mtime = tree.get_file_mtime(file_id)
757
751
        os.utime(full_path, (mtime, mtime))
758
752
        return full_path
759
753
 
760
 
    def _prepare_files(self, file_id, old_path, new_path, force_temp=False,
761
 
                       allow_write_new=False):
 
754
    def _prepare_files(self, file_id, old_path, new_path):
762
755
        old_disk_path = self._write_file(file_id, self.old_tree, 'old',
763
 
                                         old_path, force_temp)
 
756
                                         old_path)
764
757
        new_disk_path = self._write_file(file_id, self.new_tree, 'new',
765
 
                                         new_path, force_temp,
766
 
                                         allow_write=allow_write_new)
 
758
                                         new_path)
767
759
        return old_disk_path, new_disk_path
768
760
 
769
761
    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))
 
762
        osutils.rmtree(self._root)
776
763
 
777
764
    def diff(self, file_id, old_path, new_path, old_kind, new_kind):
778
765
        if (old_kind, new_kind) != ('file', 'file'):
779
766
            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()
 
767
        self._prepare_files(file_id, old_path, new_path)
 
768
        self._execute(osutils.pathjoin('old', old_path),
 
769
                      osutils.pathjoin('new', new_path))
806
770
 
807
771
 
808
772
class DiffTree(object):
946
910
                self.to_file.write("=== modified %s '%s'%s\n" % (kind[0],
947
911
                                   newpath_encoded, prop_str))
948
912
            if changed_content:
949
 
                self._diff(file_id, oldpath, newpath, kind[0], kind[1])
 
913
                self.diff(file_id, oldpath, newpath)
950
914
                has_changes = 1
951
915
            if renamed:
952
916
                has_changes = 1
967
931
            new_kind = self.new_tree.kind(file_id)
968
932
        except (errors.NoSuchId, errors.NoSuchFile):
969
933
            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):
 
934
 
974
935
        result = DiffPath._diff_many(self.differs, file_id, old_path,
975
936
                                       new_path, old_kind, new_kind)
976
937
        if result is DiffPath.CANNOT_DIFF: