/b-gtk/fix-viz

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/b-gtk/fix-viz
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
1
"""Difference window.
2
3
This module contains the code to manage the diff window which shows
4
the changes made between two revisions on a branch.
5
"""
6
613.1.1 by Vincent Ladeuil
Fix bug #286834 bu handling 'missing' files corner case.
7
__copyright__ = "Copyright 2005 Canonical Ltd."
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
8
__author__    = "Scott James Remnant <scott@ubuntu.com>"
9
10
11
from cStringIO import StringIO
12
252 by Aaron Bentley
Fix test suite
13
import pygtk
14
pygtk.require("2.0")
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
15
import gtk
16
import pango
232.1.1 by Adeodato Simó
Read ~/.colordiffrc to set colors in the diff window.
17
import os
18
import re
76 by Jelmer Vernooij
Replace non-UTF8 characters rather than generating an exception (fixes #44677).
19
import sys
666.1.1 by Pelle Johansson
find ElementTree in python 2.4
20
try:
21
    from xml.etree.ElementTree import Element, SubElement, tostring
667 by Jelmer Vernooij
Merge fix to allow finding elementtree on python2.4.
22
except ImportError:
666.1.1 by Pelle Johansson
find ElementTree in python 2.4
23
    from elementtree.ElementTree import Element, SubElement, tostring
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
24
25
try:
635.3.1 by Szilveszter Farkas
Changed all occurences of gtksourceview to version 2.
26
    import gtksourceview2
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
27
    have_gtksourceview = True
28
except ImportError:
29
    have_gtksourceview = False
232.1.3 by Adeodato Simó
Support setting diff colors from gedit's syntax highlighting config too.
30
try:
31
    import gconf
32
    have_gconf = True
33
except ImportError:
34
    have_gconf = False
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
35
434 by Aaron Bentley
Better errors, merge directive saving
36
from bzrlib import (
677 by Vincent Ladeuil
Fix 2.1.0 compatibility issue.
37
    errors,
434 by Aaron Bentley
Better errors, merge directive saving
38
    merge as _mod_merge,
39
    osutils,
40
    urlutils,
41
    workingtree,
42
)
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
43
from bzrlib.diff import show_diff_trees, internal_diff
426 by Aaron Bentley
Start support for Merge Directives
44
from bzrlib.patches import parse_patches
232.1.1 by Adeodato Simó
Read ~/.colordiffrc to set colors in the diff window.
45
from bzrlib.trace import warning
475.1.2 by Vincent Ladeuil
Fix bug #187283 fix replacing _() by _i18n().
46
from bzrlib.plugins.gtk import _i18n
298.2.1 by Daniel Schierbeck
Refactored the GTK window code, creating a single base window class that handles keyboard events.
47
from bzrlib.plugins.gtk.window import Window
430 by Aaron Bentley
Handle conflicts appropriately
48
from dialog import error_dialog, info_dialog, warning_dialog
428 by Aaron Bentley
Get merging working
49
50
657 by Jelmer Vernooij
Merge replacement for guess_language with small tweak: use the canonical implementation if we can.
51
def fallback_guess_language(slm, content_type):
52
    for lang_id in slm.get_language_ids():
53
        lang = slm.get_language(lang_id)
54
        if "text/x-patch" in lang.get_mime_types():
55
            return lang
56
    return None
57
58
428 by Aaron Bentley
Get merging working
59
class SelectCancelled(Exception):
60
61
    pass
298.2.1 by Daniel Schierbeck
Refactored the GTK window code, creating a single base window class that handles keyboard events.
62
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
63
424 by Aaron Bentley
Add ghandle-patch
64
class DiffFileView(gtk.ScrolledWindow):
432 by Aaron Bentley
Misc updates
65
    """Window for displaying diffs from a diff file"""
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
66
51 by Jelmer Vernooij
Rework some of the parameters to DiffWindow.set_diff() to be
67
    def __init__(self):
278.1.4 by John Arbash Meinel
Just playing around.
68
        gtk.ScrolledWindow.__init__(self)
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
69
        self.construct()
424 by Aaron Bentley
Add ghandle-patch
70
        self._diffs = {}
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
71
72
    def construct(self):
278.1.4 by John Arbash Meinel
Just playing around.
73
        self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
74
        self.set_shadow_type(gtk.SHADOW_IN)
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
75
76
        if have_gtksourceview:
635.3.1 by Szilveszter Farkas
Changed all occurences of gtksourceview to version 2.
77
            self.buffer = gtksourceview2.Buffer()
78
            slm = gtksourceview2.LanguageManager()
657 by Jelmer Vernooij
Merge replacement for guess_language with small tweak: use the canonical implementation if we can.
79
            guess_language = getattr(gtksourceview2.LanguageManager, 
80
                "guess_language", fallback_guess_language)
81
            gsl = guess_language(slm, content_type="text/x-patch")
232.1.3 by Adeodato Simó
Support setting diff colors from gedit's syntax highlighting config too.
82
            if have_gconf:
635.3.2 by Szilveszter Farkas
Support gedit color schemes for gtksourceview2.
83
                self.apply_gedit_colors(self.buffer)
635.3.3 by Szilveszter Farkas
Made apply_colordiff_colors() work with gtksourceview2.
84
            self.apply_colordiff_colors(self.buffer)
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
85
            self.buffer.set_language(gsl)
635.3.2 by Szilveszter Farkas
Support gedit color schemes for gtksourceview2.
86
            self.buffer.set_highlight_syntax(True)
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
87
635.3.1 by Szilveszter Farkas
Changed all occurences of gtksourceview to version 2.
88
            self.sourceview = gtksourceview2.View(self.buffer)
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
89
        else:
90
            self.buffer = gtk.TextBuffer()
555.2.1 by Jasper Groenewegen
Modify DiffWidget to include a checkbutton to wrap long lines in the diff view
91
            self.sourceview = gtk.TextView(self.buffer)
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
92
555.2.1 by Jasper Groenewegen
Modify DiffWidget to include a checkbutton to wrap long lines in the diff view
93
        self.sourceview.set_editable(False)
94
        self.sourceview.modify_font(pango.FontDescription("Monospace"))
95
        self.add(self.sourceview)
96
        self.sourceview.show()
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
97
232.1.1 by Adeodato Simó
Read ~/.colordiffrc to set colors in the diff window.
98
    @staticmethod
635.3.2 by Szilveszter Farkas
Support gedit color schemes for gtksourceview2.
99
    def apply_gedit_colors(buf):
100
        """Set style to that specified in gedit configuration.
232.1.3 by Adeodato Simó
Support setting diff colors from gedit's syntax highlighting config too.
101
102
        This method needs the gconf module.
278.1.4 by John Arbash Meinel
Just playing around.
103
635.3.2 by Szilveszter Farkas
Support gedit color schemes for gtksourceview2.
104
        :param buf: a gtksourceview2.Buffer object.
232.1.3 by Adeodato Simó
Support setting diff colors from gedit's syntax highlighting config too.
105
        """
635.3.2 by Szilveszter Farkas
Support gedit color schemes for gtksourceview2.
106
        GEDIT_SCHEME_PATH = '/apps/gedit-2/preferences/editor/colors/scheme'
232.1.3 by Adeodato Simó
Support setting diff colors from gedit's syntax highlighting config too.
107
108
        client = gconf.client_get_default()
635.3.2 by Szilveszter Farkas
Support gedit color schemes for gtksourceview2.
109
        style_scheme_name = client.get_string(GEDIT_SCHEME_PATH)
647 by Jelmer Vernooij
Skip setting gedit theme if it isn't set in gconf.
110
        if style_scheme_name is not None:
111
            style_scheme = gtksourceview2.StyleSchemeManager().get_scheme(style_scheme_name)
112
            
113
            buf.set_style_scheme(style_scheme)
232.1.3 by Adeodato Simó
Support setting diff colors from gedit's syntax highlighting config too.
114
424 by Aaron Bentley
Add ghandle-patch
115
    @classmethod
635.3.3 by Szilveszter Farkas
Made apply_colordiff_colors() work with gtksourceview2.
116
    def apply_colordiff_colors(klass, buf):
232.1.2 by Adeodato Simó
Rename apply_colordiffrc to apply_colordiff_colors, improve docstring.
117
        """Set style colors for lang using the colordiff configuration file.
232.1.1 by Adeodato Simó
Read ~/.colordiffrc to set colors in the diff window.
118
119
        Both ~/.colordiffrc and ~/.colordiffrc.bzr-gtk are read.
120
635.3.3 by Szilveszter Farkas
Made apply_colordiff_colors() work with gtksourceview2.
121
        :param buf: a "Diff" gtksourceview2.Buffer object.
232.1.1 by Adeodato Simó
Read ~/.colordiffrc to set colors in the diff window.
122
        """
635.3.3 by Szilveszter Farkas
Made apply_colordiff_colors() work with gtksourceview2.
123
        scheme_manager = gtksourceview2.StyleSchemeManager()
124
        style_scheme = scheme_manager.get_scheme('colordiff')
125
        
126
        # if style scheme not found, we'll generate it from colordiffrc
127
        # TODO: reload if colordiffrc has changed.
128
        if style_scheme is None:
129
            colors = {}
130
131
            for f in ('~/.colordiffrc', '~/.colordiffrc.bzr-gtk'):
132
                f = os.path.expanduser(f)
133
                if os.path.exists(f):
134
                    try:
135
                        f = file(f)
136
                    except IOError, e:
137
                        warning('could not open file %s: %s' % (f, str(e)))
138
                    else:
139
                        colors.update(klass.parse_colordiffrc(f))
140
                        f.close()
141
142
            if not colors:
143
                # ~/.colordiffrc does not exist
144
                return
145
            
146
            mapping = {
147
                # map GtkSourceView2 scheme styles to colordiff names
232.1.1 by Adeodato Simó
Read ~/.colordiffrc to set colors in the diff window.
148
                # since GSV is richer, accept new names for extra bits,
149
                # defaulting to old names if they're not present
635.3.3 by Szilveszter Farkas
Made apply_colordiff_colors() work with gtksourceview2.
150
                'diff:added-line': ['newtext'],
151
                'diff:removed-line': ['oldtext'],
152
                'diff:location': ['location', 'diffstuff'],
153
                'diff:file': ['file', 'diffstuff'],
154
                'diff:special-case': ['specialcase', 'diffstuff'],
155
            }
156
            
157
            converted_colors = {}
158
            for name, values in mapping.items():
159
                color = None
160
                for value in values:
161
                    color = colors.get(value, None)
162
                    if color is not None:
163
                        break
164
                if color is None:
165
                    continue
166
                converted_colors[name] = color
167
            
168
            # some xml magic to produce needed style scheme description
169
            e_style_scheme = Element('style-scheme')
170
            e_style_scheme.set('id', 'colordiff')
171
            e_style_scheme.set('_name', 'ColorDiff')
172
            e_style_scheme.set('version', '1.0')
173
            for name, color in converted_colors.items():
174
                style = SubElement(e_style_scheme, 'style')
175
                style.set('name', name)
176
                style.set('foreground', '#%s' % color)
177
            
178
            scheme_xml = tostring(e_style_scheme, 'UTF-8')
179
            if not os.path.exists(os.path.expanduser('~/.local/share/gtksourceview-2.0/styles')):
180
                os.makedirs(os.path.expanduser('~/.local/share/gtksourceview-2.0/styles'))
181
            file(os.path.expanduser('~/.local/share/gtksourceview-2.0/styles/colordiff.xml'), 'w').write(scheme_xml)
182
            
183
            scheme_manager.force_rescan()
184
            style_scheme = scheme_manager.get_scheme('colordiff')
185
        
186
        buf.set_style_scheme(style_scheme)
232.1.4 by Adeodato Simó
Add a test for parse_colordiffrc, and fix the function to handle empty lines.
187
188
    @staticmethod
189
    def parse_colordiffrc(fileobj):
190
        """Parse fileobj as a colordiff configuration file.
278.1.4 by John Arbash Meinel
Just playing around.
191
232.1.4 by Adeodato Simó
Add a test for parse_colordiffrc, and fix the function to handle empty lines.
192
        :return: A dict with the key -> value pairs.
193
        """
194
        colors = {}
195
        for line in fileobj:
196
            if re.match(r'^\s*#', line):
197
                continue
198
            if '=' not in line:
199
                continue
200
            key, val = line.split('=', 1)
201
            colors[key.strip()] = val.strip()
202
        return colors
203
278.1.4 by John Arbash Meinel
Just playing around.
204
    def set_trees(self, rev_tree, parent_tree):
205
        self.rev_tree = rev_tree
206
        self.parent_tree = parent_tree
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
207
#        self._build_delta()
208
209
#    def _build_delta(self):
210
#        self.parent_tree.lock_read()
211
#        self.rev_tree.lock_read()
212
#        try:
450 by Aaron Bentley
Update to use new Tree.iter_changes
213
#            self.delta = iter_changes_to_status(self.parent_tree, self.rev_tree)
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
214
#            self.path_to_status = {}
215
#            self.path_to_diff = {}
216
#            source_inv = self.parent_tree.inventory
217
#            target_inv = self.rev_tree.inventory
218
#            for (file_id, real_path, change_type, display_path) in self.delta:
219
#                self.path_to_status[real_path] = u'=== %s %s' % (change_type, display_path)
220
#                if change_type in ('modified', 'renamed and modified'):
221
#                    source_ie = source_inv[file_id]
222
#                    target_ie = target_inv[file_id]
223
#                    sio = StringIO()
224
#                    source_ie.diff(internal_diff, *old path, *old_tree,
225
#                                   *new_path, target_ie, self.rev_tree,
226
#                                   sio)
227
#                    self.path_to_diff[real_path] = 
228
#
229
#        finally:
230
#            self.rev_tree.unlock()
231
#            self.parent_tree.unlock()
278.1.4 by John Arbash Meinel
Just playing around.
232
233
    def show_diff(self, specific_files):
426 by Aaron Bentley
Start support for Merge Directives
234
        sections = []
235
        if specific_files is None:
236
            self.buffer.set_text(self._diffs[None])
237
        else:
238
            for specific_file in specific_files:
239
                sections.append(self._diffs[specific_file])
240
            self.buffer.set_text(''.join(sections))
424 by Aaron Bentley
Add ghandle-patch
241
242
243
class DiffView(DiffFileView):
244
    """This is the soft and chewy filling for a DiffWindow."""
245
246
    def __init__(self):
247
        DiffFileView.__init__(self)
248
        self.rev_tree = None
249
        self.parent_tree = None
250
251
    def show_diff(self, specific_files):
432 by Aaron Bentley
Misc updates
252
        """Show the diff for the specified files"""
278.1.4 by John Arbash Meinel
Just playing around.
253
        s = StringIO()
278.1.18 by John Arbash Meinel
Start checking the diff view is correct.
254
        show_diff_trees(self.parent_tree, self.rev_tree, s, specific_files,
255
                        old_label='', new_label='',
256
                        # path_encoding=sys.getdefaultencoding()
257
                        # The default is utf-8, but we interpret the file
258
                        # contents as getdefaultencoding(), so we should
259
                        # probably try to make the paths in the same encoding.
260
                        )
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
261
        # str.decode(encoding, 'replace') doesn't do anything. Because if a
262
        # character is not valid in 'encoding' there is nothing to replace, the
263
        # 'replace' is for 'str.encode()'
264
        try:
265
            decoded = s.getvalue().decode(sys.getdefaultencoding())
266
        except UnicodeDecodeError:
267
            try:
268
                decoded = s.getvalue().decode('UTF-8')
269
            except UnicodeDecodeError:
270
                decoded = s.getvalue().decode('iso-8859-1')
271
                # This always works, because every byte has a valid
272
                # mapping from iso-8859-1 to Unicode
273
        # TextBuffer must contain pure UTF-8 data
274
        self.buffer.set_text(decoded.encode('UTF-8'))
278.1.4 by John Arbash Meinel
Just playing around.
275
276
423.8.1 by Jelmer Vernooij
Allow using the diff control as a widget
277
class DiffWidget(gtk.HPaned):
278
    """Diff widget
278.1.4 by John Arbash Meinel
Just playing around.
279
280
    """
423.8.1 by Jelmer Vernooij
Allow using the diff control as a widget
281
    def __init__(self):
282
        super(DiffWidget, self).__init__()
278.1.4 by John Arbash Meinel
Just playing around.
283
284
        # The file hierarchy: a scrollable treeview
285
        scrollwin = gtk.ScrolledWindow()
286
        scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
287
        scrollwin.set_shadow_type(gtk.SHADOW_IN)
555.2.2 by Jasper Groenewegen
Change from checkbox to menu item in bzr vis, add menu bar + item in gdiff
288
        self.pack1(scrollwin)
278.1.4 by John Arbash Meinel
Just playing around.
289
        scrollwin.show()
555.2.1 by Jasper Groenewegen
Modify DiffWidget to include a checkbutton to wrap long lines in the diff view
290
        
278.1.4 by John Arbash Meinel
Just playing around.
291
        self.model = gtk.TreeStore(str, str)
292
        self.treeview = gtk.TreeView(self.model)
293
        self.treeview.set_headers_visible(False)
294
        self.treeview.set_search_column(1)
295
        self.treeview.connect("cursor-changed", self._treeview_cursor_cb)
296
        scrollwin.add(self.treeview)
297
        self.treeview.show()
298
299
        cell = gtk.CellRendererText()
300
        cell.set_property("width-chars", 20)
301
        column = gtk.TreeViewColumn()
302
        column.pack_start(cell, expand=True)
303
        column.add_attribute(cell, "text", 0)
304
        self.treeview.append_column(column)
305
429 by Aaron Bentley
Merge from mainline
306
    def set_diff_text(self, lines):
432 by Aaron Bentley
Misc updates
307
        """Set the current diff from a list of lines
308
309
        :param lines: The diff to show, in unified diff format
310
        """
278.1.4 by John Arbash Meinel
Just playing around.
311
        # The diffs of the  selected file: a scrollable source or
312
        # text view
487.2.3 by Aaron Bentley
Much refactoring
313
314
    def set_diff_text_sections(self, sections):
531.7.13 by Scott Scriven
Removed exception-swallowing hasattr()s.
315
        if getattr(self, 'diff_view', None) is None:
531.7.5 by Scott Scriven
Fixed DiffWidget so more than one call to set_diff() will work.
316
            self.diff_view = DiffFileView()
317
            self.pack2(self.diff_view)
278.1.12 by John Arbash Meinel
Delay computing the delta, and clean up some of the diff view names.
318
        self.diff_view.show()
487.2.3 by Aaron Bentley
Much refactoring
319
        for oldname, newname, patch in sections:
320
            self.diff_view._diffs[newname] = str(patch)
321
            if newname is None:
322
                newname = ''
426 by Aaron Bentley
Start support for Merge Directives
323
            self.model.append(None, [oldname, newname])
427 by Aaron Bentley
Add merge button when displaying merge directives
324
        self.diff_view.show_diff(None)
278.1.4 by John Arbash Meinel
Just playing around.
325
429 by Aaron Bentley
Merge from mainline
326
    def set_diff(self, rev_tree, parent_tree):
278.1.4 by John Arbash Meinel
Just playing around.
327
        """Set the differences showed by this window.
328
329
        Compares the two trees and populates the window with the
330
        differences.
331
        """
531.7.13 by Scott Scriven
Removed exception-swallowing hasattr()s.
332
        if getattr(self, 'diff_view', None) is None:
531.7.5 by Scott Scriven
Fixed DiffWidget so more than one call to set_diff() will work.
333
            self.diff_view = DiffView()
334
            self.pack2(self.diff_view)
424 by Aaron Bentley
Add ghandle-patch
335
        self.diff_view.show()
278.1.12 by John Arbash Meinel
Delay computing the delta, and clean up some of the diff view names.
336
        self.diff_view.set_trees(rev_tree, parent_tree)
278.1.4 by John Arbash Meinel
Just playing around.
337
        self.rev_tree = rev_tree
338
        self.parent_tree = parent_tree
339
340
        self.model.clear()
341
        delta = self.rev_tree.changes_from(self.parent_tree)
342
343
        self.model.append(None, [ "Complete Diff", "" ])
344
345
        if len(delta.added):
346
            titer = self.model.append(None, [ "Added", None ])
347
            for path, id, kind in delta.added:
348
                self.model.append(titer, [ path, path ])
349
350
        if len(delta.removed):
351
            titer = self.model.append(None, [ "Removed", None ])
352
            for path, id, kind in delta.removed:
353
                self.model.append(titer, [ path, path ])
354
355
        if len(delta.renamed):
356
            titer = self.model.append(None, [ "Renamed", None ])
357
            for oldpath, newpath, id, kind, text_modified, meta_modified \
358
                    in delta.renamed:
359
                self.model.append(titer, [ oldpath, newpath ])
360
361
        if len(delta.modified):
362
            titer = self.model.append(None, [ "Modified", None ])
363
            for path, id, kind, text_modified, meta_modified in delta.modified:
364
                self.model.append(titer, [ path, path ])
365
366
        self.treeview.expand_all()
531.7.5 by Scott Scriven
Fixed DiffWidget so more than one call to set_diff() will work.
367
        self.diff_view.show_diff(None)
278.1.4 by John Arbash Meinel
Just playing around.
368
369
    def set_file(self, file_path):
432 by Aaron Bentley
Misc updates
370
        """Select the current file to display"""
278.1.4 by John Arbash Meinel
Just playing around.
371
        tv_path = None
372
        for data in self.model:
373
            for child in data.iterchildren():
374
                if child[0] == file_path or child[1] == file_path:
375
                    tv_path = child.path
376
                    break
377
        if tv_path is None:
677 by Vincent Ladeuil
Fix 2.1.0 compatibility issue.
378
            raise errors.NoSuchFile(file_path)
278.1.4 by John Arbash Meinel
Just playing around.
379
        self.treeview.set_cursor(tv_path)
380
        self.treeview.scroll_to_cell(tv_path)
381
382
    def _treeview_cursor_cb(self, *args):
383
        """Callback for when the treeview cursor changes."""
384
        (path, col) = self.treeview.get_cursor()
385
        specific_files = [ self.model[path][1] ]
386
        if specific_files == [ None ]:
387
            return
388
        elif specific_files == [ "" ]:
389
            specific_files = None
555.2.1 by Jasper Groenewegen
Modify DiffWidget to include a checkbutton to wrap long lines in the diff view
390
        
278.1.12 by John Arbash Meinel
Delay computing the delta, and clean up some of the diff view names.
391
        self.diff_view.show_diff(specific_files)
555.2.1 by Jasper Groenewegen
Modify DiffWidget to include a checkbutton to wrap long lines in the diff view
392
    
555.2.2 by Jasper Groenewegen
Change from checkbox to menu item in bzr vis, add menu bar + item in gdiff
393
    def _on_wraplines_toggled(self, widget=None, wrap=False):
555.2.1 by Jasper Groenewegen
Modify DiffWidget to include a checkbutton to wrap long lines in the diff view
394
        """Callback for when the wrap lines checkbutton is toggled"""
555.2.2 by Jasper Groenewegen
Change from checkbox to menu item in bzr vis, add menu bar + item in gdiff
395
        if wrap or widget.get_active():
555.2.1 by Jasper Groenewegen
Modify DiffWidget to include a checkbutton to wrap long lines in the diff view
396
            self.diff_view.sourceview.set_wrap_mode(gtk.WRAP_WORD)
397
        else:
398
            self.diff_view.sourceview.set_wrap_mode(gtk.WRAP_NONE)
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
399
423.8.1 by Jelmer Vernooij
Allow using the diff control as a widget
400
class DiffWindow(Window):
401
    """Diff window.
402
403
    This object represents and manages a single window containing the
404
    differences between two revisions on a branch.
405
    """
406
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
407
    def __init__(self, parent=None, operations=None):
423.8.1 by Jelmer Vernooij
Allow using the diff control as a widget
408
        Window.__init__(self, parent)
409
        self.set_border_width(0)
410
        self.set_title("bzrk diff")
411
412
        # Use two thirds of the screen by default
413
        screen = self.get_screen()
414
        monitor = screen.get_monitor_geometry(0)
415
        width = int(monitor.width * 0.66)
416
        height = int(monitor.height * 0.66)
417
        self.set_default_size(width, height)
487.2.3 by Aaron Bentley
Much refactoring
418
        self.construct(operations)
423.8.1 by Jelmer Vernooij
Allow using the diff control as a widget
419
487.2.3 by Aaron Bentley
Much refactoring
420
    def construct(self, operations):
423.8.1 by Jelmer Vernooij
Allow using the diff control as a widget
421
        """Construct the window contents."""
429 by Aaron Bentley
Merge from mainline
422
        self.vbox = gtk.VBox()
423
        self.add(self.vbox)
424
        self.vbox.show()
555.2.2 by Jasper Groenewegen
Change from checkbox to menu item in bzr vis, add menu bar + item in gdiff
425
        self.diff = DiffWidget()
426
        self.vbox.pack_end(self.diff, True, True, 0)
427
        self.diff.show_all()
428
        # Build after DiffWidget to connect signals
429
        menubar = self._get_menu_bar()
430
        self.vbox.pack_start(menubar, False, False, 0)
487.2.3 by Aaron Bentley
Much refactoring
431
        hbox = self._get_button_bar(operations)
429 by Aaron Bentley
Merge from mainline
432
        if hbox is not None:
555.2.2 by Jasper Groenewegen
Change from checkbox to menu item in bzr vis, add menu bar + item in gdiff
433
            self.vbox.pack_start(hbox, False, True, 0)
434
        
435
    
436
    def _get_menu_bar(self):
437
        menubar = gtk.MenuBar()
438
        # View menu
439
        mb_view = gtk.MenuItem(_i18n("_View"))
440
        mb_view_menu = gtk.Menu()
571 by Jasper Groenewegen
Merge addition of word wrap control to gdiff and visualise windows
441
        mb_view_wrapsource = gtk.CheckMenuItem(_i18n("Wrap _Long Lines"))
555.2.2 by Jasper Groenewegen
Change from checkbox to menu item in bzr vis, add menu bar + item in gdiff
442
        mb_view_wrapsource.connect('activate', self.diff._on_wraplines_toggled)
443
        mb_view_wrapsource.show()
444
        mb_view_menu.append(mb_view_wrapsource)
445
        mb_view.show()
446
        mb_view.set_submenu(mb_view_menu)
447
        mb_view.show()
448
        menubar.append(mb_view)
449
        menubar.show()
450
        return menubar
451
    
487.2.3 by Aaron Bentley
Much refactoring
452
    def _get_button_bar(self, operations):
432 by Aaron Bentley
Misc updates
453
        """Return a button bar to use.
454
455
        :return: None, meaning that no button bar will be used.
456
        """
487.2.3 by Aaron Bentley
Much refactoring
457
        if operations is None:
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
458
            return None
459
        hbox = gtk.HButtonBox()
460
        hbox.set_layout(gtk.BUTTONBOX_START)
487.2.3 by Aaron Bentley
Much refactoring
461
        for title, method in operations:
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
462
            merge_button = gtk.Button(title)
463
            merge_button.show()
464
            merge_button.set_relief(gtk.RELIEF_NONE)
465
            merge_button.connect("clicked", method)
466
            hbox.pack_start(merge_button, expand=False, fill=True)
467
        hbox.show()
468
        return hbox
469
470
    def _get_merge_target(self):
471
        d = gtk.FileChooserDialog('Merge branch', self,
472
                                  gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
473
                                  buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
474
                                           gtk.STOCK_CANCEL,
475
                                           gtk.RESPONSE_CANCEL,))
476
        try:
477
            result = d.run()
478
            if result != gtk.RESPONSE_OK:
479
                raise SelectCancelled()
480
            return d.get_current_folder_uri()
481
        finally:
482
            d.destroy()
483
487.2.5 by Aaron Bentley
Test successful merge
484
    def _merge_successful(self):
485
        # No conflicts found.
492 by Aaron Bentley
Allow saving from patch window, refactoring, testing.
486
        info_dialog(_i18n('Merge successful'),
487
                    _i18n('All changes applied successfully.'))
487.2.5 by Aaron Bentley
Test successful merge
488
487.2.7 by Aaron Bentley
Add more merge tests
489
    def _conflicts(self):
492 by Aaron Bentley
Allow saving from patch window, refactoring, testing.
490
        warning_dialog(_i18n('Conflicts encountered'),
491
                       _i18n('Please resolve the conflicts manually'
492
                             ' before committing.'))
487.2.7 by Aaron Bentley
Add more merge tests
493
487.2.8 by Aaron Bentley
Update error handling to use window
494
    def _handle_error(self, e):
495
        error_dialog('Error', str(e))
487.2.7 by Aaron Bentley
Add more merge tests
496
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
497
    def _get_save_path(self, basename):
498
        d = gtk.FileChooserDialog('Save As', self,
499
                                  gtk.FILE_CHOOSER_ACTION_SAVE,
500
                                  buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
501
                                           gtk.STOCK_CANCEL,
502
                                           gtk.RESPONSE_CANCEL,))
503
        d.set_current_name(basename)
504
        try:
505
            result = d.run()
506
            if result != gtk.RESPONSE_OK:
507
                raise SelectCancelled()
508
            return urlutils.local_path_from_url(d.get_uri())
509
        finally:
510
            d.destroy()
429 by Aaron Bentley
Merge from mainline
511
423.8.1 by Jelmer Vernooij
Allow using the diff control as a widget
512
    def set_diff(self, description, rev_tree, parent_tree):
513
        """Set the differences showed by this window.
514
515
        Compares the two trees and populates the window with the
516
        differences.
517
        """
518
        self.diff.set_diff(rev_tree, parent_tree)
519
        self.set_title(description + " - bzrk diff")
520
521
    def set_file(self, file_path):
522
        self.diff.set_file(file_path)
523
524
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
525
class DiffController(object):
526
527
    def __init__(self, path, patch, window=None):
528
        self.path = path
529
        self.patch = patch
530
        if window is None:
531
            window = DiffWindow(operations=self._provide_operations())
532
            self.initialize_window(window)
487.2.3 by Aaron Bentley
Much refactoring
533
        self.window = window
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
534
535
    def initialize_window(self, window):
487.2.3 by Aaron Bentley
Much refactoring
536
        window.diff.set_diff_text_sections(self.get_diff_sections())
537
        window.set_title(self.path + " - diff")
538
539
    def get_diff_sections(self):
540
        yield "Complete Diff", None, ''.join(self.patch)
541
        for patch in parse_patches(self.patch):
542
            oldname = patch.oldname.split('\t')[0]
543
            newname = patch.newname.split('\t')[0]
544
            yield oldname, newname, str(patch)
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
545
546
    def perform_save(self, window):
547
        try:
548
            save_path = self.window._get_save_path(osutils.basename(self.path))
549
        except SelectCancelled:
550
            return
551
        source = open(self.path, 'rb')
552
        try:
553
            target = open(save_path, 'wb')
554
            try:
555
                osutils.pumpfile(source, target)
556
            finally:
557
                target.close()
558
        finally:
559
            source.close()
560
561
    def _provide_operations(self):
562
        return [('Save', self.perform_save)]
563
564
565
class MergeDirectiveController(DiffController):
566
487.2.8 by Aaron Bentley
Update error handling to use window
567
    def __init__(self, path, directive, window=None):
487.2.5 by Aaron Bentley
Test successful merge
568
        DiffController.__init__(self, path, directive.patch.splitlines(True),
569
                                window)
428 by Aaron Bentley
Get merging working
570
        self.directive = directive
487.2.1 by Aaron Bentley
Refactor merge directive window into MergeController
571
        self.merge_target = None
572
573
    def _provide_operations(self):
574
        return [('Merge', self.perform_merge), ('Save', self.perform_save)]
427 by Aaron Bentley
Add merge button when displaying merge directives
575
428 by Aaron Bentley
Get merging working
576
    def perform_merge(self, window):
487.2.1 by Aaron Bentley
Refactor merge directive window into MergeController
577
        if self.merge_target is None:
578
            try:
579
                self.merge_target = self.window._get_merge_target()
580
            except SelectCancelled:
581
                return
582
        tree = workingtree.WorkingTree.open(self.merge_target)
428 by Aaron Bentley
Get merging working
583
        tree.lock_write()
584
        try:
585
            try:
677 by Vincent Ladeuil
Fix 2.1.0 compatibility issue.
586
                if tree.has_changes():
587
                    raise errors.UncommittedChanges(tree)
588
                merger, verified = _mod_merge.Merger.from_mergeable(
589
                    tree, self.directive, pb=None)
428 by Aaron Bentley
Get merging working
590
                merger.merge_type = _mod_merge.Merge3Merger
430 by Aaron Bentley
Handle conflicts appropriately
591
                conflict_count = merger.do_merge()
428 by Aaron Bentley
Get merging working
592
                merger.set_pending()
430 by Aaron Bentley
Handle conflicts appropriately
593
                if conflict_count == 0:
487.2.5 by Aaron Bentley
Test successful merge
594
                    self.window._merge_successful()
430 by Aaron Bentley
Handle conflicts appropriately
595
                else:
487.2.7 by Aaron Bentley
Add more merge tests
596
                    self.window._conflicts()
430 by Aaron Bentley
Handle conflicts appropriately
597
                    # There are conflicts to be resolved.
487.2.1 by Aaron Bentley
Refactor merge directive window into MergeController
598
                self.window.destroy()
428 by Aaron Bentley
Get merging working
599
            except Exception, e:
487.2.8 by Aaron Bentley
Update error handling to use window
600
                self.window._handle_error(e)
428 by Aaron Bentley
Get merging working
601
        finally:
602
            tree.unlock()
603
427 by Aaron Bentley
Add merge button when displaying merge directives
604
450 by Aaron Bentley
Update to use new Tree.iter_changes
605
def iter_changes_to_status(source, target):
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
606
    """Determine the differences between trees.
607
450 by Aaron Bentley
Update to use new Tree.iter_changes
608
    This is a wrapper around iter_changes which just yields more
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
609
    understandable results.
610
611
    :param source: The source tree (basis tree)
612
    :param target: The target tree
613
    :return: A list of (file_id, real_path, change_type, display_path)
614
    """
615
    added = 'added'
616
    removed = 'removed'
617
    renamed = 'renamed'
618
    renamed_and_modified = 'renamed and modified'
619
    modified = 'modified'
620
    kind_changed = 'kind changed'
613.1.1 by Vincent Ladeuil
Fix bug #286834 bu handling 'missing' files corner case.
621
    missing = 'missing'
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
622
623
    # TODO: Handle metadata changes
624
625
    status = []
626
    target.lock_read()
627
    try:
628
        source.lock_read()
629
        try:
630
            for (file_id, paths, changed_content, versioned, parent_ids, names,
450 by Aaron Bentley
Update to use new Tree.iter_changes
631
                 kinds, executables) in target.iter_changes(source):
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
632
633
                # Skip the root entry if it isn't very interesting
634
                if parent_ids == (None, None):
635
                    continue
636
637
                change_type = None
638
                if kinds[0] is None:
639
                    source_marker = ''
640
                else:
641
                    source_marker = osutils.kind_marker(kinds[0])
613.1.1 by Vincent Ladeuil
Fix bug #286834 bu handling 'missing' files corner case.
642
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
643
                if kinds[1] is None:
613.1.1 by Vincent Ladeuil
Fix bug #286834 bu handling 'missing' files corner case.
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])
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
651
                else:
652
                    marker = osutils.kind_marker(kinds[1])
653
654
                real_path = paths[1]
655
                if real_path is None:
656
                    real_path = paths[0]
657
                assert real_path is not None
658
659
                present_source = versioned[0] and kinds[0] is not None
660
                present_target = versioned[1] and kinds[1] is not None
661
613.1.1 by Vincent Ladeuil
Fix bug #286834 bu handling 'missing' files corner case.
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:
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
666
                    if present_target:
667
                        change_type = added
668
                    else:
669
                        assert present_source
670
                        change_type = removed
613.1.1 by Vincent Ladeuil
Fix bug #286834 bu handling 'missing' files corner case.
671
                    display_path = real_path + marker
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
672
                elif names[0] != names[1] or parent_ids[0] != parent_ids[1]:
673
                    # Renamed
674
                    if changed_content or executables[0] != executables[1]:
675
                        # and modified
676
                        change_type = renamed_and_modified
677
                    else:
678
                        change_type = renamed
679
                    display_path = (paths[0] + source_marker
680
                                    + ' => ' + paths[1] + marker)
681
                elif kinds[0] != kinds[1]:
682
                    change_type = kind_changed
683
                    display_path = (paths[0] + source_marker
684
                                    + ' => ' + paths[1] + marker)
605.1.1 by John Arbash Meinel
just use changed_content as a truth value, rather than checking 'is True'
685
                elif changed_content or executables[0] != executables[1]:
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
686
                    change_type = modified
613.1.1 by Vincent Ladeuil
Fix bug #286834 bu handling 'missing' files corner case.
687
                    display_path = real_path + marker
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
688
                else:
689
                    assert False, "How did we get here?"
690
691
                status.append((file_id, real_path, change_type, display_path))
692
        finally:
693
            source.unlock()
694
    finally:
695
        target.unlock()
696
697
    return status