/b-gtk/fix-viz

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/b-gtk/fix-viz

« back to all changes in this revision

Viewing changes to diff.py

  • Committer: Szilveszter Farkas (Phanatic)
  • Date: 2007-03-15 12:43:48 UTC
  • mto: (126.1.38 bzr-gtk)
  • mto: This revision was merged to the branch mainline in revision 172.
  • Revision ID: szilveszter.farkas@gmail.com-20070315124348-0nx0zb7fv4pa8xk6
Fix the indentation error in the TortoiseBZR test.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python
1
2
# -*- coding: UTF-8 -*-
2
3
"""Difference window.
3
4
 
11
12
 
12
13
from cStringIO import StringIO
13
14
 
14
 
import pygtk
15
 
pygtk.require("2.0")
16
15
import gtk
17
16
import pango
18
 
import os
19
 
import re
20
17
import sys
21
18
 
22
19
try:
24
21
    have_gtksourceview = True
25
22
except ImportError:
26
23
    have_gtksourceview = False
27
 
try:
28
 
    import gconf
29
 
    have_gconf = True
30
 
except ImportError:
31
 
    have_gconf = False
32
 
 
33
 
from bzrlib import merge as _mod_merge, osutils, progress, workingtree
34
 
from bzrlib.diff import show_diff_trees, internal_diff
 
24
 
 
25
import bzrlib
 
26
 
 
27
from bzrlib.diff import show_diff_trees
35
28
from bzrlib.errors import NoSuchFile
36
 
from bzrlib.patches import parse_patches
37
 
from bzrlib.trace import warning
38
 
from bzrlib.plugins.gtk.window import Window
39
 
from dialog import error_dialog, info_dialog, warning_dialog
40
 
 
41
 
 
42
 
class SelectCancelled(Exception):
43
 
 
44
 
    pass
45
 
 
46
 
 
47
 
class DiffFileView(gtk.ScrolledWindow):
 
29
 
 
30
 
 
31
class DiffWindow(gtk.Window):
 
32
    """Diff window.
 
33
 
 
34
    This object represents and manages a single window containing the
 
35
    differences between two revisions on a branch.
 
36
    """
48
37
 
49
38
    def __init__(self):
50
 
        gtk.ScrolledWindow.__init__(self)
 
39
        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
 
40
        self.set_border_width(0)
 
41
        self.set_title("bzrk diff")
 
42
 
 
43
        # Use two thirds of the screen by default
 
44
        screen = self.get_screen()
 
45
        monitor = screen.get_monitor_geometry(0)
 
46
        width = int(monitor.width * 0.66)
 
47
        height = int(monitor.height * 0.66)
 
48
        self.set_default_size(width, height)
 
49
 
51
50
        self.construct()
52
 
        self._diffs = {}
53
51
 
54
52
    def construct(self):
55
 
        self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
56
 
        self.set_shadow_type(gtk.SHADOW_IN)
57
 
 
58
 
        if have_gtksourceview:
59
 
            self.buffer = gtksourceview.SourceBuffer()
60
 
            slm = gtksourceview.SourceLanguagesManager()
61
 
            gsl = slm.get_language_from_mime_type("text/x-patch")
62
 
            if have_gconf:
63
 
                self.apply_gedit_colors(gsl)
64
 
            self.apply_colordiff_colors(gsl)
65
 
            self.buffer.set_language(gsl)
66
 
            self.buffer.set_highlight(True)
67
 
 
68
 
            sourceview = gtksourceview.SourceView(self.buffer)
69
 
        else:
70
 
            self.buffer = gtk.TextBuffer()
71
 
            sourceview = gtk.TextView(self.buffer)
72
 
 
73
 
        sourceview.set_editable(False)
74
 
        sourceview.modify_font(pango.FontDescription("Monospace"))
75
 
        self.add(sourceview)
76
 
        sourceview.show()
77
 
 
78
 
    @staticmethod
79
 
    def apply_gedit_colors(lang):
80
 
        """Set style for lang to that specified in gedit configuration.
81
 
 
82
 
        This method needs the gconf module.
83
 
 
84
 
        :param lang: a gtksourceview.SourceLanguage object.
85
 
        """
86
 
        GEDIT_SYNTAX_PATH = '/apps/gedit-2/preferences/syntax_highlighting'
87
 
        GEDIT_LANG_PATH = GEDIT_SYNTAX_PATH + '/' + lang.get_id()
88
 
 
89
 
        client = gconf.client_get_default()
90
 
        client.add_dir(GEDIT_LANG_PATH, gconf.CLIENT_PRELOAD_NONE)
91
 
 
92
 
        for tag in lang.get_tags():
93
 
            tag_id = tag.get_id()
94
 
            gconf_key = GEDIT_LANG_PATH + '/' + tag_id
95
 
            style_string = client.get_string(gconf_key)
96
 
 
97
 
            if style_string is None:
98
 
                continue
99
 
 
100
 
            # function to get a bool from a string that's either '0' or '1'
101
 
            string_bool = lambda x: bool(int(x))
102
 
 
103
 
            # style_string is a string like "2/#FFCCAA/#000000/0/1/0/0"
104
 
            # values are: mask, fg, bg, italic, bold, underline, strike
105
 
            # this packs them into (str_value, attr_name, conv_func) tuples
106
 
            items = zip(style_string.split('/'), ['mask', 'foreground',
107
 
                'background', 'italic', 'bold', 'underline', 'strikethrough' ],
108
 
                [ int, gtk.gdk.color_parse, gtk.gdk.color_parse, string_bool,
109
 
                    string_bool, string_bool, string_bool ]
110
 
            )
111
 
 
112
 
            style = gtksourceview.SourceTagStyle()
113
 
 
114
 
            # XXX The mask attribute controls whether the present values of
115
 
            # foreground and background color should in fact be used. Ideally
116
 
            # (and that's what gedit does), one could set all three attributes,
117
 
            # and let the TagStyle object figure out which colors to use.
118
 
            # However, in the GtkSourceview python bindings, the mask attribute
119
 
            # is read-only, and it's derived instead from the colors being
120
 
            # set or not. This means that we have to sometimes refrain from
121
 
            # setting fg or bg colors, depending on the value of the mask.
122
 
            # This code could go away if mask were writable.
123
 
            mask = int(items[0][0])
124
 
            if not (mask & 1): # GTK_SOURCE_TAG_STYLE_USE_BACKGROUND
125
 
                items[2:3] = []
126
 
            if not (mask & 2): # GTK_SOURCE_TAG_STYLE_USE_FOREGROUND
127
 
                items[1:2] = []
128
 
            items[0:1] = [] # skip the mask unconditionally
129
 
 
130
 
            for value, attr, func in items:
131
 
                try:
132
 
                    value = func(value)
133
 
                except ValueError:
134
 
                    warning('gconf key %s contains an invalid value: %s'
135
 
                            % gconf_key, value)
136
 
                else:
137
 
                    setattr(style, attr, value)
138
 
 
139
 
            lang.set_tag_style(tag_id, style)
140
 
 
141
 
    @classmethod
142
 
    def apply_colordiff_colors(klass, lang):
143
 
        """Set style colors for lang using the colordiff configuration file.
144
 
 
145
 
        Both ~/.colordiffrc and ~/.colordiffrc.bzr-gtk are read.
146
 
 
147
 
        :param lang: a "Diff" gtksourceview.SourceLanguage object.
148
 
        """
149
 
        colors = {}
150
 
 
151
 
        for f in ('~/.colordiffrc', '~/.colordiffrc.bzr-gtk'):
152
 
            f = os.path.expanduser(f)
153
 
            if os.path.exists(f):
154
 
                try:
155
 
                    f = file(f)
156
 
                except IOError, e:
157
 
                    warning('could not open file %s: %s' % (f, str(e)))
158
 
                else:
159
 
                    colors.update(klass.parse_colordiffrc(f))
160
 
                    f.close()
161
 
 
162
 
        if not colors:
163
 
            # ~/.colordiffrc does not exist
164
 
            return
165
 
 
166
 
        mapping = {
167
 
                # map GtkSourceView tags to colordiff names
168
 
                # since GSV is richer, accept new names for extra bits,
169
 
                # defaulting to old names if they're not present
170
 
                'Added@32@line': ['newtext'],
171
 
                'Removed@32@line': ['oldtext'],
172
 
                'Location': ['location', 'diffstuff'],
173
 
                'Diff@32@file': ['file', 'diffstuff'],
174
 
                'Special@32@case': ['specialcase', 'diffstuff'],
175
 
        }
176
 
 
177
 
        for tag in lang.get_tags():
178
 
            tag_id = tag.get_id()
179
 
            keys = mapping.get(tag_id, [])
180
 
            color = None
181
 
 
182
 
            for key in keys:
183
 
                color = colors.get(key, None)
184
 
                if color is not None:
185
 
                    break
186
 
 
187
 
            if color is None:
188
 
                continue
189
 
 
190
 
            style = gtksourceview.SourceTagStyle()
191
 
            try:
192
 
                style.foreground = gtk.gdk.color_parse(color)
193
 
            except ValueError:
194
 
                warning('not a valid color: %s' % color)
195
 
            else:
196
 
                lang.set_tag_style(tag_id, style)
197
 
 
198
 
    @staticmethod
199
 
    def parse_colordiffrc(fileobj):
200
 
        """Parse fileobj as a colordiff configuration file.
201
 
 
202
 
        :return: A dict with the key -> value pairs.
203
 
        """
204
 
        colors = {}
205
 
        for line in fileobj:
206
 
            if re.match(r'^\s*#', line):
207
 
                continue
208
 
            if '=' not in line:
209
 
                continue
210
 
            key, val = line.split('=', 1)
211
 
            colors[key.strip()] = val.strip()
212
 
        return colors
213
 
 
214
 
    def set_trees(self, rev_tree, parent_tree):
215
 
        self.rev_tree = rev_tree
216
 
        self.parent_tree = parent_tree
217
 
#        self._build_delta()
218
 
 
219
 
#    def _build_delta(self):
220
 
#        self.parent_tree.lock_read()
221
 
#        self.rev_tree.lock_read()
222
 
#        try:
223
 
#            self.delta = _iter_changes_to_status(self.parent_tree, self.rev_tree)
224
 
#            self.path_to_status = {}
225
 
#            self.path_to_diff = {}
226
 
#            source_inv = self.parent_tree.inventory
227
 
#            target_inv = self.rev_tree.inventory
228
 
#            for (file_id, real_path, change_type, display_path) in self.delta:
229
 
#                self.path_to_status[real_path] = u'=== %s %s' % (change_type, display_path)
230
 
#                if change_type in ('modified', 'renamed and modified'):
231
 
#                    source_ie = source_inv[file_id]
232
 
#                    target_ie = target_inv[file_id]
233
 
#                    sio = StringIO()
234
 
#                    source_ie.diff(internal_diff, *old path, *old_tree,
235
 
#                                   *new_path, target_ie, self.rev_tree,
236
 
#                                   sio)
237
 
#                    self.path_to_diff[real_path] = 
238
 
#
239
 
#        finally:
240
 
#            self.rev_tree.unlock()
241
 
#            self.parent_tree.unlock()
242
 
 
243
 
    def show_diff(self, specific_files):
244
 
        sections = []
245
 
        if specific_files is None:
246
 
            self.buffer.set_text(self._diffs[None])
247
 
        else:
248
 
            for specific_file in specific_files:
249
 
                sections.append(self._diffs[specific_file])
250
 
            self.buffer.set_text(''.join(sections))
251
 
 
252
 
 
253
 
class DiffView(DiffFileView):
254
 
    """This is the soft and chewy filling for a DiffWindow."""
255
 
 
256
 
    def __init__(self):
257
 
        DiffFileView.__init__(self)
258
 
        self.rev_tree = None
259
 
        self.parent_tree = None
260
 
 
261
 
    def show_diff(self, specific_files):
262
 
        s = StringIO()
263
 
        show_diff_trees(self.parent_tree, self.rev_tree, s, specific_files,
264
 
                        old_label='', new_label='',
265
 
                        # path_encoding=sys.getdefaultencoding()
266
 
                        # The default is utf-8, but we interpret the file
267
 
                        # contents as getdefaultencoding(), so we should
268
 
                        # probably try to make the paths in the same encoding.
269
 
                        )
270
 
        # str.decode(encoding, 'replace') doesn't do anything. Because if a
271
 
        # character is not valid in 'encoding' there is nothing to replace, the
272
 
        # 'replace' is for 'str.encode()'
273
 
        try:
274
 
            decoded = s.getvalue().decode(sys.getdefaultencoding())
275
 
        except UnicodeDecodeError:
276
 
            try:
277
 
                decoded = s.getvalue().decode('UTF-8')
278
 
            except UnicodeDecodeError:
279
 
                decoded = s.getvalue().decode('iso-8859-1')
280
 
                # This always works, because every byte has a valid
281
 
                # mapping from iso-8859-1 to Unicode
282
 
        # TextBuffer must contain pure UTF-8 data
283
 
        self.buffer.set_text(decoded.encode('UTF-8'))
284
 
 
285
 
 
286
 
class DiffWidget(gtk.HPaned):
287
 
    """Diff widget
288
 
 
289
 
    """
290
 
    def __init__(self):
291
 
        super(DiffWidget, self).__init__()
 
53
        """Construct the window contents."""
 
54
        # The   window  consists  of   a  pane   containing:  the
 
55
        # hierarchical list  of files on  the left, and  the diff
 
56
        # for the currently selected file on the right.
 
57
        pane = gtk.HPaned()
 
58
        self.add(pane)
 
59
        pane.show()
292
60
 
293
61
        # The file hierarchy: a scrollable treeview
294
62
        scrollwin = gtk.ScrolledWindow()
295
63
        scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
296
64
        scrollwin.set_shadow_type(gtk.SHADOW_IN)
297
 
        self.pack1(scrollwin)
 
65
        pane.pack1(scrollwin)
298
66
        scrollwin.show()
299
67
 
300
68
        self.model = gtk.TreeStore(str, str)
312
80
        column.add_attribute(cell, "text", 0)
313
81
        self.treeview.append_column(column)
314
82
 
315
 
    def set_diff_text(self, lines):
316
83
        # The diffs of the  selected file: a scrollable source or
317
84
        # text view
318
 
        self.diff_view = DiffFileView()
319
 
        self.diff_view.show()
320
 
        self.pack2(self.diff_view)
321
 
        self.model.append(None, [ "Complete Diff", "" ])
322
 
        self.diff_view._diffs[None] = ''.join(lines)
323
 
        for patch in parse_patches(lines):
324
 
            oldname = patch.oldname.split('\t')[0]
325
 
            newname = patch.newname.split('\t')[0]
326
 
            self.model.append(None, [oldname, newname])
327
 
            self.diff_view._diffs[newname] = str(patch)
328
 
        self.diff_view.show_diff(None)
329
 
 
330
 
    def set_diff(self, rev_tree, parent_tree):
 
85
        scrollwin = gtk.ScrolledWindow()
 
86
        scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 
87
        scrollwin.set_shadow_type(gtk.SHADOW_IN)
 
88
        pane.pack2(scrollwin)
 
89
        scrollwin.show()
 
90
 
 
91
        if have_gtksourceview:
 
92
            self.buffer = gtksourceview.SourceBuffer()
 
93
            slm = gtksourceview.SourceLanguagesManager()
 
94
            gsl = slm.get_language_from_mime_type("text/x-patch")
 
95
            self.buffer.set_language(gsl)
 
96
            self.buffer.set_highlight(True)
 
97
 
 
98
            sourceview = gtksourceview.SourceView(self.buffer)
 
99
        else:
 
100
            self.buffer = gtk.TextBuffer()
 
101
            sourceview = gtk.TextView(self.buffer)
 
102
 
 
103
        sourceview.set_editable(False)
 
104
        sourceview.modify_font(pango.FontDescription("Monospace"))
 
105
        scrollwin.add(sourceview)
 
106
        sourceview.show()
 
107
 
 
108
    def set_diff(self, description, rev_tree, parent_tree):
331
109
        """Set the differences showed by this window.
332
110
 
333
111
        Compares the two trees and populates the window with the
334
112
        differences.
335
113
        """
336
 
        self.diff_view = DiffView()
337
 
        self.pack2(self.diff_view)
338
 
        self.diff_view.show()
339
 
        self.diff_view.set_trees(rev_tree, parent_tree)
340
114
        self.rev_tree = rev_tree
341
115
        self.parent_tree = parent_tree
342
116
 
367
141
                self.model.append(titer, [ path, path ])
368
142
 
369
143
        self.treeview.expand_all()
 
144
        self.set_title(description + " - bzrk diff")
370
145
 
371
146
    def set_file(self, file_path):
372
147
        tv_path = None
387
162
        if specific_files == [ None ]:
388
163
            return
389
164
        elif specific_files == [ "" ]:
390
 
            specific_files = None
391
 
 
392
 
        self.diff_view.show_diff(specific_files)
393
 
 
394
 
 
395
 
class DiffWindow(Window):
396
 
    """Diff window.
397
 
 
398
 
    This object represents and manages a single window containing the
399
 
    differences between two revisions on a branch.
400
 
    """
401
 
 
402
 
    def __init__(self, parent=None):
403
 
        Window.__init__(self, parent)
404
 
        self.set_border_width(0)
405
 
        self.set_title("bzrk diff")
406
 
 
407
 
        # Use two thirds of the screen by default
408
 
        screen = self.get_screen()
409
 
        monitor = screen.get_monitor_geometry(0)
410
 
        width = int(monitor.width * 0.66)
411
 
        height = int(monitor.height * 0.66)
412
 
        self.set_default_size(width, height)
413
 
 
414
 
        self.construct()
415
 
 
416
 
    def construct(self):
417
 
        """Construct the window contents."""
418
 
        self.vbox = gtk.VBox()
419
 
        self.add(self.vbox)
420
 
        self.vbox.show()
421
 
        hbox = self._get_button_bar()
422
 
        if hbox is not None:
423
 
            self.vbox.pack_start(hbox, expand=False, fill=True)
424
 
        self.diff = DiffWidget()
425
 
        self.vbox.add(self.diff)
426
 
        self.diff.show_all()
427
 
 
428
 
    def _get_button_bar(self):
429
 
        return None
430
 
 
431
 
    def set_diff_text(self, description, lines):
432
 
        self.diff.set_diff_text(lines)
433
 
        self.set_title(description + " - bzrk diff")
434
 
 
435
 
    def set_diff(self, description, rev_tree, parent_tree):
436
 
        """Set the differences showed by this window.
437
 
 
438
 
        Compares the two trees and populates the window with the
439
 
        differences.
440
 
        """
441
 
        self.diff.set_diff(rev_tree, parent_tree)
442
 
        self.set_title(description + " - bzrk diff")
443
 
 
444
 
    def set_file(self, file_path):
445
 
        self.diff.set_file(file_path)
446
 
 
447
 
 
448
 
class MergeDirectiveWindow(DiffWindow):
449
 
 
450
 
    def __init__(self, directive, parent=None):
451
 
        DiffWindow.__init__(self, parent)
452
 
        self._merge_target = None
453
 
        self.directive = directive
454
 
 
455
 
    def _get_button_bar(self):
456
 
        merge_button = gtk.Button('Merge')
457
 
        merge_button.show()
458
 
        merge_button.set_relief(gtk.RELIEF_NONE)
459
 
        merge_button.connect("clicked", self.perform_merge)
460
 
 
461
 
        hbox = gtk.HButtonBox()
462
 
        hbox.set_layout(gtk.BUTTONBOX_START)
463
 
        hbox.pack_start(merge_button, expand=False, fill=True)
464
 
        hbox.show()
465
 
        return hbox
466
 
 
467
 
    def perform_merge(self, window):
468
 
        try:
469
 
            tree = self._get_merge_target()
470
 
        except SelectCancelled:
471
 
            return
472
 
        tree.lock_write()
473
 
        try:
474
 
            try:
475
 
                merger, verified = _mod_merge.Merger.from_mergeable(tree,
476
 
                    self.directive, progress.DummyProgress())
477
 
                merger.check_basis(True)
478
 
                merger.merge_type = _mod_merge.Merge3Merger
479
 
                conflict_count = merger.do_merge()
480
 
                merger.set_pending()
481
 
                if conflict_count == 0:
482
 
                    # No conflicts found.
483
 
                    info_dialog(_('Merge successful'),
484
 
                                _('All changes applied successfully.'))
485
 
                else:
486
 
                    # There are conflicts to be resolved.
487
 
                    warning_dialog(_('Conflicts encountered'),
488
 
                                   _('Please resolve the conflicts manually'
489
 
                                     ' before committing.'))
490
 
                self.destroy()
491
 
            except Exception, e:
492
 
                error_dialog('Error', str(e))
493
 
        finally:
494
 
            tree.unlock()
495
 
 
496
 
    def _get_merge_target(self):
497
 
        if self._merge_target is not None:
498
 
            return self._merge_target
499
 
        d = gtk.FileChooserDialog('Merge branch', self,
500
 
                                  gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
501
 
                                  buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
502
 
                                           gtk.STOCK_CANCEL,
503
 
                                           gtk.RESPONSE_CANCEL,))
504
 
        try:
505
 
            result = d.run()
506
 
            if result == gtk.RESPONSE_OK:
507
 
                uri = d.get_current_folder_uri()
508
 
                return workingtree.WorkingTree.open(uri)
509
 
            else:
510
 
                raise SelectCancelled()
511
 
        finally:
512
 
            d.destroy()
513
 
 
514
 
 
515
 
def _iter_changes_to_status(source, target):
516
 
    """Determine the differences between trees.
517
 
 
518
 
    This is a wrapper around _iter_changes which just yields more
519
 
    understandable results.
520
 
 
521
 
    :param source: The source tree (basis tree)
522
 
    :param target: The target tree
523
 
    :return: A list of (file_id, real_path, change_type, display_path)
524
 
    """
525
 
    added = 'added'
526
 
    removed = 'removed'
527
 
    renamed = 'renamed'
528
 
    renamed_and_modified = 'renamed and modified'
529
 
    modified = 'modified'
530
 
    kind_changed = 'kind changed'
531
 
 
532
 
    # TODO: Handle metadata changes
533
 
 
534
 
    status = []
535
 
    target.lock_read()
536
 
    try:
537
 
        source.lock_read()
538
 
        try:
539
 
            for (file_id, paths, changed_content, versioned, parent_ids, names,
540
 
                 kinds, executables) in target._iter_changes(source):
541
 
 
542
 
                # Skip the root entry if it isn't very interesting
543
 
                if parent_ids == (None, None):
544
 
                    continue
545
 
 
546
 
                change_type = None
547
 
                if kinds[0] is None:
548
 
                    source_marker = ''
549
 
                else:
550
 
                    source_marker = osutils.kind_marker(kinds[0])
551
 
                if kinds[1] is None:
552
 
                    assert kinds[0] is not None
553
 
                    marker = osutils.kind_marker(kinds[0])
554
 
                else:
555
 
                    marker = osutils.kind_marker(kinds[1])
556
 
 
557
 
                real_path = paths[1]
558
 
                if real_path is None:
559
 
                    real_path = paths[0]
560
 
                assert real_path is not None
561
 
                display_path = real_path + marker
562
 
 
563
 
                present_source = versioned[0] and kinds[0] is not None
564
 
                present_target = versioned[1] and kinds[1] is not None
565
 
 
566
 
                if present_source != present_target:
567
 
                    if present_target:
568
 
                        change_type = added
569
 
                    else:
570
 
                        assert present_source
571
 
                        change_type = removed
572
 
                elif names[0] != names[1] or parent_ids[0] != parent_ids[1]:
573
 
                    # Renamed
574
 
                    if changed_content or executables[0] != executables[1]:
575
 
                        # and modified
576
 
                        change_type = renamed_and_modified
577
 
                    else:
578
 
                        change_type = renamed
579
 
                    display_path = (paths[0] + source_marker
580
 
                                    + ' => ' + paths[1] + marker)
581
 
                elif kinds[0] != kinds[1]:
582
 
                    change_type = kind_changed
583
 
                    display_path = (paths[0] + source_marker
584
 
                                    + ' => ' + paths[1] + marker)
585
 
                elif changed_content is True or executables[0] != executables[1]:
586
 
                    change_type = modified
587
 
                else:
588
 
                    assert False, "How did we get here?"
589
 
 
590
 
                status.append((file_id, real_path, change_type, display_path))
591
 
        finally:
592
 
            source.unlock()
593
 
    finally:
594
 
        target.unlock()
595
 
 
596
 
    return status
 
165
            specific_files = []
 
166
 
 
167
        s = StringIO()
 
168
        show_diff_trees(self.parent_tree, self.rev_tree, s, specific_files)
 
169
        self.buffer.set_text(s.getvalue().decode(sys.getdefaultencoding(), 'replace'))