~bzr-pqm/bzr/bzr.dev

5752.3.8 by John Arbash Meinel
Merge bzr.dev 5764 to resolve release-notes (aka NEWS) conflicts
1
# Copyright (C) 2005-2011 Canonical Ltd.
1711.2.54 by John Arbash Meinel
Use mkstemp instead of NamedTemporary file for external diff.
2
#
1 by mbp at sourcefrog
import from baz patch-364
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
1711.2.54 by John Arbash Meinel
Use mkstemp instead of NamedTemporary file for external diff.
7
#
1 by mbp at sourcefrog
import from baz patch-364
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
1711.2.54 by John Arbash Meinel
Use mkstemp instead of NamedTemporary file for external diff.
12
#
1 by mbp at sourcefrog
import from baz patch-364
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
4183.7.1 by Sabin Iacob
update FSF mailing address
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1 by mbp at sourcefrog
import from baz patch-364
16
6379.6.1 by Jelmer Vernooij
Import absolute_import in a few places.
17
from __future__ import absolute_import
18
2520.4.140 by Aaron Bentley
Use matching blocks from mpdiff for knit delta creation
19
import difflib
1711.2.54 by John Arbash Meinel
Use mkstemp instead of NamedTemporary file for external diff.
20
import os
1899.1.5 by John Arbash Meinel
Always buffer the output of diff, so we can check if retcode==2 is because of Binary files
21
import re
4603.1.20 by Aaron Bentley
Use string.Template substitution with @ as delimiter.
22
import string
1996.3.9 by John Arbash Meinel
lazy_import diff.py
23
import sys
24
25
from bzrlib.lazy_import import lazy_import
26
lazy_import(globals(), """
27
import errno
1692.8.7 by James Henstridge
changes suggested by John Meinel
28
import subprocess
1711.2.54 by John Arbash Meinel
Use mkstemp instead of NamedTemporary file for external diff.
29
import tempfile
1740.2.5 by Aaron Bentley
Merge from bzr.dev
30
1955.2.10 by John Arbash Meinel
Unset a few other LANG type variables when spawning diff
31
from bzrlib import (
6207.3.3 by jelmer at samba
Fix tests and the like.
32
    cleanup,
4913.5.24 by Gordon Tyler
Added cmdline.split function, which replaces commands.shlex_split_unicode.
33
    cmdline,
6207.3.3 by jelmer at samba
Fix tests and the like.
34
    controldir,
1955.2.10 by John Arbash Meinel
Unset a few other LANG type variables when spawning diff
35
    errors,
36
    osutils,
1996.3.9 by John Arbash Meinel
lazy_import diff.py
37
    patiencediff,
38
    textfile,
1551.12.29 by Aaron Bentley
Copy and extend patch date formatting code, add patch-date parsing
39
    timestamp,
3586.1.21 by Ian Clatworthy
enhance diff to support views
40
    views,
1955.2.10 by John Arbash Meinel
Unset a few other LANG type variables when spawning diff
41
    )
4845.2.1 by Gary van der Merwe
When launching an external diff app, don't write temporary files for a working tree.
42
43
from bzrlib.workingtree import WorkingTree
6138.3.4 by Jonathan Riddell
add gettext() to uses of trace.note()
44
from bzrlib.i18n import gettext
1996.3.9 by John Arbash Meinel
lazy_import diff.py
45
""")
46
5131.1.1 by Jelmer Vernooij
Add --format option to 'bzr diff'.
47
from bzrlib.registry import (
48
    Registry,
49
    )
3586.1.21 by Ian Clatworthy
enhance diff to support views
50
from bzrlib.trace import mutter, note, warning
1 by mbp at sourcefrog
import from baz patch-364
51
1711.2.24 by John Arbash Meinel
Late bind to PatienceSequenceMatcher to allow plugin to override.
52
4603.1.20 by Aaron Bentley
Use string.Template substitution with @ as delimiter.
53
class AtTemplate(string.Template):
54
    """Templating class that uses @ instead of $."""
55
56
    delimiter = '@'
57
58
767 by Martin Pool
- files are only reported as modified if their name or parent has changed,
59
# TODO: Rather than building a changeset object, we should probably
60
# invoke callbacks on an object.  That object can either accumulate a
61
# list, write them out directly, etc etc.
62
2520.4.140 by Aaron Bentley
Use matching blocks from mpdiff for knit delta creation
63
64
class _PrematchedMatcher(difflib.SequenceMatcher):
65
    """Allow SequenceMatcher operations to use predetermined blocks"""
66
67
    def __init__(self, matching_blocks):
68
        difflib.SequenceMatcher(self, None, None)
69
        self.matching_blocks = matching_blocks
70
        self.opcodes = None
71
72
1558.15.11 by Aaron Bentley
Apply merge review suggestions
73
def internal_diff(old_filename, oldlines, new_filename, newlines, to_file,
1711.2.30 by John Arbash Meinel
Fix bug in internal_diff handling of unicode paths
74
                  allow_binary=False, sequence_matcher=None,
75
                  path_encoding='utf8'):
475 by Martin Pool
- rewrite diff using compare_trees()
76
    # FIXME: difflib is wrong if there is no trailing newline.
77
    # The syntax used by patch seems to be "\ No newline at
78
    # end of file" following the last diff line from that
79
    # file.  This is not trivial to insert into the
80
    # unified_diff output and it might be better to just fix
81
    # or replace that function.
82
83
    # In the meantime we at least make sure the patch isn't
84
    # mangled.
85
86
87
    # Special workaround for Python2.3, where difflib fails if
88
    # both sequences are empty.
89
    if not oldlines and not newlines:
90
        return
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
91
1558.15.11 by Aaron Bentley
Apply merge review suggestions
92
    if allow_binary is False:
1996.3.9 by John Arbash Meinel
lazy_import diff.py
93
        textfile.check_text_lines(oldlines)
94
        textfile.check_text_lines(newlines)
475 by Martin Pool
- rewrite diff using compare_trees()
95
1185.81.8 by John Arbash Meinel
Updating unified_diff to take a factory, using the new diff algorithm in the code.
96
    if sequence_matcher is None:
1996.3.9 by John Arbash Meinel
lazy_import diff.py
97
        sequence_matcher = patiencediff.PatienceSequenceMatcher
98
    ud = patiencediff.unified_diff(oldlines, newlines,
4797.57.4 by Alexander Belchenko
if filename cannot be encoded in current path_encoding (user_encoding) then use foo.encode(path_encoding, 'replace') so we don't traceback
99
                      fromfile=old_filename.encode(path_encoding, 'replace'),
100
                      tofile=new_filename.encode(path_encoding, 'replace'),
1185.81.8 by John Arbash Meinel
Updating unified_diff to take a factory, using the new diff algorithm in the code.
101
                      sequencematcher=sequence_matcher)
475 by Martin Pool
- rewrite diff using compare_trees()
102
1092.1.50 by Robert Collins
make diff lsdiff/filterdiff friendly
103
    ud = list(ud)
3085.1.1 by John Arbash Meinel
Fix internal_diff to not fail when the texts are identical.
104
    if len(ud) == 0: # Identical contents, nothing to do
105
        return
475 by Martin Pool
- rewrite diff using compare_trees()
106
    # work-around for difflib being too smart for its own good
107
    # if /dev/null is "1,0", patch won't recognize it as /dev/null
108
    if not oldlines:
109
        ud[2] = ud[2].replace('-1,0', '-0,0')
110
    elif not newlines:
111
        ud[2] = ud[2].replace('+1,0', '+0,0')
112
804 by Martin Pool
Patch from John:
113
    for line in ud:
114
        to_file.write(line)
974.1.5 by Aaron Bentley
Fixed handling of missing newlines in udiffs
115
        if not line.endswith('\n'):
116
            to_file.write("\n\\ No newline at end of file\n")
2911.6.1 by Blake Winton
Change 'print >> f,'s to 'f.write('s.
117
    to_file.write('\n')
475 by Martin Pool
- rewrite diff using compare_trees()
118
119
1920.1.1 by John Arbash Meinel
fix bug #56307, handle binary files even when LANG is not english
120
def _spawn_external_diff(diffcmd, capture_errors=True):
121
    """Spawn the externall diff process, and return the child handle.
122
123
    :param diffcmd: The command list to spawn
2138.1.1 by Wouter van Heyst
Robuster external diff output handling.
124
    :param capture_errors: Capture stderr as well as setting LANG=C
125
        and LC_ALL=C. This lets us read and understand the output of diff,
126
        and respond to any errors.
1920.1.1 by John Arbash Meinel
fix bug #56307, handle binary files even when LANG is not english
127
    :return: A Popen object.
128
    """
129
    if capture_errors:
2321.2.2 by Alexander Belchenko
win32 fixes for test_external_diff_binary (gettext on win32 rely on $LANGUAGE)
130
        # construct minimal environment
131
        env = {}
132
        path = os.environ.get('PATH')
133
        if path is not None:
134
            env['PATH'] = path
2321.2.5 by Alexander Belchenko
external diff: no need for special code path for win32 (suggested by John Meinel)
135
        env['LANGUAGE'] = 'C'   # on win32 only LANGUAGE has effect
136
        env['LANG'] = 'C'
137
        env['LC_ALL'] = 'C'
1920.1.1 by John Arbash Meinel
fix bug #56307, handle binary files even when LANG is not english
138
        stderr = subprocess.PIPE
139
    else:
2321.2.2 by Alexander Belchenko
win32 fixes for test_external_diff_binary (gettext on win32 rely on $LANGUAGE)
140
        env = None
1920.1.1 by John Arbash Meinel
fix bug #56307, handle binary files even when LANG is not english
141
        stderr = None
142
143
    try:
144
        pipe = subprocess.Popen(diffcmd,
145
                                stdin=subprocess.PIPE,
146
                                stdout=subprocess.PIPE,
147
                                stderr=stderr,
2321.2.2 by Alexander Belchenko
win32 fixes for test_external_diff_binary (gettext on win32 rely on $LANGUAGE)
148
                                env=env)
1920.1.1 by John Arbash Meinel
fix bug #56307, handle binary files even when LANG is not english
149
    except OSError, e:
150
        if e.errno == errno.ENOENT:
151
            raise errors.NoDiff(str(e))
152
        raise
153
154
    return pipe
155
156
1185.35.29 by Aaron Bentley
Support whitespace in diff filenames
157
def external_diff(old_filename, oldlines, new_filename, newlines, to_file,
571 by Martin Pool
- new --diff-options to pass options through to external
158
                  diff_opts):
568 by Martin Pool
- start adding support for showing diffs by calling out to
159
    """Display a diff by calling out to the external diff program."""
581 by Martin Pool
- make sure any bzr output is flushed before
160
    # make sure our own output is properly ordered before the diff
161
    to_file.flush()
162
1711.2.54 by John Arbash Meinel
Use mkstemp instead of NamedTemporary file for external diff.
163
    oldtmp_fd, old_abspath = tempfile.mkstemp(prefix='bzr-diff-old-')
164
    newtmp_fd, new_abspath = tempfile.mkstemp(prefix='bzr-diff-new-')
165
    oldtmpf = os.fdopen(oldtmp_fd, 'wb')
166
    newtmpf = os.fdopen(newtmp_fd, 'wb')
568 by Martin Pool
- start adding support for showing diffs by calling out to
167
168
    try:
169
        # TODO: perhaps a special case for comparing to or from the empty
170
        # sequence; can just use /dev/null on Unix
171
172
        # TODO: if either of the files being compared already exists as a
173
        # regular named file (e.g. in the working directory) then we can
174
        # compare directly to that, rather than copying it.
175
176
        oldtmpf.writelines(oldlines)
177
        newtmpf.writelines(newlines)
178
1711.2.54 by John Arbash Meinel
Use mkstemp instead of NamedTemporary file for external diff.
179
        oldtmpf.close()
180
        newtmpf.close()
568 by Martin Pool
- start adding support for showing diffs by calling out to
181
571 by Martin Pool
- new --diff-options to pass options through to external
182
        if not diff_opts:
183
            diff_opts = []
4422.1.1 by John Arbash Meinel
Possibly fix for bug #382709 handling non-ascii external filenames.
184
        if sys.platform == 'win32':
185
            # Popen doesn't do the proper encoding for external commands
186
            # Since we are dealing with an ANSI api, use mbcs encoding
187
            old_filename = old_filename.encode('mbcs')
4422.1.2 by Martin
Fix copy-and-paste error in previous change
188
            new_filename = new_filename.encode('mbcs')
571 by Martin Pool
- new --diff-options to pass options through to external
189
        diffcmd = ['diff',
1740.2.5 by Aaron Bentley
Merge from bzr.dev
190
                   '--label', old_filename,
1711.2.54 by John Arbash Meinel
Use mkstemp instead of NamedTemporary file for external diff.
191
                   old_abspath,
1740.2.5 by Aaron Bentley
Merge from bzr.dev
192
                   '--label', new_filename,
1711.2.56 by John Arbash Meinel
Raise NoDiff if 'diff' not present.
193
                   new_abspath,
194
                   '--binary',
195
                  ]
571 by Martin Pool
- new --diff-options to pass options through to external
196
197
        # diff only allows one style to be specified; they don't override.
198
        # note that some of these take optargs, and the optargs can be
199
        # directly appended to the options.
200
        # this is only an approximate parser; it doesn't properly understand
201
        # the grammar.
202
        for s in ['-c', '-u', '-C', '-U',
203
                  '-e', '--ed',
204
                  '-q', '--brief',
205
                  '--normal',
206
                  '-n', '--rcs',
207
                  '-y', '--side-by-side',
208
                  '-D', '--ifdef']:
209
            for j in diff_opts:
210
                if j.startswith(s):
211
                    break
212
            else:
213
                continue
214
            break
215
        else:
216
            diffcmd.append('-u')
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
217
571 by Martin Pool
- new --diff-options to pass options through to external
218
        if diff_opts:
219
            diffcmd.extend(diff_opts)
220
1920.1.1 by John Arbash Meinel
fix bug #56307, handle binary files even when LANG is not english
221
        pipe = _spawn_external_diff(diffcmd, capture_errors=True)
222
        out,err = pipe.communicate()
223
        rc = pipe.returncode
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
224
1920.1.1 by John Arbash Meinel
fix bug #56307, handle binary files even when LANG is not english
225
        # internal_diff() adds a trailing newline, add one here for consistency
226
        out += '\n'
1899.1.5 by John Arbash Meinel
Always buffer the output of diff, so we can check if retcode==2 is because of Binary files
227
        if rc == 2:
228
            # 'diff' gives retcode == 2 for all sorts of errors
229
            # one of those is 'Binary files differ'.
230
            # Bad options could also be the problem.
1904.1.4 by Marien Zwart
Make external diff in binary mode work with recent versions of diffutils.
231
            # 'Binary files' is not a real error, so we suppress that error.
1920.1.1 by John Arbash Meinel
fix bug #56307, handle binary files even when LANG is not english
232
            lang_c_out = out
233
234
            # Since we got here, we want to make sure to give an i18n error
235
            pipe = _spawn_external_diff(diffcmd, capture_errors=False)
236
            out, err = pipe.communicate()
237
238
            # Write out the new i18n diff response
239
            to_file.write(out+'\n')
240
            if pipe.returncode != 2:
1996.3.9 by John Arbash Meinel
lazy_import diff.py
241
                raise errors.BzrError(
242
                               'external diff failed with exit code 2'
2138.1.1 by Wouter van Heyst
Robuster external diff output handling.
243
                               ' when run with LANG=C and LC_ALL=C,'
244
                               ' but not when run natively: %r' % (diffcmd,))
1920.1.1 by John Arbash Meinel
fix bug #56307, handle binary files even when LANG is not english
245
246
            first_line = lang_c_out.split('\n', 1)[0]
1904.1.4 by Marien Zwart
Make external diff in binary mode work with recent versions of diffutils.
247
            # Starting with diffutils 2.8.4 the word "binary" was dropped.
248
            m = re.match('^(binary )?files.*differ$', first_line, re.I)
1920.1.1 by John Arbash Meinel
fix bug #56307, handle binary files even when LANG is not english
249
            if m is None:
1996.3.9 by John Arbash Meinel
lazy_import diff.py
250
                raise errors.BzrError('external diff failed with exit code 2;'
251
                                      ' command: %r' % (diffcmd,))
1920.1.1 by John Arbash Meinel
fix bug #56307, handle binary files even when LANG is not english
252
            else:
253
                # Binary files differ, just return
254
                return
255
256
        # If we got to here, we haven't written out the output of diff
257
        # do so now
258
        to_file.write(out)
259
        if rc not in (0, 1):
571 by Martin Pool
- new --diff-options to pass options through to external
260
            # returns 1 if files differ; that's OK
261
            if rc < 0:
262
                msg = 'signal %d' % (-rc)
263
            else:
264
                msg = 'exit code %d' % rc
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
265
266
            raise errors.BzrError('external diff failed with %s; command: %r'
1996.3.9 by John Arbash Meinel
lazy_import diff.py
267
                                  % (rc, diffcmd))
1899.1.6 by John Arbash Meinel
internal_diff always adds a trailing \n, make sure external_diff does too
268
269
568 by Martin Pool
- start adding support for showing diffs by calling out to
270
    finally:
271
        oldtmpf.close()                 # and delete
272
        newtmpf.close()
1711.2.54 by John Arbash Meinel
Use mkstemp instead of NamedTemporary file for external diff.
273
        # Clean up. Warn in case the files couldn't be deleted
274
        # (in case windows still holds the file open, but not
275
        # if the files have already been deleted)
276
        try:
277
            os.remove(old_abspath)
278
        except OSError, e:
279
            if e.errno not in (errno.ENOENT,):
280
                warning('Failed to delete temporary file: %s %s',
281
                        old_abspath, e)
282
        try:
283
            os.remove(new_abspath)
284
        except OSError:
285
            if e.errno not in (errno.ENOENT,):
286
                warning('Failed to delete temporary file: %s %s',
287
                        new_abspath, e)
568 by Martin Pool
- start adding support for showing diffs by calling out to
288
1551.2.13 by Aaron Bentley
Got diff working properly with checkouts
289
5147.3.3 by Andrew Bennetts
Add get_trees_and_branches_to_diff_locked, leave get_trees_and_branches_to_diff unchanged for qbzr.
290
def get_trees_and_branches_to_diff_locked(
291
    path_list, revision_specs, old_url, new_url, add_cleanup, apply_view=True):
3072.1.1 by Ian Clatworthy
Improved diff based on feedback from abentley
292
    """Get the trees and specific files to diff given a list of paths.
293
294
    This method works out the trees to be diff'ed and the files of
295
    interest within those trees.
296
297
    :param path_list:
298
        the list of arguments passed to the diff command
299
    :param revision_specs:
300
        Zero, one or two RevisionSpecs from the diff command line,
301
        saying what revisions to compare.
302
    :param old_url:
303
        The url of the old branch or tree. If None, the tree to use is
304
        taken from the first path, if any, or the current working tree.
305
    :param new_url:
306
        The url of the new branch or tree. If None, the tree to use is
307
        taken from the first path, if any, or the current working tree.
5147.3.1 by Andrew Bennetts
Avoid 6 branch/repo relocks in cmd_diff.
308
    :param add_cleanup:
309
        a callable like Command.add_cleanup.  get_trees_and_branches_to_diff
310
        will register cleanups that must be run to unlock the trees, etc.
3586.1.21 by Ian Clatworthy
enhance diff to support views
311
    :param apply_view:
312
        if True and a view is set, apply the view or check that the paths
313
        are within it
3072.1.1 by Ian Clatworthy
Improved diff based on feedback from abentley
314
    :returns:
4739.3.1 by Jonathan Lange
Fix the docstring for get_trees_and_branches_to_diff.
315
        a tuple of (old_tree, new_tree, old_branch, new_branch,
316
        specific_files, extra_trees) where extra_trees is a sequence of
5147.3.1 by Andrew Bennetts
Avoid 6 branch/repo relocks in cmd_diff.
317
        additional trees to search in for file-ids.  The trees and branches
318
        will be read-locked until the cleanups registered via the add_cleanup
319
        param are run.
3072.1.1 by Ian Clatworthy
Improved diff based on feedback from abentley
320
    """
321
    # Get the old and new revision specs
322
    old_revision_spec = None
323
    new_revision_spec = None
324
    if revision_specs is not None:
325
        if len(revision_specs) > 0:
326
            old_revision_spec = revision_specs[0]
3072.1.5 by Ian Clatworthy
more good ideas from abentley
327
            if old_url is None:
328
                old_url = old_revision_spec.get_branch()
3072.1.1 by Ian Clatworthy
Improved diff based on feedback from abentley
329
        if len(revision_specs) > 1:
330
            new_revision_spec = revision_specs[1]
3072.1.5 by Ian Clatworthy
more good ideas from abentley
331
            if new_url is None:
332
                new_url = new_revision_spec.get_branch()
3072.1.1 by Ian Clatworthy
Improved diff based on feedback from abentley
333
3072.1.5 by Ian Clatworthy
more good ideas from abentley
334
    other_paths = []
335
    make_paths_wt_relative = True
3164.1.1 by Ian Clatworthy
diff without arguments means the current tree, not the current directory
336
    consider_relpath = True
3072.1.1 by Ian Clatworthy
Improved diff based on feedback from abentley
337
    if path_list is None or len(path_list) == 0:
3164.1.1 by Ian Clatworthy
diff without arguments means the current tree, not the current directory
338
        # If no path is given, the current working tree is used
3072.1.1 by Ian Clatworthy
Improved diff based on feedback from abentley
339
        default_location = u'.'
3164.1.1 by Ian Clatworthy
diff without arguments means the current tree, not the current directory
340
        consider_relpath = False
3072.1.1 by Ian Clatworthy
Improved diff based on feedback from abentley
341
    elif old_url is not None and new_url is not None:
342
        other_paths = path_list
3072.1.5 by Ian Clatworthy
more good ideas from abentley
343
        make_paths_wt_relative = False
3072.1.1 by Ian Clatworthy
Improved diff based on feedback from abentley
344
    else:
345
        default_location = path_list[0]
346
        other_paths = path_list[1:]
347
5147.3.1 by Andrew Bennetts
Avoid 6 branch/repo relocks in cmd_diff.
348
    def lock_tree_or_branch(wt, br):
349
        if wt is not None:
350
            wt.lock_read()
351
            add_cleanup(wt.unlock)
352
        elif br is not None:
353
            br.lock_read()
354
            add_cleanup(br.unlock)
355
3072.1.1 by Ian Clatworthy
Improved diff based on feedback from abentley
356
    # Get the old location
3072.1.2 by Ian Clatworthy
Test various --old and --new combinations
357
    specific_files = []
3072.1.1 by Ian Clatworthy
Improved diff based on feedback from abentley
358
    if old_url is None:
359
        old_url = default_location
360
    working_tree, branch, relpath = \
6207.3.3 by jelmer at samba
Fix tests and the like.
361
        controldir.ControlDir.open_containing_tree_or_branch(old_url)
5147.3.1 by Andrew Bennetts
Avoid 6 branch/repo relocks in cmd_diff.
362
    lock_tree_or_branch(working_tree, branch)
3164.1.1 by Ian Clatworthy
diff without arguments means the current tree, not the current directory
363
    if consider_relpath and relpath != '':
3586.1.21 by Ian Clatworthy
enhance diff to support views
364
        if working_tree is not None and apply_view:
4032.4.1 by Eduardo Padoan
Moved diff._check_path_in_view() to views.check_path_in_view()
365
            views.check_path_in_view(working_tree, relpath)
3072.1.2 by Ian Clatworthy
Test various --old and --new combinations
366
        specific_files.append(relpath)
3072.1.1 by Ian Clatworthy
Improved diff based on feedback from abentley
367
    old_tree = _get_tree_to_diff(old_revision_spec, working_tree, branch)
4705.1.1 by Gary van der Merwe
Change _get_trees_to_diff to get_trees_and_branches_to_diff.
368
    old_branch = branch
3072.1.1 by Ian Clatworthy
Improved diff based on feedback from abentley
369
370
    # Get the new location
371
    if new_url is None:
372
        new_url = default_location
373
    if new_url != old_url:
374
        working_tree, branch, relpath = \
6207.3.3 by jelmer at samba
Fix tests and the like.
375
            controldir.ControlDir.open_containing_tree_or_branch(new_url)
5147.3.1 by Andrew Bennetts
Avoid 6 branch/repo relocks in cmd_diff.
376
        lock_tree_or_branch(working_tree, branch)
3164.1.1 by Ian Clatworthy
diff without arguments means the current tree, not the current directory
377
        if consider_relpath and relpath != '':
3586.1.21 by Ian Clatworthy
enhance diff to support views
378
            if working_tree is not None and apply_view:
4032.4.1 by Eduardo Padoan
Moved diff._check_path_in_view() to views.check_path_in_view()
379
                views.check_path_in_view(working_tree, relpath)
3072.1.2 by Ian Clatworthy
Test various --old and --new combinations
380
            specific_files.append(relpath)
3072.1.1 by Ian Clatworthy
Improved diff based on feedback from abentley
381
    new_tree = _get_tree_to_diff(new_revision_spec, working_tree, branch,
382
        basis_is_default=working_tree is None)
4705.1.1 by Gary van der Merwe
Change _get_trees_to_diff to get_trees_and_branches_to_diff.
383
    new_branch = branch
3072.1.1 by Ian Clatworthy
Improved diff based on feedback from abentley
384
3072.1.2 by Ian Clatworthy
Test various --old and --new combinations
385
    # Get the specific files (all files is None, no files is [])
3072.1.5 by Ian Clatworthy
more good ideas from abentley
386
    if make_paths_wt_relative and working_tree is not None:
5346.4.3 by Martin Pool
PathNotChild should not give a traceback.
387
        other_paths = working_tree.safe_relpath_files(
388
            other_paths,
389
            apply_view=apply_view)
3072.1.2 by Ian Clatworthy
Test various --old and --new combinations
390
    specific_files.extend(other_paths)
391
    if len(specific_files) == 0:
392
        specific_files = None
3586.1.21 by Ian Clatworthy
enhance diff to support views
393
        if (working_tree is not None and working_tree.supports_views()
394
            and apply_view):
395
            view_files = working_tree.views.lookup_view()
396
            if view_files:
397
                specific_files = view_files
398
                view_str = views.view_display_str(view_files)
6138.3.4 by Jonathan Riddell
add gettext() to uses of trace.note()
399
                note(gettext("*** Ignoring files outside view. View is %s") % view_str)
3072.1.2 by Ian Clatworthy
Test various --old and --new combinations
400
401
    # Get extra trees that ought to be searched for file-ids
3072.1.1 by Ian Clatworthy
Improved diff based on feedback from abentley
402
    extra_trees = None
3072.1.5 by Ian Clatworthy
more good ideas from abentley
403
    if working_tree is not None and working_tree not in (old_tree, new_tree):
3072.1.1 by Ian Clatworthy
Improved diff based on feedback from abentley
404
        extra_trees = (working_tree,)
6027.1.4 by Vincent Ladeuil
Remove ``diff.get_trees_and_branches_to_diff`` deprecated in 2.2.0 and the corrsponding tests.
405
    return (old_tree, new_tree, old_branch, new_branch,
406
            specific_files, extra_trees)
3072.1.1 by Ian Clatworthy
Improved diff based on feedback from abentley
407
4739.3.1 by Jonathan Lange
Fix the docstring for get_trees_and_branches_to_diff.
408
3072.1.1 by Ian Clatworthy
Improved diff based on feedback from abentley
409
def _get_tree_to_diff(spec, tree=None, branch=None, basis_is_default=True):
410
    if branch is None and tree is not None:
411
        branch = tree.branch
412
    if spec is None or spec.spec is None:
413
        if basis_is_default:
3072.1.5 by Ian Clatworthy
more good ideas from abentley
414
            if tree is not None:
415
                return tree.basis_tree()
416
            else:
417
                return branch.basis_tree()
3072.1.1 by Ian Clatworthy
Improved diff based on feedback from abentley
418
        else:
419
            return tree
3655.3.1 by Lukáš Lalinský
Fix `bzr st -rbranch:PATH_TO_BRANCH`
420
    return spec.as_tree(branch)
3072.1.1 by Ian Clatworthy
Improved diff based on feedback from abentley
421
422
571 by Martin Pool
- new --diff-options to pass options through to external
423
def show_diff_trees(old_tree, new_tree, to_file, specific_files=None,
1684.1.6 by Martin Pool
(patch) --diff-prefix option (goffredo, alexander)
424
                    external_diff_options=None,
1551.7.17 by Aaron Bentley
Switch to PathsNotVersioned, accept extra_trees
425
                    old_label='a/', new_label='b/',
2598.6.12 by ghigo
Move the encoding of the commit message at the command line level
426
                    extra_trees=None,
3123.6.2 by Aaron Bentley
Implement diff --using natively
427
                    path_encoding='utf8',
5131.1.1 by Jelmer Vernooij
Add --format option to 'bzr diff'.
428
                    using=None,
429
                    format_cls=None):
550 by Martin Pool
- Refactor diff code into one that works purely on
430
    """Show in text form the changes from one tree to another.
431
5131.1.1 by Jelmer Vernooij
Add --format option to 'bzr diff'.
432
    :param to_file: The output stream.
5891.1.3 by Andrew Bennetts
Move docstring formatting fixes.
433
    :param specific_files: Include only changes to these files - None for all
5131.1.1 by Jelmer Vernooij
Add --format option to 'bzr diff'.
434
        changes.
435
    :param external_diff_options: If set, use an external GNU diff and pass 
436
        these options.
437
    :param extra_trees: If set, more Trees to use for looking up file ids
438
    :param path_encoding: If set, the path will be encoded as specified, 
439
        otherwise is supposed to be utf8
440
    :param format_cls: Formatter class (DiffTree subclass)
550 by Martin Pool
- Refactor diff code into one that works purely on
441
    """
5131.1.1 by Jelmer Vernooij
Add --format option to 'bzr diff'.
442
    if format_cls is None:
443
        format_cls = DiffTree
1543.1.1 by Denys Duchier
lock operations for trees - use them for diff
444
    old_tree.lock_read()
445
    try:
2255.7.38 by John Arbash Meinel
show_diff_trees() should lock any extra trees it is passed.
446
        if extra_trees is not None:
447
            for tree in extra_trees:
448
                tree.lock_read()
1543.1.1 by Denys Duchier
lock operations for trees - use them for diff
449
        new_tree.lock_read()
450
        try:
5131.1.1 by Jelmer Vernooij
Add --format option to 'bzr diff'.
451
            differ = format_cls.from_trees_options(old_tree, new_tree, to_file,
452
                                                   path_encoding,
453
                                                   external_diff_options,
454
                                                   old_label, new_label, using)
3009.2.12 by Aaron Bentley
Associate labels with text diffing only
455
            return differ.show_diff(specific_files, extra_trees)
1543.1.1 by Denys Duchier
lock operations for trees - use them for diff
456
        finally:
457
            new_tree.unlock()
2255.7.38 by John Arbash Meinel
show_diff_trees() should lock any extra trees it is passed.
458
            if extra_trees is not None:
459
                for tree in extra_trees:
460
                    tree.unlock()
1543.1.1 by Denys Duchier
lock operations for trees - use them for diff
461
    finally:
462
        old_tree.unlock()
463
464
1740.2.5 by Aaron Bentley
Merge from bzr.dev
465
def _patch_header_date(tree, file_id, path):
466
    """Returns a timestamp suitable for use in a patch header."""
4976.1.3 by Jelmer Vernooij
Cope with ghosts in 'bzr diff'
467
    try:
468
        mtime = tree.get_file_mtime(file_id, path)
469
    except errors.FileTimestampUnavailable:
470
        mtime = 0
2405.1.2 by John Arbash Meinel
Fix bug #103870 by passing None instead of a (sometimes wrong) path
471
    return timestamp.format_patch_date(mtime)
1740.2.5 by Aaron Bentley
Merge from bzr.dev
472
473
3268.1.1 by C Miller
Describe the property changes in diffs. Currently, this is the executable-bit
474
def get_executable_change(old_is_x, new_is_x):
475
    descr = { True:"+x", False:"-x", None:"??" }
476
    if old_is_x != new_is_x:
477
        return ["%s to %s" % (descr[old_is_x], descr[new_is_x],)]
478
    else:
479
        return []
480
1398 by Robert Collins
integrate in Gustavos x-bit patch
481
3009.2.22 by Aaron Bentley
Update names & docstring
482
class DiffPath(object):
3009.2.14 by Aaron Bentley
Update return type handling
483
    """Base type for command object that compare files"""
3009.2.17 by Aaron Bentley
Update docs
484
3009.2.14 by Aaron Bentley
Update return type handling
485
    # The type or contents of the file were unsuitable for diffing
3009.2.29 by Aaron Bentley
Change constants to strings
486
    CANNOT_DIFF = 'CANNOT_DIFF'
3009.2.14 by Aaron Bentley
Update return type handling
487
    # The file has changed in a semantic way
3009.2.29 by Aaron Bentley
Change constants to strings
488
    CHANGED = 'CHANGED'
489
    # The file content may have changed, but there is no semantic change
490
    UNCHANGED = 'UNCHANGED'
3009.2.11 by Aaron Bentley
Refactor diff to be more pluggable
491
3009.2.13 by Aaron Bentley
Refactor differ to support registering differ factories
492
    def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8'):
3009.2.17 by Aaron Bentley
Update docs
493
        """Constructor.
494
495
        :param old_tree: The tree to show as the old tree in the comparison
496
        :param new_tree: The tree to show as new in the comparison
497
        :param to_file: The file to write comparison data to
498
        :param path_encoding: The character encoding to write paths in
499
        """
3009.2.11 by Aaron Bentley
Refactor diff to be more pluggable
500
        self.old_tree = old_tree
501
        self.new_tree = new_tree
502
        self.to_file = to_file
3009.2.13 by Aaron Bentley
Refactor differ to support registering differ factories
503
        self.path_encoding = path_encoding
3009.2.11 by Aaron Bentley
Refactor diff to be more pluggable
504
3123.6.2 by Aaron Bentley
Implement diff --using natively
505
    def finish(self):
506
        pass
507
3009.2.28 by Aaron Bentley
Add from_diff_tree factories
508
    @classmethod
509
    def from_diff_tree(klass, diff_tree):
510
        return klass(diff_tree.old_tree, diff_tree.new_tree,
511
                     diff_tree.to_file, diff_tree.path_encoding)
512
3009.2.11 by Aaron Bentley
Refactor diff to be more pluggable
513
    @staticmethod
514
    def _diff_many(differs, file_id, old_path, new_path, old_kind, new_kind):
515
        for file_differ in differs:
516
            result = file_differ.diff(file_id, old_path, new_path, old_kind,
517
                                      new_kind)
3009.2.22 by Aaron Bentley
Update names & docstring
518
            if result is not DiffPath.CANNOT_DIFF:
3009.2.11 by Aaron Bentley
Refactor diff to be more pluggable
519
                return result
520
        else:
3009.2.22 by Aaron Bentley
Update names & docstring
521
            return DiffPath.CANNOT_DIFF
522
523
524
class DiffKindChange(object):
3009.2.17 by Aaron Bentley
Update docs
525
    """Special differ for file kind changes.
3009.2.11 by Aaron Bentley
Refactor diff to be more pluggable
526
3009.2.17 by Aaron Bentley
Update docs
527
    Represents kind change as deletion + creation.  Uses the other differs
528
    to do this.
529
    """
3009.2.11 by Aaron Bentley
Refactor diff to be more pluggable
530
    def __init__(self, differs):
531
        self.differs = differs
532
3123.6.2 by Aaron Bentley
Implement diff --using natively
533
    def finish(self):
534
        pass
535
3009.2.28 by Aaron Bentley
Add from_diff_tree factories
536
    @classmethod
537
    def from_diff_tree(klass, diff_tree):
538
        return klass(diff_tree.differs)
539
3009.2.11 by Aaron Bentley
Refactor diff to be more pluggable
540
    def diff(self, file_id, old_path, new_path, old_kind, new_kind):
3009.2.17 by Aaron Bentley
Update docs
541
        """Perform comparison
542
543
        :param file_id: The file_id of the file to compare
544
        :param old_path: Path of the file in the old tree
545
        :param new_path: Path of the file in the new tree
546
        :param old_kind: Old file-kind of the file
547
        :param new_kind: New file-kind of the file
548
        """
3009.2.18 by Aaron Bentley
Change KindChangeDiffer's anti-recursion to avoid kind pairs with None
549
        if None in (old_kind, new_kind):
3009.2.22 by Aaron Bentley
Update names & docstring
550
            return DiffPath.CANNOT_DIFF
551
        result = DiffPath._diff_many(self.differs, file_id, old_path,
3009.2.18 by Aaron Bentley
Change KindChangeDiffer's anti-recursion to avoid kind pairs with None
552
                                       new_path, old_kind, None)
3009.2.22 by Aaron Bentley
Update names & docstring
553
        if result is DiffPath.CANNOT_DIFF:
3009.2.11 by Aaron Bentley
Refactor diff to be more pluggable
554
            return result
3009.2.22 by Aaron Bentley
Update names & docstring
555
        return DiffPath._diff_many(self.differs, file_id, old_path, new_path,
3009.2.11 by Aaron Bentley
Refactor diff to be more pluggable
556
                                     None, new_kind)
557
558
3009.2.22 by Aaron Bentley
Update names & docstring
559
class DiffDirectory(DiffPath):
3009.2.19 by Aaron Bentley
Implement directory diffing
560
561
    def diff(self, file_id, old_path, new_path, old_kind, new_kind):
562
        """Perform comparison between two directories.  (dummy)
563
564
        """
565
        if 'directory' not in (old_kind, new_kind):
566
            return self.CANNOT_DIFF
567
        if old_kind not in ('directory', None):
568
            return self.CANNOT_DIFF
569
        if new_kind not in ('directory', None):
570
            return self.CANNOT_DIFF
571
        return self.CHANGED
572
3009.2.20 by Aaron Bentley
PEP8
573
3009.2.22 by Aaron Bentley
Update names & docstring
574
class DiffSymlink(DiffPath):
3009.2.11 by Aaron Bentley
Refactor diff to be more pluggable
575
576
    def diff(self, file_id, old_path, new_path, old_kind, new_kind):
3009.2.17 by Aaron Bentley
Update docs
577
        """Perform comparison between two symlinks
578
579
        :param file_id: The file_id of the file to compare
580
        :param old_path: Path of the file in the old tree
581
        :param new_path: Path of the file in the new tree
582
        :param old_kind: Old file-kind of the file
583
        :param new_kind: New file-kind of the file
584
        """
3009.2.11 by Aaron Bentley
Refactor diff to be more pluggable
585
        if 'symlink' not in (old_kind, new_kind):
3009.2.14 by Aaron Bentley
Update return type handling
586
            return self.CANNOT_DIFF
3009.2.11 by Aaron Bentley
Refactor diff to be more pluggable
587
        if old_kind == 'symlink':
588
            old_target = self.old_tree.get_symlink_target(file_id)
589
        elif old_kind is None:
590
            old_target = None
591
        else:
3009.2.14 by Aaron Bentley
Update return type handling
592
            return self.CANNOT_DIFF
3009.2.11 by Aaron Bentley
Refactor diff to be more pluggable
593
        if new_kind == 'symlink':
594
            new_target = self.new_tree.get_symlink_target(file_id)
595
        elif new_kind is None:
596
            new_target = None
597
        else:
3009.2.14 by Aaron Bentley
Update return type handling
598
            return self.CANNOT_DIFF
3009.2.11 by Aaron Bentley
Refactor diff to be more pluggable
599
        return self.diff_symlink(old_target, new_target)
600
601
    def diff_symlink(self, old_target, new_target):
602
        if old_target is None:
603
            self.to_file.write('=== target is %r\n' % new_target)
604
        elif new_target is None:
605
            self.to_file.write('=== target was %r\n' % old_target)
606
        else:
607
            self.to_file.write('=== target changed %r => %r\n' %
608
                              (old_target, new_target))
3009.2.14 by Aaron Bentley
Update return type handling
609
        return self.CHANGED
3009.2.11 by Aaron Bentley
Refactor diff to be more pluggable
610
611
3009.2.22 by Aaron Bentley
Update names & docstring
612
class DiffText(DiffPath):
3009.2.2 by Aaron Bentley
Implement Differ object for abstracting diffing
613
3009.2.7 by Aaron Bentley
Move responsibility for generating diff labels into Differ.diff
614
    # GNU Patch uses the epoch date to detect files that are being added
615
    # or removed in a diff.
616
    EPOCH_DATE = '1970-01-01 00:00:00 +0000'
617
3009.2.13 by Aaron Bentley
Refactor differ to support registering differ factories
618
    def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8',
619
                 old_label='', new_label='', text_differ=internal_diff):
3009.2.22 by Aaron Bentley
Update names & docstring
620
        DiffPath.__init__(self, old_tree, new_tree, to_file, path_encoding)
3009.2.11 by Aaron Bentley
Refactor diff to be more pluggable
621
        self.text_differ = text_differ
622
        self.old_label = old_label
623
        self.new_label = new_label
3009.2.12 by Aaron Bentley
Associate labels with text diffing only
624
        self.path_encoding = path_encoding
3009.2.11 by Aaron Bentley
Refactor diff to be more pluggable
625
626
    def diff(self, file_id, old_path, new_path, old_kind, new_kind):
3009.2.17 by Aaron Bentley
Update docs
627
        """Compare two files in unified diff format
628
629
        :param file_id: The file_id of the file to compare
630
        :param old_path: Path of the file in the old tree
631
        :param new_path: Path of the file in the new tree
632
        :param old_kind: Old file-kind of the file
633
        :param new_kind: New file-kind of the file
634
        """
3009.2.11 by Aaron Bentley
Refactor diff to be more pluggable
635
        if 'file' not in (old_kind, new_kind):
3009.2.14 by Aaron Bentley
Update return type handling
636
            return self.CANNOT_DIFF
3009.2.11 by Aaron Bentley
Refactor diff to be more pluggable
637
        from_file_id = to_file_id = file_id
638
        if old_kind == 'file':
639
            old_date = _patch_header_date(self.old_tree, file_id, old_path)
640
        elif old_kind is None:
641
            old_date = self.EPOCH_DATE
3009.2.12 by Aaron Bentley
Associate labels with text diffing only
642
            from_file_id = None
3009.2.11 by Aaron Bentley
Refactor diff to be more pluggable
643
        else:
3009.2.14 by Aaron Bentley
Update return type handling
644
            return self.CANNOT_DIFF
3009.2.11 by Aaron Bentley
Refactor diff to be more pluggable
645
        if new_kind == 'file':
646
            new_date = _patch_header_date(self.new_tree, file_id, new_path)
647
        elif new_kind is None:
648
            new_date = self.EPOCH_DATE
649
            to_file_id = None
650
        else:
3009.2.14 by Aaron Bentley
Update return type handling
651
            return self.CANNOT_DIFF
3009.2.11 by Aaron Bentley
Refactor diff to be more pluggable
652
        from_label = '%s%s\t%s' % (self.old_label, old_path, old_date)
653
        to_label = '%s%s\t%s' % (self.new_label, new_path, new_date)
4377.3.3 by Ian Clatworthy
avoid unnecessary id2path calculation when diffing
654
        return self.diff_text(from_file_id, to_file_id, from_label, to_label,
655
            old_path, new_path)
3009.2.11 by Aaron Bentley
Refactor diff to be more pluggable
656
4377.3.3 by Ian Clatworthy
avoid unnecessary id2path calculation when diffing
657
    def diff_text(self, from_file_id, to_file_id, from_label, to_label,
658
        from_path=None, to_path=None):
3009.2.11 by Aaron Bentley
Refactor diff to be more pluggable
659
        """Diff the content of given files in two trees
660
661
        :param from_file_id: The id of the file in the from tree.  If None,
662
            the file is not present in the from tree.
663
        :param to_file_id: The id of the file in the to tree.  This may refer
664
            to a different file from from_file_id.  If None,
665
            the file is not present in the to tree.
4377.3.3 by Ian Clatworthy
avoid unnecessary id2path calculation when diffing
666
        :param from_path: The path in the from tree or None if unknown.
667
        :param to_path: The path in the to tree or None if unknown.
3009.2.11 by Aaron Bentley
Refactor diff to be more pluggable
668
        """
4377.3.3 by Ian Clatworthy
avoid unnecessary id2path calculation when diffing
669
        def _get_text(tree, file_id, path):
3009.2.11 by Aaron Bentley
Refactor diff to be more pluggable
670
            if file_id is not None:
4708.2.2 by Martin
Workingtree changes sitting around since November, more explict closing of files in bzrlib
671
                return tree.get_file_lines(file_id, path)
3009.2.11 by Aaron Bentley
Refactor diff to be more pluggable
672
            else:
673
                return []
674
        try:
4377.3.3 by Ian Clatworthy
avoid unnecessary id2path calculation when diffing
675
            from_text = _get_text(self.old_tree, from_file_id, from_path)
676
            to_text = _get_text(self.new_tree, to_file_id, to_path)
3009.2.11 by Aaron Bentley
Refactor diff to be more pluggable
677
            self.text_differ(from_label, from_text, to_label, to_text,
4797.57.1 by Alexander Belchenko
pass encoding down the diff layers
678
                             self.to_file, path_encoding=self.path_encoding)
3009.2.11 by Aaron Bentley
Refactor diff to be more pluggable
679
        except errors.BinaryFile:
680
            self.to_file.write(
681
                  ("Binary files %s and %s differ\n" %
4797.57.4 by Alexander Belchenko
if filename cannot be encoded in current path_encoding (user_encoding) then use foo.encode(path_encoding, 'replace') so we don't traceback
682
                  (from_label, to_label)).encode(self.path_encoding,'replace'))
3009.2.14 by Aaron Bentley
Update return type handling
683
        return self.CHANGED
3009.2.11 by Aaron Bentley
Refactor diff to be more pluggable
684
685
3123.6.2 by Aaron Bentley
Implement diff --using natively
686
class DiffFromTool(DiffPath):
687
688
    def __init__(self, command_template, old_tree, new_tree, to_file,
689
                 path_encoding='utf-8'):
690
        DiffPath.__init__(self, old_tree, new_tree, to_file, path_encoding)
691
        self.command_template = command_template
3638.3.2 by Vincent Ladeuil
Fix all calls to tempfile.mkdtemp to osutils.mkdtemp.
692
        self._root = osutils.mkdtemp(prefix='bzr-diff-')
3123.6.2 by Aaron Bentley
Implement diff --using natively
693
694
    @classmethod
695
    def from_string(klass, command_string, old_tree, new_tree, to_file,
696
                    path_encoding='utf-8'):
4913.5.24 by Gordon Tyler
Added cmdline.split function, which replaces commands.shlex_split_unicode.
697
        command_template = cmdline.split(command_string)
4603.1.20 by Aaron Bentley
Use string.Template substitution with @ as delimiter.
698
        if '@' not in command_string:
699
            command_template.extend(['@old_path', '@new_path'])
3123.6.2 by Aaron Bentley
Implement diff --using natively
700
        return klass(command_template, old_tree, new_tree, to_file,
701
                     path_encoding)
702
703
    @classmethod
5349.1.4 by Matthäus G. Chajdas
Allow both --using and --diff-options.
704
    def make_from_diff_tree(klass, command_string, external_diff_options=None):
3123.6.2 by Aaron Bentley
Implement diff --using natively
705
        def from_diff_tree(diff_tree):
5349.1.4 by Matthäus G. Chajdas
Allow both --using and --diff-options.
706
            full_command_string = [command_string]
707
            if external_diff_options is not None:
708
                full_command_string += ' ' + external_diff_options
709
            return klass.from_string(full_command_string, diff_tree.old_tree,
3123.6.2 by Aaron Bentley
Implement diff --using natively
710
                                     diff_tree.new_tree, diff_tree.to_file)
711
        return from_diff_tree
712
713
    def _get_command(self, old_path, new_path):
714
        my_map = {'old_path': old_path, 'new_path': new_path}
5074.5.1 by INADA Naoki
merge #523746 fix from lp:~songofacandy/bzr/fix-523746-2
715
        command = [AtTemplate(t).substitute(my_map) for t in
716
                   self.command_template]
4634.171.2 by INADA Naoki
Make temporary filename more friendly for non ascii filename.
717
        if sys.platform == 'win32': # Popen doesn't accept unicode on win32
718
            command_encoded = []
719
            for c in command:
720
                if isinstance(c, unicode):
721
                    command_encoded.append(c.encode('mbcs'))
722
                else:
723
                    command_encoded.append(c)
724
            return command_encoded
725
        else:
726
            return command
3123.6.2 by Aaron Bentley
Implement diff --using natively
727
728
    def _execute(self, old_path, new_path):
3145.1.1 by Aaron Bentley
Handle missing tools gracefully in diff --using
729
        command = self._get_command(old_path, new_path)
730
        try:
731
            proc = subprocess.Popen(command, stdout=subprocess.PIPE,
732
                                    cwd=self._root)
733
        except OSError, e:
734
            if e.errno == errno.ENOENT:
735
                raise errors.ExecutableMissing(command[0])
3145.1.2 by Aaron Bentley
Don't swallow other OSErrors
736
            else:
737
                raise
3123.6.2 by Aaron Bentley
Implement diff --using natively
738
        self.to_file.write(proc.stdout.read())
739
        return proc.wait()
740
3123.6.5 by Aaron Bentley
Symlink to real files if possible
741
    def _try_symlink_root(self, tree, prefix):
3287.18.3 by Matt McClure
Toward a more acceptable patch for bug 209281.
742
        if (getattr(tree, 'abspath', None) is None
3287.18.14 by Matt McClure
Extracted a host_os_dereferences_symlinks method.
743
            or not osutils.host_os_dereferences_symlinks()):
3123.6.5 by Aaron Bentley
Symlink to real files if possible
744
            return False
745
        try:
746
            os.symlink(tree.abspath(''), osutils.pathjoin(self._root, prefix))
747
        except OSError, e:
748
            if e.errno != errno.EEXIST:
749
                raise
750
        return True
751
5074.5.8 by INADA Naoki
Use tempfile when filepath in tree is not be able to encode with fsencoding.
752
    @staticmethod
753
    def _fenc():
5074.5.9 by INADA Naoki
Make additional comments to clarify
754
        """Returns safe encoding for passing file path to diff tool"""
5074.5.8 by INADA Naoki
Use tempfile when filepath in tree is not be able to encode with fsencoding.
755
        if sys.platform == 'win32':
756
            return 'mbcs'
757
        else:
758
            # Don't fallback to 'utf-8' because subprocess may not be able to
759
            # handle utf-8 correctly when locale is not utf-8.
760
            return sys.getfilesystemencoding() or 'ascii'
761
762
    def _is_safepath(self, path):
763
        """Return true if `path` may be able to pass to subprocess."""
764
        fenc = self._fenc()
765
        try:
766
            return path == path.encode(fenc).decode(fenc)
767
        except UnicodeError:
768
            return False
769
5074.5.7 by INADA Naoki
Test for filename encoding can't test subprocess execution because
770
    def _safe_filename(self, prefix, relpath):
5074.5.8 by INADA Naoki
Use tempfile when filepath in tree is not be able to encode with fsencoding.
771
        """Replace unsafe character in `relpath` then join `self._root`,
772
        `prefix` and `relpath`."""
773
        fenc = self._fenc()
4634.171.4 by INADA Naoki
Append comment for why decode() needed before replace().
774
        # encoded_str.replace('?', '_') may break multibyte char.
775
        # So we should encode, decode, then replace(u'?', u'_')
4634.171.2 by INADA Naoki
Make temporary filename more friendly for non ascii filename.
776
        relpath_tmp = relpath.encode(fenc, 'replace').decode(fenc, 'replace')
4634.171.3 by INADA Naoki
Fix easy miss.
777
        relpath_tmp = relpath_tmp.replace(u'?', u'_')
5074.5.7 by INADA Naoki
Test for filename encoding can't test subprocess execution because
778
        return osutils.pathjoin(self._root, prefix, relpath_tmp)
779
780
    def _write_file(self, file_id, tree, prefix, relpath, force_temp=False,
781
                    allow_write=False):
782
        if not force_temp and isinstance(tree, WorkingTree):
5074.5.8 by INADA Naoki
Use tempfile when filepath in tree is not be able to encode with fsencoding.
783
            full_path = tree.abspath(tree.id2path(file_id))
784
            if self._is_safepath(full_path):
785
                return full_path
5074.5.7 by INADA Naoki
Test for filename encoding can't test subprocess execution because
786
787
        full_path = self._safe_filename(prefix, relpath)
4603.1.4 by Aaron Bentley
Implement DiffFromTool.edit_file
788
        if not force_temp and self._try_symlink_root(tree, prefix):
3123.6.5 by Aaron Bentley
Symlink to real files if possible
789
            return full_path
3123.6.4 by Aaron Bentley
Set mtime (and atime) on files for --using
790
        parent_dir = osutils.dirname(full_path)
3123.6.2 by Aaron Bentley
Implement diff --using natively
791
        try:
792
            os.makedirs(parent_dir)
793
        except OSError, e:
794
            if e.errno != errno.EEXIST:
795
                raise
3123.6.6 by Aaron Bentley
Use relpath for get_file
796
        source = tree.get_file(file_id, relpath)
3123.6.2 by Aaron Bentley
Implement diff --using natively
797
        try:
3123.6.4 by Aaron Bentley
Set mtime (and atime) on files for --using
798
            target = open(full_path, 'wb')
3123.6.2 by Aaron Bentley
Implement diff --using natively
799
            try:
800
                osutils.pumpfile(source, target)
801
            finally:
802
                target.close()
803
        finally:
804
            source.close()
4976.1.3 by Jelmer Vernooij
Cope with ghosts in 'bzr diff'
805
        try:
806
            mtime = tree.get_file_mtime(file_id)
807
        except errors.FileTimestampUnavailable:
5151.3.2 by Martin
Don't try and warp files back to the 70s if no timestamp is available
808
            pass
809
        else:
810
            os.utime(full_path, (mtime, mtime))
5151.3.1 by Martin
Fix os.utime test failures, three on FAT filesystems and one with readonly files
811
        if not allow_write:
812
            osutils.make_readonly(full_path)
3123.6.4 by Aaron Bentley
Set mtime (and atime) on files for --using
813
        return full_path
3123.6.2 by Aaron Bentley
Implement diff --using natively
814
4603.1.4 by Aaron Bentley
Implement DiffFromTool.edit_file
815
    def _prepare_files(self, file_id, old_path, new_path, force_temp=False,
816
                       allow_write_new=False):
3123.6.2 by Aaron Bentley
Implement diff --using natively
817
        old_disk_path = self._write_file(file_id, self.old_tree, 'old',
4603.1.4 by Aaron Bentley
Implement DiffFromTool.edit_file
818
                                         old_path, force_temp)
3123.6.2 by Aaron Bentley
Implement diff --using natively
819
        new_disk_path = self._write_file(file_id, self.new_tree, 'new',
4603.1.4 by Aaron Bentley
Implement DiffFromTool.edit_file
820
                                         new_path, force_temp,
821
                                         allow_write=allow_write_new)
3123.6.2 by Aaron Bentley
Implement diff --using natively
822
        return old_disk_path, new_disk_path
823
824
    def finish(self):
4354.6.1 by Martitza Mendez
Fix 363837 : catch OSError from osutils.rmtree and mutter to trace file.
825
        try:
826
            osutils.rmtree(self._root)
827
        except OSError, e:
828
            if e.errno != errno.ENOENT:
4399.1.1 by Ian Clatworthy
(igc) address temp file issue with diff --using on Windows (Martitza Mendez)
829
                mutter("The temporary directory \"%s\" was not "
830
                        "cleanly removed: %s." % (self._root, e))
3123.6.2 by Aaron Bentley
Implement diff --using natively
831
832
    def diff(self, file_id, old_path, new_path, old_kind, new_kind):
833
        if (old_kind, new_kind) != ('file', 'file'):
834
            return DiffPath.CANNOT_DIFF
4845.2.1 by Gary van der Merwe
When launching an external diff app, don't write temporary files for a working tree.
835
        (old_disk_path, new_disk_path) = self._prepare_files(
836
                                                file_id, old_path, new_path)
837
        self._execute(old_disk_path, new_disk_path)
4603.1.1 by Aaron Bentley
Initial pass at shelve-via-editor.
838
4603.1.4 by Aaron Bentley
Implement DiffFromTool.edit_file
839
    def edit_file(self, file_id):
840
        """Use this tool to edit a file.
841
842
        A temporary copy will be edited, and the new contents will be
843
        returned.
844
845
        :param file_id: The id of the file to edit.
846
        :return: The new contents of the file.
847
        """
848
        old_path = self.old_tree.id2path(file_id)
849
        new_path = self.new_tree.id2path(file_id)
5074.5.1 by INADA Naoki
merge #523746 fix from lp:~songofacandy/bzr/fix-523746-2
850
        old_abs_path, new_abs_path = self._prepare_files(
851
                                            file_id, old_path, new_path,
852
                                            allow_write_new=True,
853
                                            force_temp=True)
854
        command = self._get_command(old_abs_path, new_abs_path)
4603.1.24 by Aaron Bentley
Fix call import/invocation.
855
        subprocess.call(command, cwd=self._root)
5074.5.1 by INADA Naoki
merge #523746 fix from lp:~songofacandy/bzr/fix-523746-2
856
        new_file = open(new_abs_path, 'rb')
4603.1.4 by Aaron Bentley
Implement DiffFromTool.edit_file
857
        try:
858
            return new_file.read()
859
        finally:
860
            new_file.close()
861
3123.6.2 by Aaron Bentley
Implement diff --using natively
862
3009.2.22 by Aaron Bentley
Update names & docstring
863
class DiffTree(object):
864
    """Provides textual representations of the difference between two trees.
865
866
    A DiffTree examines two trees and where a file-id has altered
867
    between them, generates a textual representation of the difference.
868
    DiffTree uses a sequence of DiffPath objects which are each
869
    given the opportunity to handle a given altered fileid. The list
870
    of DiffPath objects can be extended globally by appending to
871
    DiffTree.diff_factories, or for a specific diff operation by
3009.2.27 by Aaron Bentley
Use extra_factories instead of extra_diffs
872
    supplying the extra_factories option to the appropriate method.
3009.2.22 by Aaron Bentley
Update names & docstring
873
    """
874
875
    # list of factories that can provide instances of DiffPath objects
3009.2.17 by Aaron Bentley
Update docs
876
    # may be extended by plugins.
3009.2.28 by Aaron Bentley
Add from_diff_tree factories
877
    diff_factories = [DiffSymlink.from_diff_tree,
878
                      DiffDirectory.from_diff_tree]
3009.2.13 by Aaron Bentley
Refactor differ to support registering differ factories
879
3009.2.12 by Aaron Bentley
Associate labels with text diffing only
880
    def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8',
3009.2.28 by Aaron Bentley
Add from_diff_tree factories
881
                 diff_text=None, extra_factories=None):
3009.2.17 by Aaron Bentley
Update docs
882
        """Constructor
883
884
        :param old_tree: Tree to show as old in the comparison
885
        :param new_tree: Tree to show as new in the comparison
886
        :param to_file: File to write comparision to
887
        :param path_encoding: Character encoding to write paths in
3009.2.28 by Aaron Bentley
Add from_diff_tree factories
888
        :param diff_text: DiffPath-type object to use as a last resort for
3009.2.17 by Aaron Bentley
Update docs
889
            diffing text files.
3009.2.27 by Aaron Bentley
Use extra_factories instead of extra_diffs
890
        :param extra_factories: Factories of DiffPaths to try before any other
891
            DiffPaths"""
3009.2.28 by Aaron Bentley
Add from_diff_tree factories
892
        if diff_text is None:
893
            diff_text = DiffText(old_tree, new_tree, to_file, path_encoding,
894
                                 '', '',  internal_diff)
3009.2.4 by Aaron Bentley
Make old_tree/new_tree construction parameters of Differ
895
        self.old_tree = old_tree
896
        self.new_tree = new_tree
3009.2.2 by Aaron Bentley
Implement Differ object for abstracting diffing
897
        self.to_file = to_file
3009.2.28 by Aaron Bentley
Add from_diff_tree factories
898
        self.path_encoding = path_encoding
3009.2.13 by Aaron Bentley
Refactor differ to support registering differ factories
899
        self.differs = []
3009.2.27 by Aaron Bentley
Use extra_factories instead of extra_diffs
900
        if extra_factories is not None:
3009.2.28 by Aaron Bentley
Add from_diff_tree factories
901
            self.differs.extend(f(self) for f in extra_factories)
902
        self.differs.extend(f(self) for f in self.diff_factories)
903
        self.differs.extend([diff_text, DiffKindChange.from_diff_tree(self)])
3009.2.6 by Aaron Bentley
Convert show_diff_trees into a Differ method
904
905
    @classmethod
906
    def from_trees_options(klass, old_tree, new_tree, to_file,
3009.2.17 by Aaron Bentley
Update docs
907
                           path_encoding, external_diff_options, old_label,
3123.6.2 by Aaron Bentley
Implement diff --using natively
908
                           new_label, using):
3009.2.22 by Aaron Bentley
Update names & docstring
909
        """Factory for producing a DiffTree.
3009.2.17 by Aaron Bentley
Update docs
910
911
        Designed to accept options used by show_diff_trees.
5891.1.3 by Andrew Bennetts
Move docstring formatting fixes.
912
3009.2.17 by Aaron Bentley
Update docs
913
        :param old_tree: The tree to show as old in the comparison
914
        :param new_tree: The tree to show as new in the comparison
915
        :param to_file: File to write comparisons to
916
        :param path_encoding: Character encoding to use for writing paths
917
        :param external_diff_options: If supplied, use the installed diff
918
            binary to perform file comparison, using supplied options.
919
        :param old_label: Prefix to use for old file labels
920
        :param new_label: Prefix to use for new file labels
3123.6.2 by Aaron Bentley
Implement diff --using natively
921
        :param using: Commandline to use to invoke an external diff tool
3009.2.17 by Aaron Bentley
Update docs
922
        """
3123.6.2 by Aaron Bentley
Implement diff --using natively
923
        if using is not None:
5349.1.4 by Matthäus G. Chajdas
Allow both --using and --diff-options.
924
            extra_factories = [DiffFromTool.make_from_diff_tree(using, external_diff_options)]
3123.6.2 by Aaron Bentley
Implement diff --using natively
925
        else:
926
            extra_factories = []
3009.2.6 by Aaron Bentley
Convert show_diff_trees into a Differ method
927
        if external_diff_options:
928
            opts = external_diff_options.split()
4797.57.2 by Alexander Belchenko
fixing test with external_diff
929
            def diff_file(olab, olines, nlab, nlines, to_file, path_encoding=None):
930
                """:param path_encoding: not used but required
931
                        to match the signature of internal_diff.
932
                """
3009.2.6 by Aaron Bentley
Convert show_diff_trees into a Differ method
933
                external_diff(olab, olines, nlab, nlines, to_file, opts)
934
        else:
935
            diff_file = internal_diff
3009.2.28 by Aaron Bentley
Add from_diff_tree factories
936
        diff_text = DiffText(old_tree, new_tree, to_file, path_encoding,
937
                             old_label, new_label, diff_file)
3123.6.2 by Aaron Bentley
Implement diff --using natively
938
        return klass(old_tree, new_tree, to_file, path_encoding, diff_text,
939
                     extra_factories)
3009.2.6 by Aaron Bentley
Convert show_diff_trees into a Differ method
940
3009.2.12 by Aaron Bentley
Associate labels with text diffing only
941
    def show_diff(self, specific_files, extra_trees=None):
3009.2.17 by Aaron Bentley
Update docs
942
        """Write tree diff to self.to_file
943
5131.1.4 by Jelmer Vernooij
Add test for custom diff format.
944
        :param specific_files: the specific files to compare (recursive)
3009.2.17 by Aaron Bentley
Update docs
945
        :param extra_trees: extra trees to use for mapping paths to file_ids
946
        """
3123.6.2 by Aaron Bentley
Implement diff --using natively
947
        try:
948
            return self._show_diff(specific_files, extra_trees)
949
        finally:
950
            for differ in self.differs:
951
                differ.finish()
952
953
    def _show_diff(self, specific_files, extra_trees):
3009.2.6 by Aaron Bentley
Convert show_diff_trees into a Differ method
954
        # TODO: Generation of pseudo-diffs for added/deleted files could
955
        # be usefully made into a much faster special case.
3254.1.1 by Aaron Bentley
Make Tree.iter_changes a public method
956
        iterator = self.new_tree.iter_changes(self.old_tree,
3123.4.1 by Aaron Bentley
Diff sorts files in alphabetical order
957
                                               specific_files=specific_files,
958
                                               extra_trees=extra_trees,
959
                                               require_versioned=True)
3009.2.6 by Aaron Bentley
Convert show_diff_trees into a Differ method
960
        has_changes = 0
3123.4.1 by Aaron Bentley
Diff sorts files in alphabetical order
961
        def changes_key(change):
962
            old_path, new_path = change[1]
963
            path = new_path
964
            if path is None:
965
                path = old_path
966
            return path
967
        def get_encoded_path(path):
968
            if path is not None:
969
                return path.encode(self.path_encoding, "replace")
970
        for (file_id, paths, changed_content, versioned, parent, name, kind,
971
             executable) in sorted(iterator, key=changes_key):
3619.4.2 by Robert Collins
Change bzrlib.diff.DiffTree.show_diff to skip entries missing in both trees.
972
            # The root does not get diffed, and items with no known kind (that
973
            # is, missing) in both trees are skipped as well.
974
            if parent == (None, None) or kind == (None, None):
3123.4.3 by Aaron Bentley
Tweak path handling
975
                continue
976
            oldpath, newpath = paths
3123.4.1 by Aaron Bentley
Diff sorts files in alphabetical order
977
            oldpath_encoded = get_encoded_path(paths[0])
978
            newpath_encoded = get_encoded_path(paths[1])
979
            old_present = (kind[0] is not None and versioned[0])
980
            new_present = (kind[1] is not None and versioned[1])
981
            renamed = (parent[0], name[0]) != (parent[1], name[1])
3268.1.1 by C Miller
Describe the property changes in diffs. Currently, this is the executable-bit
982
983
            properties_changed = []
984
            properties_changed.extend(get_executable_change(executable[0], executable[1]))
985
986
            if properties_changed:
987
                prop_str = " (properties changed: %s)" % (", ".join(properties_changed),)
988
            else:
989
                prop_str = ""
990
3123.4.1 by Aaron Bentley
Diff sorts files in alphabetical order
991
            if (old_present, new_present) == (True, False):
992
                self.to_file.write("=== removed %s '%s'\n" %
993
                                   (kind[0], oldpath_encoded))
3123.4.3 by Aaron Bentley
Tweak path handling
994
                newpath = oldpath
3123.4.1 by Aaron Bentley
Diff sorts files in alphabetical order
995
            elif (old_present, new_present) == (False, True):
996
                self.to_file.write("=== added %s '%s'\n" %
997
                                   (kind[1], newpath_encoded))
3123.4.3 by Aaron Bentley
Tweak path handling
998
                oldpath = newpath
3123.4.1 by Aaron Bentley
Diff sorts files in alphabetical order
999
            elif renamed:
1000
                self.to_file.write("=== renamed %s '%s' => '%s'%s\n" %
1001
                    (kind[0], oldpath_encoded, newpath_encoded, prop_str))
3123.4.2 by Aaron Bentley
Handle diff with property change correctly
1002
            else:
3254.1.1 by Aaron Bentley
Make Tree.iter_changes a public method
1003
                # if it was produced by iter_changes, it must be
3123.4.2 by Aaron Bentley
Handle diff with property change correctly
1004
                # modified *somehow*, either content or execute bit.
3123.4.1 by Aaron Bentley
Diff sorts files in alphabetical order
1005
                self.to_file.write("=== modified %s '%s'%s\n" % (kind[0],
1006
                                   newpath_encoded, prop_str))
1007
            if changed_content:
4377.3.1 by Ian Clatworthy
faster diff on large trees
1008
                self._diff(file_id, oldpath, newpath, kind[0], kind[1])
3123.4.1 by Aaron Bentley
Diff sorts files in alphabetical order
1009
                has_changes = 1
1010
            if renamed:
1011
                has_changes = 1
3009.2.6 by Aaron Bentley
Convert show_diff_trees into a Differ method
1012
        return has_changes
3009.2.2 by Aaron Bentley
Implement Differ object for abstracting diffing
1013
3009.2.12 by Aaron Bentley
Associate labels with text diffing only
1014
    def diff(self, file_id, old_path, new_path):
3009.2.17 by Aaron Bentley
Update docs
1015
        """Perform a diff of a single file
1016
1017
        :param file_id: file-id of the file
1018
        :param old_path: The path of the file in the old tree
1019
        :param new_path: The path of the file in the new tree
1020
        """
3009.2.2 by Aaron Bentley
Implement Differ object for abstracting diffing
1021
        try:
3009.2.8 by Aaron Bentley
Support diffing without indirecting through inventory entries
1022
            old_kind = self.old_tree.kind(file_id)
3087.1.1 by Aaron Bentley
Diff handles missing files correctly, with no tracebacks
1023
        except (errors.NoSuchId, errors.NoSuchFile):
3009.2.8 by Aaron Bentley
Support diffing without indirecting through inventory entries
1024
            old_kind = None
3009.2.3 by Aaron Bentley
Detect missing files from inv operation
1025
        try:
3009.2.8 by Aaron Bentley
Support diffing without indirecting through inventory entries
1026
            new_kind = self.new_tree.kind(file_id)
3087.1.1 by Aaron Bentley
Diff handles missing files correctly, with no tracebacks
1027
        except (errors.NoSuchId, errors.NoSuchFile):
3009.2.8 by Aaron Bentley
Support diffing without indirecting through inventory entries
1028
            new_kind = None
4377.3.1 by Ian Clatworthy
faster diff on large trees
1029
        self._diff(file_id, old_path, new_path, old_kind, new_kind)
1030
1031
1032
    def _diff(self, file_id, old_path, new_path, old_kind, new_kind):
3009.2.22 by Aaron Bentley
Update names & docstring
1033
        result = DiffPath._diff_many(self.differs, file_id, old_path,
3009.2.11 by Aaron Bentley
Refactor diff to be more pluggable
1034
                                       new_path, old_kind, new_kind)
3009.2.22 by Aaron Bentley
Update names & docstring
1035
        if result is DiffPath.CANNOT_DIFF:
3009.2.11 by Aaron Bentley
Refactor diff to be more pluggable
1036
            error_path = new_path
1037
            if error_path is None:
1038
                error_path = old_path
3009.2.22 by Aaron Bentley
Update names & docstring
1039
            raise errors.NoDiffFound(error_path)
5131.1.1 by Jelmer Vernooij
Add --format option to 'bzr diff'.
1040
1041
1042
format_registry = Registry()
1043
format_registry.register('default', DiffTree)