/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: Curtis Hovey
  • Date: 2012-01-23 18:10:44 UTC
  • mto: This revision was merged to the branch mainline in revision 772.
  • Revision ID: sinzui.is@verizon.net-20120123181044-iflf8h79dg6y8fit
Update test to verify that the correct text is shown.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- coding: UTF-8 -*-
2
1
"""Difference window.
3
2
 
4
3
This module contains the code to manage the diff window which shows
5
4
the changes made between two revisions on a branch.
6
5
"""
7
6
 
8
 
__copyright__ = "Copyright © 2005 Canonical Ltd."
 
7
__copyright__ = "Copyright 2005 Canonical Ltd."
9
8
__author__    = "Scott James Remnant <scott@ubuntu.com>"
10
9
 
11
10
 
12
11
from cStringIO import StringIO
13
12
 
14
 
import pygtk
15
 
pygtk.require("2.0")
16
 
import gtk
17
 
import pango
 
13
from gi.repository import Gtk
 
14
from gi.repository import Pango
18
15
import os
19
16
import re
20
17
import sys
 
18
import inspect
 
19
try:
 
20
    from xml.etree.ElementTree import Element, SubElement, tostring
 
21
except ImportError:
 
22
    from elementtree.ElementTree import Element, SubElement, tostring
21
23
 
22
24
try:
23
 
    import gtksourceview
 
25
    from gi.repository import GtkSource
24
26
    have_gtksourceview = True
25
27
except ImportError:
26
28
    have_gtksourceview = False
27
29
try:
28
 
    import gconf
 
30
    from gi.repository import GConf
29
31
    have_gconf = True
30
32
except ImportError:
31
33
    have_gconf = False
32
34
 
33
35
from bzrlib import (
 
36
    errors,
34
37
    merge as _mod_merge,
35
38
    osutils,
36
 
    progress,
37
39
    urlutils,
38
40
    workingtree,
39
41
)
40
 
from bzrlib.diff import show_diff_trees, internal_diff
41
 
from bzrlib.errors import NoSuchFile
 
42
from bzrlib.diff import show_diff_trees
42
43
from bzrlib.patches import parse_patches
43
44
from bzrlib.trace import warning
44
 
from bzrlib.plugins.gtk import _i18n
 
45
from bzrlib.plugins.gtk.dialog import (
 
46
    error_dialog,
 
47
    info_dialog,
 
48
    warning_dialog,
 
49
    )
 
50
from bzrlib.plugins.gtk.i18n import _i18n
45
51
from bzrlib.plugins.gtk.window import Window
46
 
from dialog import error_dialog, info_dialog, warning_dialog
 
52
 
 
53
 
 
54
def fallback_guess_language(slm, content_type):
 
55
    for lang_id in slm.get_language_ids():
 
56
        lang = slm.get_language(lang_id)
 
57
        if "text/x-patch" in lang.get_mime_types():
 
58
            return lang
 
59
    return None
47
60
 
48
61
 
49
62
class SelectCancelled(Exception):
51
64
    pass
52
65
 
53
66
 
54
 
class DiffFileView(gtk.ScrolledWindow):
 
67
class DiffFileView(Gtk.ScrolledWindow):
55
68
    """Window for displaying diffs from a diff file"""
56
69
 
57
70
    def __init__(self):
58
 
        gtk.ScrolledWindow.__init__(self)
 
71
        super(DiffFileView, self).__init__()
59
72
        self.construct()
60
73
        self._diffs = {}
61
74
 
62
75
    def construct(self):
63
 
        self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
64
 
        self.set_shadow_type(gtk.SHADOW_IN)
 
76
        self.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
 
77
        self.set_shadow_type(Gtk.ShadowType.IN)
65
78
 
66
79
        if have_gtksourceview:
67
 
            self.buffer = gtksourceview.SourceBuffer()
68
 
            slm = gtksourceview.SourceLanguagesManager()
69
 
            gsl = slm.get_language_from_mime_type("text/x-patch")
 
80
            self.buffer = GtkSource.Buffer()
 
81
            lang_manager = GtkSource.LanguageManager.get_default()
 
82
            language = lang_manager.guess_language(None, "text/x-patch")
70
83
            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)
 
84
                self.apply_gedit_colors(self.buffer)
 
85
            self.apply_colordiff_colors(self.buffer)
 
86
            self.buffer.set_language(language)
 
87
            self.buffer.set_highlight_syntax(True)
75
88
 
76
 
            sourceview = gtksourceview.SourceView(self.buffer)
 
89
            self.sourceview = GtkSource.View(buffer=self.buffer)
77
90
        else:
78
 
            self.buffer = gtk.TextBuffer()
79
 
            sourceview = gtk.TextView(self.buffer)
 
91
            self.buffer = Gtk.TextBuffer()
 
92
            self.sourceview = Gtk.TextView(self.buffer)
80
93
 
81
 
        sourceview.set_editable(False)
82
 
        sourceview.modify_font(pango.FontDescription("Monospace"))
83
 
        self.add(sourceview)
84
 
        sourceview.show()
 
94
        self.sourceview.set_editable(False)
 
95
        self.sourceview.modify_font(Pango.FontDescription("Monospace"))
 
96
        self.add(self.sourceview)
 
97
        self.sourceview.show()
85
98
 
86
99
    @staticmethod
87
 
    def apply_gedit_colors(lang):
88
 
        """Set style for lang to that specified in gedit configuration.
 
100
    def apply_gedit_colors(buf):
 
101
        """Set style to that specified in gedit configuration.
89
102
 
90
103
        This method needs the gconf module.
91
104
 
92
 
        :param lang: a gtksourceview.SourceLanguage object.
 
105
        :param buf: a GtkSource.Buffer object.
93
106
        """
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)
 
107
        GEDIT_SCHEME_PATH = '/apps/gedit-2/preferences/editor/colors/scheme'
 
108
        GEDIT_USER_STYLES_PATH = os.path.expanduser('~/.gnome2/gedit/styles')
 
109
 
 
110
        client = GConf.Client.get_default()
 
111
        style_scheme_name = client.get_string(GEDIT_SCHEME_PATH)
 
112
        if style_scheme_name is not None:
 
113
            style_scheme_mgr = GtkSource.StyleSchemeManager()
 
114
            style_scheme_mgr.append_search_path(GEDIT_USER_STYLES_PATH)
 
115
            
 
116
            style_scheme = style_scheme_mgr.get_scheme(style_scheme_name)
 
117
            
 
118
            if style_scheme is not None:
 
119
                buf.set_style_scheme(style_scheme)
148
120
 
149
121
    @classmethod
150
 
    def apply_colordiff_colors(klass, lang):
 
122
    def apply_colordiff_colors(klass, buf):
151
123
        """Set style colors for lang using the colordiff configuration file.
152
124
 
153
125
        Both ~/.colordiffrc and ~/.colordiffrc.bzr-gtk are read.
154
126
 
155
 
        :param lang: a "Diff" gtksourceview.SourceLanguage object.
 
127
        :param buf: a "Diff" GtkSource.Buffer object.
156
128
        """
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
 
129
        scheme_manager = GtkSource.StyleSchemeManager()
 
130
        style_scheme = scheme_manager.get_scheme('colordiff')
 
131
        
 
132
        # if style scheme not found, we'll generate it from colordiffrc
 
133
        # TODO: reload if colordiffrc has changed.
 
134
        if style_scheme is None:
 
135
            colors = {}
 
136
 
 
137
            for f in ('~/.colordiffrc', '~/.colordiffrc.bzr-gtk'):
 
138
                f = os.path.expanduser(f)
 
139
                if os.path.exists(f):
 
140
                    try:
 
141
                        f = file(f)
 
142
                    except IOError, e:
 
143
                        warning('could not open file %s: %s' % (f, str(e)))
 
144
                    else:
 
145
                        colors.update(klass.parse_colordiffrc(f))
 
146
                        f.close()
 
147
 
 
148
            if not colors:
 
149
                # ~/.colordiffrc does not exist
 
150
                return
 
151
            
 
152
            mapping = {
 
153
                # map GtkSourceView2 scheme styles to colordiff names
176
154
                # since GSV is richer, accept new names for extra bits,
177
155
                # 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)
 
156
                'diff:added-line': ['newtext'],
 
157
                'diff:removed-line': ['oldtext'],
 
158
                'diff:location': ['location', 'diffstuff'],
 
159
                'diff:file': ['file', 'diffstuff'],
 
160
                'diff:special-case': ['specialcase', 'diffstuff'],
 
161
            }
 
162
            
 
163
            converted_colors = {}
 
164
            for name, values in mapping.items():
 
165
                color = None
 
166
                for value in values:
 
167
                    color = colors.get(value, None)
 
168
                    if color is not None:
 
169
                        break
 
170
                if color is None:
 
171
                    continue
 
172
                converted_colors[name] = color
 
173
            
 
174
            # some xml magic to produce needed style scheme description
 
175
            e_style_scheme = Element('style-scheme')
 
176
            e_style_scheme.set('id', 'colordiff')
 
177
            e_style_scheme.set('_name', 'ColorDiff')
 
178
            e_style_scheme.set('version', '1.0')
 
179
            for name, color in converted_colors.items():
 
180
                style = SubElement(e_style_scheme, 'style')
 
181
                style.set('name', name)
 
182
                style.set('foreground', '#%s' % color)
 
183
            
 
184
            scheme_xml = tostring(e_style_scheme, 'UTF-8')
 
185
            if not os.path.exists(os.path.expanduser('~/.local/share/gtksourceview-2.0/styles')):
 
186
                os.makedirs(os.path.expanduser('~/.local/share/gtksourceview-2.0/styles'))
 
187
            file(os.path.expanduser('~/.local/share/gtksourceview-2.0/styles/colordiff.xml'), 'w').write(scheme_xml)
 
188
            
 
189
            scheme_manager.force_rescan()
 
190
            style_scheme = scheme_manager.get_scheme('colordiff')
 
191
        
 
192
        buf.set_style_scheme(style_scheme)
205
193
 
206
194
    @staticmethod
207
195
    def parse_colordiffrc(fileobj):
262
250
    """This is the soft and chewy filling for a DiffWindow."""
263
251
 
264
252
    def __init__(self):
265
 
        DiffFileView.__init__(self)
 
253
        super(DiffView, self).__init__()
266
254
        self.rev_tree = None
267
255
        self.parent_tree = None
268
256
 
292
280
        self.buffer.set_text(decoded.encode('UTF-8'))
293
281
 
294
282
 
295
 
class DiffWidget(gtk.HPaned):
 
283
class DiffWidget(Gtk.HPaned):
296
284
    """Diff widget
297
285
 
298
286
    """
 
287
 
 
288
    SHOW_WIDGETS = True
 
289
 
299
290
    def __init__(self):
300
291
        super(DiffWidget, self).__init__()
301
292
 
302
293
        # The file hierarchy: a scrollable treeview
303
 
        scrollwin = gtk.ScrolledWindow()
304
 
        scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
305
 
        scrollwin.set_shadow_type(gtk.SHADOW_IN)
 
294
        scrollwin = Gtk.ScrolledWindow()
 
295
        scrollwin.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
 
296
        scrollwin.set_shadow_type(Gtk.ShadowType.IN)
306
297
        self.pack1(scrollwin)
307
 
        scrollwin.show()
308
 
 
309
 
        self.model = gtk.TreeStore(str, str)
310
 
        self.treeview = gtk.TreeView(self.model)
 
298
        if self.SHOW_WIDGETS:
 
299
            scrollwin.show()
 
300
        
 
301
        self.model = Gtk.TreeStore(str, str)
 
302
        self.treeview = Gtk.TreeView(model=self.model)
311
303
        self.treeview.set_headers_visible(False)
312
304
        self.treeview.set_search_column(1)
313
305
        self.treeview.connect("cursor-changed", self._treeview_cursor_cb)
314
306
        scrollwin.add(self.treeview)
315
 
        self.treeview.show()
 
307
        if self.SHOW_WIDGETS:
 
308
            self.treeview.show()
316
309
 
317
 
        cell = gtk.CellRendererText()
 
310
        cell = Gtk.CellRendererText()
318
311
        cell.set_property("width-chars", 20)
319
 
        column = gtk.TreeViewColumn()
320
 
        column.pack_start(cell, expand=True)
 
312
        column = Gtk.TreeViewColumn()
 
313
        column.pack_start(cell, True)
321
314
        column.add_attribute(cell, "text", 0)
322
315
        self.treeview.append_column(column)
323
316
 
330
323
        # text view
331
324
 
332
325
    def set_diff_text_sections(self, sections):
333
 
        self.diff_view = DiffFileView()
334
 
        self.diff_view.show()
335
 
        self.pack2(self.diff_view)
 
326
        if getattr(self, 'diff_view', None) is None:
 
327
            self.diff_view = DiffFileView()
 
328
            self.pack2(self.diff_view)
 
329
        if self.SHOW_WIDGETS:
 
330
            self.diff_view.show()
336
331
        for oldname, newname, patch in sections:
337
332
            self.diff_view._diffs[newname] = str(patch)
338
333
            if newname is None:
346
341
        Compares the two trees and populates the window with the
347
342
        differences.
348
343
        """
349
 
        self.diff_view = DiffView()
350
 
        self.pack2(self.diff_view)
351
 
        self.diff_view.show()
 
344
        if getattr(self, 'diff_view', None) is None:
 
345
            self.diff_view = DiffView()
 
346
            self.pack2(self.diff_view)
 
347
        if self.SHOW_WIDGETS:
 
348
            self.diff_view.show()
352
349
        self.diff_view.set_trees(rev_tree, parent_tree)
353
350
        self.rev_tree = rev_tree
354
351
        self.parent_tree = parent_tree
380
377
                self.model.append(titer, [ path, path ])
381
378
 
382
379
        self.treeview.expand_all()
 
380
        self.diff_view.show_diff(None)
383
381
 
384
382
    def set_file(self, file_path):
385
383
        """Select the current file to display"""
390
388
                    tv_path = child.path
391
389
                    break
392
390
        if tv_path is None:
393
 
            raise NoSuchFile(file_path)
394
 
        self.treeview.set_cursor(tv_path)
 
391
            raise errors.NoSuchFile(file_path)
 
392
        self.treeview.set_cursor(tv_path, None, False)
395
393
        self.treeview.scroll_to_cell(tv_path)
396
394
 
397
395
    def _treeview_cursor_cb(self, *args):
398
396
        """Callback for when the treeview cursor changes."""
399
397
        (path, col) = self.treeview.get_cursor()
 
398
        if path is None:
 
399
            return
400
400
        specific_files = [ self.model[path][1] ]
401
401
        if specific_files == [ None ]:
402
402
            return
403
403
        elif specific_files == [ "" ]:
404
404
            specific_files = None
405
 
 
 
405
        
406
406
        self.diff_view.show_diff(specific_files)
407
 
 
 
407
    
 
408
    def _on_wraplines_toggled(self, widget=None, wrap=False):
 
409
        """Callback for when the wrap lines checkbutton is toggled"""
 
410
        if wrap or widget.get_active():
 
411
            self.diff_view.sourceview.set_wrap_mode(Gtk.WrapMode.WORD)
 
412
        else:
 
413
            self.diff_view.sourceview.set_wrap_mode(Gtk.WrapMode.NONE)
408
414
 
409
415
class DiffWindow(Window):
410
416
    """Diff window.
414
420
    """
415
421
 
416
422
    def __init__(self, parent=None, operations=None):
417
 
        Window.__init__(self, parent)
 
423
        super(DiffWindow, self).__init__(parent=parent)
418
424
        self.set_border_width(0)
419
425
        self.set_title("bzrk diff")
420
426
 
428
434
 
429
435
    def construct(self, operations):
430
436
        """Construct the window contents."""
431
 
        self.vbox = gtk.VBox()
 
437
        self.vbox = Gtk.VBox()
432
438
        self.add(self.vbox)
433
439
        self.vbox.show()
 
440
        self.diff = DiffWidget()
 
441
        self.vbox.pack_end(self.diff, True, True, 0)
 
442
        self.diff.show_all()
 
443
        # Build after DiffWidget to connect signals
 
444
        menubar = self._get_menu_bar()
 
445
        self.vbox.pack_start(menubar, False, False, 0)
434
446
        hbox = self._get_button_bar(operations)
435
447
        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
 
 
 
448
            self.vbox.pack_start(hbox, False, True, 0)
 
449
        
 
450
    
 
451
    def _get_menu_bar(self):
 
452
        menubar = Gtk.MenuBar()
 
453
        # View menu
 
454
        mb_view = Gtk.MenuItem.new_with_mnemonic(_i18n("_View"))
 
455
        mb_view_menu = Gtk.Menu()
 
456
        mb_view_wrapsource = Gtk.CheckMenuItem.new_with_mnemonic(
 
457
            _i18n("Wrap _Long Lines"))
 
458
        mb_view_wrapsource.connect('activate', self.diff._on_wraplines_toggled)
 
459
        mb_view_wrapsource.show()
 
460
        mb_view_menu.append(mb_view_wrapsource)
 
461
        mb_view.show()
 
462
        mb_view.set_submenu(mb_view_menu)
 
463
        mb_view.show()
 
464
        menubar.append(mb_view)
 
465
        menubar.show()
 
466
        return menubar
 
467
    
441
468
    def _get_button_bar(self, operations):
442
469
        """Return a button bar to use.
443
470
 
445
472
        """
446
473
        if operations is None:
447
474
            return None
448
 
        hbox = gtk.HButtonBox()
449
 
        hbox.set_layout(gtk.BUTTONBOX_START)
 
475
        hbox = Gtk.HButtonBox()
 
476
        hbox.set_layout(Gtk.ButtonBoxStyle.START)
450
477
        for title, method in operations:
451
 
            merge_button = gtk.Button(title)
 
478
            merge_button = Gtk.Button(title)
452
479
            merge_button.show()
453
 
            merge_button.set_relief(gtk.RELIEF_NONE)
 
480
            merge_button.set_relief(Gtk.ReliefStyle.NONE)
454
481
            merge_button.connect("clicked", method)
455
482
            hbox.pack_start(merge_button, expand=False, fill=True)
456
483
        hbox.show()
457
484
        return hbox
458
485
 
459
486
    def _get_merge_target(self):
460
 
        d = gtk.FileChooserDialog('Merge branch', self,
461
 
                                  gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
462
 
                                  buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
463
 
                                           gtk.STOCK_CANCEL,
464
 
                                           gtk.RESPONSE_CANCEL,))
 
487
        d = Gtk.FileChooserDialog('Merge branch', self,
 
488
                                  Gtk.FileChooserAction.SELECT_FOLDER,
 
489
                                  buttons=(Gtk.STOCK_OK, Gtk.ResponseType.OK,
 
490
                                           Gtk.STOCK_CANCEL,
 
491
                                           Gtk.ResponseType.CANCEL,))
465
492
        try:
466
493
            result = d.run()
467
 
            if result != gtk.RESPONSE_OK:
 
494
            if result != Gtk.ResponseType.OK:
468
495
                raise SelectCancelled()
469
496
            return d.get_current_folder_uri()
470
497
        finally:
484
511
        error_dialog('Error', str(e))
485
512
 
486
513
    def _get_save_path(self, basename):
487
 
        d = gtk.FileChooserDialog('Save As', self,
488
 
                                  gtk.FILE_CHOOSER_ACTION_SAVE,
489
 
                                  buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
490
 
                                           gtk.STOCK_CANCEL,
491
 
                                           gtk.RESPONSE_CANCEL,))
 
514
        d = Gtk.FileChooserDialog('Save As', self,
 
515
                                  Gtk.FileChooserAction.SAVE,
 
516
                                  buttons=(Gtk.STOCK_OK, Gtk.ResponseType.OK,
 
517
                                           Gtk.STOCK_CANCEL,
 
518
                                           Gtk.ResponseType.CANCEL,))
492
519
        d.set_current_name(basename)
493
520
        try:
494
521
            result = d.run()
495
 
            if result != gtk.RESPONSE_OK:
 
522
            if result != Gtk.ResponseType.OK:
496
523
                raise SelectCancelled()
497
524
            return urlutils.local_path_from_url(d.get_uri())
498
525
        finally:
513
540
 
514
541
class DiffController(object):
515
542
 
516
 
    def __init__(self, path, patch, window=None):
 
543
    def __init__(self, path, patch, window=None, allow_dirty=False):
517
544
        self.path = path
518
545
        self.patch = patch
 
546
        self.allow_dirty = allow_dirty
519
547
        if window is None:
520
548
            window = DiffWindow(operations=self._provide_operations())
521
549
            self.initialize_window(window)
527
555
 
528
556
    def get_diff_sections(self):
529
557
        yield "Complete Diff", None, ''.join(self.patch)
530
 
        for patch in parse_patches(self.patch):
 
558
        # allow_dirty was added to parse_patches in bzrlib 2.2b1
 
559
        if 'allow_dirty' in inspect.getargspec(parse_patches).args:
 
560
            patches = parse_patches(self.patch, allow_dirty=self.allow_dirty)
 
561
        else:
 
562
            patches = parse_patches(self.patch)
 
563
        for patch in patches:
531
564
            oldname = patch.oldname.split('\t')[0]
532
565
            newname = patch.newname.split('\t')[0]
533
566
            yield oldname, newname, str(patch)
554
587
class MergeDirectiveController(DiffController):
555
588
 
556
589
    def __init__(self, path, directive, window=None):
557
 
        DiffController.__init__(self, path, directive.patch.splitlines(True),
558
 
                                window)
 
590
        super(MergeDirectiveController, self).__init__(
 
591
            path, directive.patch.splitlines(True), window)
559
592
        self.directive = directive
560
593
        self.merge_target = None
561
594
 
572
605
        tree.lock_write()
573
606
        try:
574
607
            try:
575
 
                merger, verified = _mod_merge.Merger.from_mergeable(tree,
576
 
                    self.directive, progress.DummyProgress())
577
 
                merger.check_basis(True)
 
608
                if tree.has_changes():
 
609
                    raise errors.UncommittedChanges(tree)
 
610
                merger, verified = _mod_merge.Merger.from_mergeable(
 
611
                    tree, self.directive, pb=None)
578
612
                merger.merge_type = _mod_merge.Merge3Merger
579
613
                conflict_count = merger.do_merge()
580
614
                merger.set_pending()
606
640
    renamed_and_modified = 'renamed and modified'
607
641
    modified = 'modified'
608
642
    kind_changed = 'kind changed'
 
643
    missing = 'missing'
609
644
 
610
645
    # TODO: Handle metadata changes
611
646
 
626
661
                    source_marker = ''
627
662
                else:
628
663
                    source_marker = osutils.kind_marker(kinds[0])
 
664
 
629
665
                if kinds[1] is None:
630
 
                    assert kinds[0] is not None
631
 
                    marker = osutils.kind_marker(kinds[0])
 
666
                    if kinds[0] is None:
 
667
                        # We assume bzr will flag only files in that case,
 
668
                        # there may be a bzr bug there as only files seems to
 
669
                        # not receive any kind.
 
670
                        marker = osutils.kind_marker('file')
 
671
                    else:
 
672
                        marker = osutils.kind_marker(kinds[0])
632
673
                else:
633
674
                    marker = osutils.kind_marker(kinds[1])
634
675
 
636
677
                if real_path is None:
637
678
                    real_path = paths[0]
638
679
                assert real_path is not None
639
 
                display_path = real_path + marker
640
680
 
641
681
                present_source = versioned[0] and kinds[0] is not None
642
682
                present_target = versioned[1] and kinds[1] is not None
643
683
 
644
 
                if present_source != present_target:
 
684
                if kinds[0] is None and kinds[1] is None:
 
685
                    change_type = missing
 
686
                    display_path = real_path + marker
 
687
                elif present_source != present_target:
645
688
                    if present_target:
646
689
                        change_type = added
647
690
                    else:
648
691
                        assert present_source
649
692
                        change_type = removed
 
693
                    display_path = real_path + marker
650
694
                elif names[0] != names[1] or parent_ids[0] != parent_ids[1]:
651
695
                    # Renamed
652
696
                    if changed_content or executables[0] != executables[1]:
660
704
                    change_type = kind_changed
661
705
                    display_path = (paths[0] + source_marker
662
706
                                    + ' => ' + paths[1] + marker)
663
 
                elif changed_content is True or executables[0] != executables[1]:
 
707
                elif changed_content or executables[0] != executables[1]:
664
708
                    change_type = modified
 
709
                    display_path = real_path + marker
665
710
                else:
666
711
                    assert False, "How did we get here?"
667
712