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