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