/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 viz/diffwin.py

  • Committer: Jelmer Vernooij
  • Date: 2006-05-19 16:56:46 UTC
  • mfrom: (0.1.25 gannotate)
  • Revision ID: jelmer@samba.org-20060519165646-0d867938fdbc9097
Merge in Dan Loda's gannotate plugin and put it in annotate/

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