~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/diff.py

Optimize Tree._iter_changes with specific file_ids

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
 
17
import os
 
18
import re
 
19
import sys
 
20
 
 
21
from bzrlib.lazy_import import lazy_import
 
22
lazy_import(globals(), """
17
23
import errno
18
 
import os
19
24
import subprocess
20
 
import sys
21
25
import tempfile
22
26
import time
23
27
 
 
28
from bzrlib import (
 
29
    errors,
 
30
    osutils,
 
31
    patiencediff,
 
32
    textfile,
 
33
    )
 
34
""")
 
35
 
 
36
# compatability - plugins import compare_trees from diff!!!
 
37
# deprecated as of 0.10
24
38
from bzrlib.delta import compare_trees
25
 
from bzrlib.errors import BzrError
26
 
import bzrlib.errors as errors
27
 
import bzrlib.osutils
28
 
from bzrlib.patiencediff import unified_diff
29
 
import bzrlib.patiencediff
30
 
from bzrlib.symbol_versioning import (deprecated_function,
31
 
        zero_eight)
32
 
from bzrlib.textfile import check_text_lines
 
39
from bzrlib.symbol_versioning import (
 
40
        deprecated_function,
 
41
        zero_eight,
 
42
        )
33
43
from bzrlib.trace import mutter, warning
34
44
 
35
45
 
57
67
        return
58
68
    
59
69
    if allow_binary is False:
60
 
        check_text_lines(oldlines)
61
 
        check_text_lines(newlines)
 
70
        textfile.check_text_lines(oldlines)
 
71
        textfile.check_text_lines(newlines)
62
72
 
63
73
    if sequence_matcher is None:
64
 
        sequence_matcher = bzrlib.patiencediff.PatienceSequenceMatcher
65
 
    ud = unified_diff(oldlines, newlines,
 
74
        sequence_matcher = patiencediff.PatienceSequenceMatcher
 
75
    ud = patiencediff.unified_diff(oldlines, newlines,
66
76
                      fromfile=old_filename.encode(path_encoding),
67
77
                      tofile=new_filename.encode(path_encoding),
68
78
                      sequencematcher=sequence_matcher)
85
95
    print >>to_file
86
96
 
87
97
 
 
98
def _set_lang_C():
 
99
    """Set the env vars LANG=C and LC_ALL=C."""
 
100
    osutils.set_or_unset_env('LANG', 'C')
 
101
    osutils.set_or_unset_env('LC_ALL', 'C')
 
102
    osutils.set_or_unset_env('LC_CTYPE', None)
 
103
    osutils.set_or_unset_env('LANGUAGE', None)
 
104
 
 
105
 
 
106
def _spawn_external_diff(diffcmd, capture_errors=True):
 
107
    """Spawn the externall diff process, and return the child handle.
 
108
 
 
109
    :param diffcmd: The command list to spawn
 
110
    :param capture_errors: Capture stderr as well as setting LANG=C
 
111
        and LC_ALL=C. This lets us read and understand the output of diff,
 
112
        and respond to any errors.
 
113
    :return: A Popen object.
 
114
    """
 
115
    if capture_errors:
 
116
        if sys.platform == 'win32':
 
117
            # Win32 doesn't support preexec_fn, but that is
 
118
            # okay, because it doesn't support LANG and LC_ALL either.
 
119
            preexec_fn = None
 
120
        else:
 
121
            preexec_fn = _set_lang_C
 
122
        stderr = subprocess.PIPE
 
123
    else:
 
124
        preexec_fn = None
 
125
        stderr = None
 
126
 
 
127
    try:
 
128
        pipe = subprocess.Popen(diffcmd,
 
129
                                stdin=subprocess.PIPE,
 
130
                                stdout=subprocess.PIPE,
 
131
                                stderr=stderr,
 
132
                                preexec_fn=preexec_fn)
 
133
    except OSError, e:
 
134
        if e.errno == errno.ENOENT:
 
135
            raise errors.NoDiff(str(e))
 
136
        raise
 
137
 
 
138
    return pipe
 
139
 
 
140
 
88
141
def external_diff(old_filename, oldlines, new_filename, newlines, to_file,
89
142
                  diff_opts):
90
143
    """Display a diff by calling out to the external diff program."""
91
 
    if hasattr(to_file, 'fileno'):
92
 
        out_file = to_file
93
 
        have_fileno = True
94
 
    else:
95
 
        out_file = subprocess.PIPE
96
 
        have_fileno = False
97
 
    
98
144
    # make sure our own output is properly ordered before the diff
99
145
    to_file.flush()
100
146
 
151
197
        if diff_opts:
152
198
            diffcmd.extend(diff_opts)
153
199
 
154
 
        try:
155
 
            pipe = subprocess.Popen(diffcmd,
156
 
                                    stdin=subprocess.PIPE,
157
 
                                    stdout=out_file)
158
 
        except OSError, e:
159
 
            if e.errno == errno.ENOENT:
160
 
                raise errors.NoDiff(str(e))
161
 
            raise
162
 
        pipe.stdin.close()
163
 
 
164
 
        if not have_fileno:
165
 
            bzrlib.osutils.pumpfile(pipe.stdout, to_file)
166
 
        rc = pipe.wait()
 
200
        pipe = _spawn_external_diff(diffcmd, capture_errors=True)
 
201
        out,err = pipe.communicate()
 
202
        rc = pipe.returncode
167
203
        
168
 
        if rc != 0 and rc != 1:
 
204
        # internal_diff() adds a trailing newline, add one here for consistency
 
205
        out += '\n'
 
206
        if rc == 2:
 
207
            # 'diff' gives retcode == 2 for all sorts of errors
 
208
            # one of those is 'Binary files differ'.
 
209
            # Bad options could also be the problem.
 
210
            # 'Binary files' is not a real error, so we suppress that error.
 
211
            lang_c_out = out
 
212
 
 
213
            # Since we got here, we want to make sure to give an i18n error
 
214
            pipe = _spawn_external_diff(diffcmd, capture_errors=False)
 
215
            out, err = pipe.communicate()
 
216
 
 
217
            # Write out the new i18n diff response
 
218
            to_file.write(out+'\n')
 
219
            if pipe.returncode != 2:
 
220
                raise errors.BzrError(
 
221
                               'external diff failed with exit code 2'
 
222
                               ' when run with LANG=C and LC_ALL=C,'
 
223
                               ' but not when run natively: %r' % (diffcmd,))
 
224
 
 
225
            first_line = lang_c_out.split('\n', 1)[0]
 
226
            # Starting with diffutils 2.8.4 the word "binary" was dropped.
 
227
            m = re.match('^(binary )?files.*differ$', first_line, re.I)
 
228
            if m is None:
 
229
                raise errors.BzrError('external diff failed with exit code 2;'
 
230
                                      ' command: %r' % (diffcmd,))
 
231
            else:
 
232
                # Binary files differ, just return
 
233
                return
 
234
 
 
235
        # If we got to here, we haven't written out the output of diff
 
236
        # do so now
 
237
        to_file.write(out)
 
238
        if rc not in (0, 1):
169
239
            # returns 1 if files differ; that's OK
170
240
            if rc < 0:
171
241
                msg = 'signal %d' % (-rc)
172
242
            else:
173
243
                msg = 'exit code %d' % rc
174
244
                
175
 
            raise BzrError('external diff failed with %s; command: %r' % (rc, diffcmd))
 
245
            raise errors.BzrError('external diff failed with %s; command: %r' 
 
246
                                  % (rc, diffcmd))
 
247
 
 
248
 
176
249
    finally:
177
250
        oldtmpf.close()                 # and delete
178
251
        newtmpf.close()
233
306
 
234
307
def diff_cmd_helper(tree, specific_files, external_diff_options, 
235
308
                    old_revision_spec=None, new_revision_spec=None,
 
309
                    revision_specs=None,
236
310
                    old_label='a/', new_label='b/'):
237
311
    """Helper for cmd_diff.
238
312
 
239
 
   tree 
 
313
    :param tree:
240
314
        A WorkingTree
241
315
 
242
 
    specific_files
 
316
    :param specific_files:
243
317
        The specific files to compare, or None
244
318
 
245
 
    external_diff_options
 
319
    :param external_diff_options:
246
320
        If non-None, run an external diff, and pass it these options
247
321
 
248
 
    old_revision_spec
 
322
    :param old_revision_spec:
249
323
        If None, use basis tree as old revision, otherwise use the tree for
250
324
        the specified revision. 
251
325
 
252
 
    new_revision_spec
 
326
    :param new_revision_spec:
253
327
        If None, use working tree as new revision, otherwise use the tree for
254
328
        the specified revision.
255
329
    
 
330
    :param revision_specs: 
 
331
        Zero, one or two RevisionSpecs from the command line, saying what revisions 
 
332
        to compare.  This can be passed as an alternative to the old_revision_spec 
 
333
        and new_revision_spec parameters.
 
334
 
256
335
    The more general form is show_diff_trees(), where the caller
257
336
    supplies any two trees.
258
337
    """
 
338
 
 
339
    # TODO: perhaps remove the old parameters old_revision_spec and
 
340
    # new_revision_spec, since this is only really for use from cmd_diff and
 
341
    # it now always passes through a sequence of revision_specs -- mbp
 
342
    # 20061221
 
343
 
259
344
    def spec_tree(spec):
260
345
        if tree:
261
346
            revision = spec.in_store(tree.branch)
264
349
        revision_id = revision.rev_id
265
350
        branch = revision.branch
266
351
        return branch.repository.revision_tree(revision_id)
 
352
 
 
353
    if revision_specs is not None:
 
354
        assert (old_revision_spec is None
 
355
                and new_revision_spec is None)
 
356
        if len(revision_specs) > 0:
 
357
            old_revision_spec = revision_specs[0]
 
358
        if len(revision_specs) > 1:
 
359
            new_revision_spec = revision_specs[1]
 
360
 
267
361
    if old_revision_spec is None:
268
362
        old_tree = tree.basis_tree()
269
363
    else:
270
364
        old_tree = spec_tree(old_revision_spec)
271
365
 
272
 
    if new_revision_spec is None:
 
366
    if (new_revision_spec is None
 
367
        or new_revision_spec.spec is None):
273
368
        new_tree = tree
274
369
    else:
275
370
        new_tree = spec_tree(new_revision_spec)
 
371
 
276
372
    if new_tree is not tree:
277
373
        extra_trees = (tree,)
278
374
    else:
332
428
    else:
333
429
        diff_file = internal_diff
334
430
    
335
 
    delta = compare_trees(old_tree, new_tree, want_unchanged=False,
336
 
                          specific_files=specific_files, 
337
 
                          extra_trees=extra_trees, require_versioned=True)
 
431
    delta = new_tree.changes_from(old_tree,
 
432
        specific_files=specific_files,
 
433
        extra_trees=extra_trees, require_versioned=True)
338
434
 
339
435
    has_changes = 0
340
436
    for path, file_id, kind in delta.removed: