~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/diff.py

  • Committer: Alexander Belchenko
  • Date: 2006-07-30 16:43:12 UTC
  • mto: (1711.2.111 jam-integration)
  • mto: This revision was merged to the branch mainline in revision 1906.
  • Revision ID: bialix@ukr.net-20060730164312-b025fd3ff0cee59e
rename  gpl.txt => COPYING.txt

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 errno
17
18
import os
18
 
import re
 
19
import subprocess
19
20
import sys
20
 
 
21
 
from bzrlib.lazy_import import lazy_import
22
 
lazy_import(globals(), """
23
 
import errno
24
 
import subprocess
25
21
import tempfile
26
22
import time
27
23
 
28
 
from bzrlib import (
29
 
    errors,
30
 
    osutils,
31
 
    patiencediff,
32
 
    textfile,
33
 
    timestamp,
34
 
    )
35
 
""")
36
 
 
37
24
# compatability - plugins import compare_trees from diff!!!
38
25
# deprecated as of 0.10
39
26
from bzrlib.delta import compare_trees
40
 
from bzrlib.symbol_versioning import (
41
 
        deprecated_function,
42
 
        zero_eight,
43
 
        )
 
27
from bzrlib.errors import BzrError
 
28
import bzrlib.errors as errors
 
29
import bzrlib.osutils
 
30
from bzrlib.patiencediff import unified_diff
 
31
import bzrlib.patiencediff
 
32
from bzrlib.symbol_versioning import (deprecated_function,
 
33
        zero_eight)
 
34
from bzrlib.textfile import check_text_lines
44
35
from bzrlib.trace import mutter, warning
45
36
 
46
37
 
68
59
        return
69
60
    
70
61
    if allow_binary is False:
71
 
        textfile.check_text_lines(oldlines)
72
 
        textfile.check_text_lines(newlines)
 
62
        check_text_lines(oldlines)
 
63
        check_text_lines(newlines)
73
64
 
74
65
    if sequence_matcher is None:
75
 
        sequence_matcher = patiencediff.PatienceSequenceMatcher
76
 
    ud = patiencediff.unified_diff(oldlines, newlines,
 
66
        sequence_matcher = bzrlib.patiencediff.PatienceSequenceMatcher
 
67
    ud = unified_diff(oldlines, newlines,
77
68
                      fromfile=old_filename.encode(path_encoding),
78
69
                      tofile=new_filename.encode(path_encoding),
79
70
                      sequencematcher=sequence_matcher)
96
87
    print >>to_file
97
88
 
98
89
 
99
 
def _spawn_external_diff(diffcmd, capture_errors=True):
100
 
    """Spawn the externall diff process, and return the child handle.
101
 
 
102
 
    :param diffcmd: The command list to spawn
103
 
    :param capture_errors: Capture stderr as well as setting LANG=C
104
 
        and LC_ALL=C. This lets us read and understand the output of diff,
105
 
        and respond to any errors.
106
 
    :return: A Popen object.
107
 
    """
108
 
    if capture_errors:
109
 
        # construct minimal environment
110
 
        env = {}
111
 
        path = os.environ.get('PATH')
112
 
        if path is not None:
113
 
            env['PATH'] = path
114
 
        env['LANGUAGE'] = 'C'   # on win32 only LANGUAGE has effect
115
 
        env['LANG'] = 'C'
116
 
        env['LC_ALL'] = 'C'
117
 
        stderr = subprocess.PIPE
118
 
    else:
119
 
        env = None
120
 
        stderr = None
121
 
 
122
 
    try:
123
 
        pipe = subprocess.Popen(diffcmd,
124
 
                                stdin=subprocess.PIPE,
125
 
                                stdout=subprocess.PIPE,
126
 
                                stderr=stderr,
127
 
                                env=env)
128
 
    except OSError, e:
129
 
        if e.errno == errno.ENOENT:
130
 
            raise errors.NoDiff(str(e))
131
 
        raise
132
 
 
133
 
    return pipe
134
 
 
135
 
 
136
90
def external_diff(old_filename, oldlines, new_filename, newlines, to_file,
137
91
                  diff_opts):
138
92
    """Display a diff by calling out to the external diff program."""
 
93
    if hasattr(to_file, 'fileno'):
 
94
        out_file = to_file
 
95
        have_fileno = True
 
96
    else:
 
97
        out_file = subprocess.PIPE
 
98
        have_fileno = False
 
99
    
139
100
    # make sure our own output is properly ordered before the diff
140
101
    to_file.flush()
141
102
 
192
153
        if diff_opts:
193
154
            diffcmd.extend(diff_opts)
194
155
 
195
 
        pipe = _spawn_external_diff(diffcmd, capture_errors=True)
196
 
        out,err = pipe.communicate()
197
 
        rc = pipe.returncode
 
156
        try:
 
157
            pipe = subprocess.Popen(diffcmd,
 
158
                                    stdin=subprocess.PIPE,
 
159
                                    stdout=out_file)
 
160
        except OSError, e:
 
161
            if e.errno == errno.ENOENT:
 
162
                raise errors.NoDiff(str(e))
 
163
            raise
 
164
        pipe.stdin.close()
 
165
 
 
166
        if not have_fileno:
 
167
            bzrlib.osutils.pumpfile(pipe.stdout, to_file)
 
168
        rc = pipe.wait()
198
169
        
199
 
        # internal_diff() adds a trailing newline, add one here for consistency
200
 
        out += '\n'
201
 
        if rc == 2:
202
 
            # 'diff' gives retcode == 2 for all sorts of errors
203
 
            # one of those is 'Binary files differ'.
204
 
            # Bad options could also be the problem.
205
 
            # 'Binary files' is not a real error, so we suppress that error.
206
 
            lang_c_out = out
207
 
 
208
 
            # Since we got here, we want to make sure to give an i18n error
209
 
            pipe = _spawn_external_diff(diffcmd, capture_errors=False)
210
 
            out, err = pipe.communicate()
211
 
 
212
 
            # Write out the new i18n diff response
213
 
            to_file.write(out+'\n')
214
 
            if pipe.returncode != 2:
215
 
                raise errors.BzrError(
216
 
                               'external diff failed with exit code 2'
217
 
                               ' when run with LANG=C and LC_ALL=C,'
218
 
                               ' but not when run natively: %r' % (diffcmd,))
219
 
 
220
 
            first_line = lang_c_out.split('\n', 1)[0]
221
 
            # Starting with diffutils 2.8.4 the word "binary" was dropped.
222
 
            m = re.match('^(binary )?files.*differ$', first_line, re.I)
223
 
            if m is None:
224
 
                raise errors.BzrError('external diff failed with exit code 2;'
225
 
                                      ' command: %r' % (diffcmd,))
226
 
            else:
227
 
                # Binary files differ, just return
228
 
                return
229
 
 
230
 
        # If we got to here, we haven't written out the output of diff
231
 
        # do so now
232
 
        to_file.write(out)
233
 
        if rc not in (0, 1):
 
170
        if rc != 0 and rc != 1:
234
171
            # returns 1 if files differ; that's OK
235
172
            if rc < 0:
236
173
                msg = 'signal %d' % (-rc)
237
174
            else:
238
175
                msg = 'exit code %d' % rc
239
176
                
240
 
            raise errors.BzrError('external diff failed with %s; command: %r' 
241
 
                                  % (rc, diffcmd))
242
 
 
243
 
 
 
177
            raise BzrError('external diff failed with %s; command: %r' % (rc, diffcmd))
244
178
    finally:
245
179
        oldtmpf.close()                 # and delete
246
180
        newtmpf.close()
301
235
 
302
236
def diff_cmd_helper(tree, specific_files, external_diff_options, 
303
237
                    old_revision_spec=None, new_revision_spec=None,
304
 
                    revision_specs=None,
305
238
                    old_label='a/', new_label='b/'):
306
239
    """Helper for cmd_diff.
307
240
 
308
 
    :param tree:
 
241
   tree 
309
242
        A WorkingTree
310
243
 
311
 
    :param specific_files:
 
244
    specific_files
312
245
        The specific files to compare, or None
313
246
 
314
 
    :param external_diff_options:
 
247
    external_diff_options
315
248
        If non-None, run an external diff, and pass it these options
316
249
 
317
 
    :param old_revision_spec:
 
250
    old_revision_spec
318
251
        If None, use basis tree as old revision, otherwise use the tree for
319
252
        the specified revision. 
320
253
 
321
 
    :param new_revision_spec:
 
254
    new_revision_spec
322
255
        If None, use working tree as new revision, otherwise use the tree for
323
256
        the specified revision.
324
257
    
325
 
    :param revision_specs: 
326
 
        Zero, one or two RevisionSpecs from the command line, saying what revisions 
327
 
        to compare.  This can be passed as an alternative to the old_revision_spec 
328
 
        and new_revision_spec parameters.
329
 
 
330
258
    The more general form is show_diff_trees(), where the caller
331
259
    supplies any two trees.
332
260
    """
333
 
 
334
 
    # TODO: perhaps remove the old parameters old_revision_spec and
335
 
    # new_revision_spec, since this is only really for use from cmd_diff and
336
 
    # it now always passes through a sequence of revision_specs -- mbp
337
 
    # 20061221
338
 
 
339
261
    def spec_tree(spec):
340
262
        if tree:
341
263
            revision = spec.in_store(tree.branch)
344
266
        revision_id = revision.rev_id
345
267
        branch = revision.branch
346
268
        return branch.repository.revision_tree(revision_id)
347
 
 
348
 
    if revision_specs is not None:
349
 
        assert (old_revision_spec is None
350
 
                and new_revision_spec is None)
351
 
        if len(revision_specs) > 0:
352
 
            old_revision_spec = revision_specs[0]
353
 
        if len(revision_specs) > 1:
354
 
            new_revision_spec = revision_specs[1]
355
 
 
356
269
    if old_revision_spec is None:
357
270
        old_tree = tree.basis_tree()
358
271
    else:
359
272
        old_tree = spec_tree(old_revision_spec)
360
273
 
361
 
    if (new_revision_spec is None
362
 
        or new_revision_spec.spec is None):
 
274
    if new_revision_spec is None:
363
275
        new_tree = tree
364
276
    else:
365
277
        new_tree = spec_tree(new_revision_spec)
366
 
 
367
278
    if new_tree is not tree:
368
279
        extra_trees = (tree,)
369
280
    else:
392
303
    """
393
304
    old_tree.lock_read()
394
305
    try:
395
 
        if extra_trees is not None:
396
 
            for tree in extra_trees:
397
 
                tree.lock_read()
398
306
        new_tree.lock_read()
399
307
        try:
400
308
            return _show_diff_trees(old_tree, new_tree, to_file,
403
311
                                    extra_trees=extra_trees)
404
312
        finally:
405
313
            new_tree.unlock()
406
 
            if extra_trees is not None:
407
 
                for tree in extra_trees:
408
 
                    tree.unlock()
409
314
    finally:
410
315
        old_tree.unlock()
411
316
 
471
376
        has_changes = 1
472
377
        prop_str = get_prop_change(meta_modified)
473
378
        print >>to_file, '=== modified %s %r%s' % (kind, path.encode('utf8'), prop_str)
474
 
        # The file may be in a different location in the old tree (because
475
 
        # the containing dir was renamed, but the file itself was not)
476
 
        old_path = old_tree.id2path(file_id)
477
 
        old_name = '%s%s\t%s' % (old_label, old_path,
478
 
                                 _patch_header_date(old_tree, file_id, old_path))
 
379
        old_name = '%s%s\t%s' % (old_label, path,
 
380
                                 _patch_header_date(old_tree, file_id, path))
479
381
        new_name = '%s%s\t%s' % (new_label, path,
480
382
                                 _patch_header_date(new_tree, file_id, path))
481
383
        if text_modified:
488
390
 
489
391
def _patch_header_date(tree, file_id, path):
490
392
    """Returns a timestamp suitable for use in a patch header."""
491
 
    mtime = tree.get_file_mtime(file_id, path)
492
 
    assert mtime is not None, \
493
 
        "got an mtime of None for file-id %s, path %s in tree %s" % (
494
 
                file_id, path, tree)
495
 
    return timestamp.format_patch_date(mtime)
 
393
    tm = time.gmtime(tree.get_file_mtime(file_id, path))
 
394
    return time.strftime('%Y-%m-%d %H:%M:%S +0000', tm)
496
395
 
497
396
 
498
397
def _raise_if_nonexistent(paths, old_tree, new_tree):