/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: Andrew Starr-Bochicchio
  • Author(s): Mathias Brodala
  • Date: 2010-01-25 01:58:58 UTC
  • mto: This revision was merged to the branch mainline in revision 673.
  • Revision ID: a.starr.b@gmail.com-20100125015858-tphqkvgyfrvob63o
olive/menu.py: Correctly import commit dialog.

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
 
18
17
import os
19
18
import re
20
19
import sys
 
20
try:
 
21
    from xml.etree.ElementTree import Element, SubElement, tostring
 
22
except ImportError:
 
23
    from elementtree.ElementTree import Element, SubElement, tostring
21
24
 
22
25
try:
23
 
    import gtksourceview
 
26
    import gtksourceview2
24
27
    have_gtksourceview = True
25
28
except ImportError:
26
29
    have_gtksourceview = False
30
33
except ImportError:
31
34
    have_gconf = False
32
35
 
33
 
from bzrlib import osutils
 
36
from bzrlib import (
 
37
    merge as _mod_merge,
 
38
    osutils,
 
39
    progress,
 
40
    urlutils,
 
41
    workingtree,
 
42
)
34
43
from bzrlib.diff import show_diff_trees, internal_diff
35
44
from bzrlib.errors import NoSuchFile
 
45
from bzrlib.patches import parse_patches
36
46
from bzrlib.trace import warning
 
47
from bzrlib.plugins.gtk import _i18n
37
48
from bzrlib.plugins.gtk.window import Window
38
 
 
39
 
 
40
 
class DiffView(gtk.ScrolledWindow):
41
 
    """This is the soft and chewy filling for a DiffWindow."""
 
49
from dialog import error_dialog, info_dialog, warning_dialog
 
50
 
 
51
 
 
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
class SelectCancelled(Exception):
 
61
 
 
62
    pass
 
63
 
 
64
 
 
65
class DiffFileView(gtk.ScrolledWindow):
 
66
    """Window for displaying diffs from a diff file"""
42
67
 
43
68
    def __init__(self):
44
69
        gtk.ScrolledWindow.__init__(self)
45
 
 
46
70
        self.construct()
47
 
        self.rev_tree = None
48
 
        self.parent_tree = None
 
71
        self._diffs = {}
49
72
 
50
73
    def construct(self):
51
74
        self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
52
75
        self.set_shadow_type(gtk.SHADOW_IN)
53
76
 
54
77
        if have_gtksourceview:
55
 
            self.buffer = gtksourceview.SourceBuffer()
56
 
            slm = gtksourceview.SourceLanguagesManager()
57
 
            gsl = slm.get_language_from_mime_type("text/x-patch")
 
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")
58
83
            if have_gconf:
59
 
                self.apply_gedit_colors(gsl)
60
 
            self.apply_colordiff_colors(gsl)
 
84
                self.apply_gedit_colors(self.buffer)
 
85
            self.apply_colordiff_colors(self.buffer)
61
86
            self.buffer.set_language(gsl)
62
 
            self.buffer.set_highlight(True)
 
87
            self.buffer.set_highlight_syntax(True)
63
88
 
64
 
            sourceview = gtksourceview.SourceView(self.buffer)
 
89
            self.sourceview = gtksourceview2.View(self.buffer)
65
90
        else:
66
91
            self.buffer = gtk.TextBuffer()
67
 
            sourceview = gtk.TextView(self.buffer)
 
92
            self.sourceview = gtk.TextView(self.buffer)
68
93
 
69
 
        sourceview.set_editable(False)
70
 
        sourceview.modify_font(pango.FontDescription("Monospace"))
71
 
        self.add(sourceview)
72
 
        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()
73
98
 
74
99
    @staticmethod
75
 
    def apply_gedit_colors(lang):
76
 
        """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.
77
102
 
78
103
        This method needs the gconf module.
79
104
 
80
 
        :param lang: a gtksourceview.SourceLanguage object.
 
105
        :param buf: a gtksourceview2.Buffer object.
81
106
        """
82
 
        GEDIT_SYNTAX_PATH = '/apps/gedit-2/preferences/syntax_highlighting'
83
 
        GEDIT_LANG_PATH = GEDIT_SYNTAX_PATH + '/' + lang.get_id()
 
107
        GEDIT_SCHEME_PATH = '/apps/gedit-2/preferences/editor/colors/scheme'
84
108
 
85
109
        client = gconf.client_get_default()
86
 
        client.add_dir(GEDIT_LANG_PATH, gconf.CLIENT_PRELOAD_NONE)
87
 
 
88
 
        for tag in lang.get_tags():
89
 
            tag_id = tag.get_id()
90
 
            gconf_key = GEDIT_LANG_PATH + '/' + tag_id
91
 
            style_string = client.get_string(gconf_key)
92
 
 
93
 
            if style_string is None:
94
 
                continue
95
 
 
96
 
            # function to get a bool from a string that's either '0' or '1'
97
 
            string_bool = lambda x: bool(int(x))
98
 
 
99
 
            # style_string is a string like "2/#FFCCAA/#000000/0/1/0/0"
100
 
            # values are: mask, fg, bg, italic, bold, underline, strike
101
 
            # this packs them into (str_value, attr_name, conv_func) tuples
102
 
            items = zip(style_string.split('/'), ['mask', 'foreground',
103
 
                'background', 'italic', 'bold', 'underline', 'strikethrough' ],
104
 
                [ int, gtk.gdk.color_parse, gtk.gdk.color_parse, string_bool,
105
 
                    string_bool, string_bool, string_bool ]
106
 
            )
107
 
 
108
 
            style = gtksourceview.SourceTagStyle()
109
 
 
110
 
            # XXX The mask attribute controls whether the present values of
111
 
            # foreground and background color should in fact be used. Ideally
112
 
            # (and that's what gedit does), one could set all three attributes,
113
 
            # and let the TagStyle object figure out which colors to use.
114
 
            # However, in the GtkSourceview python bindings, the mask attribute
115
 
            # is read-only, and it's derived instead from the colors being
116
 
            # set or not. This means that we have to sometimes refrain from
117
 
            # setting fg or bg colors, depending on the value of the mask.
118
 
            # This code could go away if mask were writable.
119
 
            mask = int(items[0][0])
120
 
            if not (mask & 1): # GTK_SOURCE_TAG_STYLE_USE_BACKGROUND
121
 
                items[2:3] = []
122
 
            if not (mask & 2): # GTK_SOURCE_TAG_STYLE_USE_FOREGROUND
123
 
                items[1:2] = []
124
 
            items[0:1] = [] # skip the mask unconditionally
125
 
 
126
 
            for value, attr, func in items:
127
 
                try:
128
 
                    value = func(value)
129
 
                except ValueError:
130
 
                    warning('gconf key %s contains an invalid value: %s'
131
 
                            % gconf_key, value)
132
 
                else:
133
 
                    setattr(style, attr, value)
134
 
 
135
 
            lang.set_tag_style(tag_id, style)
136
 
 
137
 
    @staticmethod
138
 
    def apply_colordiff_colors(lang):
 
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)
 
115
 
 
116
    @classmethod
 
117
    def apply_colordiff_colors(klass, buf):
139
118
        """Set style colors for lang using the colordiff configuration file.
140
119
 
141
120
        Both ~/.colordiffrc and ~/.colordiffrc.bzr-gtk are read.
142
121
 
143
 
        :param lang: a "Diff" gtksourceview.SourceLanguage object.
 
122
        :param buf: a "Diff" gtksourceview2.Buffer object.
144
123
        """
145
 
        colors = {}
146
 
 
147
 
        for f in ('~/.colordiffrc', '~/.colordiffrc.bzr-gtk'):
148
 
            f = os.path.expanduser(f)
149
 
            if os.path.exists(f):
150
 
                try:
151
 
                    f = file(f)
152
 
                except IOError, e:
153
 
                    warning('could not open file %s: %s' % (f, str(e)))
154
 
                else:
155
 
                    colors.update(DiffView.parse_colordiffrc(f))
156
 
                    f.close()
157
 
 
158
 
        if not colors:
159
 
            # ~/.colordiffrc does not exist
160
 
            return
161
 
 
162
 
        mapping = {
163
 
                # map GtkSourceView tags to colordiff names
 
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
164
149
                # since GSV is richer, accept new names for extra bits,
165
150
                # defaulting to old names if they're not present
166
 
                'Added@32@line': ['newtext'],
167
 
                'Removed@32@line': ['oldtext'],
168
 
                'Location': ['location', 'diffstuff'],
169
 
                'Diff@32@file': ['file', 'diffstuff'],
170
 
                'Special@32@case': ['specialcase', 'diffstuff'],
171
 
        }
172
 
 
173
 
        for tag in lang.get_tags():
174
 
            tag_id = tag.get_id()
175
 
            keys = mapping.get(tag_id, [])
176
 
            color = None
177
 
 
178
 
            for key in keys:
179
 
                color = colors.get(key, None)
180
 
                if color is not None:
181
 
                    break
182
 
 
183
 
            if color is None:
184
 
                continue
185
 
 
186
 
            style = gtksourceview.SourceTagStyle()
187
 
            try:
188
 
                style.foreground = gtk.gdk.color_parse(color)
189
 
            except ValueError:
190
 
                warning('not a valid color: %s' % color)
191
 
            else:
192
 
                lang.set_tag_style(tag_id, style)
 
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)
193
188
 
194
189
    @staticmethod
195
190
    def parse_colordiffrc(fileobj):
216
211
#        self.parent_tree.lock_read()
217
212
#        self.rev_tree.lock_read()
218
213
#        try:
219
 
#            self.delta = _iter_changes_to_status(self.parent_tree, self.rev_tree)
 
214
#            self.delta = iter_changes_to_status(self.parent_tree, self.rev_tree)
220
215
#            self.path_to_status = {}
221
216
#            self.path_to_diff = {}
222
217
#            source_inv = self.parent_tree.inventory
237
232
#            self.parent_tree.unlock()
238
233
 
239
234
    def show_diff(self, specific_files):
 
235
        sections = []
 
236
        if specific_files is None:
 
237
            self.buffer.set_text(self._diffs[None])
 
238
        else:
 
239
            for specific_file in specific_files:
 
240
                sections.append(self._diffs[specific_file])
 
241
            self.buffer.set_text(''.join(sections))
 
242
 
 
243
 
 
244
class DiffView(DiffFileView):
 
245
    """This is the soft and chewy filling for a DiffWindow."""
 
246
 
 
247
    def __init__(self):
 
248
        DiffFileView.__init__(self)
 
249
        self.rev_tree = None
 
250
        self.parent_tree = None
 
251
 
 
252
    def show_diff(self, specific_files):
 
253
        """Show the diff for the specified files"""
240
254
        s = StringIO()
241
255
        show_diff_trees(self.parent_tree, self.rev_tree, s, specific_files,
242
256
                        old_label='', new_label='',
261
275
        self.buffer.set_text(decoded.encode('UTF-8'))
262
276
 
263
277
 
264
 
class DiffWindow(Window):
265
 
    """Diff window.
 
278
class DiffWidget(gtk.HPaned):
 
279
    """Diff widget
266
280
 
267
 
    This object represents and manages a single window containing the
268
 
    differences between two revisions on a branch.
269
281
    """
270
 
 
271
 
    def __init__(self, parent=None):
272
 
        Window.__init__(self, parent)
273
 
        self.set_border_width(0)
274
 
        self.set_title("bzrk diff")
275
 
 
276
 
        # Use two thirds of the screen by default
277
 
        screen = self.get_screen()
278
 
        monitor = screen.get_monitor_geometry(0)
279
 
        width = int(monitor.width * 0.66)
280
 
        height = int(monitor.height * 0.66)
281
 
        self.set_default_size(width, height)
282
 
 
283
 
        self.construct()
284
 
 
285
 
    def construct(self):
286
 
        """Construct the window contents."""
287
 
        # The   window  consists  of   a  pane   containing:  the
288
 
        # hierarchical list  of files on  the left, and  the diff
289
 
        # for the currently selected file on the right.
290
 
        pane = gtk.HPaned()
291
 
        self.add(pane)
292
 
        pane.show()
 
282
    def __init__(self):
 
283
        super(DiffWidget, self).__init__()
293
284
 
294
285
        # The file hierarchy: a scrollable treeview
295
286
        scrollwin = gtk.ScrolledWindow()
296
287
        scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
297
288
        scrollwin.set_shadow_type(gtk.SHADOW_IN)
298
 
        pane.pack1(scrollwin)
 
289
        self.pack1(scrollwin)
299
290
        scrollwin.show()
300
 
 
 
291
        
301
292
        self.model = gtk.TreeStore(str, str)
302
293
        self.treeview = gtk.TreeView(self.model)
303
294
        self.treeview.set_headers_visible(False)
313
304
        column.add_attribute(cell, "text", 0)
314
305
        self.treeview.append_column(column)
315
306
 
 
307
    def set_diff_text(self, lines):
 
308
        """Set the current diff from a list of lines
 
309
 
 
310
        :param lines: The diff to show, in unified diff format
 
311
        """
316
312
        # The diffs of the  selected file: a scrollable source or
317
313
        # text view
318
 
        self.diff_view = DiffView()
319
 
        pane.pack2(self.diff_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)
320
319
        self.diff_view.show()
 
320
        for oldname, newname, patch in sections:
 
321
            self.diff_view._diffs[newname] = str(patch)
 
322
            if newname is None:
 
323
                newname = ''
 
324
            self.model.append(None, [oldname, newname])
 
325
        self.diff_view.show_diff(None)
321
326
 
322
 
    def set_diff(self, description, rev_tree, parent_tree):
 
327
    def set_diff(self, rev_tree, parent_tree):
323
328
        """Set the differences showed by this window.
324
329
 
325
330
        Compares the two trees and populates the window with the
326
331
        differences.
327
332
        """
 
333
        if getattr(self, 'diff_view', None) is None:
 
334
            self.diff_view = DiffView()
 
335
            self.pack2(self.diff_view)
 
336
        self.diff_view.show()
328
337
        self.diff_view.set_trees(rev_tree, parent_tree)
329
338
        self.rev_tree = rev_tree
330
339
        self.parent_tree = parent_tree
356
365
                self.model.append(titer, [ path, path ])
357
366
 
358
367
        self.treeview.expand_all()
359
 
        self.set_title(description + " - bzrk diff")
 
368
        self.diff_view.show_diff(None)
360
369
 
361
370
    def set_file(self, file_path):
 
371
        """Select the current file to display"""
362
372
        tv_path = None
363
373
        for data in self.model:
364
374
            for child in data.iterchildren():
378
388
            return
379
389
        elif specific_files == [ "" ]:
380
390
            specific_files = None
381
 
 
 
391
        
382
392
        self.diff_view.show_diff(specific_files)
383
 
 
384
 
 
385
 
def _iter_changes_to_status(source, target):
 
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)
 
400
 
 
401
class DiffWindow(Window):
 
402
    """Diff window.
 
403
 
 
404
    This object represents and manages a single window containing the
 
405
    differences between two revisions on a branch.
 
406
    """
 
407
 
 
408
    def __init__(self, parent=None, operations=None):
 
409
        Window.__init__(self, parent)
 
410
        self.set_border_width(0)
 
411
        self.set_title("bzrk diff")
 
412
 
 
413
        # Use two thirds of the screen by default
 
414
        screen = self.get_screen()
 
415
        monitor = screen.get_monitor_geometry(0)
 
416
        width = int(monitor.width * 0.66)
 
417
        height = int(monitor.height * 0.66)
 
418
        self.set_default_size(width, height)
 
419
        self.construct(operations)
 
420
 
 
421
    def construct(self, operations):
 
422
        """Construct the window contents."""
 
423
        self.vbox = gtk.VBox()
 
424
        self.add(self.vbox)
 
425
        self.vbox.show()
 
426
        self.diff = DiffWidget()
 
427
        self.vbox.pack_end(self.diff, True, True, 0)
 
428
        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):
 
454
        """Return a button bar to use.
 
455
 
 
456
        :return: None, meaning that no button bar will be used.
 
457
        """
 
458
        if operations is None:
 
459
            return None
 
460
        hbox = gtk.HButtonBox()
 
461
        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)
 
468
        hbox.show()
 
469
        return hbox
 
470
 
 
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
    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)
 
584
        tree.lock_write()
 
585
        try:
 
586
            try:
 
587
                merger, verified = _mod_merge.Merger.from_mergeable(tree,
 
588
                    self.directive, progress.DummyProgress())
 
589
                merger.check_basis(True)
 
590
                merger.merge_type = _mod_merge.Merge3Merger
 
591
                conflict_count = merger.do_merge()
 
592
                merger.set_pending()
 
593
                if conflict_count == 0:
 
594
                    self.window._merge_successful()
 
595
                else:
 
596
                    self.window._conflicts()
 
597
                    # There are conflicts to be resolved.
 
598
                self.window.destroy()
 
599
            except Exception, e:
 
600
                self.window._handle_error(e)
 
601
        finally:
 
602
            tree.unlock()
 
603
 
 
604
 
 
605
def iter_changes_to_status(source, target):
386
606
    """Determine the differences between trees.
387
607
 
388
 
    This is a wrapper around _iter_changes which just yields more
 
608
    This is a wrapper around iter_changes which just yields more
389
609
    understandable results.
390
610
 
391
611
    :param source: The source tree (basis tree)
398
618
    renamed_and_modified = 'renamed and modified'
399
619
    modified = 'modified'
400
620
    kind_changed = 'kind changed'
 
621
    missing = 'missing'
401
622
 
402
623
    # TODO: Handle metadata changes
403
624
 
407
628
        source.lock_read()
408
629
        try:
409
630
            for (file_id, paths, changed_content, versioned, parent_ids, names,
410
 
                 kinds, executables) in target._iter_changes(source):
 
631
                 kinds, executables) in target.iter_changes(source):
411
632
 
412
633
                # Skip the root entry if it isn't very interesting
413
634
                if parent_ids == (None, None):
418
639
                    source_marker = ''
419
640
                else:
420
641
                    source_marker = osutils.kind_marker(kinds[0])
 
642
 
421
643
                if kinds[1] is None:
422
 
                    assert kinds[0] is not None
423
 
                    marker = osutils.kind_marker(kinds[0])
 
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])
424
651
                else:
425
652
                    marker = osutils.kind_marker(kinds[1])
426
653
 
428
655
                if real_path is None:
429
656
                    real_path = paths[0]
430
657
                assert real_path is not None
431
 
                display_path = real_path + marker
432
658
 
433
659
                present_source = versioned[0] and kinds[0] is not None
434
660
                present_target = versioned[1] and kinds[1] is not None
435
661
 
436
 
                if present_source != present_target:
 
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:
437
666
                    if present_target:
438
667
                        change_type = added
439
668
                    else:
440
669
                        assert present_source
441
670
                        change_type = removed
 
671
                    display_path = real_path + marker
442
672
                elif names[0] != names[1] or parent_ids[0] != parent_ids[1]:
443
673
                    # Renamed
444
674
                    if changed_content or executables[0] != executables[1]:
452
682
                    change_type = kind_changed
453
683
                    display_path = (paths[0] + source_marker
454
684
                                    + ' => ' + paths[1] + marker)
455
 
                elif changed_content is True or executables[0] != executables[1]:
 
685
                elif changed_content or executables[0] != executables[1]:
456
686
                    change_type = modified
 
687
                    display_path = real_path + marker
457
688
                else:
458
689
                    assert False, "How did we get here?"
459
690