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