/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: Jelmer Vernooij
  • Date: 2012-07-09 15:23:26 UTC
  • mto: This revision was merged to the branch mainline in revision 794.
  • Revision ID: jelmer@samba.org-20120709152326-dzxb8zoz0btull7n
Remove bzr-notify.

Show diffs side-by-side

added added

removed removed

Lines of Context:
5
5
"""
6
6
 
7
7
__copyright__ = "Copyright 2005 Canonical Ltd."
8
 
__author__    = "Scott James Remnant <scott@ubuntu.com>"
 
8
__author__ = "Scott James Remnant <scott@ubuntu.com>"
9
9
 
10
10
 
11
11
from cStringIO import StringIO
12
12
 
13
 
import pygtk
14
 
pygtk.require("2.0")
15
 
import gtk
16
 
import pango
17
 
import os
18
 
import re
19
13
import sys
20
 
try:
21
 
    from xml.etree.ElementTree import Element, SubElement, tostring
22
 
except ImportError:
23
 
    from elementtree.ElementTree import Element, SubElement, tostring
 
14
import inspect
24
15
 
 
16
from gi.repository import Gtk
 
17
from gi.repository import Pango
25
18
try:
26
 
    import gtksourceview2
 
19
    from gi.repository import GtkSource
27
20
    have_gtksourceview = True
28
21
except ImportError:
29
22
    have_gtksourceview = False
30
 
try:
31
 
    import gconf
32
 
    have_gconf = True
33
 
except ImportError:
34
 
    have_gconf = False
35
23
 
36
24
from bzrlib import (
37
25
    errors,
39
27
    osutils,
40
28
    urlutils,
41
29
    workingtree,
42
 
)
43
 
from bzrlib.diff import show_diff_trees, internal_diff
 
30
    )
 
31
from bzrlib.diff import show_diff_trees
44
32
from bzrlib.patches import parse_patches
45
 
from bzrlib.trace import warning
46
 
from bzrlib.plugins.gtk import _i18n
 
33
from bzrlib.plugins.gtk.dialog import (
 
34
    error_dialog,
 
35
    info_dialog,
 
36
    warning_dialog,
 
37
    )
 
38
from bzrlib.plugins.gtk.i18n import _i18n
47
39
from bzrlib.plugins.gtk.window import Window
48
 
from dialog import error_dialog, info_dialog, warning_dialog
49
40
 
50
41
 
51
42
def fallback_guess_language(slm, content_type):
61
52
    pass
62
53
 
63
54
 
64
 
class DiffFileView(gtk.ScrolledWindow):
 
55
class DiffFileView(Gtk.ScrolledWindow):
65
56
    """Window for displaying diffs from a diff file"""
66
57
 
 
58
    SHOW_WIDGETS = True
 
59
 
67
60
    def __init__(self):
68
 
        gtk.ScrolledWindow.__init__(self)
 
61
        super(DiffFileView, self).__init__()
69
62
        self.construct()
70
63
        self._diffs = {}
71
64
 
72
65
    def construct(self):
73
 
        self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
74
 
        self.set_shadow_type(gtk.SHADOW_IN)
 
66
        self.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
 
67
        self.set_shadow_type(Gtk.ShadowType.IN)
75
68
 
76
69
        if have_gtksourceview:
77
 
            self.buffer = gtksourceview2.Buffer()
78
 
            slm = gtksourceview2.LanguageManager()
79
 
            guess_language = getattr(gtksourceview2.LanguageManager, 
80
 
                "guess_language", fallback_guess_language)
81
 
            gsl = guess_language(slm, content_type="text/x-patch")
82
 
            if have_gconf:
83
 
                self.apply_gedit_colors(self.buffer)
84
 
            self.apply_colordiff_colors(self.buffer)
85
 
            self.buffer.set_language(gsl)
 
70
            self.buffer = GtkSource.Buffer()
 
71
            lang_manager = GtkSource.LanguageManager.get_default()
 
72
            language = lang_manager.guess_language(None, "text/x-patch")
 
73
            self.buffer.set_language(language)
86
74
            self.buffer.set_highlight_syntax(True)
87
 
 
88
 
            self.sourceview = gtksourceview2.View(self.buffer)
 
75
            self.sourceview = GtkSource.View(buffer=self.buffer)
89
76
        else:
90
 
            self.buffer = gtk.TextBuffer()
91
 
            self.sourceview = gtk.TextView(self.buffer)
 
77
            self.buffer = Gtk.TextBuffer()
 
78
            self.sourceview = Gtk.TextView(self.buffer)
92
79
 
93
80
        self.sourceview.set_editable(False)
94
 
        self.sourceview.modify_font(pango.FontDescription("Monospace"))
 
81
        self.sourceview.override_font(Pango.FontDescription("Monospace"))
95
82
        self.add(self.sourceview)
96
 
        self.sourceview.show()
97
 
 
98
 
    @staticmethod
99
 
    def apply_gedit_colors(buf):
100
 
        """Set style to that specified in gedit configuration.
101
 
 
102
 
        This method needs the gconf module.
103
 
 
104
 
        :param buf: a gtksourceview2.Buffer object.
105
 
        """
106
 
        GEDIT_SCHEME_PATH = '/apps/gedit-2/preferences/editor/colors/scheme'
107
 
 
108
 
        client = gconf.client_get_default()
109
 
        style_scheme_name = client.get_string(GEDIT_SCHEME_PATH)
110
 
        if style_scheme_name is not None:
111
 
            style_scheme = gtksourceview2.StyleSchemeManager().get_scheme(style_scheme_name)
112
 
            
113
 
            buf.set_style_scheme(style_scheme)
114
 
 
115
 
    @classmethod
116
 
    def apply_colordiff_colors(klass, buf):
117
 
        """Set style colors for lang using the colordiff configuration file.
118
 
 
119
 
        Both ~/.colordiffrc and ~/.colordiffrc.bzr-gtk are read.
120
 
 
121
 
        :param buf: a "Diff" gtksourceview2.Buffer object.
122
 
        """
123
 
        scheme_manager = gtksourceview2.StyleSchemeManager()
124
 
        style_scheme = scheme_manager.get_scheme('colordiff')
125
 
        
126
 
        # if style scheme not found, we'll generate it from colordiffrc
127
 
        # TODO: reload if colordiffrc has changed.
128
 
        if style_scheme is None:
129
 
            colors = {}
130
 
 
131
 
            for f in ('~/.colordiffrc', '~/.colordiffrc.bzr-gtk'):
132
 
                f = os.path.expanduser(f)
133
 
                if os.path.exists(f):
134
 
                    try:
135
 
                        f = file(f)
136
 
                    except IOError, e:
137
 
                        warning('could not open file %s: %s' % (f, str(e)))
138
 
                    else:
139
 
                        colors.update(klass.parse_colordiffrc(f))
140
 
                        f.close()
141
 
 
142
 
            if not colors:
143
 
                # ~/.colordiffrc does not exist
144
 
                return
145
 
            
146
 
            mapping = {
147
 
                # map GtkSourceView2 scheme styles to colordiff names
148
 
                # since GSV is richer, accept new names for extra bits,
149
 
                # defaulting to old names if they're not present
150
 
                'diff:added-line': ['newtext'],
151
 
                'diff:removed-line': ['oldtext'],
152
 
                'diff:location': ['location', 'diffstuff'],
153
 
                'diff:file': ['file', 'diffstuff'],
154
 
                'diff:special-case': ['specialcase', 'diffstuff'],
155
 
            }
156
 
            
157
 
            converted_colors = {}
158
 
            for name, values in mapping.items():
159
 
                color = None
160
 
                for value in values:
161
 
                    color = colors.get(value, None)
162
 
                    if color is not None:
163
 
                        break
164
 
                if color is None:
165
 
                    continue
166
 
                converted_colors[name] = color
167
 
            
168
 
            # some xml magic to produce needed style scheme description
169
 
            e_style_scheme = Element('style-scheme')
170
 
            e_style_scheme.set('id', 'colordiff')
171
 
            e_style_scheme.set('_name', 'ColorDiff')
172
 
            e_style_scheme.set('version', '1.0')
173
 
            for name, color in converted_colors.items():
174
 
                style = SubElement(e_style_scheme, 'style')
175
 
                style.set('name', name)
176
 
                style.set('foreground', '#%s' % color)
177
 
            
178
 
            scheme_xml = tostring(e_style_scheme, 'UTF-8')
179
 
            if not os.path.exists(os.path.expanduser('~/.local/share/gtksourceview-2.0/styles')):
180
 
                os.makedirs(os.path.expanduser('~/.local/share/gtksourceview-2.0/styles'))
181
 
            file(os.path.expanduser('~/.local/share/gtksourceview-2.0/styles/colordiff.xml'), 'w').write(scheme_xml)
182
 
            
183
 
            scheme_manager.force_rescan()
184
 
            style_scheme = scheme_manager.get_scheme('colordiff')
185
 
        
186
 
        buf.set_style_scheme(style_scheme)
187
 
 
188
 
    @staticmethod
189
 
    def parse_colordiffrc(fileobj):
190
 
        """Parse fileobj as a colordiff configuration file.
191
 
 
192
 
        :return: A dict with the key -> value pairs.
193
 
        """
194
 
        colors = {}
195
 
        for line in fileobj:
196
 
            if re.match(r'^\s*#', line):
197
 
                continue
198
 
            if '=' not in line:
199
 
                continue
200
 
            key, val = line.split('=', 1)
201
 
            colors[key.strip()] = val.strip()
202
 
        return colors
 
83
        if self.SHOW_WIDGETS:
 
84
            self.sourceview.show()
203
85
 
204
86
    def set_trees(self, rev_tree, parent_tree):
205
87
        self.rev_tree = rev_tree
210
92
#        self.parent_tree.lock_read()
211
93
#        self.rev_tree.lock_read()
212
94
#        try:
213
 
#            self.delta = iter_changes_to_status(self.parent_tree, self.rev_tree)
 
95
#            self.delta = iter_changes_to_status(
 
96
#               self.parent_tree, self.rev_tree)
214
97
#            self.path_to_status = {}
215
98
#            self.path_to_diff = {}
216
99
#            source_inv = self.parent_tree.inventory
217
100
#            target_inv = self.rev_tree.inventory
218
101
#            for (file_id, real_path, change_type, display_path) in self.delta:
219
 
#                self.path_to_status[real_path] = u'=== %s %s' % (change_type, display_path)
 
102
#                self.path_to_status[real_path] = u'=== %s %s' % (
 
103
#                    change_type, display_path)
220
104
#                if change_type in ('modified', 'renamed and modified'):
221
105
#                    source_ie = source_inv[file_id]
222
106
#                    target_ie = target_inv[file_id]
224
108
#                    source_ie.diff(internal_diff, *old path, *old_tree,
225
109
#                                   *new_path, target_ie, self.rev_tree,
226
110
#                                   sio)
227
 
#                    self.path_to_diff[real_path] = 
 
111
#                    self.path_to_diff[real_path] =
228
112
#
229
113
#        finally:
230
114
#            self.rev_tree.unlock()
244
128
    """This is the soft and chewy filling for a DiffWindow."""
245
129
 
246
130
    def __init__(self):
247
 
        DiffFileView.__init__(self)
 
131
        super(DiffView, self).__init__()
248
132
        self.rev_tree = None
249
133
        self.parent_tree = None
250
134
 
274
158
        self.buffer.set_text(decoded.encode('UTF-8'))
275
159
 
276
160
 
277
 
class DiffWidget(gtk.HPaned):
 
161
class DiffWidget(Gtk.HPaned):
278
162
    """Diff widget
279
163
 
280
164
    """
 
165
 
 
166
    SHOW_WIDGETS = True
 
167
 
281
168
    def __init__(self):
282
169
        super(DiffWidget, self).__init__()
283
170
 
284
171
        # The file hierarchy: a scrollable treeview
285
 
        scrollwin = gtk.ScrolledWindow()
286
 
        scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
287
 
        scrollwin.set_shadow_type(gtk.SHADOW_IN)
 
172
        scrollwin = Gtk.ScrolledWindow()
 
173
        scrollwin.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
 
174
        scrollwin.set_shadow_type(Gtk.ShadowType.IN)
288
175
        self.pack1(scrollwin)
289
 
        scrollwin.show()
290
 
        
291
 
        self.model = gtk.TreeStore(str, str)
292
 
        self.treeview = gtk.TreeView(self.model)
 
176
        if self.SHOW_WIDGETS:
 
177
            scrollwin.show()
 
178
 
 
179
        self.model = Gtk.TreeStore(str, str)
 
180
        self.treeview = Gtk.TreeView(model=self.model)
293
181
        self.treeview.set_headers_visible(False)
294
182
        self.treeview.set_search_column(1)
295
183
        self.treeview.connect("cursor-changed", self._treeview_cursor_cb)
296
184
        scrollwin.add(self.treeview)
297
 
        self.treeview.show()
 
185
        if self.SHOW_WIDGETS:
 
186
            self.treeview.show()
298
187
 
299
 
        cell = gtk.CellRendererText()
 
188
        cell = Gtk.CellRendererText()
300
189
        cell.set_property("width-chars", 20)
301
 
        column = gtk.TreeViewColumn()
302
 
        column.pack_start(cell, expand=True)
 
190
        column = Gtk.TreeViewColumn()
 
191
        column.pack_start(cell, True)
303
192
        column.add_attribute(cell, "text", 0)
304
193
        self.treeview.append_column(column)
305
194
 
315
204
        if getattr(self, 'diff_view', None) is None:
316
205
            self.diff_view = DiffFileView()
317
206
            self.pack2(self.diff_view)
318
 
        self.diff_view.show()
 
207
        if self.SHOW_WIDGETS:
 
208
            self.diff_view.show()
319
209
        for oldname, newname, patch in sections:
320
210
            self.diff_view._diffs[newname] = str(patch)
321
211
            if newname is None:
332
222
        if getattr(self, 'diff_view', None) is None:
333
223
            self.diff_view = DiffView()
334
224
            self.pack2(self.diff_view)
335
 
        self.diff_view.show()
 
225
        if self.SHOW_WIDGETS:
 
226
            self.diff_view.show()
336
227
        self.diff_view.set_trees(rev_tree, parent_tree)
337
228
        self.rev_tree = rev_tree
338
229
        self.parent_tree = parent_tree
340
231
        self.model.clear()
341
232
        delta = self.rev_tree.changes_from(self.parent_tree)
342
233
 
343
 
        self.model.append(None, [ "Complete Diff", "" ])
 
234
        self.model.append(None, ["Complete Diff", ""])
344
235
 
345
236
        if len(delta.added):
346
 
            titer = self.model.append(None, [ "Added", None ])
 
237
            titer = self.model.append(None, ["Added", None])
347
238
            for path, id, kind in delta.added:
348
 
                self.model.append(titer, [ path, path ])
 
239
                self.model.append(titer, [path, path])
349
240
 
350
241
        if len(delta.removed):
351
 
            titer = self.model.append(None, [ "Removed", None ])
 
242
            titer = self.model.append(None, ["Removed", None])
352
243
            for path, id, kind in delta.removed:
353
 
                self.model.append(titer, [ path, path ])
 
244
                self.model.append(titer, [path, path])
354
245
 
355
246
        if len(delta.renamed):
356
 
            titer = self.model.append(None, [ "Renamed", None ])
 
247
            titer = self.model.append(None, ["Renamed", None])
357
248
            for oldpath, newpath, id, kind, text_modified, meta_modified \
358
249
                    in delta.renamed:
359
 
                self.model.append(titer, [ oldpath, newpath ])
 
250
                self.model.append(titer, [oldpath, newpath])
360
251
 
361
252
        if len(delta.modified):
362
 
            titer = self.model.append(None, [ "Modified", None ])
 
253
            titer = self.model.append(None, ["Modified", None])
363
254
            for path, id, kind, text_modified, meta_modified in delta.modified:
364
 
                self.model.append(titer, [ path, path ])
 
255
                self.model.append(titer, [path, path])
365
256
 
366
257
        self.treeview.expand_all()
367
258
        self.diff_view.show_diff(None)
376
267
                    break
377
268
        if tv_path is None:
378
269
            raise errors.NoSuchFile(file_path)
379
 
        self.treeview.set_cursor(tv_path)
 
270
        self.treeview.set_cursor(tv_path, None, False)
380
271
        self.treeview.scroll_to_cell(tv_path)
381
272
 
382
273
    def _treeview_cursor_cb(self, *args):
383
274
        """Callback for when the treeview cursor changes."""
384
275
        (path, col) = self.treeview.get_cursor()
385
 
        specific_files = [ self.model[path][1] ]
386
 
        if specific_files == [ None ]:
387
 
            return
388
 
        elif specific_files == [ "" ]:
 
276
        if path is None:
 
277
            return
 
278
        specific_files = [self.model[path][1]]
 
279
        if specific_files == [None]:
 
280
            return
 
281
        elif specific_files == [""]:
389
282
            specific_files = None
390
 
        
 
283
 
391
284
        self.diff_view.show_diff(specific_files)
392
 
    
 
285
 
393
286
    def _on_wraplines_toggled(self, widget=None, wrap=False):
394
287
        """Callback for when the wrap lines checkbutton is toggled"""
395
288
        if wrap or widget.get_active():
396
 
            self.diff_view.sourceview.set_wrap_mode(gtk.WRAP_WORD)
 
289
            self.diff_view.sourceview.set_wrap_mode(Gtk.WrapMode.WORD)
397
290
        else:
398
 
            self.diff_view.sourceview.set_wrap_mode(gtk.WRAP_NONE)
 
291
            self.diff_view.sourceview.set_wrap_mode(Gtk.WrapMode.NONE)
 
292
 
399
293
 
400
294
class DiffWindow(Window):
401
295
    """Diff window.
404
298
    differences between two revisions on a branch.
405
299
    """
406
300
 
 
301
    SHOW_WIDGETS = True
 
302
 
407
303
    def __init__(self, parent=None, operations=None):
408
 
        Window.__init__(self, parent)
 
304
        super(DiffWindow, self).__init__(parent=parent)
409
305
        self.set_border_width(0)
410
 
        self.set_title("bzrk diff")
 
306
        self.set_title("bzr diff")
411
307
 
412
308
        # Use two thirds of the screen by default
413
309
        screen = self.get_screen()
419
315
 
420
316
    def construct(self, operations):
421
317
        """Construct the window contents."""
422
 
        self.vbox = gtk.VBox()
 
318
        self.vbox = Gtk.VBox()
423
319
        self.add(self.vbox)
424
 
        self.vbox.show()
 
320
        if self.SHOW_WIDGETS:
 
321
            self.vbox.show()
425
322
        self.diff = DiffWidget()
426
323
        self.vbox.pack_end(self.diff, True, True, 0)
427
 
        self.diff.show_all()
 
324
        if self.SHOW_WIDGETS:
 
325
            self.diff.show_all()
428
326
        # Build after DiffWidget to connect signals
429
327
        menubar = self._get_menu_bar()
430
328
        self.vbox.pack_start(menubar, False, False, 0)
431
329
        hbox = self._get_button_bar(operations)
432
330
        if hbox is not None:
433
331
            self.vbox.pack_start(hbox, False, True, 0)
434
 
        
435
 
    
 
332
 
436
333
    def _get_menu_bar(self):
437
 
        menubar = gtk.MenuBar()
 
334
        menubar = Gtk.MenuBar()
438
335
        # View menu
439
 
        mb_view = gtk.MenuItem(_i18n("_View"))
440
 
        mb_view_menu = gtk.Menu()
441
 
        mb_view_wrapsource = gtk.CheckMenuItem(_i18n("Wrap _Long Lines"))
 
336
        mb_view = Gtk.MenuItem.new_with_mnemonic(_i18n("_View"))
 
337
        mb_view_menu = Gtk.Menu()
 
338
        mb_view_wrapsource = Gtk.CheckMenuItem.new_with_mnemonic(
 
339
            _i18n("Wrap _Long Lines"))
442
340
        mb_view_wrapsource.connect('activate', self.diff._on_wraplines_toggled)
443
 
        mb_view_wrapsource.show()
444
341
        mb_view_menu.append(mb_view_wrapsource)
445
 
        mb_view.show()
446
342
        mb_view.set_submenu(mb_view_menu)
447
 
        mb_view.show()
448
343
        menubar.append(mb_view)
449
 
        menubar.show()
 
344
        if self.SHOW_WIDGETS:
 
345
            menubar.show_all()
450
346
        return menubar
451
 
    
 
347
 
452
348
    def _get_button_bar(self, operations):
453
349
        """Return a button bar to use.
454
350
 
456
352
        """
457
353
        if operations is None:
458
354
            return None
459
 
        hbox = gtk.HButtonBox()
460
 
        hbox.set_layout(gtk.BUTTONBOX_START)
 
355
        hbox = Gtk.HButtonBox()
 
356
        hbox.set_layout(Gtk.ButtonBoxStyle.START)
461
357
        for title, method in operations:
462
 
            merge_button = gtk.Button(title)
463
 
            merge_button.show()
464
 
            merge_button.set_relief(gtk.RELIEF_NONE)
 
358
            merge_button = Gtk.Button(title)
 
359
            if self.SHOW_WIDGETS:
 
360
                merge_button.show()
 
361
            merge_button.set_relief(Gtk.ReliefStyle.NONE)
465
362
            merge_button.connect("clicked", method)
466
 
            hbox.pack_start(merge_button, expand=False, fill=True)
467
 
        hbox.show()
 
363
            hbox.pack_start(merge_button, False, True, 0)
 
364
        if self.SHOW_WIDGETS:
 
365
            hbox.show()
468
366
        return hbox
469
367
 
470
368
    def _get_merge_target(self):
471
 
        d = gtk.FileChooserDialog('Merge branch', self,
472
 
                                  gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
473
 
                                  buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
474
 
                                           gtk.STOCK_CANCEL,
475
 
                                           gtk.RESPONSE_CANCEL,))
 
369
        d = Gtk.FileChooserDialog('Merge branch', self,
 
370
                                  Gtk.FileChooserAction.SELECT_FOLDER,
 
371
                                  buttons=(Gtk.STOCK_OK, Gtk.ResponseType.OK,
 
372
                                           Gtk.STOCK_CANCEL,
 
373
                                           Gtk.ResponseType.CANCEL,))
476
374
        try:
477
375
            result = d.run()
478
 
            if result != gtk.RESPONSE_OK:
 
376
            if result != Gtk.ResponseType.OK:
479
377
                raise SelectCancelled()
480
378
            return d.get_current_folder_uri()
481
379
        finally:
495
393
        error_dialog('Error', str(e))
496
394
 
497
395
    def _get_save_path(self, basename):
498
 
        d = gtk.FileChooserDialog('Save As', self,
499
 
                                  gtk.FILE_CHOOSER_ACTION_SAVE,
500
 
                                  buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
501
 
                                           gtk.STOCK_CANCEL,
502
 
                                           gtk.RESPONSE_CANCEL,))
 
396
        d = Gtk.FileChooserDialog('Save As', self,
 
397
                                  Gtk.FileChooserAction.SAVE,
 
398
                                  buttons=(Gtk.STOCK_OK, Gtk.ResponseType.OK,
 
399
                                           Gtk.STOCK_CANCEL,
 
400
                                           Gtk.ResponseType.CANCEL,))
503
401
        d.set_current_name(basename)
504
402
        try:
505
403
            result = d.run()
506
 
            if result != gtk.RESPONSE_OK:
 
404
            if result != Gtk.ResponseType.OK:
507
405
                raise SelectCancelled()
508
406
            return urlutils.local_path_from_url(d.get_uri())
509
407
        finally:
524
422
 
525
423
class DiffController(object):
526
424
 
527
 
    def __init__(self, path, patch, window=None):
 
425
    def __init__(self, path, patch, window=None, allow_dirty=False):
528
426
        self.path = path
529
427
        self.patch = patch
 
428
        self.allow_dirty = allow_dirty
530
429
        if window is None:
531
430
            window = DiffWindow(operations=self._provide_operations())
532
431
            self.initialize_window(window)
538
437
 
539
438
    def get_diff_sections(self):
540
439
        yield "Complete Diff", None, ''.join(self.patch)
541
 
        for patch in parse_patches(self.patch):
 
440
        # allow_dirty was added to parse_patches in bzrlib 2.2b1
 
441
        if 'allow_dirty' in inspect.getargspec(parse_patches).args:
 
442
            patches = parse_patches(self.patch, allow_dirty=self.allow_dirty)
 
443
        else:
 
444
            patches = parse_patches(self.patch)
 
445
        for patch in patches:
542
446
            oldname = patch.oldname.split('\t')[0]
543
447
            newname = patch.newname.split('\t')[0]
544
448
            yield oldname, newname, str(patch)
565
469
class MergeDirectiveController(DiffController):
566
470
 
567
471
    def __init__(self, path, directive, window=None):
568
 
        DiffController.__init__(self, path, directive.patch.splitlines(True),
569
 
                                window)
 
472
        super(MergeDirectiveController, self).__init__(
 
473
            path, directive.patch.splitlines(True), window)
570
474
        self.directive = directive
571
475
        self.merge_target = None
572
476