/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: Richard Wilbur
  • Date: 2016-02-04 19:07:28 UTC
  • mto: This revision was merged to the branch mainline in revision 6618.
  • Revision ID: richard.wilbur@gmail.com-20160204190728-p0zvfii6zase0fw7
Update COPYING.txt from the original http://www.gnu.org/licenses/gpl-2.0.txt  (Only differences were in whitespace.)  Thanks to Petr Stodulka for pointing out the discrepancy.

Show diffs side-by-side

added added

removed removed

Lines of Context:
22
22
import string
23
23
import sys
24
24
 
25
 
from .lazy_import import lazy_import
 
25
from bzrlib.lazy_import import lazy_import
26
26
lazy_import(globals(), """
27
27
import errno
28
 
import patiencediff
29
28
import subprocess
30
29
import tempfile
31
30
 
32
 
from breezy import (
 
31
from bzrlib import (
 
32
    cleanup,
33
33
    cmdline,
34
34
    controldir,
35
35
    errors,
36
36
    osutils,
 
37
    patiencediff,
37
38
    textfile,
38
39
    timestamp,
39
40
    views,
40
41
    )
41
42
 
42
 
from breezy.workingtree import WorkingTree
43
 
from breezy.i18n import gettext
 
43
from bzrlib.workingtree import WorkingTree
 
44
from bzrlib.i18n import gettext
44
45
""")
45
46
 
46
 
from .registry import (
 
47
from bzrlib.registry import (
47
48
    Registry,
48
49
    )
49
 
from .sixish import text_type
50
 
from .trace import mutter, note, warning
51
 
from .tree import FileTimestampUnavailable
52
 
 
 
50
from bzrlib.trace import mutter, note, warning
53
51
 
54
52
DEFAULT_CONTEXT_AMOUNT = 3
55
53
 
56
 
 
57
54
class AtTemplate(string.Template):
58
55
    """Templating class that uses @ instead of $."""
59
56
 
74
71
        self.opcodes = None
75
72
 
76
73
 
77
 
def internal_diff(old_label, oldlines, new_label, newlines, to_file,
 
74
def internal_diff(old_filename, oldlines, new_filename, newlines, to_file,
78
75
                  allow_binary=False, sequence_matcher=None,
79
76
                  path_encoding='utf8', context_lines=DEFAULT_CONTEXT_AMOUNT):
80
77
    # FIXME: difflib is wrong if there is no trailing newline.
87
84
    # In the meantime we at least make sure the patch isn't
88
85
    # mangled.
89
86
 
 
87
 
 
88
    # Special workaround for Python2.3, where difflib fails if
 
89
    # both sequences are empty.
 
90
    if not oldlines and not newlines:
 
91
        return
 
92
 
90
93
    if allow_binary is False:
91
94
        textfile.check_text_lines(oldlines)
92
95
        textfile.check_text_lines(newlines)
93
96
 
94
97
    if sequence_matcher is None:
95
98
        sequence_matcher = patiencediff.PatienceSequenceMatcher
96
 
    ud = unified_diff_bytes(
97
 
        oldlines, newlines,
98
 
        fromfile=old_label.encode(path_encoding, 'replace'),
99
 
        tofile=new_label.encode(path_encoding, 'replace'),
100
 
        n=context_lines, sequencematcher=sequence_matcher)
 
99
    ud = patiencediff.unified_diff(oldlines, newlines,
 
100
                      fromfile=old_filename.encode(path_encoding, 'replace'),
 
101
                      tofile=new_filename.encode(path_encoding, 'replace'),
 
102
                      n=context_lines, sequencematcher=sequence_matcher)
101
103
 
102
104
    ud = list(ud)
103
 
    if len(ud) == 0:  # Identical contents, nothing to do
 
105
    if len(ud) == 0: # Identical contents, nothing to do
104
106
        return
105
107
    # work-around for difflib being too smart for its own good
106
108
    # if /dev/null is "1,0", patch won't recognize it as /dev/null
107
109
    if not oldlines:
108
 
        ud[2] = ud[2].replace(b'-1,0', b'-0,0')
 
110
        ud[2] = ud[2].replace('-1,0', '-0,0')
109
111
    elif not newlines:
110
 
        ud[2] = ud[2].replace(b'+1,0', b'+0,0')
 
112
        ud[2] = ud[2].replace('+1,0', '+0,0')
111
113
 
112
114
    for line in ud:
113
115
        to_file.write(line)
114
 
        if not line.endswith(b'\n'):
115
 
            to_file.write(b"\n\\ No newline at end of file\n")
116
 
    to_file.write(b'\n')
117
 
 
118
 
 
119
 
def unified_diff_bytes(a, b, fromfile=b'', tofile=b'', fromfiledate=b'',
120
 
                       tofiledate=b'', n=3, lineterm=b'\n', sequencematcher=None):
121
 
    r"""
122
 
    Compare two sequences of lines; generate the delta as a unified diff.
123
 
 
124
 
    Unified diffs are a compact way of showing line changes and a few
125
 
    lines of context.  The number of context lines is set by 'n' which
126
 
    defaults to three.
127
 
 
128
 
    By default, the diff control lines (those with ---, +++, or @@) are
129
 
    created with a trailing newline.  This is helpful so that inputs
130
 
    created from file.readlines() result in diffs that are suitable for
131
 
    file.writelines() since both the inputs and outputs have trailing
132
 
    newlines.
133
 
 
134
 
    For inputs that do not have trailing newlines, set the lineterm
135
 
    argument to "" so that the output will be uniformly newline free.
136
 
 
137
 
    The unidiff format normally has a header for filenames and modification
138
 
    times.  Any or all of these may be specified using strings for
139
 
    'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'.  The modification
140
 
    times are normally expressed in the format returned by time.ctime().
141
 
 
142
 
    Example:
143
 
 
144
 
    >>> for line in bytes_unified_diff(b'one two three four'.split(),
145
 
    ...             b'zero one tree four'.split(), b'Original', b'Current',
146
 
    ...             b'Sat Jan 26 23:30:50 1991', b'Fri Jun 06 10:20:52 2003',
147
 
    ...             lineterm=b''):
148
 
    ...     print line
149
 
    --- Original Sat Jan 26 23:30:50 1991
150
 
    +++ Current Fri Jun 06 10:20:52 2003
151
 
    @@ -1,4 +1,4 @@
152
 
    +zero
153
 
     one
154
 
    -two
155
 
    -three
156
 
    +tree
157
 
     four
158
 
    """
159
 
    if sequencematcher is None:
160
 
        sequencematcher = difflib.SequenceMatcher
161
 
 
162
 
    if fromfiledate:
163
 
        fromfiledate = b'\t' + bytes(fromfiledate)
164
 
    if tofiledate:
165
 
        tofiledate = b'\t' + bytes(tofiledate)
166
 
 
167
 
    started = False
168
 
    for group in sequencematcher(None, a, b).get_grouped_opcodes(n):
169
 
        if not started:
170
 
            yield b'--- %s%s%s' % (fromfile, fromfiledate, lineterm)
171
 
            yield b'+++ %s%s%s' % (tofile, tofiledate, lineterm)
172
 
            started = True
173
 
        i1, i2, j1, j2 = group[0][1], group[-1][2], group[0][3], group[-1][4]
174
 
        yield b"@@ -%d,%d +%d,%d @@%s" % (i1 + 1, i2 - i1, j1 + 1, j2 - j1, lineterm)
175
 
        for tag, i1, i2, j1, j2 in group:
176
 
            if tag == 'equal':
177
 
                for line in a[i1:i2]:
178
 
                    yield b' ' + line
179
 
                continue
180
 
            if tag == 'replace' or tag == 'delete':
181
 
                for line in a[i1:i2]:
182
 
                    yield b'-' + line
183
 
            if tag == 'replace' or tag == 'insert':
184
 
                for line in b[j1:j2]:
185
 
                    yield b'+' + line
 
116
        if not line.endswith('\n'):
 
117
            to_file.write("\n\\ No newline at end of file\n")
 
118
    to_file.write('\n')
186
119
 
187
120
 
188
121
def _spawn_external_diff(diffcmd, capture_errors=True):
214
147
                                stdout=subprocess.PIPE,
215
148
                                stderr=stderr,
216
149
                                env=env)
217
 
    except OSError as e:
 
150
    except OSError, e:
218
151
        if e.errno == errno.ENOENT:
219
152
            raise errors.NoDiff(str(e))
220
153
        raise
221
154
 
222
155
    return pipe
223
156
 
224
 
 
225
157
# diff style options as of GNU diff v3.2
226
158
style_option_list = ['-c', '-C', '--context',
227
159
                     '-e', '--ed',
233
165
                     '-y', '--side-by-side',
234
166
                     '-D', '--ifdef']
235
167
 
236
 
 
237
168
def default_style_unified(diff_opts):
238
169
    """Default to unified diff style if alternative not specified in diff_opts.
239
170
 
258
189
    return diff_opts
259
190
 
260
191
 
261
 
def external_diff(old_label, oldlines, new_label, newlines, to_file,
 
192
def external_diff(old_filename, oldlines, new_filename, newlines, to_file,
262
193
                  diff_opts):
263
194
    """Display a diff by calling out to the external diff program."""
264
195
    # make sure our own output is properly ordered before the diff
265
196
    to_file.flush()
266
197
 
267
 
    oldtmp_fd, old_abspath = tempfile.mkstemp(prefix='brz-diff-old-')
268
 
    newtmp_fd, new_abspath = tempfile.mkstemp(prefix='brz-diff-new-')
 
198
    oldtmp_fd, old_abspath = tempfile.mkstemp(prefix='bzr-diff-old-')
 
199
    newtmp_fd, new_abspath = tempfile.mkstemp(prefix='bzr-diff-new-')
269
200
    oldtmpf = os.fdopen(oldtmp_fd, 'wb')
270
201
    newtmpf = os.fdopen(newtmp_fd, 'wb')
271
202
 
288
219
        if sys.platform == 'win32':
289
220
            # Popen doesn't do the proper encoding for external commands
290
221
            # Since we are dealing with an ANSI api, use mbcs encoding
291
 
            old_label = old_label.encode('mbcs')
292
 
            new_label = new_label.encode('mbcs')
 
222
            old_filename = old_filename.encode('mbcs')
 
223
            new_filename = new_filename.encode('mbcs')
293
224
        diffcmd = ['diff',
294
 
                   '--label', old_label,
 
225
                   '--label', old_filename,
295
226
                   old_abspath,
296
 
                   '--label', new_label,
 
227
                   '--label', new_filename,
297
228
                   new_abspath,
298
229
                   '--binary',
299
 
                   ]
 
230
                  ]
300
231
 
301
232
        diff_opts = default_style_unified(diff_opts)
302
233
 
304
235
            diffcmd.extend(diff_opts)
305
236
 
306
237
        pipe = _spawn_external_diff(diffcmd, capture_errors=True)
307
 
        out, err = pipe.communicate()
 
238
        out,err = pipe.communicate()
308
239
        rc = pipe.returncode
309
240
 
310
241
        # internal_diff() adds a trailing newline, add one here for consistency
311
 
        out += b'\n'
 
242
        out += '\n'
312
243
        if rc == 2:
313
244
            # 'diff' gives retcode == 2 for all sorts of errors
314
245
            # one of those is 'Binary files differ'.
321
252
            out, err = pipe.communicate()
322
253
 
323
254
            # Write out the new i18n diff response
324
 
            to_file.write(out + b'\n')
 
255
            to_file.write(out+'\n')
325
256
            if pipe.returncode != 2:
326
257
                raise errors.BzrError(
327
 
                    'external diff failed with exit code 2'
328
 
                    ' when run with LANG=C and LC_ALL=C,'
329
 
                    ' but not when run natively: %r' % (diffcmd,))
 
258
                               'external diff failed with exit code 2'
 
259
                               ' when run with LANG=C and LC_ALL=C,'
 
260
                               ' but not when run natively: %r' % (diffcmd,))
330
261
 
331
 
            first_line = lang_c_out.split(b'\n', 1)[0]
 
262
            first_line = lang_c_out.split('\n', 1)[0]
332
263
            # Starting with diffutils 2.8.4 the word "binary" was dropped.
333
 
            m = re.match(b'^(binary )?files.*differ$', first_line, re.I)
 
264
            m = re.match('^(binary )?files.*differ$', first_line, re.I)
334
265
            if m is None:
335
266
                raise errors.BzrError('external diff failed with exit code 2;'
336
267
                                      ' command: %r' % (diffcmd,))
351
282
            raise errors.BzrError('external diff failed with %s; command: %r'
352
283
                                  % (msg, diffcmd))
353
284
 
 
285
 
354
286
    finally:
355
287
        oldtmpf.close()                 # and delete
356
288
        newtmpf.close()
361
293
            # deleted)
362
294
            try:
363
295
                os.remove(path)
364
 
            except OSError as e:
 
296
            except OSError, e:
365
297
                if e.errno not in (errno.ENOENT,):
366
298
                    warning('Failed to delete temporary file: %s %s', path, e)
367
299
 
370
302
 
371
303
 
372
304
def get_trees_and_branches_to_diff_locked(
373
 
        path_list, revision_specs, old_url, new_url, add_cleanup, apply_view=True):
 
305
    path_list, revision_specs, old_url, new_url, add_cleanup, apply_view=True):
374
306
    """Get the trees and specific files to diff given a list of paths.
375
307
 
376
308
    This method works out the trees to be diff'ed and the files of
461
393
                views.check_path_in_view(working_tree, relpath)
462
394
            specific_files.append(relpath)
463
395
    new_tree = _get_tree_to_diff(new_revision_spec, working_tree, branch,
464
 
                                 basis_is_default=working_tree is None)
 
396
        basis_is_default=working_tree is None)
465
397
    new_branch = branch
466
398
 
467
399
    # Get the specific files (all files is None, no files is [])
472
404
    specific_files.extend(other_paths)
473
405
    if len(specific_files) == 0:
474
406
        specific_files = None
475
 
        if (working_tree is not None and working_tree.supports_views() and
476
 
                apply_view):
 
407
        if (working_tree is not None and working_tree.supports_views()
 
408
            and apply_view):
477
409
            view_files = working_tree.views.lookup_view()
478
410
            if view_files:
479
411
                specific_files = view_files
515
447
    :param to_file: The output stream.
516
448
    :param specific_files: Include only changes to these files - None for all
517
449
        changes.
518
 
    :param external_diff_options: If set, use an external GNU diff and pass
 
450
    :param external_diff_options: If set, use an external GNU diff and pass 
519
451
        these options.
520
452
    :param extra_trees: If set, more Trees to use for looking up file ids
521
 
    :param path_encoding: If set, the path will be encoded as specified,
 
453
    :param path_encoding: If set, the path will be encoded as specified, 
522
454
        otherwise is supposed to be utf8
523
455
    :param format_cls: Formatter class (DiffTree subclass)
524
456
    """
526
458
        context = DEFAULT_CONTEXT_AMOUNT
527
459
    if format_cls is None:
528
460
        format_cls = DiffTree
529
 
    with old_tree.lock_read():
 
461
    old_tree.lock_read()
 
462
    try:
530
463
        if extra_trees is not None:
531
464
            for tree in extra_trees:
532
465
                tree.lock_read()
543
476
            if extra_trees is not None:
544
477
                for tree in extra_trees:
545
478
                    tree.unlock()
546
 
 
547
 
 
548
 
def _patch_header_date(tree, path):
 
479
    finally:
 
480
        old_tree.unlock()
 
481
 
 
482
 
 
483
def _patch_header_date(tree, file_id, path):
549
484
    """Returns a timestamp suitable for use in a patch header."""
550
485
    try:
551
 
        mtime = tree.get_file_mtime(path)
552
 
    except FileTimestampUnavailable:
 
486
        mtime = tree.get_file_mtime(file_id, path)
 
487
    except errors.FileTimestampUnavailable:
553
488
        mtime = 0
554
489
    return timestamp.format_patch_date(mtime)
555
490
 
556
491
 
557
492
def get_executable_change(old_is_x, new_is_x):
558
 
    descr = {True: b"+x", False: b"-x", None: b"??"}
 
493
    descr = { True:"+x", False:"-x", None:"??" }
559
494
    if old_is_x != new_is_x:
560
 
        return [b"%s to %s" % (descr[old_is_x], descr[new_is_x],)]
 
495
        return ["%s to %s" % (descr[old_is_x], descr[new_is_x],)]
561
496
    else:
562
497
        return []
563
498
 
594
529
                     diff_tree.to_file, diff_tree.path_encoding)
595
530
 
596
531
    @staticmethod
597
 
    def _diff_many(differs, old_path, new_path, old_kind, new_kind):
 
532
    def _diff_many(differs, file_id, old_path, new_path, old_kind, new_kind):
598
533
        for file_differ in differs:
599
 
            result = file_differ.diff(old_path, new_path, old_kind, new_kind)
 
534
            result = file_differ.diff(file_id, old_path, new_path, old_kind,
 
535
                                      new_kind)
600
536
            if result is not DiffPath.CANNOT_DIFF:
601
537
                return result
602
538
        else:
609
545
    Represents kind change as deletion + creation.  Uses the other differs
610
546
    to do this.
611
547
    """
612
 
 
613
548
    def __init__(self, differs):
614
549
        self.differs = differs
615
550
 
620
555
    def from_diff_tree(klass, diff_tree):
621
556
        return klass(diff_tree.differs)
622
557
 
623
 
    def diff(self, old_path, new_path, old_kind, new_kind):
 
558
    def diff(self, file_id, old_path, new_path, old_kind, new_kind):
624
559
        """Perform comparison
625
560
 
 
561
        :param file_id: The file_id of the file to compare
626
562
        :param old_path: Path of the file in the old tree
627
563
        :param new_path: Path of the file in the new tree
628
564
        :param old_kind: Old file-kind of the file
630
566
        """
631
567
        if None in (old_kind, new_kind):
632
568
            return DiffPath.CANNOT_DIFF
633
 
        result = DiffPath._diff_many(
634
 
            self.differs, old_path, new_path, old_kind, None)
 
569
        result = DiffPath._diff_many(self.differs, file_id, old_path,
 
570
                                       new_path, old_kind, None)
635
571
        if result is DiffPath.CANNOT_DIFF:
636
572
            return result
637
 
        return DiffPath._diff_many(
638
 
            self.differs, old_path, new_path, None, new_kind)
 
573
        return DiffPath._diff_many(self.differs, file_id, old_path, new_path,
 
574
                                     None, new_kind)
639
575
 
640
576
 
641
577
class DiffDirectory(DiffPath):
642
578
 
643
 
    def diff(self, old_path, new_path, old_kind, new_kind):
 
579
    def diff(self, file_id, old_path, new_path, old_kind, new_kind):
644
580
        """Perform comparison between two directories.  (dummy)
645
581
 
646
582
        """
655
591
 
656
592
class DiffSymlink(DiffPath):
657
593
 
658
 
    def diff(self, old_path, new_path, old_kind, new_kind):
 
594
    def diff(self, file_id, old_path, new_path, old_kind, new_kind):
659
595
        """Perform comparison between two symlinks
660
596
 
 
597
        :param file_id: The file_id of the file to compare
661
598
        :param old_path: Path of the file in the old tree
662
599
        :param new_path: Path of the file in the new tree
663
600
        :param old_kind: Old file-kind of the file
666
603
        if 'symlink' not in (old_kind, new_kind):
667
604
            return self.CANNOT_DIFF
668
605
        if old_kind == 'symlink':
669
 
            old_target = self.old_tree.get_symlink_target(old_path)
 
606
            old_target = self.old_tree.get_symlink_target(file_id)
670
607
        elif old_kind is None:
671
608
            old_target = None
672
609
        else:
673
610
            return self.CANNOT_DIFF
674
611
        if new_kind == 'symlink':
675
 
            new_target = self.new_tree.get_symlink_target(new_path)
 
612
            new_target = self.new_tree.get_symlink_target(file_id)
676
613
        elif new_kind is None:
677
614
            new_target = None
678
615
        else:
681
618
 
682
619
    def diff_symlink(self, old_target, new_target):
683
620
        if old_target is None:
684
 
            self.to_file.write(b'=== target is \'%s\'\n' %
685
 
                               new_target.encode(self.path_encoding, 'replace'))
 
621
            self.to_file.write('=== target is %r\n' % new_target)
686
622
        elif new_target is None:
687
 
            self.to_file.write(b'=== target was \'%s\'\n' %
688
 
                               old_target.encode(self.path_encoding, 'replace'))
 
623
            self.to_file.write('=== target was %r\n' % old_target)
689
624
        else:
690
 
            self.to_file.write(b'=== target changed \'%s\' => \'%s\'\n' %
691
 
                               (old_target.encode(self.path_encoding, 'replace'),
692
 
                                new_target.encode(self.path_encoding, 'replace')))
 
625
            self.to_file.write('=== target changed %r => %r\n' %
 
626
                              (old_target, new_target))
693
627
        return self.CHANGED
694
628
 
695
629
 
699
633
    # or removed in a diff.
700
634
    EPOCH_DATE = '1970-01-01 00:00:00 +0000'
701
635
 
702
 
    def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8',
703
 
                 old_label='', new_label='', text_differ=internal_diff,
 
636
    def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8', 
 
637
                 old_label='', new_label='', text_differ=internal_diff, 
704
638
                 context_lines=DEFAULT_CONTEXT_AMOUNT):
705
639
        DiffPath.__init__(self, old_tree, new_tree, to_file, path_encoding)
706
640
        self.text_differ = text_differ
709
643
        self.path_encoding = path_encoding
710
644
        self.context_lines = context_lines
711
645
 
712
 
    def diff(self, old_path, new_path, old_kind, new_kind):
 
646
    def diff(self, file_id, old_path, new_path, old_kind, new_kind):
713
647
        """Compare two files in unified diff format
714
648
 
 
649
        :param file_id: The file_id of the file to compare
715
650
        :param old_path: Path of the file in the old tree
716
651
        :param new_path: Path of the file in the new tree
717
652
        :param old_kind: Old file-kind of the file
719
654
        """
720
655
        if 'file' not in (old_kind, new_kind):
721
656
            return self.CANNOT_DIFF
 
657
        from_file_id = to_file_id = file_id
722
658
        if old_kind == 'file':
723
 
            old_date = _patch_header_date(self.old_tree, old_path)
 
659
            old_date = _patch_header_date(self.old_tree, file_id, old_path)
724
660
        elif old_kind is None:
725
661
            old_date = self.EPOCH_DATE
 
662
            from_file_id = None
726
663
        else:
727
664
            return self.CANNOT_DIFF
728
665
        if new_kind == 'file':
729
 
            new_date = _patch_header_date(self.new_tree, new_path)
 
666
            new_date = _patch_header_date(self.new_tree, file_id, new_path)
730
667
        elif new_kind is None:
731
668
            new_date = self.EPOCH_DATE
 
669
            to_file_id = None
732
670
        else:
733
671
            return self.CANNOT_DIFF
734
 
        from_label = '%s%s\t%s' % (self.old_label, old_path,
735
 
                                   old_date)
736
 
        to_label = '%s%s\t%s' % (self.new_label, new_path,
737
 
                                 new_date)
738
 
        return self.diff_text(old_path, new_path, from_label, to_label)
 
672
        from_label = '%s%s\t%s' % (self.old_label, old_path, old_date)
 
673
        to_label = '%s%s\t%s' % (self.new_label, new_path, new_date)
 
674
        return self.diff_text(from_file_id, to_file_id, from_label, to_label,
 
675
            old_path, new_path)
739
676
 
740
 
    def diff_text(self, from_path, to_path, from_label, to_label):
 
677
    def diff_text(self, from_file_id, to_file_id, from_label, to_label,
 
678
        from_path=None, to_path=None):
741
679
        """Diff the content of given files in two trees
742
680
 
743
 
        :param from_path: The path in the from tree. If None,
 
681
        :param from_file_id: The id of the file in the from tree.  If None,
744
682
            the file is not present in the from tree.
745
 
        :param to_path: The path in the to tree. This may refer
746
 
            to a different file from from_path.  If None,
 
683
        :param to_file_id: The id of the file in the to tree.  This may refer
 
684
            to a different file from from_file_id.  If None,
747
685
            the file is not present in the to tree.
 
686
        :param from_path: The path in the from tree or None if unknown.
 
687
        :param to_path: The path in the to tree or None if unknown.
748
688
        """
749
 
        def _get_text(tree, path):
750
 
            if path is None:
751
 
                return []
752
 
            try:
753
 
                return tree.get_file_lines(path)
754
 
            except errors.NoSuchFile:
 
689
        def _get_text(tree, file_id, path):
 
690
            if file_id is not None:
 
691
                return tree.get_file_lines(file_id, path)
 
692
            else:
755
693
                return []
756
694
        try:
757
 
            from_text = _get_text(self.old_tree, from_path)
758
 
            to_text = _get_text(self.new_tree, to_path)
 
695
            from_text = _get_text(self.old_tree, from_file_id, from_path)
 
696
            to_text = _get_text(self.new_tree, to_file_id, to_path)
759
697
            self.text_differ(from_label, from_text, to_label, to_text,
760
698
                             self.to_file, path_encoding=self.path_encoding,
761
699
                             context_lines=self.context_lines)
762
700
        except errors.BinaryFile:
763
701
            self.to_file.write(
764
 
                ("Binary files %s and %s differ\n" %
765
 
                 (from_label, to_label)).encode(self.path_encoding, 'replace'))
 
702
                  ("Binary files %s and %s differ\n" %
 
703
                  (from_label, to_label)).encode(self.path_encoding,'replace'))
766
704
        return self.CHANGED
767
705
 
768
706
 
772
710
                 path_encoding='utf-8'):
773
711
        DiffPath.__init__(self, old_tree, new_tree, to_file, path_encoding)
774
712
        self.command_template = command_template
775
 
        self._root = osutils.mkdtemp(prefix='brz-diff-')
 
713
        self._root = osutils.mkdtemp(prefix='bzr-diff-')
776
714
 
777
715
    @classmethod
778
716
    def from_string(klass, command_string, old_tree, new_tree, to_file,
797
735
        my_map = {'old_path': old_path, 'new_path': new_path}
798
736
        command = [AtTemplate(t).substitute(my_map) for t in
799
737
                   self.command_template]
800
 
        if sys.platform == 'win32':  # Popen doesn't accept unicode on win32
 
738
        if sys.platform == 'win32': # Popen doesn't accept unicode on win32
801
739
            command_encoded = []
802
740
            for c in command:
803
 
                if isinstance(c, text_type):
 
741
                if isinstance(c, unicode):
804
742
                    command_encoded.append(c.encode('mbcs'))
805
743
                else:
806
744
                    command_encoded.append(c)
813
751
        try:
814
752
            proc = subprocess.Popen(command, stdout=subprocess.PIPE,
815
753
                                    cwd=self._root)
816
 
        except OSError as e:
 
754
        except OSError, e:
817
755
            if e.errno == errno.ENOENT:
818
756
                raise errors.ExecutableMissing(command[0])
819
757
            else:
820
758
                raise
821
759
        self.to_file.write(proc.stdout.read())
822
 
        proc.stdout.close()
823
760
        return proc.wait()
824
761
 
825
762
    def _try_symlink_root(self, tree, prefix):
826
 
        if (getattr(tree, 'abspath', None) is None or
827
 
                not osutils.host_os_dereferences_symlinks()):
 
763
        if (getattr(tree, 'abspath', None) is None
 
764
            or not osutils.host_os_dereferences_symlinks()):
828
765
            return False
829
766
        try:
830
767
            os.symlink(tree.abspath(''), osutils.pathjoin(self._root, prefix))
831
 
        except OSError as e:
 
768
        except OSError, e:
832
769
            if e.errno != errno.EEXIST:
833
770
                raise
834
771
        return True
861
798
        relpath_tmp = relpath_tmp.replace(u'?', u'_')
862
799
        return osutils.pathjoin(self._root, prefix, relpath_tmp)
863
800
 
864
 
    def _write_file(self, relpath, tree, prefix, force_temp=False,
 
801
    def _write_file(self, file_id, tree, prefix, relpath, force_temp=False,
865
802
                    allow_write=False):
866
803
        if not force_temp and isinstance(tree, WorkingTree):
867
 
            full_path = tree.abspath(relpath)
 
804
            full_path = tree.abspath(tree.id2path(file_id))
868
805
            if self._is_safepath(full_path):
869
806
                return full_path
870
807
 
874
811
        parent_dir = osutils.dirname(full_path)
875
812
        try:
876
813
            os.makedirs(parent_dir)
877
 
        except OSError as e:
 
814
        except OSError, e:
878
815
            if e.errno != errno.EEXIST:
879
816
                raise
880
 
        source = tree.get_file(relpath)
 
817
        source = tree.get_file(file_id, relpath)
881
818
        try:
882
 
            with open(full_path, 'wb') as target:
 
819
            target = open(full_path, 'wb')
 
820
            try:
883
821
                osutils.pumpfile(source, target)
 
822
            finally:
 
823
                target.close()
884
824
        finally:
885
825
            source.close()
886
826
        try:
887
 
            mtime = tree.get_file_mtime(relpath)
888
 
        except FileTimestampUnavailable:
 
827
            mtime = tree.get_file_mtime(file_id)
 
828
        except errors.FileTimestampUnavailable:
889
829
            pass
890
830
        else:
891
831
            os.utime(full_path, (mtime, mtime))
893
833
            osutils.make_readonly(full_path)
894
834
        return full_path
895
835
 
896
 
    def _prepare_files(self, old_path, new_path, force_temp=False,
 
836
    def _prepare_files(self, file_id, old_path, new_path, force_temp=False,
897
837
                       allow_write_new=False):
898
 
        old_disk_path = self._write_file(
899
 
            old_path, self.old_tree, 'old', force_temp)
900
 
        new_disk_path = self._write_file(
901
 
            new_path, self.new_tree, 'new', force_temp,
902
 
            allow_write=allow_write_new)
 
838
        old_disk_path = self._write_file(file_id, self.old_tree, 'old',
 
839
                                         old_path, force_temp)
 
840
        new_disk_path = self._write_file(file_id, self.new_tree, 'new',
 
841
                                         new_path, force_temp,
 
842
                                         allow_write=allow_write_new)
903
843
        return old_disk_path, new_disk_path
904
844
 
905
845
    def finish(self):
906
846
        try:
907
847
            osutils.rmtree(self._root)
908
 
        except OSError as e:
 
848
        except OSError, e:
909
849
            if e.errno != errno.ENOENT:
910
850
                mutter("The temporary directory \"%s\" was not "
911
 
                       "cleanly removed: %s." % (self._root, e))
 
851
                        "cleanly removed: %s." % (self._root, e))
912
852
 
913
 
    def diff(self, old_path, new_path, old_kind, new_kind):
 
853
    def diff(self, file_id, old_path, new_path, old_kind, new_kind):
914
854
        if (old_kind, new_kind) != ('file', 'file'):
915
855
            return DiffPath.CANNOT_DIFF
916
856
        (old_disk_path, new_disk_path) = self._prepare_files(
917
 
            old_path, new_path)
 
857
                                                file_id, old_path, new_path)
918
858
        self._execute(old_disk_path, new_disk_path)
919
859
 
920
 
    def edit_file(self, old_path, new_path):
 
860
    def edit_file(self, file_id):
921
861
        """Use this tool to edit a file.
922
862
 
923
863
        A temporary copy will be edited, and the new contents will be
924
864
        returned.
925
865
 
 
866
        :param file_id: The id of the file to edit.
926
867
        :return: The new contents of the file.
927
868
        """
 
869
        old_path = self.old_tree.id2path(file_id)
 
870
        new_path = self.new_tree.id2path(file_id)
928
871
        old_abs_path, new_abs_path = self._prepare_files(
929
 
            old_path, new_path, allow_write_new=True, force_temp=True)
 
872
                                            file_id, old_path, new_path,
 
873
                                            allow_write_new=True,
 
874
                                            force_temp=True)
930
875
        command = self._get_command(old_abs_path, new_abs_path)
931
876
        subprocess.call(command, cwd=self._root)
932
 
        with open(new_abs_path, 'rb') as new_file:
 
877
        new_file = open(new_abs_path, 'rb')
 
878
        try:
933
879
            return new_file.read()
 
880
        finally:
 
881
            new_file.close()
934
882
 
935
883
 
936
884
class DiffTree(object):
964
912
            DiffPaths"""
965
913
        if diff_text is None:
966
914
            diff_text = DiffText(old_tree, new_tree, to_file, path_encoding,
967
 
                                 '', '', internal_diff)
 
915
                                 '', '',  internal_diff)
968
916
        self.old_tree = old_tree
969
917
        self.new_tree = new_tree
970
918
        self.to_file = to_file
994
942
        :param using: Commandline to use to invoke an external diff tool
995
943
        """
996
944
        if using is not None:
997
 
            extra_factories = [DiffFromTool.make_from_diff_tree(
998
 
                using, external_diff_options)]
 
945
            extra_factories = [DiffFromTool.make_from_diff_tree(using, external_diff_options)]
999
946
        else:
1000
947
            extra_factories = []
1001
948
        if external_diff_options:
1002
949
            opts = external_diff_options.split()
1003
 
 
1004
950
            def diff_file(olab, olines, nlab, nlines, to_file, path_encoding=None, context_lines=None):
1005
951
                """:param path_encoding: not used but required
1006
952
                        to match the signature of internal_diff.
1029
975
        # TODO: Generation of pseudo-diffs for added/deleted files could
1030
976
        # be usefully made into a much faster special case.
1031
977
        iterator = self.new_tree.iter_changes(self.old_tree,
1032
 
                                              specific_files=specific_files,
1033
 
                                              extra_trees=extra_trees,
1034
 
                                              require_versioned=True)
 
978
                                               specific_files=specific_files,
 
979
                                               extra_trees=extra_trees,
 
980
                                               require_versioned=True)
1035
981
        has_changes = 0
1036
 
 
1037
982
        def changes_key(change):
1038
983
            old_path, new_path = change[1]
1039
984
            path = new_path
1040
985
            if path is None:
1041
986
                path = old_path
1042
987
            return path
1043
 
 
1044
988
        def get_encoded_path(path):
1045
989
            if path is not None:
1046
990
                return path.encode(self.path_encoding, "replace")
1058
1002
            renamed = (parent[0], name[0]) != (parent[1], name[1])
1059
1003
 
1060
1004
            properties_changed = []
1061
 
            properties_changed.extend(
1062
 
                get_executable_change(executable[0], executable[1]))
 
1005
            properties_changed.extend(get_executable_change(executable[0], executable[1]))
1063
1006
 
1064
1007
            if properties_changed:
1065
 
                prop_str = b" (properties changed: %s)" % (
1066
 
                    b", ".join(properties_changed),)
 
1008
                prop_str = " (properties changed: %s)" % (", ".join(properties_changed),)
1067
1009
            else:
1068
 
                prop_str = b""
 
1010
                prop_str = ""
1069
1011
 
1070
1012
            if (old_present, new_present) == (True, False):
1071
 
                self.to_file.write(b"=== removed %s '%s'\n" %
1072
 
                                   (kind[0].encode('ascii'), oldpath_encoded))
 
1013
                self.to_file.write("=== removed %s '%s'\n" %
 
1014
                                   (kind[0], oldpath_encoded))
1073
1015
                newpath = oldpath
1074
1016
            elif (old_present, new_present) == (False, True):
1075
 
                self.to_file.write(b"=== added %s '%s'\n" %
1076
 
                                   (kind[1].encode('ascii'), newpath_encoded))
 
1017
                self.to_file.write("=== added %s '%s'\n" %
 
1018
                                   (kind[1], newpath_encoded))
1077
1019
                oldpath = newpath
1078
1020
            elif renamed:
1079
 
                self.to_file.write(b"=== renamed %s '%s' => '%s'%s\n" %
1080
 
                                   (kind[0].encode('ascii'), oldpath_encoded, newpath_encoded, prop_str))
 
1021
                self.to_file.write("=== renamed %s '%s' => '%s'%s\n" %
 
1022
                    (kind[0], oldpath_encoded, newpath_encoded, prop_str))
1081
1023
            else:
1082
1024
                # if it was produced by iter_changes, it must be
1083
1025
                # modified *somehow*, either content or execute bit.
1084
 
                self.to_file.write(b"=== modified %s '%s'%s\n" % (kind[0].encode('ascii'),
1085
 
                                                                  newpath_encoded, prop_str))
 
1026
                self.to_file.write("=== modified %s '%s'%s\n" % (kind[0],
 
1027
                                   newpath_encoded, prop_str))
1086
1028
            if changed_content:
1087
 
                self._diff(oldpath, newpath, kind[0], kind[1])
 
1029
                self._diff(file_id, oldpath, newpath, kind[0], kind[1])
1088
1030
                has_changes = 1
1089
1031
            if renamed:
1090
1032
                has_changes = 1
1091
1033
        return has_changes
1092
1034
 
1093
 
    def diff(self, old_path, new_path):
 
1035
    def diff(self, file_id, old_path, new_path):
1094
1036
        """Perform a diff of a single file
1095
1037
 
 
1038
        :param file_id: file-id of the file
1096
1039
        :param old_path: The path of the file in the old tree
1097
1040
        :param new_path: The path of the file in the new tree
1098
1041
        """
1099
 
        if old_path is None:
 
1042
        try:
 
1043
            old_kind = self.old_tree.kind(file_id)
 
1044
        except (errors.NoSuchId, errors.NoSuchFile):
1100
1045
            old_kind = None
1101
 
        else:
1102
 
            old_kind = self.old_tree.kind(old_path)
1103
 
        if new_path is None:
 
1046
        try:
 
1047
            new_kind = self.new_tree.kind(file_id)
 
1048
        except (errors.NoSuchId, errors.NoSuchFile):
1104
1049
            new_kind = None
1105
 
        else:
1106
 
            new_kind = self.new_tree.kind(new_path)
1107
 
        self._diff(old_path, new_path, old_kind, new_kind)
1108
 
 
1109
 
    def _diff(self, old_path, new_path, old_kind, new_kind):
1110
 
        result = DiffPath._diff_many(
1111
 
            self.differs, old_path, new_path, old_kind, new_kind)
 
1050
        self._diff(file_id, old_path, new_path, old_kind, new_kind)
 
1051
 
 
1052
 
 
1053
    def _diff(self, file_id, old_path, new_path, old_kind, new_kind):
 
1054
        result = DiffPath._diff_many(self.differs, file_id, old_path,
 
1055
                                       new_path, old_kind, new_kind)
1112
1056
        if result is DiffPath.CANNOT_DIFF:
1113
1057
            error_path = new_path
1114
1058
            if error_path is None: