/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-06-10 15:50:23 UTC
  • mfrom: (195.1.26 browse-remote-branches)
  • Revision ID: szilveszter.farkas@gmail.com-20070610155023-o3moy5a3wrp9o412
Merge browse-remote-branches branch (experimental).

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