/brz/remove-bazaar

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