~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/diff.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-08-17 07:52:09 UTC
  • mfrom: (1910.3.4 trivial)
  • Revision ID: pqm@pqm.ubuntu.com-20060817075209-e85a1f9e05ff8b87
(andrew) Trivial fixes to NotImplemented errors.

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 difflib
 
17
import errno
18
18
import os
19
19
import re
 
20
import subprocess
20
21
import sys
21
 
 
22
 
from bzrlib.lazy_import import lazy_import
23
 
lazy_import(globals(), """
24
 
import errno
25
 
import subprocess
26
22
import tempfile
27
23
import time
28
24
 
29
 
from bzrlib import (
30
 
    errors,
31
 
    osutils,
32
 
    patiencediff,
33
 
    textfile,
34
 
    timestamp,
35
 
    )
36
 
""")
37
 
 
38
 
from bzrlib.symbol_versioning import (
39
 
        deprecated_function,
40
 
        )
 
25
# compatability - plugins import compare_trees from diff!!!
 
26
# deprecated as of 0.10
 
27
from bzrlib.delta import compare_trees
 
28
from bzrlib.errors import BzrError
 
29
import bzrlib.errors as errors
 
30
import bzrlib.osutils
 
31
from bzrlib.patiencediff import unified_diff
 
32
import bzrlib.patiencediff
 
33
from bzrlib.symbol_versioning import (deprecated_function,
 
34
        zero_eight)
 
35
from bzrlib.textfile import check_text_lines
41
36
from bzrlib.trace import mutter, warning
42
37
 
43
38
 
45
40
# invoke callbacks on an object.  That object can either accumulate a
46
41
# list, write them out directly, etc etc.
47
42
 
48
 
 
49
 
class _PrematchedMatcher(difflib.SequenceMatcher):
50
 
    """Allow SequenceMatcher operations to use predetermined blocks"""
51
 
 
52
 
    def __init__(self, matching_blocks):
53
 
        difflib.SequenceMatcher(self, None, None)
54
 
        self.matching_blocks = matching_blocks
55
 
        self.opcodes = None
56
 
 
57
 
 
58
43
def internal_diff(old_filename, oldlines, new_filename, newlines, to_file,
59
44
                  allow_binary=False, sequence_matcher=None,
60
45
                  path_encoding='utf8'):
75
60
        return
76
61
    
77
62
    if allow_binary is False:
78
 
        textfile.check_text_lines(oldlines)
79
 
        textfile.check_text_lines(newlines)
 
63
        check_text_lines(oldlines)
 
64
        check_text_lines(newlines)
80
65
 
81
66
    if sequence_matcher is None:
82
 
        sequence_matcher = patiencediff.PatienceSequenceMatcher
83
 
    ud = patiencediff.unified_diff(oldlines, newlines,
 
67
        sequence_matcher = bzrlib.patiencediff.PatienceSequenceMatcher
 
68
    ud = unified_diff(oldlines, newlines,
84
69
                      fromfile=old_filename.encode(path_encoding),
85
70
                      tofile=new_filename.encode(path_encoding),
86
71
                      sequencematcher=sequence_matcher)
100
85
        to_file.write(line)
101
86
        if not line.endswith('\n'):
102
87
            to_file.write("\n\\ No newline at end of file\n")
103
 
    to_file.write('\n')
 
88
    print >>to_file
 
89
 
 
90
 
 
91
def _set_lang_C():
 
92
    """Set the env var LANG=C"""
 
93
    os.environ['LANG'] = 'C'
104
94
 
105
95
 
106
96
def _spawn_external_diff(diffcmd, capture_errors=True):
107
97
    """Spawn the externall diff process, and return the child handle.
108
98
 
109
99
    :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.
 
100
    :param capture_errors: Capture stderr as well as setting LANG=C.
 
101
        This lets us read and understand the output of diff, and respond 
 
102
        to any errors.
113
103
    :return: A Popen object.
114
104
    """
115
105
    if capture_errors:
116
 
        # construct minimal environment
117
 
        env = {}
118
 
        path = os.environ.get('PATH')
119
 
        if path is not None:
120
 
            env['PATH'] = path
121
 
        env['LANGUAGE'] = 'C'   # on win32 only LANGUAGE has effect
122
 
        env['LANG'] = 'C'
123
 
        env['LC_ALL'] = 'C'
 
106
        preexec_fn = _set_lang_C
124
107
        stderr = subprocess.PIPE
125
108
    else:
126
 
        env = None
 
109
        preexec_fn = None
127
110
        stderr = None
128
111
 
129
112
    try:
131
114
                                stdin=subprocess.PIPE,
132
115
                                stdout=subprocess.PIPE,
133
116
                                stderr=stderr,
134
 
                                env=env)
 
117
                                preexec_fn=preexec_fn)
135
118
    except OSError, e:
136
119
        if e.errno == errno.ENOENT:
137
120
            raise errors.NoDiff(str(e))
209
192
            # 'diff' gives retcode == 2 for all sorts of errors
210
193
            # one of those is 'Binary files differ'.
211
194
            # Bad options could also be the problem.
212
 
            # 'Binary files' is not a real error, so we suppress that error.
 
195
            # 'Binary files' is not a real error, so we suppress that error
213
196
            lang_c_out = out
214
197
 
215
198
            # Since we got here, we want to make sure to give an i18n error
219
202
            # Write out the new i18n diff response
220
203
            to_file.write(out+'\n')
221
204
            if pipe.returncode != 2:
222
 
                raise errors.BzrError(
223
 
                               'external diff failed with exit code 2'
224
 
                               ' when run with LANG=C and LC_ALL=C,'
225
 
                               ' but not when run natively: %r' % (diffcmd,))
 
205
                raise BzrError('external diff failed with exit code 2'
 
206
                               ' when run with LANG=C, but not when run'
 
207
                               ' natively: %r' % (diffcmd,))
226
208
 
227
209
            first_line = lang_c_out.split('\n', 1)[0]
228
 
            # Starting with diffutils 2.8.4 the word "binary" was dropped.
229
 
            m = re.match('^(binary )?files.*differ$', first_line, re.I)
 
210
            m = re.match('^binary files.*differ$', first_line, re.I)
230
211
            if m is None:
231
 
                raise errors.BzrError('external diff failed with exit code 2;'
232
 
                                      ' command: %r' % (diffcmd,))
 
212
                raise BzrError('external diff failed with exit code 2;'
 
213
                               ' command: %r' % (diffcmd,))
233
214
            else:
234
215
                # Binary files differ, just return
235
216
                return
244
225
            else:
245
226
                msg = 'exit code %d' % rc
246
227
                
247
 
            raise errors.BzrError('external diff failed with %s; command: %r' 
248
 
                                  % (rc, diffcmd))
 
228
            raise BzrError('external diff failed with %s; command: %r' 
 
229
                           % (rc, diffcmd))
249
230
 
250
231
 
251
232
    finally:
268
249
                        new_abspath, e)
269
250
 
270
251
 
 
252
@deprecated_function(zero_eight)
 
253
def show_diff(b, from_spec, specific_files, external_diff_options=None,
 
254
              revision2=None, output=None, b2=None):
 
255
    """Shortcut for showing the diff to the working tree.
 
256
 
 
257
    Please use show_diff_trees instead.
 
258
 
 
259
    b
 
260
        Branch.
 
261
 
 
262
    revision
 
263
        None for 'basis tree', or otherwise the old revision to compare against.
 
264
    
 
265
    The more general form is show_diff_trees(), where the caller
 
266
    supplies any two trees.
 
267
    """
 
268
    if output is None:
 
269
        output = sys.stdout
 
270
 
 
271
    if from_spec is None:
 
272
        old_tree = b.bzrdir.open_workingtree()
 
273
        if b2 is None:
 
274
            old_tree = old_tree = old_tree.basis_tree()
 
275
    else:
 
276
        old_tree = b.repository.revision_tree(from_spec.in_history(b).rev_id)
 
277
 
 
278
    if revision2 is None:
 
279
        if b2 is None:
 
280
            new_tree = b.bzrdir.open_workingtree()
 
281
        else:
 
282
            new_tree = b2.bzrdir.open_workingtree()
 
283
    else:
 
284
        new_tree = b.repository.revision_tree(revision2.in_history(b).rev_id)
 
285
 
 
286
    return show_diff_trees(old_tree, new_tree, output, specific_files,
 
287
                           external_diff_options)
 
288
 
 
289
 
271
290
def diff_cmd_helper(tree, specific_files, external_diff_options, 
272
291
                    old_revision_spec=None, new_revision_spec=None,
273
 
                    revision_specs=None,
274
292
                    old_label='a/', new_label='b/'):
275
293
    """Helper for cmd_diff.
276
294
 
277
 
    :param tree:
 
295
   tree 
278
296
        A WorkingTree
279
297
 
280
 
    :param specific_files:
 
298
    specific_files
281
299
        The specific files to compare, or None
282
300
 
283
 
    :param external_diff_options:
 
301
    external_diff_options
284
302
        If non-None, run an external diff, and pass it these options
285
303
 
286
 
    :param old_revision_spec:
 
304
    old_revision_spec
287
305
        If None, use basis tree as old revision, otherwise use the tree for
288
306
        the specified revision. 
289
307
 
290
 
    :param new_revision_spec:
 
308
    new_revision_spec
291
309
        If None, use working tree as new revision, otherwise use the tree for
292
310
        the specified revision.
293
311
    
294
 
    :param revision_specs: 
295
 
        Zero, one or two RevisionSpecs from the command line, saying what revisions 
296
 
        to compare.  This can be passed as an alternative to the old_revision_spec 
297
 
        and new_revision_spec parameters.
298
 
 
299
312
    The more general form is show_diff_trees(), where the caller
300
313
    supplies any two trees.
301
314
    """
302
 
 
303
 
    # TODO: perhaps remove the old parameters old_revision_spec and
304
 
    # new_revision_spec, since this is only really for use from cmd_diff and
305
 
    # it now always passes through a sequence of revision_specs -- mbp
306
 
    # 20061221
307
 
 
308
315
    def spec_tree(spec):
309
316
        if tree:
310
317
            revision = spec.in_store(tree.branch)
313
320
        revision_id = revision.rev_id
314
321
        branch = revision.branch
315
322
        return branch.repository.revision_tree(revision_id)
316
 
 
317
 
    if revision_specs is not None:
318
 
        assert (old_revision_spec is None
319
 
                and new_revision_spec is None)
320
 
        if len(revision_specs) > 0:
321
 
            old_revision_spec = revision_specs[0]
322
 
        if len(revision_specs) > 1:
323
 
            new_revision_spec = revision_specs[1]
324
 
 
325
323
    if old_revision_spec is None:
326
324
        old_tree = tree.basis_tree()
327
325
    else:
328
326
        old_tree = spec_tree(old_revision_spec)
329
327
 
330
 
    if (new_revision_spec is None
331
 
        or new_revision_spec.spec is None):
 
328
    if new_revision_spec is None:
332
329
        new_tree = tree
333
330
    else:
334
331
        new_tree = spec_tree(new_revision_spec)
335
 
 
336
332
    if new_tree is not tree:
337
333
        extra_trees = (tree,)
338
334
    else:
347
343
def show_diff_trees(old_tree, new_tree, to_file, specific_files=None,
348
344
                    external_diff_options=None,
349
345
                    old_label='a/', new_label='b/',
350
 
                    extra_trees=None,
351
 
                    path_encoding='utf8'):
 
346
                    extra_trees=None):
352
347
    """Show in text form the changes from one tree to another.
353
348
 
354
349
    to_files
359
354
 
360
355
    extra_trees
361
356
        If set, more Trees to use for looking up file ids
362
 
 
363
 
    path_encoding
364
 
        If set, the path will be encoded as specified, otherwise is supposed
365
 
        to be utf8
366
357
    """
367
358
    old_tree.lock_read()
368
359
    try:
369
 
        if extra_trees is not None:
370
 
            for tree in extra_trees:
371
 
                tree.lock_read()
372
360
        new_tree.lock_read()
373
361
        try:
374
362
            return _show_diff_trees(old_tree, new_tree, to_file,
375
363
                                    specific_files, external_diff_options,
376
364
                                    old_label=old_label, new_label=new_label,
377
 
                                    extra_trees=extra_trees,
378
 
                                    path_encoding=path_encoding)
 
365
                                    extra_trees=extra_trees)
379
366
        finally:
380
367
            new_tree.unlock()
381
 
            if extra_trees is not None:
382
 
                for tree in extra_trees:
383
 
                    tree.unlock()
384
368
    finally:
385
369
        old_tree.unlock()
386
370
 
387
371
 
388
372
def _show_diff_trees(old_tree, new_tree, to_file,
389
 
                     specific_files, external_diff_options, path_encoding,
 
373
                     specific_files, external_diff_options, 
390
374
                     old_label='a/', new_label='b/', extra_trees=None):
391
375
 
392
376
    # GNU Patch uses the epoch date to detect files that are being added
411
395
    has_changes = 0
412
396
    for path, file_id, kind in delta.removed:
413
397
        has_changes = 1
414
 
        path_encoded = path.encode(path_encoding, "replace")
415
 
        to_file.write("=== removed %s '%s'\n" % (kind, path_encoded))
 
398
        print >>to_file, '=== removed %s %r' % (kind, path.encode('utf8'))
416
399
        old_name = '%s%s\t%s' % (old_label, path,
417
400
                                 _patch_header_date(old_tree, file_id, path))
418
401
        new_name = '%s%s\t%s' % (new_label, path, EPOCH_DATE)
420
403
                                         new_name, None, None, to_file)
421
404
    for path, file_id, kind in delta.added:
422
405
        has_changes = 1
423
 
        path_encoded = path.encode(path_encoding, "replace")
424
 
        to_file.write("=== added %s '%s'\n" % (kind, path_encoded))
 
406
        print >>to_file, '=== added %s %r' % (kind, path.encode('utf8'))
425
407
        old_name = '%s%s\t%s' % (old_label, path, EPOCH_DATE)
426
408
        new_name = '%s%s\t%s' % (new_label, path,
427
409
                                 _patch_header_date(new_tree, file_id, path))
432
414
         text_modified, meta_modified) in delta.renamed:
433
415
        has_changes = 1
434
416
        prop_str = get_prop_change(meta_modified)
435
 
        oldpath_encoded = old_path.encode(path_encoding, "replace")
436
 
        newpath_encoded = new_path.encode(path_encoding, "replace")
437
 
        to_file.write("=== renamed %s '%s' => '%s'%s\n" % (kind,
438
 
                            oldpath_encoded, newpath_encoded, prop_str))
 
417
        print >>to_file, '=== renamed %s %r => %r%s' % (
 
418
                    kind, old_path.encode('utf8'),
 
419
                    new_path.encode('utf8'), prop_str)
439
420
        old_name = '%s%s\t%s' % (old_label, old_path,
440
421
                                 _patch_header_date(old_tree, file_id,
441
422
                                                    old_path))
448
429
    for path, file_id, kind, text_modified, meta_modified in delta.modified:
449
430
        has_changes = 1
450
431
        prop_str = get_prop_change(meta_modified)
451
 
        path_encoded = path.encode(path_encoding, "replace")
452
 
        to_file.write("=== modified %s '%s'%s\n" % (kind,
453
 
                            path_encoded, prop_str))
454
 
        # The file may be in a different location in the old tree (because
455
 
        # the containing dir was renamed, but the file itself was not)
456
 
        old_path = old_tree.id2path(file_id)
457
 
        old_name = '%s%s\t%s' % (old_label, old_path,
458
 
                                 _patch_header_date(old_tree, file_id, old_path))
 
432
        print >>to_file, '=== modified %s %r%s' % (kind, path.encode('utf8'), prop_str)
 
433
        old_name = '%s%s\t%s' % (old_label, path,
 
434
                                 _patch_header_date(old_tree, file_id, path))
459
435
        new_name = '%s%s\t%s' % (new_label, path,
460
436
                                 _patch_header_date(new_tree, file_id, path))
461
437
        if text_modified:
468
444
 
469
445
def _patch_header_date(tree, file_id, path):
470
446
    """Returns a timestamp suitable for use in a patch header."""
471
 
    mtime = tree.get_file_mtime(file_id, path)
472
 
    assert mtime is not None, \
473
 
        "got an mtime of None for file-id %s, path %s in tree %s" % (
474
 
                file_id, path, tree)
475
 
    return timestamp.format_patch_date(mtime)
 
447
    tm = time.gmtime(tree.get_file_mtime(file_id, path))
 
448
    return time.strftime('%Y-%m-%d %H:%M:%S +0000', tm)
476
449
 
477
450
 
478
451
def _raise_if_nonexistent(paths, old_tree, new_tree):