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