/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: Vincent Ladeuil
  • Date: 2008-05-05 18:16:46 UTC
  • mto: (487.1.1 gtk)
  • mto: This revision was merged to the branch mainline in revision 490.
  • Revision ID: v.ladeuil+lp@free.fr-20080505181646-n95l8ltw2u6jtr26
Fix bug #187283 fix replacing _() by _i18n().

* genpot.sh 
Remove duplication. Add the ability to specify the genrated pot
file on command-line for debugging purposes.

* po/olive-gtk.pot:
Regenerated.

* __init__.py, branch.py, branchview/treeview.py, checkout.py,
commit.py, conflicts.py, diff.py, errors.py, initialize.py,
merge.py, nautilus-bzr.py, olive/__init__.py, olive/add.py,
olive/bookmark.py, olive/guifiles.py, olive/info.py,
olive/menu.py, olive/mkdir.py, olive/move.py, olive/remove.py,
olive/rename.py, push.py, revbrowser.py, status.py, tags.py:
Replace all calls to _() by calls to _i18n(), the latter being
defined in __init__.py and imported in the other modules from
there. This fix the problem encountered countless times when
running bzr selftest and getting silly error messages about
boolean not being callables.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: UTF-8 -*-
1
2
"""Difference window.
2
3
 
3
4
This module contains the code to manage the diff window which shows
4
5
the changes made between two revisions on a branch.
5
6
"""
6
7
 
7
 
__copyright__ = "Copyright 2005 Canonical Ltd."
 
8
__copyright__ = "Copyright © 2005 Canonical Ltd."
8
9
__author__    = "Scott James Remnant <scott@ubuntu.com>"
9
10
 
10
11
 
17
18
import os
18
19
import re
19
20
import sys
20
 
try:
21
 
    from xml.etree.ElementTree import Element, SubElement, tostring
22
 
except ImportError:
23
 
    from elementtree.ElementTree import Element, SubElement, tostring
24
21
 
25
22
try:
26
 
    import gtksourceview2
 
23
    import gtksourceview
27
24
    have_gtksourceview = True
28
25
except ImportError:
29
26
    have_gtksourceview = False
49
46
from dialog import error_dialog, info_dialog, warning_dialog
50
47
 
51
48
 
52
 
def fallback_guess_language(slm, content_type):
53
 
    for lang_id in slm.get_language_ids():
54
 
        lang = slm.get_language(lang_id)
55
 
        if "text/x-patch" in lang.get_mime_types():
56
 
            return lang
57
 
    return None
58
 
 
59
 
 
60
49
class SelectCancelled(Exception):
61
50
 
62
51
    pass
75
64
        self.set_shadow_type(gtk.SHADOW_IN)
76
65
 
77
66
        if have_gtksourceview:
78
 
            self.buffer = gtksourceview2.Buffer()
79
 
            slm = gtksourceview2.LanguageManager()
80
 
            guess_language = getattr(gtksourceview2.LanguageManager, 
81
 
                "guess_language", fallback_guess_language)
82
 
            gsl = guess_language(slm, content_type="text/x-patch")
 
67
            self.buffer = gtksourceview.SourceBuffer()
 
68
            slm = gtksourceview.SourceLanguagesManager()
 
69
            gsl = slm.get_language_from_mime_type("text/x-patch")
83
70
            if have_gconf:
84
 
                self.apply_gedit_colors(self.buffer)
85
 
            self.apply_colordiff_colors(self.buffer)
 
71
                self.apply_gedit_colors(gsl)
 
72
            self.apply_colordiff_colors(gsl)
86
73
            self.buffer.set_language(gsl)
87
 
            self.buffer.set_highlight_syntax(True)
 
74
            self.buffer.set_highlight(True)
88
75
 
89
 
            self.sourceview = gtksourceview2.View(self.buffer)
 
76
            sourceview = gtksourceview.SourceView(self.buffer)
90
77
        else:
91
78
            self.buffer = gtk.TextBuffer()
92
 
            self.sourceview = gtk.TextView(self.buffer)
 
79
            sourceview = gtk.TextView(self.buffer)
93
80
 
94
 
        self.sourceview.set_editable(False)
95
 
        self.sourceview.modify_font(pango.FontDescription("Monospace"))
96
 
        self.add(self.sourceview)
97
 
        self.sourceview.show()
 
81
        sourceview.set_editable(False)
 
82
        sourceview.modify_font(pango.FontDescription("Monospace"))
 
83
        self.add(sourceview)
 
84
        sourceview.show()
98
85
 
99
86
    @staticmethod
100
 
    def apply_gedit_colors(buf):
101
 
        """Set style to that specified in gedit configuration.
 
87
    def apply_gedit_colors(lang):
 
88
        """Set style for lang to that specified in gedit configuration.
102
89
 
103
90
        This method needs the gconf module.
104
91
 
105
 
        :param buf: a gtksourceview2.Buffer object.
 
92
        :param lang: a gtksourceview.SourceLanguage object.
106
93
        """
107
 
        GEDIT_SCHEME_PATH = '/apps/gedit-2/preferences/editor/colors/scheme'
 
94
        GEDIT_SYNTAX_PATH = '/apps/gedit-2/preferences/syntax_highlighting'
 
95
        GEDIT_LANG_PATH = GEDIT_SYNTAX_PATH + '/' + lang.get_id()
108
96
 
109
97
        client = gconf.client_get_default()
110
 
        style_scheme_name = client.get_string(GEDIT_SCHEME_PATH)
111
 
        if style_scheme_name is not None:
112
 
            style_scheme = gtksourceview2.StyleSchemeManager().get_scheme(style_scheme_name)
113
 
            
114
 
            buf.set_style_scheme(style_scheme)
 
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)
115
148
 
116
149
    @classmethod
117
 
    def apply_colordiff_colors(klass, buf):
 
150
    def apply_colordiff_colors(klass, lang):
118
151
        """Set style colors for lang using the colordiff configuration file.
119
152
 
120
153
        Both ~/.colordiffrc and ~/.colordiffrc.bzr-gtk are read.
121
154
 
122
 
        :param buf: a "Diff" gtksourceview2.Buffer object.
 
155
        :param lang: a "Diff" gtksourceview.SourceLanguage object.
123
156
        """
124
 
        scheme_manager = gtksourceview2.StyleSchemeManager()
125
 
        style_scheme = scheme_manager.get_scheme('colordiff')
126
 
        
127
 
        # if style scheme not found, we'll generate it from colordiffrc
128
 
        # TODO: reload if colordiffrc has changed.
129
 
        if style_scheme is None:
130
 
            colors = {}
131
 
 
132
 
            for f in ('~/.colordiffrc', '~/.colordiffrc.bzr-gtk'):
133
 
                f = os.path.expanduser(f)
134
 
                if os.path.exists(f):
135
 
                    try:
136
 
                        f = file(f)
137
 
                    except IOError, e:
138
 
                        warning('could not open file %s: %s' % (f, str(e)))
139
 
                    else:
140
 
                        colors.update(klass.parse_colordiffrc(f))
141
 
                        f.close()
142
 
 
143
 
            if not colors:
144
 
                # ~/.colordiffrc does not exist
145
 
                return
146
 
            
147
 
            mapping = {
148
 
                # map GtkSourceView2 scheme styles to colordiff names
 
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
149
176
                # since GSV is richer, accept new names for extra bits,
150
177
                # defaulting to old names if they're not present
151
 
                'diff:added-line': ['newtext'],
152
 
                'diff:removed-line': ['oldtext'],
153
 
                'diff:location': ['location', 'diffstuff'],
154
 
                'diff:file': ['file', 'diffstuff'],
155
 
                'diff:special-case': ['specialcase', 'diffstuff'],
156
 
            }
157
 
            
158
 
            converted_colors = {}
159
 
            for name, values in mapping.items():
160
 
                color = None
161
 
                for value in values:
162
 
                    color = colors.get(value, None)
163
 
                    if color is not None:
164
 
                        break
165
 
                if color is None:
166
 
                    continue
167
 
                converted_colors[name] = color
168
 
            
169
 
            # some xml magic to produce needed style scheme description
170
 
            e_style_scheme = Element('style-scheme')
171
 
            e_style_scheme.set('id', 'colordiff')
172
 
            e_style_scheme.set('_name', 'ColorDiff')
173
 
            e_style_scheme.set('version', '1.0')
174
 
            for name, color in converted_colors.items():
175
 
                style = SubElement(e_style_scheme, 'style')
176
 
                style.set('name', name)
177
 
                style.set('foreground', '#%s' % color)
178
 
            
179
 
            scheme_xml = tostring(e_style_scheme, 'UTF-8')
180
 
            if not os.path.exists(os.path.expanduser('~/.local/share/gtksourceview-2.0/styles')):
181
 
                os.makedirs(os.path.expanduser('~/.local/share/gtksourceview-2.0/styles'))
182
 
            file(os.path.expanduser('~/.local/share/gtksourceview-2.0/styles/colordiff.xml'), 'w').write(scheme_xml)
183
 
            
184
 
            scheme_manager.force_rescan()
185
 
            style_scheme = scheme_manager.get_scheme('colordiff')
186
 
        
187
 
        buf.set_style_scheme(style_scheme)
 
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)
188
205
 
189
206
    @staticmethod
190
207
    def parse_colordiffrc(fileobj):
288
305
        scrollwin.set_shadow_type(gtk.SHADOW_IN)
289
306
        self.pack1(scrollwin)
290
307
        scrollwin.show()
291
 
        
 
308
 
292
309
        self.model = gtk.TreeStore(str, str)
293
310
        self.treeview = gtk.TreeView(self.model)
294
311
        self.treeview.set_headers_visible(False)
311
328
        """
312
329
        # The diffs of the  selected file: a scrollable source or
313
330
        # text view
314
 
 
315
 
    def set_diff_text_sections(self, sections):
316
 
        if getattr(self, 'diff_view', None) is None:
317
 
            self.diff_view = DiffFileView()
318
 
            self.pack2(self.diff_view)
 
331
        self.diff_view = DiffFileView()
319
332
        self.diff_view.show()
320
 
        for oldname, newname, patch in sections:
 
333
        self.pack2(self.diff_view)
 
334
        self.model.append(None, [ "Complete Diff", "" ])
 
335
        self.diff_view._diffs[None] = ''.join(lines)
 
336
        for patch in parse_patches(lines):
 
337
            oldname = patch.oldname.split('\t')[0]
 
338
            newname = patch.newname.split('\t')[0]
 
339
            self.model.append(None, [oldname, newname])
321
340
            self.diff_view._diffs[newname] = str(patch)
322
 
            if newname is None:
323
 
                newname = ''
324
 
            self.model.append(None, [oldname, newname])
325
341
        self.diff_view.show_diff(None)
326
342
 
327
343
    def set_diff(self, rev_tree, parent_tree):
330
346
        Compares the two trees and populates the window with the
331
347
        differences.
332
348
        """
333
 
        if getattr(self, 'diff_view', None) is None:
334
 
            self.diff_view = DiffView()
335
 
            self.pack2(self.diff_view)
 
349
        self.diff_view = DiffView()
 
350
        self.pack2(self.diff_view)
336
351
        self.diff_view.show()
337
352
        self.diff_view.set_trees(rev_tree, parent_tree)
338
353
        self.rev_tree = rev_tree
365
380
                self.model.append(titer, [ path, path ])
366
381
 
367
382
        self.treeview.expand_all()
368
 
        self.diff_view.show_diff(None)
369
383
 
370
384
    def set_file(self, file_path):
371
385
        """Select the current file to display"""
388
402
            return
389
403
        elif specific_files == [ "" ]:
390
404
            specific_files = None
391
 
        
 
405
 
392
406
        self.diff_view.show_diff(specific_files)
393
 
    
394
 
    def _on_wraplines_toggled(self, widget=None, wrap=False):
395
 
        """Callback for when the wrap lines checkbutton is toggled"""
396
 
        if wrap or widget.get_active():
397
 
            self.diff_view.sourceview.set_wrap_mode(gtk.WRAP_WORD)
398
 
        else:
399
 
            self.diff_view.sourceview.set_wrap_mode(gtk.WRAP_NONE)
 
407
 
400
408
 
401
409
class DiffWindow(Window):
402
410
    """Diff window.
405
413
    differences between two revisions on a branch.
406
414
    """
407
415
 
408
 
    def __init__(self, parent=None, operations=None):
 
416
    def __init__(self, parent=None):
409
417
        Window.__init__(self, parent)
410
418
        self.set_border_width(0)
411
419
        self.set_title("bzrk diff")
416
424
        width = int(monitor.width * 0.66)
417
425
        height = int(monitor.height * 0.66)
418
426
        self.set_default_size(width, height)
419
 
        self.construct(operations)
420
 
 
421
 
    def construct(self, operations):
 
427
 
 
428
        self.construct()
 
429
 
 
430
    def construct(self):
422
431
        """Construct the window contents."""
423
432
        self.vbox = gtk.VBox()
424
433
        self.add(self.vbox)
425
434
        self.vbox.show()
 
435
        hbox = self._get_button_bar()
 
436
        if hbox is not None:
 
437
            self.vbox.pack_start(hbox, expand=False, fill=True)
426
438
        self.diff = DiffWidget()
427
 
        self.vbox.pack_end(self.diff, True, True, 0)
 
439
        self.vbox.add(self.diff)
428
440
        self.diff.show_all()
429
 
        # Build after DiffWidget to connect signals
430
 
        menubar = self._get_menu_bar()
431
 
        self.vbox.pack_start(menubar, False, False, 0)
432
 
        hbox = self._get_button_bar(operations)
433
 
        if hbox is not None:
434
 
            self.vbox.pack_start(hbox, False, True, 0)
435
 
        
436
 
    
437
 
    def _get_menu_bar(self):
438
 
        menubar = gtk.MenuBar()
439
 
        # View menu
440
 
        mb_view = gtk.MenuItem(_i18n("_View"))
441
 
        mb_view_menu = gtk.Menu()
442
 
        mb_view_wrapsource = gtk.CheckMenuItem(_i18n("Wrap _Long Lines"))
443
 
        mb_view_wrapsource.connect('activate', self.diff._on_wraplines_toggled)
444
 
        mb_view_wrapsource.show()
445
 
        mb_view_menu.append(mb_view_wrapsource)
446
 
        mb_view.show()
447
 
        mb_view.set_submenu(mb_view_menu)
448
 
        mb_view.show()
449
 
        menubar.append(mb_view)
450
 
        menubar.show()
451
 
        return menubar
452
 
    
453
 
    def _get_button_bar(self, operations):
 
441
 
 
442
    def _get_button_bar(self):
454
443
        """Return a button bar to use.
455
444
 
456
445
        :return: None, meaning that no button bar will be used.
457
446
        """
458
 
        if operations is None:
459
 
            return None
 
447
        return None
 
448
 
 
449
    def set_diff_text(self, description, lines):
 
450
        """Set the diff from a text.
 
451
 
 
452
        The diff must be in unified diff format, and will be parsed to
 
453
        determine filenames.
 
454
        """
 
455
        self.diff.set_diff_text(lines)
 
456
        self.set_title(description + " - bzrk diff")
 
457
 
 
458
    def set_diff(self, description, rev_tree, parent_tree):
 
459
        """Set the differences showed by this window.
 
460
 
 
461
        Compares the two trees and populates the window with the
 
462
        differences.
 
463
        """
 
464
        self.diff.set_diff(rev_tree, parent_tree)
 
465
        self.set_title(description + " - bzrk diff")
 
466
 
 
467
    def set_file(self, file_path):
 
468
        self.diff.set_file(file_path)
 
469
 
 
470
 
 
471
class MergeDirectiveWindow(DiffWindow):
 
472
 
 
473
    def __init__(self, directive, path):
 
474
        DiffWindow.__init__(self, None)
 
475
        self._merge_target = None
 
476
        self.directive = directive
 
477
        self.path = path
 
478
 
 
479
    def _get_button_bar(self):
 
480
        """The button bar has only the Merge button"""
 
481
        merge_button = gtk.Button('Merge')
 
482
        merge_button.show()
 
483
        merge_button.set_relief(gtk.RELIEF_NONE)
 
484
        merge_button.connect("clicked", self.perform_merge)
 
485
 
 
486
        save_button = gtk.Button('Save')
 
487
        save_button.show()
 
488
        save_button.set_relief(gtk.RELIEF_NONE)
 
489
        save_button.connect("clicked", self.perform_save)
 
490
 
460
491
        hbox = gtk.HButtonBox()
461
492
        hbox.set_layout(gtk.BUTTONBOX_START)
462
 
        for title, method in operations:
463
 
            merge_button = gtk.Button(title)
464
 
            merge_button.show()
465
 
            merge_button.set_relief(gtk.RELIEF_NONE)
466
 
            merge_button.connect("clicked", method)
467
 
            hbox.pack_start(merge_button, expand=False, fill=True)
 
493
        hbox.pack_start(merge_button, expand=False, fill=True)
 
494
        hbox.pack_start(save_button, expand=False, fill=True)
468
495
        hbox.show()
469
496
        return hbox
470
497
 
471
 
    def _get_merge_target(self):
472
 
        d = gtk.FileChooserDialog('Merge branch', self,
473
 
                                  gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
474
 
                                  buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
475
 
                                           gtk.STOCK_CANCEL,
476
 
                                           gtk.RESPONSE_CANCEL,))
477
 
        try:
478
 
            result = d.run()
479
 
            if result != gtk.RESPONSE_OK:
480
 
                raise SelectCancelled()
481
 
            return d.get_current_folder_uri()
482
 
        finally:
483
 
            d.destroy()
484
 
 
485
 
    def _merge_successful(self):
486
 
        # No conflicts found.
487
 
        info_dialog(_i18n('Merge successful'),
488
 
                    _i18n('All changes applied successfully.'))
489
 
 
490
 
    def _conflicts(self):
491
 
        warning_dialog(_i18n('Conflicts encountered'),
492
 
                       _i18n('Please resolve the conflicts manually'
493
 
                             ' before committing.'))
494
 
 
495
 
    def _handle_error(self, e):
496
 
        error_dialog('Error', str(e))
497
 
 
498
 
    def _get_save_path(self, basename):
499
 
        d = gtk.FileChooserDialog('Save As', self,
500
 
                                  gtk.FILE_CHOOSER_ACTION_SAVE,
501
 
                                  buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
502
 
                                           gtk.STOCK_CANCEL,
503
 
                                           gtk.RESPONSE_CANCEL,))
504
 
        d.set_current_name(basename)
505
 
        try:
506
 
            result = d.run()
507
 
            if result != gtk.RESPONSE_OK:
508
 
                raise SelectCancelled()
509
 
            return urlutils.local_path_from_url(d.get_uri())
510
 
        finally:
511
 
            d.destroy()
512
 
 
513
 
    def set_diff(self, description, rev_tree, parent_tree):
514
 
        """Set the differences showed by this window.
515
 
 
516
 
        Compares the two trees and populates the window with the
517
 
        differences.
518
 
        """
519
 
        self.diff.set_diff(rev_tree, parent_tree)
520
 
        self.set_title(description + " - bzrk diff")
521
 
 
522
 
    def set_file(self, file_path):
523
 
        self.diff.set_file(file_path)
524
 
 
525
 
 
526
 
class DiffController(object):
527
 
 
528
 
    def __init__(self, path, patch, window=None):
529
 
        self.path = path
530
 
        self.patch = patch
531
 
        if window is None:
532
 
            window = DiffWindow(operations=self._provide_operations())
533
 
            self.initialize_window(window)
534
 
        self.window = window
535
 
 
536
 
    def initialize_window(self, window):
537
 
        window.diff.set_diff_text_sections(self.get_diff_sections())
538
 
        window.set_title(self.path + " - diff")
539
 
 
540
 
    def get_diff_sections(self):
541
 
        yield "Complete Diff", None, ''.join(self.patch)
542
 
        for patch in parse_patches(self.patch):
543
 
            oldname = patch.oldname.split('\t')[0]
544
 
            newname = patch.newname.split('\t')[0]
545
 
            yield oldname, newname, str(patch)
546
 
 
547
 
    def perform_save(self, window):
548
 
        try:
549
 
            save_path = self.window._get_save_path(osutils.basename(self.path))
550
 
        except SelectCancelled:
551
 
            return
552
 
        source = open(self.path, 'rb')
553
 
        try:
554
 
            target = open(save_path, 'wb')
555
 
            try:
556
 
                osutils.pumpfile(source, target)
557
 
            finally:
558
 
                target.close()
559
 
        finally:
560
 
            source.close()
561
 
 
562
 
    def _provide_operations(self):
563
 
        return [('Save', self.perform_save)]
564
 
 
565
 
 
566
 
class MergeDirectiveController(DiffController):
567
 
 
568
 
    def __init__(self, path, directive, window=None):
569
 
        DiffController.__init__(self, path, directive.patch.splitlines(True),
570
 
                                window)
571
 
        self.directive = directive
572
 
        self.merge_target = None
573
 
 
574
 
    def _provide_operations(self):
575
 
        return [('Merge', self.perform_merge), ('Save', self.perform_save)]
576
 
 
577
498
    def perform_merge(self, window):
578
 
        if self.merge_target is None:
579
 
            try:
580
 
                self.merge_target = self.window._get_merge_target()
581
 
            except SelectCancelled:
582
 
                return
583
 
        tree = workingtree.WorkingTree.open(self.merge_target)
 
499
        try:
 
500
            tree = self._get_merge_target()
 
501
        except SelectCancelled:
 
502
            return
584
503
        tree.lock_write()
585
504
        try:
586
505
            try:
591
510
                conflict_count = merger.do_merge()
592
511
                merger.set_pending()
593
512
                if conflict_count == 0:
594
 
                    self.window._merge_successful()
 
513
                    # No conflicts found.
 
514
                    info_dialog(_i18n('Merge successful'),
 
515
                                _i18n('All changes applied successfully.'))
595
516
                else:
596
 
                    self.window._conflicts()
597
517
                    # There are conflicts to be resolved.
598
 
                self.window.destroy()
 
518
                    warning_dialog(_i18n('Conflicts encountered'),
 
519
                                   _i18n('Please resolve the conflicts manually'
 
520
                                         ' before committing.'))
 
521
                self.destroy()
599
522
            except Exception, e:
600
 
                self.window._handle_error(e)
 
523
                error_dialog('Error', str(e))
601
524
        finally:
602
525
            tree.unlock()
603
526
 
 
527
    def _get_merge_target(self):
 
528
        if self._merge_target is not None:
 
529
            return self._merge_target
 
530
        d = gtk.FileChooserDialog('Merge branch', self,
 
531
                                  gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
 
532
                                  buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
 
533
                                           gtk.STOCK_CANCEL,
 
534
                                           gtk.RESPONSE_CANCEL,))
 
535
        try:
 
536
            result = d.run()
 
537
            if result != gtk.RESPONSE_OK:
 
538
                raise SelectCancelled()
 
539
            uri = d.get_current_folder_uri()
 
540
        finally:
 
541
            d.destroy()
 
542
        return workingtree.WorkingTree.open(uri)
 
543
 
 
544
    def perform_save(self, window):
 
545
        d = gtk.FileChooserDialog('Save As', self,
 
546
                                  gtk.FILE_CHOOSER_ACTION_SAVE,
 
547
                                  buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
 
548
                                           gtk.STOCK_CANCEL,
 
549
                                           gtk.RESPONSE_CANCEL,))
 
550
        d.set_current_name(osutils.basename(self.path))
 
551
        try:
 
552
            try:
 
553
                result = d.run()
 
554
                if result != gtk.RESPONSE_OK:
 
555
                    raise SelectCancelled()
 
556
                uri = d.get_uri()
 
557
            finally:
 
558
                d.destroy()
 
559
        except SelectCancelled:
 
560
            return
 
561
        source = open(self.path, 'rb')
 
562
        try:
 
563
            target = open(urlutils.local_path_from_url(uri), 'wb')
 
564
            try:
 
565
                target.write(source.read())
 
566
            finally:
 
567
                target.close()
 
568
        finally:
 
569
            source.close()
 
570
 
604
571
 
605
572
def iter_changes_to_status(source, target):
606
573
    """Determine the differences between trees.
618
585
    renamed_and_modified = 'renamed and modified'
619
586
    modified = 'modified'
620
587
    kind_changed = 'kind changed'
621
 
    missing = 'missing'
622
588
 
623
589
    # TODO: Handle metadata changes
624
590
 
639
605
                    source_marker = ''
640
606
                else:
641
607
                    source_marker = osutils.kind_marker(kinds[0])
642
 
 
643
608
                if kinds[1] is None:
644
 
                    if kinds[0] is None:
645
 
                        # We assume bzr will flag only files in that case,
646
 
                        # there may be a bzr bug there as only files seems to
647
 
                        # not receive any kind.
648
 
                        marker = osutils.kind_marker('file')
649
 
                    else:
650
 
                        marker = osutils.kind_marker(kinds[0])
 
609
                    assert kinds[0] is not None
 
610
                    marker = osutils.kind_marker(kinds[0])
651
611
                else:
652
612
                    marker = osutils.kind_marker(kinds[1])
653
613
 
655
615
                if real_path is None:
656
616
                    real_path = paths[0]
657
617
                assert real_path is not None
 
618
                display_path = real_path + marker
658
619
 
659
620
                present_source = versioned[0] and kinds[0] is not None
660
621
                present_target = versioned[1] and kinds[1] is not None
661
622
 
662
 
                if kinds[0] is None and kinds[1] is None:
663
 
                    change_type = missing
664
 
                    display_path = real_path + marker
665
 
                elif present_source != present_target:
 
623
                if present_source != present_target:
666
624
                    if present_target:
667
625
                        change_type = added
668
626
                    else:
669
627
                        assert present_source
670
628
                        change_type = removed
671
 
                    display_path = real_path + marker
672
629
                elif names[0] != names[1] or parent_ids[0] != parent_ids[1]:
673
630
                    # Renamed
674
631
                    if changed_content or executables[0] != executables[1]:
682
639
                    change_type = kind_changed
683
640
                    display_path = (paths[0] + source_marker
684
641
                                    + ' => ' + paths[1] + marker)
685
 
                elif changed_content or executables[0] != executables[1]:
 
642
                elif changed_content is True or executables[0] != executables[1]:
686
643
                    change_type = modified
687
 
                    display_path = real_path + marker
688
644
                else:
689
645
                    assert False, "How did we get here?"
690
646