/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: 2011-09-05 03:44:26 UTC
  • mto: This revision was merged to the branch mainline in revision 741.
  • Revision ID: sinzui.is@verizon.net-20110905034426-p98pxnay9rmzkr99
Fix the initializer for many classes.
Replace Gtk.Dialog.vbox with .get_content_area().

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
 
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
44
51
from bzrlib.plugins.gtk.window import Window
45
 
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
46
60
 
47
61
 
48
62
class SelectCancelled(Exception):
50
64
    pass
51
65
 
52
66
 
53
 
class DiffFileView(gtk.ScrolledWindow):
 
67
class DiffFileView(Gtk.ScrolledWindow):
54
68
    """Window for displaying diffs from a diff file"""
55
69
 
56
70
    def __init__(self):
57
 
        gtk.ScrolledWindow.__init__(self)
 
71
        super(DiffFileView, self).__init__()
58
72
        self.construct()
59
73
        self._diffs = {}
60
74
 
61
75
    def construct(self):
62
 
        self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
63
 
        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)
64
78
 
65
79
        if have_gtksourceview:
66
 
            self.buffer = gtksourceview.SourceBuffer()
67
 
            slm = gtksourceview.SourceLanguagesManager()
68
 
            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")
69
83
            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)
 
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)
74
88
 
75
 
            sourceview = gtksourceview.SourceView(self.buffer)
 
89
            self.sourceview = GtkSource.View(buffer=self.buffer)
76
90
        else:
77
 
            self.buffer = gtk.TextBuffer()
78
 
            sourceview = gtk.TextView(self.buffer)
 
91
            self.buffer = Gtk.TextBuffer()
 
92
            self.sourceview = Gtk.TextView(self.buffer)
79
93
 
80
 
        sourceview.set_editable(False)
81
 
        sourceview.modify_font(pango.FontDescription("Monospace"))
82
 
        self.add(sourceview)
83
 
        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()
84
98
 
85
99
    @staticmethod
86
 
    def apply_gedit_colors(lang):
87
 
        """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.
88
102
 
89
103
        This method needs the gconf module.
90
104
 
91
 
        :param lang: a gtksourceview.SourceLanguage object.
 
105
        :param buf: a GtkSource.Buffer object.
92
106
        """
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)
 
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)
147
120
 
148
121
    @classmethod
149
 
    def apply_colordiff_colors(klass, lang):
 
122
    def apply_colordiff_colors(klass, buf):
150
123
        """Set style colors for lang using the colordiff configuration file.
151
124
 
152
125
        Both ~/.colordiffrc and ~/.colordiffrc.bzr-gtk are read.
153
126
 
154
 
        :param lang: a "Diff" gtksourceview.SourceLanguage object.
 
127
        :param buf: a "Diff" GtkSource.Buffer object.
155
128
        """
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
 
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
175
154
                # since GSV is richer, accept new names for extra bits,
176
155
                # 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)
 
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)
204
193
 
205
194
    @staticmethod
206
195
    def parse_colordiffrc(fileobj):
261
250
    """This is the soft and chewy filling for a DiffWindow."""
262
251
 
263
252
    def __init__(self):
264
 
        DiffFileView.__init__(self)
 
253
        super(DiffView, self).__init__()
265
254
        self.rev_tree = None
266
255
        self.parent_tree = None
267
256
 
291
280
        self.buffer.set_text(decoded.encode('UTF-8'))
292
281
 
293
282
 
294
 
class DiffWidget(gtk.HPaned):
 
283
class DiffWidget(Gtk.HPaned):
295
284
    """Diff widget
296
285
 
297
286
    """
299
288
        super(DiffWidget, self).__init__()
300
289
 
301
290
        # The file hierarchy: a scrollable treeview
302
 
        scrollwin = gtk.ScrolledWindow()
303
 
        scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
304
 
        scrollwin.set_shadow_type(gtk.SHADOW_IN)
 
291
        scrollwin = Gtk.ScrolledWindow()
 
292
        scrollwin.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
 
293
        scrollwin.set_shadow_type(Gtk.ShadowType.IN)
305
294
        self.pack1(scrollwin)
306
295
        scrollwin.show()
307
 
 
308
 
        self.model = gtk.TreeStore(str, str)
309
 
        self.treeview = gtk.TreeView(self.model)
 
296
        
 
297
        self.model = Gtk.TreeStore(str, str)
 
298
        self.treeview = Gtk.TreeView(model=self.model)
310
299
        self.treeview.set_headers_visible(False)
311
300
        self.treeview.set_search_column(1)
312
301
        self.treeview.connect("cursor-changed", self._treeview_cursor_cb)
313
302
        scrollwin.add(self.treeview)
314
303
        self.treeview.show()
315
304
 
316
 
        cell = gtk.CellRendererText()
 
305
        cell = Gtk.CellRendererText()
317
306
        cell.set_property("width-chars", 20)
318
 
        column = gtk.TreeViewColumn()
319
 
        column.pack_start(cell, expand=True)
 
307
        column = Gtk.TreeViewColumn()
 
308
        column.pack_start(cell, True)
320
309
        column.add_attribute(cell, "text", 0)
321
310
        self.treeview.append_column(column)
322
311
 
327
316
        """
328
317
        # The diffs of the  selected file: a scrollable source or
329
318
        # text view
330
 
        self.diff_view = DiffFileView()
 
319
 
 
320
    def set_diff_text_sections(self, sections):
 
321
        if getattr(self, 'diff_view', None) is None:
 
322
            self.diff_view = DiffFileView()
 
323
            self.pack2(self.diff_view)
331
324
        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]
 
325
        for oldname, newname, patch in sections:
 
326
            self.diff_view._diffs[newname] = str(patch)
 
327
            if newname is None:
 
328
                newname = ''
338
329
            self.model.append(None, [oldname, newname])
339
 
            self.diff_view._diffs[newname] = str(patch)
340
330
        self.diff_view.show_diff(None)
341
331
 
342
332
    def set_diff(self, rev_tree, parent_tree):
345
335
        Compares the two trees and populates the window with the
346
336
        differences.
347
337
        """
348
 
        self.diff_view = DiffView()
349
 
        self.pack2(self.diff_view)
 
338
        if getattr(self, 'diff_view', None) is None:
 
339
            self.diff_view = DiffView()
 
340
            self.pack2(self.diff_view)
350
341
        self.diff_view.show()
351
342
        self.diff_view.set_trees(rev_tree, parent_tree)
352
343
        self.rev_tree = rev_tree
379
370
                self.model.append(titer, [ path, path ])
380
371
 
381
372
        self.treeview.expand_all()
 
373
        self.diff_view.show_diff(None)
382
374
 
383
375
    def set_file(self, file_path):
384
376
        """Select the current file to display"""
389
381
                    tv_path = child.path
390
382
                    break
391
383
        if tv_path is None:
392
 
            raise NoSuchFile(file_path)
393
 
        self.treeview.set_cursor(tv_path)
 
384
            raise errors.NoSuchFile(file_path)
 
385
        self.treeview.set_cursor(tv_path, None, False)
394
386
        self.treeview.scroll_to_cell(tv_path)
395
387
 
396
388
    def _treeview_cursor_cb(self, *args):
401
393
            return
402
394
        elif specific_files == [ "" ]:
403
395
            specific_files = None
404
 
 
 
396
        
405
397
        self.diff_view.show_diff(specific_files)
406
 
 
 
398
    
 
399
    def _on_wraplines_toggled(self, widget=None, wrap=False):
 
400
        """Callback for when the wrap lines checkbutton is toggled"""
 
401
        if wrap or widget.get_active():
 
402
            self.diff_view.sourceview.set_wrap_mode(Gtk.WrapMode.WORD)
 
403
        else:
 
404
            self.diff_view.sourceview.set_wrap_mode(Gtk.WrapMode.NONE)
407
405
 
408
406
class DiffWindow(Window):
409
407
    """Diff window.
412
410
    differences between two revisions on a branch.
413
411
    """
414
412
 
415
 
    def __init__(self, parent=None):
416
 
        Window.__init__(self, parent)
 
413
    def __init__(self, parent=None, operations=None):
 
414
        super(DiffWindow, self).__init__(parent=parent)
417
415
        self.set_border_width(0)
418
416
        self.set_title("bzrk diff")
419
417
 
423
421
        width = int(monitor.width * 0.66)
424
422
        height = int(monitor.height * 0.66)
425
423
        self.set_default_size(width, height)
426
 
 
427
 
        self.construct()
428
 
 
429
 
    def construct(self):
 
424
        self.construct(operations)
 
425
 
 
426
    def construct(self, operations):
430
427
        """Construct the window contents."""
431
 
        self.vbox = gtk.VBox()
 
428
        self.vbox = Gtk.VBox()
432
429
        self.add(self.vbox)
433
430
        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
431
        self.diff = DiffWidget()
438
 
        self.vbox.add(self.diff)
 
432
        self.vbox.pack_end(self.diff, True, True, 0)
439
433
        self.diff.show_all()
440
 
 
441
 
    def _get_button_bar(self):
 
434
        # Build after DiffWidget to connect signals
 
435
        menubar = self._get_menu_bar()
 
436
        self.vbox.pack_start(menubar, False, False, 0)
 
437
        hbox = self._get_button_bar(operations)
 
438
        if hbox is not None:
 
439
            self.vbox.pack_start(hbox, False, True, 0)
 
440
        
 
441
    
 
442
    def _get_menu_bar(self):
 
443
        menubar = Gtk.MenuBar()
 
444
        # View menu
 
445
        mb_view = Gtk.MenuItem.new_with_mnemonic(_i18n("_View"))
 
446
        mb_view_menu = Gtk.Menu()
 
447
        mb_view_wrapsource = Gtk.CheckMenuItem.new_with_mnemonic(
 
448
            _i18n("Wrap _Long Lines"))
 
449
        mb_view_wrapsource.connect('activate', self.diff._on_wraplines_toggled)
 
450
        mb_view_wrapsource.show()
 
451
        mb_view_menu.append(mb_view_wrapsource)
 
452
        mb_view.show()
 
453
        mb_view.set_submenu(mb_view_menu)
 
454
        mb_view.show()
 
455
        menubar.append(mb_view)
 
456
        menubar.show()
 
457
        return menubar
 
458
    
 
459
    def _get_button_bar(self, operations):
442
460
        """Return a button bar to use.
443
461
 
444
462
        :return: None, meaning that no button bar will be used.
445
463
        """
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")
 
464
        if operations is None:
 
465
            return None
 
466
        hbox = Gtk.HButtonBox()
 
467
        hbox.set_layout(Gtk.ButtonBoxStyle.START)
 
468
        for title, method in operations:
 
469
            merge_button = Gtk.Button(title)
 
470
            merge_button.show()
 
471
            merge_button.set_relief(Gtk.ReliefStyle.NONE)
 
472
            merge_button.connect("clicked", method)
 
473
            hbox.pack_start(merge_button, expand=False, fill=True)
 
474
        hbox.show()
 
475
        return hbox
 
476
 
 
477
    def _get_merge_target(self):
 
478
        d = Gtk.FileChooserDialog('Merge branch', self,
 
479
                                  Gtk.FileChooserAction.SELECT_FOLDER,
 
480
                                  buttons=(Gtk.STOCK_OK, Gtk.ResponseType.OK,
 
481
                                           Gtk.STOCK_CANCEL,
 
482
                                           Gtk.ResponseType.CANCEL,))
 
483
        try:
 
484
            result = d.run()
 
485
            if result != Gtk.ResponseType.OK:
 
486
                raise SelectCancelled()
 
487
            return d.get_current_folder_uri()
 
488
        finally:
 
489
            d.destroy()
 
490
 
 
491
    def _merge_successful(self):
 
492
        # No conflicts found.
 
493
        info_dialog(_i18n('Merge successful'),
 
494
                    _i18n('All changes applied successfully.'))
 
495
 
 
496
    def _conflicts(self):
 
497
        warning_dialog(_i18n('Conflicts encountered'),
 
498
                       _i18n('Please resolve the conflicts manually'
 
499
                             ' before committing.'))
 
500
 
 
501
    def _handle_error(self, e):
 
502
        error_dialog('Error', str(e))
 
503
 
 
504
    def _get_save_path(self, basename):
 
505
        d = Gtk.FileChooserDialog('Save As', self,
 
506
                                  Gtk.FileChooserAction.SAVE,
 
507
                                  buttons=(Gtk.STOCK_OK, Gtk.ResponseType.OK,
 
508
                                           Gtk.STOCK_CANCEL,
 
509
                                           Gtk.ResponseType.CANCEL,))
 
510
        d.set_current_name(basename)
 
511
        try:
 
512
            result = d.run()
 
513
            if result != Gtk.ResponseType.OK:
 
514
                raise SelectCancelled()
 
515
            return urlutils.local_path_from_url(d.get_uri())
 
516
        finally:
 
517
            d.destroy()
456
518
 
457
519
    def set_diff(self, description, rev_tree, parent_tree):
458
520
        """Set the differences showed by this window.
467
529
        self.diff.set_file(file_path)
468
530
 
469
531
 
470
 
class MergeDirectiveWindow(DiffWindow):
471
 
 
472
 
    def __init__(self, directive, path):
473
 
        DiffWindow.__init__(self, None)
474
 
        self._merge_target = None
 
532
class DiffController(object):
 
533
 
 
534
    def __init__(self, path, patch, window=None, allow_dirty=False):
 
535
        self.path = path
 
536
        self.patch = patch
 
537
        self.allow_dirty = allow_dirty
 
538
        if window is None:
 
539
            window = DiffWindow(operations=self._provide_operations())
 
540
            self.initialize_window(window)
 
541
        self.window = window
 
542
 
 
543
    def initialize_window(self, window):
 
544
        window.diff.set_diff_text_sections(self.get_diff_sections())
 
545
        window.set_title(self.path + " - diff")
 
546
 
 
547
    def get_diff_sections(self):
 
548
        yield "Complete Diff", None, ''.join(self.patch)
 
549
        # allow_dirty was added to parse_patches in bzrlib 2.2b1
 
550
        if 'allow_dirty' in inspect.getargspec(parse_patches).args:
 
551
            patches = parse_patches(self.patch, allow_dirty=self.allow_dirty)
 
552
        else:
 
553
            patches = parse_patches(self.patch)
 
554
        for patch in patches:
 
555
            oldname = patch.oldname.split('\t')[0]
 
556
            newname = patch.newname.split('\t')[0]
 
557
            yield oldname, newname, str(patch)
 
558
 
 
559
    def perform_save(self, window):
 
560
        try:
 
561
            save_path = self.window._get_save_path(osutils.basename(self.path))
 
562
        except SelectCancelled:
 
563
            return
 
564
        source = open(self.path, 'rb')
 
565
        try:
 
566
            target = open(save_path, 'wb')
 
567
            try:
 
568
                osutils.pumpfile(source, target)
 
569
            finally:
 
570
                target.close()
 
571
        finally:
 
572
            source.close()
 
573
 
 
574
    def _provide_operations(self):
 
575
        return [('Save', self.perform_save)]
 
576
 
 
577
 
 
578
class MergeDirectiveController(DiffController):
 
579
 
 
580
    def __init__(self, path, directive, window=None):
 
581
        super(MergeDirectiveController, self).__init__(
 
582
            path, directive.patch.splitlines(True), window)
475
583
        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
 
584
        self.merge_target = None
 
585
 
 
586
    def _provide_operations(self):
 
587
        return [('Merge', self.perform_merge), ('Save', self.perform_save)]
496
588
 
497
589
    def perform_merge(self, window):
498
 
        try:
499
 
            tree = self._get_merge_target()
500
 
        except SelectCancelled:
501
 
            return
 
590
        if self.merge_target is None:
 
591
            try:
 
592
                self.merge_target = self.window._get_merge_target()
 
593
            except SelectCancelled:
 
594
                return
 
595
        tree = workingtree.WorkingTree.open(self.merge_target)
502
596
        tree.lock_write()
503
597
        try:
504
598
            try:
505
 
                merger, verified = _mod_merge.Merger.from_mergeable(tree,
506
 
                    self.directive, progress.DummyProgress())
507
 
                merger.check_basis(True)
 
599
                if tree.has_changes():
 
600
                    raise errors.UncommittedChanges(tree)
 
601
                merger, verified = _mod_merge.Merger.from_mergeable(
 
602
                    tree, self.directive, pb=None)
508
603
                merger.merge_type = _mod_merge.Merge3Merger
509
604
                conflict_count = merger.do_merge()
510
605
                merger.set_pending()
511
606
                if conflict_count == 0:
512
 
                    # No conflicts found.
513
 
                    info_dialog(_('Merge successful'),
514
 
                                _('All changes applied successfully.'))
 
607
                    self.window._merge_successful()
515
608
                else:
 
609
                    self.window._conflicts()
516
610
                    # There are conflicts to be resolved.
517
 
                    warning_dialog(_('Conflicts encountered'),
518
 
                                   _('Please resolve the conflicts manually'
519
 
                                     ' before committing.'))
520
 
                self.destroy()
 
611
                self.window.destroy()
521
612
            except Exception, e:
522
 
                error_dialog('Error', str(e))
 
613
                self.window._handle_error(e)
523
614
        finally:
524
615
            tree.unlock()
525
616
 
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
617
 
571
618
def iter_changes_to_status(source, target):
572
619
    """Determine the differences between trees.
584
631
    renamed_and_modified = 'renamed and modified'
585
632
    modified = 'modified'
586
633
    kind_changed = 'kind changed'
 
634
    missing = 'missing'
587
635
 
588
636
    # TODO: Handle metadata changes
589
637
 
604
652
                    source_marker = ''
605
653
                else:
606
654
                    source_marker = osutils.kind_marker(kinds[0])
 
655
 
607
656
                if kinds[1] is None:
608
 
                    assert kinds[0] is not None
609
 
                    marker = osutils.kind_marker(kinds[0])
 
657
                    if kinds[0] is None:
 
658
                        # We assume bzr will flag only files in that case,
 
659
                        # there may be a bzr bug there as only files seems to
 
660
                        # not receive any kind.
 
661
                        marker = osutils.kind_marker('file')
 
662
                    else:
 
663
                        marker = osutils.kind_marker(kinds[0])
610
664
                else:
611
665
                    marker = osutils.kind_marker(kinds[1])
612
666
 
614
668
                if real_path is None:
615
669
                    real_path = paths[0]
616
670
                assert real_path is not None
617
 
                display_path = real_path + marker
618
671
 
619
672
                present_source = versioned[0] and kinds[0] is not None
620
673
                present_target = versioned[1] and kinds[1] is not None
621
674
 
622
 
                if present_source != present_target:
 
675
                if kinds[0] is None and kinds[1] is None:
 
676
                    change_type = missing
 
677
                    display_path = real_path + marker
 
678
                elif present_source != present_target:
623
679
                    if present_target:
624
680
                        change_type = added
625
681
                    else:
626
682
                        assert present_source
627
683
                        change_type = removed
 
684
                    display_path = real_path + marker
628
685
                elif names[0] != names[1] or parent_ids[0] != parent_ids[1]:
629
686
                    # Renamed
630
687
                    if changed_content or executables[0] != executables[1]:
638
695
                    change_type = kind_changed
639
696
                    display_path = (paths[0] + source_marker
640
697
                                    + ' => ' + paths[1] + marker)
641
 
                elif changed_content is True or executables[0] != executables[1]:
 
698
                elif changed_content or executables[0] != executables[1]:
642
699
                    change_type = modified
 
700
                    display_path = real_path + marker
643
701
                else:
644
702
                    assert False, "How did we get here?"
645
703