/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: 2007-02-01 15:50:40 UTC
  • Revision ID: jelmer@samba.org-20070201155040-3hq4mfbxs99kzazy
add framework for tests.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python
 
2
# -*- coding: UTF-8 -*-
1
3
"""Difference window.
2
4
 
3
5
This module contains the code to manage the diff window which shows
4
6
the changes made between two revisions on a branch.
5
7
"""
6
8
 
7
 
__copyright__ = "Copyright 2005 Canonical Ltd."
 
9
__copyright__ = "Copyright © 2005 Canonical Ltd."
8
10
__author__    = "Scott James Remnant <scott@ubuntu.com>"
9
11
 
10
12
 
11
13
from cStringIO import StringIO
12
14
 
13
 
import pygtk
14
 
pygtk.require("2.0")
15
15
import gtk
16
16
import pango
17
 
import os
18
 
import re
19
17
import sys
20
 
from xml.etree.ElementTree import Element, SubElement, tostring
21
18
 
22
19
try:
23
 
    import gtksourceview2
 
20
    import gtksourceview
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 = gtksourceview2.Buffer()
68
 
            slm = gtksourceview2.LanguageManager()
69
 
            gsl = slm.guess_language(content_type="text/x-patch")
70
 
            if have_gconf:
71
 
                self.apply_gedit_colors(self.buffer)
72
 
            self.apply_colordiff_colors(self.buffer)
73
 
            self.buffer.set_language(gsl)
74
 
            self.buffer.set_highlight_syntax(True)
75
 
 
76
 
            self.sourceview = gtksourceview2.View(self.buffer)
77
 
        else:
78
 
            self.buffer = gtk.TextBuffer()
79
 
            self.sourceview = gtk.TextView(self.buffer)
80
 
 
81
 
        self.sourceview.set_editable(False)
82
 
        self.sourceview.modify_font(pango.FontDescription("Monospace"))
83
 
        self.add(self.sourceview)
84
 
        self.sourceview.show()
85
 
 
86
 
    @staticmethod
87
 
    def apply_gedit_colors(buf):
88
 
        """Set style to that specified in gedit configuration.
89
 
 
90
 
        This method needs the gconf module.
91
 
 
92
 
        :param buf: a gtksourceview2.Buffer object.
93
 
        """
94
 
        GEDIT_SCHEME_PATH = '/apps/gedit-2/preferences/editor/colors/scheme'
95
 
 
96
 
        client = gconf.client_get_default()
97
 
        style_scheme_name = client.get_string(GEDIT_SCHEME_PATH)
98
 
        if style_scheme_name is not None:
99
 
            style_scheme = gtksourceview2.StyleSchemeManager().get_scheme(style_scheme_name)
100
 
            
101
 
            buf.set_style_scheme(style_scheme)
102
 
 
103
 
    @classmethod
104
 
    def apply_colordiff_colors(klass, buf):
105
 
        """Set style colors for lang using the colordiff configuration file.
106
 
 
107
 
        Both ~/.colordiffrc and ~/.colordiffrc.bzr-gtk are read.
108
 
 
109
 
        :param buf: a "Diff" gtksourceview2.Buffer object.
110
 
        """
111
 
        scheme_manager = gtksourceview2.StyleSchemeManager()
112
 
        style_scheme = scheme_manager.get_scheme('colordiff')
113
 
        
114
 
        # if style scheme not found, we'll generate it from colordiffrc
115
 
        # TODO: reload if colordiffrc has changed.
116
 
        if style_scheme is None:
117
 
            colors = {}
118
 
 
119
 
            for f in ('~/.colordiffrc', '~/.colordiffrc.bzr-gtk'):
120
 
                f = os.path.expanduser(f)
121
 
                if os.path.exists(f):
122
 
                    try:
123
 
                        f = file(f)
124
 
                    except IOError, e:
125
 
                        warning('could not open file %s: %s' % (f, str(e)))
126
 
                    else:
127
 
                        colors.update(klass.parse_colordiffrc(f))
128
 
                        f.close()
129
 
 
130
 
            if not colors:
131
 
                # ~/.colordiffrc does not exist
132
 
                return
133
 
            
134
 
            mapping = {
135
 
                # map GtkSourceView2 scheme styles to colordiff names
136
 
                # since GSV is richer, accept new names for extra bits,
137
 
                # defaulting to old names if they're not present
138
 
                'diff:added-line': ['newtext'],
139
 
                'diff:removed-line': ['oldtext'],
140
 
                'diff:location': ['location', 'diffstuff'],
141
 
                'diff:file': ['file', 'diffstuff'],
142
 
                'diff:special-case': ['specialcase', 'diffstuff'],
143
 
            }
144
 
            
145
 
            converted_colors = {}
146
 
            for name, values in mapping.items():
147
 
                color = None
148
 
                for value in values:
149
 
                    color = colors.get(value, None)
150
 
                    if color is not None:
151
 
                        break
152
 
                if color is None:
153
 
                    continue
154
 
                converted_colors[name] = color
155
 
            
156
 
            # some xml magic to produce needed style scheme description
157
 
            e_style_scheme = Element('style-scheme')
158
 
            e_style_scheme.set('id', 'colordiff')
159
 
            e_style_scheme.set('_name', 'ColorDiff')
160
 
            e_style_scheme.set('version', '1.0')
161
 
            for name, color in converted_colors.items():
162
 
                style = SubElement(e_style_scheme, 'style')
163
 
                style.set('name', name)
164
 
                style.set('foreground', '#%s' % color)
165
 
            
166
 
            scheme_xml = tostring(e_style_scheme, 'UTF-8')
167
 
            if not os.path.exists(os.path.expanduser('~/.local/share/gtksourceview-2.0/styles')):
168
 
                os.makedirs(os.path.expanduser('~/.local/share/gtksourceview-2.0/styles'))
169
 
            file(os.path.expanduser('~/.local/share/gtksourceview-2.0/styles/colordiff.xml'), 'w').write(scheme_xml)
170
 
            
171
 
            scheme_manager.force_rescan()
172
 
            style_scheme = scheme_manager.get_scheme('colordiff')
173
 
        
174
 
        buf.set_style_scheme(style_scheme)
175
 
 
176
 
    @staticmethod
177
 
    def parse_colordiffrc(fileobj):
178
 
        """Parse fileobj as a colordiff configuration file.
179
 
 
180
 
        :return: A dict with the key -> value pairs.
181
 
        """
182
 
        colors = {}
183
 
        for line in fileobj:
184
 
            if re.match(r'^\s*#', line):
185
 
                continue
186
 
            if '=' not in line:
187
 
                continue
188
 
            key, val = line.split('=', 1)
189
 
            colors[key.strip()] = val.strip()
190
 
        return colors
191
 
 
192
 
    def set_trees(self, rev_tree, parent_tree):
193
 
        self.rev_tree = rev_tree
194
 
        self.parent_tree = parent_tree
195
 
#        self._build_delta()
196
 
 
197
 
#    def _build_delta(self):
198
 
#        self.parent_tree.lock_read()
199
 
#        self.rev_tree.lock_read()
200
 
#        try:
201
 
#            self.delta = iter_changes_to_status(self.parent_tree, self.rev_tree)
202
 
#            self.path_to_status = {}
203
 
#            self.path_to_diff = {}
204
 
#            source_inv = self.parent_tree.inventory
205
 
#            target_inv = self.rev_tree.inventory
206
 
#            for (file_id, real_path, change_type, display_path) in self.delta:
207
 
#                self.path_to_status[real_path] = u'=== %s %s' % (change_type, display_path)
208
 
#                if change_type in ('modified', 'renamed and modified'):
209
 
#                    source_ie = source_inv[file_id]
210
 
#                    target_ie = target_inv[file_id]
211
 
#                    sio = StringIO()
212
 
#                    source_ie.diff(internal_diff, *old path, *old_tree,
213
 
#                                   *new_path, target_ie, self.rev_tree,
214
 
#                                   sio)
215
 
#                    self.path_to_diff[real_path] = 
216
 
#
217
 
#        finally:
218
 
#            self.rev_tree.unlock()
219
 
#            self.parent_tree.unlock()
220
 
 
221
 
    def show_diff(self, specific_files):
222
 
        sections = []
223
 
        if specific_files is None:
224
 
            self.buffer.set_text(self._diffs[None])
225
 
        else:
226
 
            for specific_file in specific_files:
227
 
                sections.append(self._diffs[specific_file])
228
 
            self.buffer.set_text(''.join(sections))
229
 
 
230
 
 
231
 
class DiffView(DiffFileView):
232
 
    """This is the soft and chewy filling for a DiffWindow."""
233
 
 
234
 
    def __init__(self):
235
 
        DiffFileView.__init__(self)
236
 
        self.rev_tree = None
237
 
        self.parent_tree = None
238
 
 
239
 
    def show_diff(self, specific_files):
240
 
        """Show the diff for the specified files"""
241
 
        s = StringIO()
242
 
        show_diff_trees(self.parent_tree, self.rev_tree, s, specific_files,
243
 
                        old_label='', new_label='',
244
 
                        # path_encoding=sys.getdefaultencoding()
245
 
                        # The default is utf-8, but we interpret the file
246
 
                        # contents as getdefaultencoding(), so we should
247
 
                        # probably try to make the paths in the same encoding.
248
 
                        )
249
 
        # str.decode(encoding, 'replace') doesn't do anything. Because if a
250
 
        # character is not valid in 'encoding' there is nothing to replace, the
251
 
        # 'replace' is for 'str.encode()'
252
 
        try:
253
 
            decoded = s.getvalue().decode(sys.getdefaultencoding())
254
 
        except UnicodeDecodeError:
255
 
            try:
256
 
                decoded = s.getvalue().decode('UTF-8')
257
 
            except UnicodeDecodeError:
258
 
                decoded = s.getvalue().decode('iso-8859-1')
259
 
                # This always works, because every byte has a valid
260
 
                # mapping from iso-8859-1 to Unicode
261
 
        # TextBuffer must contain pure UTF-8 data
262
 
        self.buffer.set_text(decoded.encode('UTF-8'))
263
 
 
264
 
 
265
 
class DiffWidget(gtk.HPaned):
266
 
    """Diff widget
267
 
 
268
 
    """
269
 
    def __init__(self):
270
 
        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()
271
60
 
272
61
        # The file hierarchy: a scrollable treeview
273
62
        scrollwin = gtk.ScrolledWindow()
274
63
        scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
275
64
        scrollwin.set_shadow_type(gtk.SHADOW_IN)
276
 
        self.pack1(scrollwin)
 
65
        pane.pack1(scrollwin)
277
66
        scrollwin.show()
278
 
        
 
67
 
279
68
        self.model = gtk.TreeStore(str, str)
280
69
        self.treeview = gtk.TreeView(self.model)
281
70
        self.treeview.set_headers_visible(False)
291
80
        column.add_attribute(cell, "text", 0)
292
81
        self.treeview.append_column(column)
293
82
 
294
 
    def set_diff_text(self, lines):
295
 
        """Set the current diff from a list of lines
296
 
 
297
 
        :param lines: The diff to show, in unified diff format
298
 
        """
299
83
        # The diffs of the  selected file: a scrollable source or
300
84
        # text view
301
 
 
302
 
    def set_diff_text_sections(self, sections):
303
 
        if getattr(self, 'diff_view', None) is None:
304
 
            self.diff_view = DiffFileView()
305
 
            self.pack2(self.diff_view)
306
 
        self.diff_view.show()
307
 
        for oldname, newname, patch in sections:
308
 
            self.diff_view._diffs[newname] = str(patch)
309
 
            if newname is None:
310
 
                newname = ''
311
 
            self.model.append(None, [oldname, newname])
312
 
        self.diff_view.show_diff(None)
313
 
 
314
 
    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):
315
109
        """Set the differences showed by this window.
316
110
 
317
111
        Compares the two trees and populates the window with the
318
112
        differences.
319
113
        """
320
 
        if getattr(self, 'diff_view', None) is None:
321
 
            self.diff_view = DiffView()
322
 
            self.pack2(self.diff_view)
323
 
        self.diff_view.show()
324
 
        self.diff_view.set_trees(rev_tree, parent_tree)
325
114
        self.rev_tree = rev_tree
326
115
        self.parent_tree = parent_tree
327
116
 
352
141
                self.model.append(titer, [ path, path ])
353
142
 
354
143
        self.treeview.expand_all()
355
 
        self.diff_view.show_diff(None)
 
144
        self.set_title(description + " - bzrk diff")
356
145
 
357
146
    def set_file(self, file_path):
358
 
        """Select the current file to display"""
359
147
        tv_path = None
360
148
        for data in self.model:
361
149
            for child in data.iterchildren():
374
162
        if specific_files == [ None ]:
375
163
            return
376
164
        elif specific_files == [ "" ]:
377
 
            specific_files = None
378
 
        
379
 
        self.diff_view.show_diff(specific_files)
380
 
    
381
 
    def _on_wraplines_toggled(self, widget=None, wrap=False):
382
 
        """Callback for when the wrap lines checkbutton is toggled"""
383
 
        if wrap or widget.get_active():
384
 
            self.diff_view.sourceview.set_wrap_mode(gtk.WRAP_WORD)
385
 
        else:
386
 
            self.diff_view.sourceview.set_wrap_mode(gtk.WRAP_NONE)
387
 
 
388
 
class DiffWindow(Window):
389
 
    """Diff window.
390
 
 
391
 
    This object represents and manages a single window containing the
392
 
    differences between two revisions on a branch.
393
 
    """
394
 
 
395
 
    def __init__(self, parent=None, operations=None):
396
 
        Window.__init__(self, parent)
397
 
        self.set_border_width(0)
398
 
        self.set_title("bzrk diff")
399
 
 
400
 
        # Use two thirds of the screen by default
401
 
        screen = self.get_screen()
402
 
        monitor = screen.get_monitor_geometry(0)
403
 
        width = int(monitor.width * 0.66)
404
 
        height = int(monitor.height * 0.66)
405
 
        self.set_default_size(width, height)
406
 
        self.construct(operations)
407
 
 
408
 
    def construct(self, operations):
409
 
        """Construct the window contents."""
410
 
        self.vbox = gtk.VBox()
411
 
        self.add(self.vbox)
412
 
        self.vbox.show()
413
 
        self.diff = DiffWidget()
414
 
        self.vbox.pack_end(self.diff, True, True, 0)
415
 
        self.diff.show_all()
416
 
        # Build after DiffWidget to connect signals
417
 
        menubar = self._get_menu_bar()
418
 
        self.vbox.pack_start(menubar, False, False, 0)
419
 
        hbox = self._get_button_bar(operations)
420
 
        if hbox is not None:
421
 
            self.vbox.pack_start(hbox, False, True, 0)
422
 
        
423
 
    
424
 
    def _get_menu_bar(self):
425
 
        menubar = gtk.MenuBar()
426
 
        # View menu
427
 
        mb_view = gtk.MenuItem(_i18n("_View"))
428
 
        mb_view_menu = gtk.Menu()
429
 
        mb_view_wrapsource = gtk.CheckMenuItem(_i18n("Wrap _Long Lines"))
430
 
        mb_view_wrapsource.connect('activate', self.diff._on_wraplines_toggled)
431
 
        mb_view_wrapsource.show()
432
 
        mb_view_menu.append(mb_view_wrapsource)
433
 
        mb_view.show()
434
 
        mb_view.set_submenu(mb_view_menu)
435
 
        mb_view.show()
436
 
        menubar.append(mb_view)
437
 
        menubar.show()
438
 
        return menubar
439
 
    
440
 
    def _get_button_bar(self, operations):
441
 
        """Return a button bar to use.
442
 
 
443
 
        :return: None, meaning that no button bar will be used.
444
 
        """
445
 
        if operations is None:
446
 
            return None
447
 
        hbox = gtk.HButtonBox()
448
 
        hbox.set_layout(gtk.BUTTONBOX_START)
449
 
        for title, method in operations:
450
 
            merge_button = gtk.Button(title)
451
 
            merge_button.show()
452
 
            merge_button.set_relief(gtk.RELIEF_NONE)
453
 
            merge_button.connect("clicked", method)
454
 
            hbox.pack_start(merge_button, expand=False, fill=True)
455
 
        hbox.show()
456
 
        return hbox
457
 
 
458
 
    def _get_merge_target(self):
459
 
        d = gtk.FileChooserDialog('Merge branch', self,
460
 
                                  gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
461
 
                                  buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
462
 
                                           gtk.STOCK_CANCEL,
463
 
                                           gtk.RESPONSE_CANCEL,))
464
 
        try:
465
 
            result = d.run()
466
 
            if result != gtk.RESPONSE_OK:
467
 
                raise SelectCancelled()
468
 
            return d.get_current_folder_uri()
469
 
        finally:
470
 
            d.destroy()
471
 
 
472
 
    def _merge_successful(self):
473
 
        # No conflicts found.
474
 
        info_dialog(_i18n('Merge successful'),
475
 
                    _i18n('All changes applied successfully.'))
476
 
 
477
 
    def _conflicts(self):
478
 
        warning_dialog(_i18n('Conflicts encountered'),
479
 
                       _i18n('Please resolve the conflicts manually'
480
 
                             ' before committing.'))
481
 
 
482
 
    def _handle_error(self, e):
483
 
        error_dialog('Error', str(e))
484
 
 
485
 
    def _get_save_path(self, basename):
486
 
        d = gtk.FileChooserDialog('Save As', self,
487
 
                                  gtk.FILE_CHOOSER_ACTION_SAVE,
488
 
                                  buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
489
 
                                           gtk.STOCK_CANCEL,
490
 
                                           gtk.RESPONSE_CANCEL,))
491
 
        d.set_current_name(basename)
492
 
        try:
493
 
            result = d.run()
494
 
            if result != gtk.RESPONSE_OK:
495
 
                raise SelectCancelled()
496
 
            return urlutils.local_path_from_url(d.get_uri())
497
 
        finally:
498
 
            d.destroy()
499
 
 
500
 
    def set_diff(self, description, rev_tree, parent_tree):
501
 
        """Set the differences showed by this window.
502
 
 
503
 
        Compares the two trees and populates the window with the
504
 
        differences.
505
 
        """
506
 
        self.diff.set_diff(rev_tree, parent_tree)
507
 
        self.set_title(description + " - bzrk diff")
508
 
 
509
 
    def set_file(self, file_path):
510
 
        self.diff.set_file(file_path)
511
 
 
512
 
 
513
 
class DiffController(object):
514
 
 
515
 
    def __init__(self, path, patch, window=None):
516
 
        self.path = path
517
 
        self.patch = patch
518
 
        if window is None:
519
 
            window = DiffWindow(operations=self._provide_operations())
520
 
            self.initialize_window(window)
521
 
        self.window = window
522
 
 
523
 
    def initialize_window(self, window):
524
 
        window.diff.set_diff_text_sections(self.get_diff_sections())
525
 
        window.set_title(self.path + " - diff")
526
 
 
527
 
    def get_diff_sections(self):
528
 
        yield "Complete Diff", None, ''.join(self.patch)
529
 
        for patch in parse_patches(self.patch):
530
 
            oldname = patch.oldname.split('\t')[0]
531
 
            newname = patch.newname.split('\t')[0]
532
 
            yield oldname, newname, str(patch)
533
 
 
534
 
    def perform_save(self, window):
535
 
        try:
536
 
            save_path = self.window._get_save_path(osutils.basename(self.path))
537
 
        except SelectCancelled:
538
 
            return
539
 
        source = open(self.path, 'rb')
540
 
        try:
541
 
            target = open(save_path, 'wb')
542
 
            try:
543
 
                osutils.pumpfile(source, target)
544
 
            finally:
545
 
                target.close()
546
 
        finally:
547
 
            source.close()
548
 
 
549
 
    def _provide_operations(self):
550
 
        return [('Save', self.perform_save)]
551
 
 
552
 
 
553
 
class MergeDirectiveController(DiffController):
554
 
 
555
 
    def __init__(self, path, directive, window=None):
556
 
        DiffController.__init__(self, path, directive.patch.splitlines(True),
557
 
                                window)
558
 
        self.directive = directive
559
 
        self.merge_target = None
560
 
 
561
 
    def _provide_operations(self):
562
 
        return [('Merge', self.perform_merge), ('Save', self.perform_save)]
563
 
 
564
 
    def perform_merge(self, window):
565
 
        if self.merge_target is None:
566
 
            try:
567
 
                self.merge_target = self.window._get_merge_target()
568
 
            except SelectCancelled:
569
 
                return
570
 
        tree = workingtree.WorkingTree.open(self.merge_target)
571
 
        tree.lock_write()
572
 
        try:
573
 
            try:
574
 
                merger, verified = _mod_merge.Merger.from_mergeable(tree,
575
 
                    self.directive, progress.DummyProgress())
576
 
                merger.check_basis(True)
577
 
                merger.merge_type = _mod_merge.Merge3Merger
578
 
                conflict_count = merger.do_merge()
579
 
                merger.set_pending()
580
 
                if conflict_count == 0:
581
 
                    self.window._merge_successful()
582
 
                else:
583
 
                    self.window._conflicts()
584
 
                    # There are conflicts to be resolved.
585
 
                self.window.destroy()
586
 
            except Exception, e:
587
 
                self.window._handle_error(e)
588
 
        finally:
589
 
            tree.unlock()
590
 
 
591
 
 
592
 
def iter_changes_to_status(source, target):
593
 
    """Determine the differences between trees.
594
 
 
595
 
    This is a wrapper around iter_changes which just yields more
596
 
    understandable results.
597
 
 
598
 
    :param source: The source tree (basis tree)
599
 
    :param target: The target tree
600
 
    :return: A list of (file_id, real_path, change_type, display_path)
601
 
    """
602
 
    added = 'added'
603
 
    removed = 'removed'
604
 
    renamed = 'renamed'
605
 
    renamed_and_modified = 'renamed and modified'
606
 
    modified = 'modified'
607
 
    kind_changed = 'kind changed'
608
 
    missing = 'missing'
609
 
 
610
 
    # TODO: Handle metadata changes
611
 
 
612
 
    status = []
613
 
    target.lock_read()
614
 
    try:
615
 
        source.lock_read()
616
 
        try:
617
 
            for (file_id, paths, changed_content, versioned, parent_ids, names,
618
 
                 kinds, executables) in target.iter_changes(source):
619
 
 
620
 
                # Skip the root entry if it isn't very interesting
621
 
                if parent_ids == (None, None):
622
 
                    continue
623
 
 
624
 
                change_type = None
625
 
                if kinds[0] is None:
626
 
                    source_marker = ''
627
 
                else:
628
 
                    source_marker = osutils.kind_marker(kinds[0])
629
 
 
630
 
                if kinds[1] is None:
631
 
                    if kinds[0] is None:
632
 
                        # We assume bzr will flag only files in that case,
633
 
                        # there may be a bzr bug there as only files seems to
634
 
                        # not receive any kind.
635
 
                        marker = osutils.kind_marker('file')
636
 
                    else:
637
 
                        marker = osutils.kind_marker(kinds[0])
638
 
                else:
639
 
                    marker = osutils.kind_marker(kinds[1])
640
 
 
641
 
                real_path = paths[1]
642
 
                if real_path is None:
643
 
                    real_path = paths[0]
644
 
                assert real_path is not None
645
 
 
646
 
                present_source = versioned[0] and kinds[0] is not None
647
 
                present_target = versioned[1] and kinds[1] is not None
648
 
 
649
 
                if kinds[0] is None and kinds[1] is None:
650
 
                    change_type = missing
651
 
                    display_path = real_path + marker
652
 
                elif present_source != present_target:
653
 
                    if present_target:
654
 
                        change_type = added
655
 
                    else:
656
 
                        assert present_source
657
 
                        change_type = removed
658
 
                    display_path = real_path + marker
659
 
                elif names[0] != names[1] or parent_ids[0] != parent_ids[1]:
660
 
                    # Renamed
661
 
                    if changed_content or executables[0] != executables[1]:
662
 
                        # and modified
663
 
                        change_type = renamed_and_modified
664
 
                    else:
665
 
                        change_type = renamed
666
 
                    display_path = (paths[0] + source_marker
667
 
                                    + ' => ' + paths[1] + marker)
668
 
                elif kinds[0] != kinds[1]:
669
 
                    change_type = kind_changed
670
 
                    display_path = (paths[0] + source_marker
671
 
                                    + ' => ' + paths[1] + marker)
672
 
                elif changed_content or executables[0] != executables[1]:
673
 
                    change_type = modified
674
 
                    display_path = real_path + marker
675
 
                else:
676
 
                    assert False, "How did we get here?"
677
 
 
678
 
                status.append((file_id, real_path, change_type, display_path))
679
 
        finally:
680
 
            source.unlock()
681
 
    finally:
682
 
        target.unlock()
683
 
 
684
 
    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'))