/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/diff.py

  • Committer: Alexander Belchenko
  • Date: 2007-10-26 21:49:15 UTC
  • mto: (2947.4.2 0.92)
  • mto: This revision was merged to the branch mainline in revision 2971.
  • Revision ID: bialix@ukr.net-20071026214915-5eacqh9k2ps6jagj
windows python-based installer: shortcut for uninstall action

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
import difflib
18
18
import os
19
19
import re
 
20
import sys
 
21
 
 
22
from bzrlib.lazy_import import lazy_import
 
23
lazy_import(globals(), """
 
24
import errno
20
25
import subprocess
21
 
import sys
22
26
import tempfile
23
27
import time
24
28
 
25
29
from bzrlib import (
26
30
    errors,
27
31
    osutils,
 
32
    patiencediff,
 
33
    textfile,
 
34
    timestamp,
28
35
    )
29
 
# compatability - plugins import compare_trees from diff!!!
30
 
# deprecated as of 0.10
31
 
from bzrlib.delta import compare_trees
32
 
from bzrlib.errors import BzrError
33
 
from bzrlib.patiencediff import unified_diff
34
 
import bzrlib.patiencediff
35
 
from bzrlib.symbol_versioning import (deprecated_function,
36
 
        zero_eight)
37
 
from bzrlib.textfile import check_text_lines
 
36
""")
 
37
 
 
38
from bzrlib.symbol_versioning import (
 
39
        deprecated_function,
 
40
        )
38
41
from bzrlib.trace import mutter, warning
39
42
 
40
43
 
42
45
# invoke callbacks on an object.  That object can either accumulate a
43
46
# list, write them out directly, etc etc.
44
47
 
 
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
 
45
58
def internal_diff(old_filename, oldlines, new_filename, newlines, to_file,
46
59
                  allow_binary=False, sequence_matcher=None,
47
60
                  path_encoding='utf8'):
62
75
        return
63
76
    
64
77
    if allow_binary is False:
65
 
        check_text_lines(oldlines)
66
 
        check_text_lines(newlines)
 
78
        textfile.check_text_lines(oldlines)
 
79
        textfile.check_text_lines(newlines)
67
80
 
68
81
    if sequence_matcher is None:
69
 
        sequence_matcher = bzrlib.patiencediff.PatienceSequenceMatcher
70
 
    ud = unified_diff(oldlines, newlines,
 
82
        sequence_matcher = patiencediff.PatienceSequenceMatcher
 
83
    ud = patiencediff.unified_diff(oldlines, newlines,
71
84
                      fromfile=old_filename.encode(path_encoding),
72
85
                      tofile=new_filename.encode(path_encoding),
73
86
                      sequencematcher=sequence_matcher)
87
100
        to_file.write(line)
88
101
        if not line.endswith('\n'):
89
102
            to_file.write("\n\\ No newline at end of file\n")
90
 
    print >>to_file
91
 
 
92
 
 
93
 
def _set_lang_C():
94
 
    """Set the env var LANG=C"""
95
 
    osutils.set_or_unset_env('LANG', 'C')
96
 
    osutils.set_or_unset_env('LC_ALL', None)
97
 
    osutils.set_or_unset_env('LC_CTYPE', None)
98
 
    osutils.set_or_unset_env('LANGUAGE', None)
 
103
    to_file.write('\n')
99
104
 
100
105
 
101
106
def _spawn_external_diff(diffcmd, capture_errors=True):
102
107
    """Spawn the externall diff process, and return the child handle.
103
108
 
104
109
    :param diffcmd: The command list to spawn
105
 
    :param capture_errors: Capture stderr as well as setting LANG=C.
106
 
        This lets us read and understand the output of diff, and respond 
107
 
        to any errors.
 
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.
108
113
    :return: A Popen object.
109
114
    """
110
115
    if capture_errors:
111
 
        if sys.platform == 'win32':
112
 
            # Win32 doesn't support preexec_fn, but that is
113
 
            # okay, because it doesn't support LANG either.
114
 
            preexec_fn = None
115
 
        else:
116
 
            preexec_fn = _set_lang_C
 
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'
117
124
        stderr = subprocess.PIPE
118
125
    else:
119
 
        preexec_fn = None
 
126
        env = None
120
127
        stderr = None
121
128
 
122
129
    try:
124
131
                                stdin=subprocess.PIPE,
125
132
                                stdout=subprocess.PIPE,
126
133
                                stderr=stderr,
127
 
                                preexec_fn=preexec_fn)
 
134
                                env=env)
128
135
    except OSError, e:
129
136
        if e.errno == errno.ENOENT:
130
137
            raise errors.NoDiff(str(e))
212
219
            # Write out the new i18n diff response
213
220
            to_file.write(out+'\n')
214
221
            if pipe.returncode != 2:
215
 
                raise BzrError('external diff failed with exit code 2'
216
 
                               ' when run with LANG=C, but not when run'
217
 
                               ' natively: %r' % (diffcmd,))
 
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,))
218
226
 
219
227
            first_line = lang_c_out.split('\n', 1)[0]
220
228
            # Starting with diffutils 2.8.4 the word "binary" was dropped.
221
229
            m = re.match('^(binary )?files.*differ$', first_line, re.I)
222
230
            if m is None:
223
 
                raise BzrError('external diff failed with exit code 2;'
224
 
                               ' command: %r' % (diffcmd,))
 
231
                raise errors.BzrError('external diff failed with exit code 2;'
 
232
                                      ' command: %r' % (diffcmd,))
225
233
            else:
226
234
                # Binary files differ, just return
227
235
                return
236
244
            else:
237
245
                msg = 'exit code %d' % rc
238
246
                
239
 
            raise BzrError('external diff failed with %s; command: %r' 
240
 
                           % (rc, diffcmd))
 
247
            raise errors.BzrError('external diff failed with %s; command: %r' 
 
248
                                  % (rc, diffcmd))
241
249
 
242
250
 
243
251
    finally:
260
268
                        new_abspath, e)
261
269
 
262
270
 
263
 
@deprecated_function(zero_eight)
264
 
def show_diff(b, from_spec, specific_files, external_diff_options=None,
265
 
              revision2=None, output=None, b2=None):
266
 
    """Shortcut for showing the diff to the working tree.
267
 
 
268
 
    Please use show_diff_trees instead.
269
 
 
270
 
    b
271
 
        Branch.
272
 
 
273
 
    revision
274
 
        None for 'basis tree', or otherwise the old revision to compare against.
275
 
    
276
 
    The more general form is show_diff_trees(), where the caller
277
 
    supplies any two trees.
278
 
    """
279
 
    if output is None:
280
 
        output = sys.stdout
281
 
 
282
 
    if from_spec is None:
283
 
        old_tree = b.bzrdir.open_workingtree()
284
 
        if b2 is None:
285
 
            old_tree = old_tree = old_tree.basis_tree()
286
 
    else:
287
 
        old_tree = b.repository.revision_tree(from_spec.in_history(b).rev_id)
288
 
 
289
 
    if revision2 is None:
290
 
        if b2 is None:
291
 
            new_tree = b.bzrdir.open_workingtree()
292
 
        else:
293
 
            new_tree = b2.bzrdir.open_workingtree()
294
 
    else:
295
 
        new_tree = b.repository.revision_tree(revision2.in_history(b).rev_id)
296
 
 
297
 
    return show_diff_trees(old_tree, new_tree, output, specific_files,
298
 
                           external_diff_options)
299
 
 
300
 
 
301
271
def diff_cmd_helper(tree, specific_files, external_diff_options, 
302
272
                    old_revision_spec=None, new_revision_spec=None,
 
273
                    revision_specs=None,
303
274
                    old_label='a/', new_label='b/'):
304
275
    """Helper for cmd_diff.
305
276
 
306
 
   tree 
 
277
    :param tree:
307
278
        A WorkingTree
308
279
 
309
 
    specific_files
 
280
    :param specific_files:
310
281
        The specific files to compare, or None
311
282
 
312
 
    external_diff_options
 
283
    :param external_diff_options:
313
284
        If non-None, run an external diff, and pass it these options
314
285
 
315
 
    old_revision_spec
 
286
    :param old_revision_spec:
316
287
        If None, use basis tree as old revision, otherwise use the tree for
317
288
        the specified revision. 
318
289
 
319
 
    new_revision_spec
 
290
    :param new_revision_spec:
320
291
        If None, use working tree as new revision, otherwise use the tree for
321
292
        the specified revision.
322
293
    
 
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
 
323
299
    The more general form is show_diff_trees(), where the caller
324
300
    supplies any two trees.
325
301
    """
 
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
 
326
308
    def spec_tree(spec):
327
309
        if tree:
328
310
            revision = spec.in_store(tree.branch)
331
313
        revision_id = revision.rev_id
332
314
        branch = revision.branch
333
315
        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
 
334
325
    if old_revision_spec is None:
335
326
        old_tree = tree.basis_tree()
336
327
    else:
337
328
        old_tree = spec_tree(old_revision_spec)
338
329
 
339
 
    if new_revision_spec is None:
 
330
    if (new_revision_spec is None
 
331
        or new_revision_spec.spec is None):
340
332
        new_tree = tree
341
333
    else:
342
334
        new_tree = spec_tree(new_revision_spec)
 
335
 
343
336
    if new_tree is not tree:
344
337
        extra_trees = (tree,)
345
338
    else:
354
347
def show_diff_trees(old_tree, new_tree, to_file, specific_files=None,
355
348
                    external_diff_options=None,
356
349
                    old_label='a/', new_label='b/',
357
 
                    extra_trees=None):
 
350
                    extra_trees=None,
 
351
                    path_encoding='utf8'):
358
352
    """Show in text form the changes from one tree to another.
359
353
 
360
354
    to_files
365
359
 
366
360
    extra_trees
367
361
        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
368
366
    """
369
367
    old_tree.lock_read()
370
368
    try:
 
369
        if extra_trees is not None:
 
370
            for tree in extra_trees:
 
371
                tree.lock_read()
371
372
        new_tree.lock_read()
372
373
        try:
373
374
            return _show_diff_trees(old_tree, new_tree, to_file,
374
375
                                    specific_files, external_diff_options,
375
376
                                    old_label=old_label, new_label=new_label,
376
 
                                    extra_trees=extra_trees)
 
377
                                    extra_trees=extra_trees,
 
378
                                    path_encoding=path_encoding)
377
379
        finally:
378
380
            new_tree.unlock()
 
381
            if extra_trees is not None:
 
382
                for tree in extra_trees:
 
383
                    tree.unlock()
379
384
    finally:
380
385
        old_tree.unlock()
381
386
 
382
387
 
383
388
def _show_diff_trees(old_tree, new_tree, to_file,
384
 
                     specific_files, external_diff_options, 
 
389
                     specific_files, external_diff_options, path_encoding,
385
390
                     old_label='a/', new_label='b/', extra_trees=None):
386
391
 
387
392
    # GNU Patch uses the epoch date to detect files that are being added
406
411
    has_changes = 0
407
412
    for path, file_id, kind in delta.removed:
408
413
        has_changes = 1
409
 
        print >>to_file, '=== removed %s %r' % (kind, path.encode('utf8'))
 
414
        path_encoded = path.encode(path_encoding, "replace")
 
415
        to_file.write("=== removed %s '%s'\n" % (kind, path_encoded))
410
416
        old_name = '%s%s\t%s' % (old_label, path,
411
417
                                 _patch_header_date(old_tree, file_id, path))
412
418
        new_name = '%s%s\t%s' % (new_label, path, EPOCH_DATE)
414
420
                                         new_name, None, None, to_file)
415
421
    for path, file_id, kind in delta.added:
416
422
        has_changes = 1
417
 
        print >>to_file, '=== added %s %r' % (kind, path.encode('utf8'))
 
423
        path_encoded = path.encode(path_encoding, "replace")
 
424
        to_file.write("=== added %s '%s'\n" % (kind, path_encoded))
418
425
        old_name = '%s%s\t%s' % (old_label, path, EPOCH_DATE)
419
426
        new_name = '%s%s\t%s' % (new_label, path,
420
427
                                 _patch_header_date(new_tree, file_id, path))
425
432
         text_modified, meta_modified) in delta.renamed:
426
433
        has_changes = 1
427
434
        prop_str = get_prop_change(meta_modified)
428
 
        print >>to_file, '=== renamed %s %r => %r%s' % (
429
 
                    kind, old_path.encode('utf8'),
430
 
                    new_path.encode('utf8'), prop_str)
 
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))
431
439
        old_name = '%s%s\t%s' % (old_label, old_path,
432
440
                                 _patch_header_date(old_tree, file_id,
433
441
                                                    old_path))
440
448
    for path, file_id, kind, text_modified, meta_modified in delta.modified:
441
449
        has_changes = 1
442
450
        prop_str = get_prop_change(meta_modified)
443
 
        print >>to_file, '=== modified %s %r%s' % (kind, path.encode('utf8'), prop_str)
444
 
        old_name = '%s%s\t%s' % (old_label, path,
445
 
                                 _patch_header_date(old_tree, file_id, path))
 
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))
446
459
        new_name = '%s%s\t%s' % (new_label, path,
447
460
                                 _patch_header_date(new_tree, file_id, path))
448
461
        if text_modified:
455
468
 
456
469
def _patch_header_date(tree, file_id, path):
457
470
    """Returns a timestamp suitable for use in a patch header."""
458
 
    tm = time.gmtime(tree.get_file_mtime(file_id, path))
459
 
    return time.strftime('%Y-%m-%d %H:%M:%S +0000', tm)
 
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)
460
476
 
461
477
 
462
478
def _raise_if_nonexistent(paths, old_tree, new_tree):