/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
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
13
from gi.repository import Gtk
14
from gi.repository import Pango
232.1.1 by Adeodato Simó
Read ~/.colordiffrc to set colors in the diff window.
15
import os
16
import re
76 by Jelmer Vernooij
Replace non-UTF8 characters rather than generating an exception (fixes #44677).
17
import sys
688.3.1 by Toshio Kuratomi
Fix bzr-handle-patch to process patches with leading and trailing comments with
18
import inspect
666.1.1 by Pelle Johansson
find ElementTree in python 2.4
19
try:
20
    from xml.etree.ElementTree import Element, SubElement, tostring
667 by Jelmer Vernooij
Merge fix to allow finding elementtree on python2.4.
21
except ImportError:
666.1.1 by Pelle Johansson
find ElementTree in python 2.4
22
    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
23
24
try:
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
25
    from gi.repository import GtkSource
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
26
    have_gtksourceview = True
27
except ImportError:
28
    have_gtksourceview = False
232.1.3 by Adeodato Simó
Support setting diff colors from gedit's syntax highlighting config too.
29
try:
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
30
    from gi.repository import GConf
232.1.3 by Adeodato Simó
Support setting diff colors from gedit's syntax highlighting config too.
31
    have_gconf = True
32
except ImportError:
33
    have_gconf = False
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
34
434 by Aaron Bentley
Better errors, merge directive saving
35
from bzrlib import (
677 by Vincent Ladeuil
Fix 2.1.0 compatibility issue.
36
    errors,
434 by Aaron Bentley
Better errors, merge directive saving
37
    merge as _mod_merge,
38
    osutils,
39
    urlutils,
40
    workingtree,
41
)
724 by Jelmer Vernooij
Fix formatting, imports.
42
from bzrlib.diff import show_diff_trees
426 by Aaron Bentley
Start support for Merge Directives
43
from bzrlib.patches import parse_patches
232.1.1 by Adeodato Simó
Read ~/.colordiffrc to set colors in the diff window.
44
from bzrlib.trace import warning
724 by Jelmer Vernooij
Fix formatting, imports.
45
from bzrlib.plugins.gtk.dialog import (
46
    error_dialog,
47
    info_dialog,
48
    warning_dialog,
49
    )
729.1.1 by Jelmer Vernooij
Move i18n support to a separate file, so gettext files aren't loaded unless bzr-gtk is used.
50
from bzrlib.plugins.gtk.i18n import _i18n
51
from bzrlib.plugins.gtk.window import Window
428 by Aaron Bentley
Get merging working
52
53
657 by Jelmer Vernooij
Merge replacement for guess_language with small tweak: use the canonical implementation if we can.
54
def fallback_guess_language(slm, content_type):
55
    for lang_id in slm.get_language_ids():
56
        lang = slm.get_language(lang_id)
57
        if "text/x-patch" in lang.get_mime_types():
58
            return lang
59
    return None
60
61
428 by Aaron Bentley
Get merging working
62
class SelectCancelled(Exception):
63
64
    pass
298.2.1 by Daniel Schierbeck
Refactored the GTK window code, creating a single base window class that handles keyboard events.
65
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
66
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
67
class DiffFileView(Gtk.ScrolledWindow):
432 by Aaron Bentley
Misc updates
68
    """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
69
51 by Jelmer Vernooij
Rework some of the parameters to DiffWindow.set_diff() to be
70
    def __init__(self):
734.1.51 by Curtis Hovey
Fix the initializer for many classes.
71
        super(DiffFileView, self).__init__()
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
72
        self.construct()
424 by Aaron Bentley
Add ghandle-patch
73
        self._diffs = {}
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
74
75
    def construct(self):
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
76
        self.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
77
        self.set_shadow_type(Gtk.ShadowType.IN)
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
78
79
        if have_gtksourceview:
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
80
            self.buffer = GtkSource.Buffer()
734.1.3 by Curtis Hovey
Update diff to gtk3.
81
            lang_manager = GtkSource.LanguageManager.get_default()
82
            language = lang_manager.guess_language(None, "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)
734.1.3 by Curtis Hovey
Update diff to gtk3.
86
            self.buffer.set_language(language)
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
734.1.3 by Curtis Hovey
Update diff to gtk3.
89
            self.sourceview = GtkSource.View(buffer=self.buffer)
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
90
        else:
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
91
            self.buffer = Gtk.TextBuffer()
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)
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
95
        self.sourceview.modify_font(Pango.FontDescription("Monospace"))
555.2.1 by Jasper Groenewegen
Modify DiffWidget to include a checkbutton to wrap long lines in the diff view
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
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
105
        :param buf: a GtkSource.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
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
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:
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
113
            style_scheme_mgr = GtkSource.StyleSchemeManager()
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
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
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
127
        :param buf: a "Diff" GtkSource.Buffer object.
232.1.1 by Adeodato Simó
Read ~/.colordiffrc to set colors in the diff window.
128
        """
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
129
        scheme_manager = GtkSource.StyleSchemeManager()
635.3.3 by Szilveszter Farkas
Made apply_colordiff_colors() work with gtksourceview2.
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):
734.1.51 by Curtis Hovey
Fix the initializer for many classes.
253
        super(DiffView, self).__init__()
424 by Aaron Bentley
Add ghandle-patch
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
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
283
class DiffWidget(Gtk.HPaned):
423.8.1 by Jelmer Vernooij
Allow using the diff control as a widget
284
    """Diff widget
278.1.4 by John Arbash Meinel
Just playing around.
285
286
    """
771.1.1 by Curtis Hovey
Added hooks for testing.
287
288
    SHOW_WIDGETS = True
289
423.8.1 by Jelmer Vernooij
Allow using the diff control as a widget
290
    def __init__(self):
291
        super(DiffWidget, self).__init__()
278.1.4 by John Arbash Meinel
Just playing around.
292
293
        # The file hierarchy: a scrollable treeview
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
294
        scrollwin = Gtk.ScrolledWindow()
295
        scrollwin.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
296
        scrollwin.set_shadow_type(Gtk.ShadowType.IN)
555.2.2 by Jasper Groenewegen
Change from checkbox to menu item in bzr vis, add menu bar + item in gdiff
297
        self.pack1(scrollwin)
771.1.1 by Curtis Hovey
Added hooks for testing.
298
        if self.SHOW_WIDGETS:
299
            scrollwin.show()
555.2.1 by Jasper Groenewegen
Modify DiffWidget to include a checkbutton to wrap long lines in the diff view
300
        
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
301
        self.model = Gtk.TreeStore(str, str)
734.1.3 by Curtis Hovey
Update diff to gtk3.
302
        self.treeview = Gtk.TreeView(model=self.model)
278.1.4 by John Arbash Meinel
Just playing around.
303
        self.treeview.set_headers_visible(False)
304
        self.treeview.set_search_column(1)
305
        self.treeview.connect("cursor-changed", self._treeview_cursor_cb)
306
        scrollwin.add(self.treeview)
771.1.1 by Curtis Hovey
Added hooks for testing.
307
        if self.SHOW_WIDGETS:
308
            self.treeview.show()
278.1.4 by John Arbash Meinel
Just playing around.
309
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
310
        cell = Gtk.CellRendererText()
278.1.4 by John Arbash Meinel
Just playing around.
311
        cell.set_property("width-chars", 20)
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
312
        column = Gtk.TreeViewColumn()
734.1.3 by Curtis Hovey
Update diff to gtk3.
313
        column.pack_start(cell, True)
278.1.4 by John Arbash Meinel
Just playing around.
314
        column.add_attribute(cell, "text", 0)
315
        self.treeview.append_column(column)
316
429 by Aaron Bentley
Merge from mainline
317
    def set_diff_text(self, lines):
432 by Aaron Bentley
Misc updates
318
        """Set the current diff from a list of lines
319
320
        :param lines: The diff to show, in unified diff format
321
        """
278.1.4 by John Arbash Meinel
Just playing around.
322
        # The diffs of the  selected file: a scrollable source or
323
        # text view
487.2.3 by Aaron Bentley
Much refactoring
324
325
    def set_diff_text_sections(self, sections):
531.7.13 by Scott Scriven
Removed exception-swallowing hasattr()s.
326
        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.
327
            self.diff_view = DiffFileView()
328
            self.pack2(self.diff_view)
771.1.3 by Curtis Hovey
Do not show widgets when not needed.
329
        if self.SHOW_WIDGETS:
330
            self.diff_view.show()
487.2.3 by Aaron Bentley
Much refactoring
331
        for oldname, newname, patch in sections:
332
            self.diff_view._diffs[newname] = str(patch)
333
            if newname is None:
334
                newname = ''
426 by Aaron Bentley
Start support for Merge Directives
335
            self.model.append(None, [oldname, newname])
427 by Aaron Bentley
Add merge button when displaying merge directives
336
        self.diff_view.show_diff(None)
278.1.4 by John Arbash Meinel
Just playing around.
337
429 by Aaron Bentley
Merge from mainline
338
    def set_diff(self, rev_tree, parent_tree):
278.1.4 by John Arbash Meinel
Just playing around.
339
        """Set the differences showed by this window.
340
341
        Compares the two trees and populates the window with the
342
        differences.
343
        """
531.7.13 by Scott Scriven
Removed exception-swallowing hasattr()s.
344
        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.
345
            self.diff_view = DiffView()
346
            self.pack2(self.diff_view)
771.1.1 by Curtis Hovey
Added hooks for testing.
347
        if self.SHOW_WIDGETS:
348
            self.diff_view.show()
278.1.12 by John Arbash Meinel
Delay computing the delta, and clean up some of the diff view names.
349
        self.diff_view.set_trees(rev_tree, parent_tree)
278.1.4 by John Arbash Meinel
Just playing around.
350
        self.rev_tree = rev_tree
351
        self.parent_tree = parent_tree
352
353
        self.model.clear()
354
        delta = self.rev_tree.changes_from(self.parent_tree)
355
356
        self.model.append(None, [ "Complete Diff", "" ])
357
358
        if len(delta.added):
359
            titer = self.model.append(None, [ "Added", None ])
360
            for path, id, kind in delta.added:
361
                self.model.append(titer, [ path, path ])
362
363
        if len(delta.removed):
364
            titer = self.model.append(None, [ "Removed", None ])
365
            for path, id, kind in delta.removed:
366
                self.model.append(titer, [ path, path ])
367
368
        if len(delta.renamed):
369
            titer = self.model.append(None, [ "Renamed", None ])
370
            for oldpath, newpath, id, kind, text_modified, meta_modified \
371
                    in delta.renamed:
372
                self.model.append(titer, [ oldpath, newpath ])
373
374
        if len(delta.modified):
375
            titer = self.model.append(None, [ "Modified", None ])
376
            for path, id, kind, text_modified, meta_modified in delta.modified:
377
                self.model.append(titer, [ path, path ])
378
379
        self.treeview.expand_all()
531.7.5 by Scott Scriven
Fixed DiffWidget so more than one call to set_diff() will work.
380
        self.diff_view.show_diff(None)
278.1.4 by John Arbash Meinel
Just playing around.
381
382
    def set_file(self, file_path):
432 by Aaron Bentley
Misc updates
383
        """Select the current file to display"""
278.1.4 by John Arbash Meinel
Just playing around.
384
        tv_path = None
385
        for data in self.model:
386
            for child in data.iterchildren():
387
                if child[0] == file_path or child[1] == file_path:
388
                    tv_path = child.path
389
                    break
390
        if tv_path is None:
677 by Vincent Ladeuil
Fix 2.1.0 compatibility issue.
391
            raise errors.NoSuchFile(file_path)
734.1.12 by Curtis Hovey
Fixed row/path issues in gannoate.
392
        self.treeview.set_cursor(tv_path, None, False)
278.1.4 by John Arbash Meinel
Just playing around.
393
        self.treeview.scroll_to_cell(tv_path)
394
395
    def _treeview_cursor_cb(self, *args):
396
        """Callback for when the treeview cursor changes."""
397
        (path, col) = self.treeview.get_cursor()
771.1.4 by Curtis Hovey
Do not update diff_view when the treeview is being destroyed.
398
        if path is None:
399
            return
278.1.4 by John Arbash Meinel
Just playing around.
400
        specific_files = [ self.model[path][1] ]
401
        if specific_files == [ None ]:
402
            return
403
        elif specific_files == [ "" ]:
404
            specific_files = None
555.2.1 by Jasper Groenewegen
Modify DiffWidget to include a checkbutton to wrap long lines in the diff view
405
        
278.1.12 by John Arbash Meinel
Delay computing the delta, and clean up some of the diff view names.
406
        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
407
    
555.2.2 by Jasper Groenewegen
Change from checkbox to menu item in bzr vis, add menu bar + item in gdiff
408
    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
409
        """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
410
        if wrap or widget.get_active():
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
411
            self.diff_view.sourceview.set_wrap_mode(Gtk.WrapMode.WORD)
555.2.1 by Jasper Groenewegen
Modify DiffWidget to include a checkbutton to wrap long lines in the diff view
412
        else:
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
413
            self.diff_view.sourceview.set_wrap_mode(Gtk.WrapMode.NONE)
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
414
423.8.1 by Jelmer Vernooij
Allow using the diff control as a widget
415
class DiffWindow(Window):
416
    """Diff window.
417
418
    This object represents and manages a single window containing the
419
    differences between two revisions on a branch.
420
    """
421
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
422
    def __init__(self, parent=None, operations=None):
734.1.51 by Curtis Hovey
Fix the initializer for many classes.
423
        super(DiffWindow, self).__init__(parent=parent)
423.8.1 by Jelmer Vernooij
Allow using the diff control as a widget
424
        self.set_border_width(0)
425
        self.set_title("bzrk diff")
426
427
        # Use two thirds of the screen by default
428
        screen = self.get_screen()
429
        monitor = screen.get_monitor_geometry(0)
430
        width = int(monitor.width * 0.66)
431
        height = int(monitor.height * 0.66)
432
        self.set_default_size(width, height)
487.2.3 by Aaron Bentley
Much refactoring
433
        self.construct(operations)
423.8.1 by Jelmer Vernooij
Allow using the diff control as a widget
434
487.2.3 by Aaron Bentley
Much refactoring
435
    def construct(self, operations):
423.8.1 by Jelmer Vernooij
Allow using the diff control as a widget
436
        """Construct the window contents."""
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
437
        self.vbox = Gtk.VBox()
429 by Aaron Bentley
Merge from mainline
438
        self.add(self.vbox)
439
        self.vbox.show()
555.2.2 by Jasper Groenewegen
Change from checkbox to menu item in bzr vis, add menu bar + item in gdiff
440
        self.diff = DiffWidget()
441
        self.vbox.pack_end(self.diff, True, True, 0)
442
        self.diff.show_all()
443
        # Build after DiffWidget to connect signals
444
        menubar = self._get_menu_bar()
445
        self.vbox.pack_start(menubar, False, False, 0)
487.2.3 by Aaron Bentley
Much refactoring
446
        hbox = self._get_button_bar(operations)
429 by Aaron Bentley
Merge from mainline
447
        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
448
            self.vbox.pack_start(hbox, False, True, 0)
449
        
450
    
451
    def _get_menu_bar(self):
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
452
        menubar = Gtk.MenuBar()
555.2.2 by Jasper Groenewegen
Change from checkbox to menu item in bzr vis, add menu bar + item in gdiff
453
        # View menu
734.1.38 by Curtis Hovey
Menu fixes.
454
        mb_view = Gtk.MenuItem.new_with_mnemonic(_i18n("_View"))
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
455
        mb_view_menu = Gtk.Menu()
734.1.38 by Curtis Hovey
Menu fixes.
456
        mb_view_wrapsource = Gtk.CheckMenuItem.new_with_mnemonic(
457
            _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
458
        mb_view_wrapsource.connect('activate', self.diff._on_wraplines_toggled)
459
        mb_view_wrapsource.show()
460
        mb_view_menu.append(mb_view_wrapsource)
461
        mb_view.show()
462
        mb_view.set_submenu(mb_view_menu)
463
        mb_view.show()
464
        menubar.append(mb_view)
465
        menubar.show()
466
        return menubar
467
    
487.2.3 by Aaron Bentley
Much refactoring
468
    def _get_button_bar(self, operations):
432 by Aaron Bentley
Misc updates
469
        """Return a button bar to use.
470
471
        :return: None, meaning that no button bar will be used.
472
        """
487.2.3 by Aaron Bentley
Much refactoring
473
        if operations is None:
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
474
            return None
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
475
        hbox = Gtk.HButtonBox()
476
        hbox.set_layout(Gtk.ButtonBoxStyle.START)
487.2.3 by Aaron Bentley
Much refactoring
477
        for title, method in operations:
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
478
            merge_button = Gtk.Button(title)
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
479
            merge_button.show()
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
480
            merge_button.set_relief(Gtk.ReliefStyle.NONE)
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
481
            merge_button.connect("clicked", method)
482
            hbox.pack_start(merge_button, expand=False, fill=True)
483
        hbox.show()
484
        return hbox
485
486
    def _get_merge_target(self):
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
487
        d = Gtk.FileChooserDialog('Merge branch', self,
488
                                  Gtk.FileChooserAction.SELECT_FOLDER,
489
                                  buttons=(Gtk.STOCK_OK, Gtk.ResponseType.OK,
490
                                           Gtk.STOCK_CANCEL,
491
                                           Gtk.ResponseType.CANCEL,))
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
492
        try:
493
            result = d.run()
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
494
            if result != Gtk.ResponseType.OK:
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
495
                raise SelectCancelled()
496
            return d.get_current_folder_uri()
497
        finally:
498
            d.destroy()
499
487.2.5 by Aaron Bentley
Test successful merge
500
    def _merge_successful(self):
501
        # No conflicts found.
492 by Aaron Bentley
Allow saving from patch window, refactoring, testing.
502
        info_dialog(_i18n('Merge successful'),
503
                    _i18n('All changes applied successfully.'))
487.2.5 by Aaron Bentley
Test successful merge
504
487.2.7 by Aaron Bentley
Add more merge tests
505
    def _conflicts(self):
492 by Aaron Bentley
Allow saving from patch window, refactoring, testing.
506
        warning_dialog(_i18n('Conflicts encountered'),
507
                       _i18n('Please resolve the conflicts manually'
508
                             ' before committing.'))
487.2.7 by Aaron Bentley
Add more merge tests
509
487.2.8 by Aaron Bentley
Update error handling to use window
510
    def _handle_error(self, e):
511
        error_dialog('Error', str(e))
487.2.7 by Aaron Bentley
Add more merge tests
512
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
513
    def _get_save_path(self, basename):
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
514
        d = Gtk.FileChooserDialog('Save As', self,
515
                                  Gtk.FileChooserAction.SAVE,
516
                                  buttons=(Gtk.STOCK_OK, Gtk.ResponseType.OK,
517
                                           Gtk.STOCK_CANCEL,
518
                                           Gtk.ResponseType.CANCEL,))
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
519
        d.set_current_name(basename)
520
        try:
521
            result = d.run()
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
522
            if result != Gtk.ResponseType.OK:
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
523
                raise SelectCancelled()
524
            return urlutils.local_path_from_url(d.get_uri())
525
        finally:
526
            d.destroy()
429 by Aaron Bentley
Merge from mainline
527
423.8.1 by Jelmer Vernooij
Allow using the diff control as a widget
528
    def set_diff(self, description, rev_tree, parent_tree):
529
        """Set the differences showed by this window.
530
531
        Compares the two trees and populates the window with the
532
        differences.
533
        """
534
        self.diff.set_diff(rev_tree, parent_tree)
535
        self.set_title(description + " - bzrk diff")
536
537
    def set_file(self, file_path):
538
        self.diff.set_file(file_path)
539
540
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
541
class DiffController(object):
542
688.3.1 by Toshio Kuratomi
Fix bzr-handle-patch to process patches with leading and trailing comments with
543
    def __init__(self, path, patch, window=None, allow_dirty=False):
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
544
        self.path = path
545
        self.patch = patch
688.3.1 by Toshio Kuratomi
Fix bzr-handle-patch to process patches with leading and trailing comments with
546
        self.allow_dirty = allow_dirty
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
547
        if window is None:
548
            window = DiffWindow(operations=self._provide_operations())
549
            self.initialize_window(window)
487.2.3 by Aaron Bentley
Much refactoring
550
        self.window = window
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
551
552
    def initialize_window(self, window):
487.2.3 by Aaron Bentley
Much refactoring
553
        window.diff.set_diff_text_sections(self.get_diff_sections())
554
        window.set_title(self.path + " - diff")
555
556
    def get_diff_sections(self):
557
        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
558
        # allow_dirty was added to parse_patches in bzrlib 2.2b1
559
        if 'allow_dirty' in inspect.getargspec(parse_patches).args:
560
            patches = parse_patches(self.patch, allow_dirty=self.allow_dirty)
561
        else:
562
            patches = parse_patches(self.patch)
563
        for patch in patches:
487.2.3 by Aaron Bentley
Much refactoring
564
            oldname = patch.oldname.split('\t')[0]
565
            newname = patch.newname.split('\t')[0]
566
            yield oldname, newname, str(patch)
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
567
568
    def perform_save(self, window):
569
        try:
570
            save_path = self.window._get_save_path(osutils.basename(self.path))
571
        except SelectCancelled:
572
            return
573
        source = open(self.path, 'rb')
574
        try:
575
            target = open(save_path, 'wb')
576
            try:
577
                osutils.pumpfile(source, target)
578
            finally:
579
                target.close()
580
        finally:
581
            source.close()
582
583
    def _provide_operations(self):
584
        return [('Save', self.perform_save)]
585
586
587
class MergeDirectiveController(DiffController):
588
487.2.8 by Aaron Bentley
Update error handling to use window
589
    def __init__(self, path, directive, window=None):
734.1.51 by Curtis Hovey
Fix the initializer for many classes.
590
        super(MergeDirectiveController, self).__init__(
591
            path, directive.patch.splitlines(True), window)
428 by Aaron Bentley
Get merging working
592
        self.directive = directive
487.2.1 by Aaron Bentley
Refactor merge directive window into MergeController
593
        self.merge_target = None
594
595
    def _provide_operations(self):
596
        return [('Merge', self.perform_merge), ('Save', self.perform_save)]
427 by Aaron Bentley
Add merge button when displaying merge directives
597
428 by Aaron Bentley
Get merging working
598
    def perform_merge(self, window):
487.2.1 by Aaron Bentley
Refactor merge directive window into MergeController
599
        if self.merge_target is None:
600
            try:
601
                self.merge_target = self.window._get_merge_target()
602
            except SelectCancelled:
603
                return
604
        tree = workingtree.WorkingTree.open(self.merge_target)
428 by Aaron Bentley
Get merging working
605
        tree.lock_write()
606
        try:
607
            try:
677 by Vincent Ladeuil
Fix 2.1.0 compatibility issue.
608
                if tree.has_changes():
609
                    raise errors.UncommittedChanges(tree)
610
                merger, verified = _mod_merge.Merger.from_mergeable(
611
                    tree, self.directive, pb=None)
428 by Aaron Bentley
Get merging working
612
                merger.merge_type = _mod_merge.Merge3Merger
430 by Aaron Bentley
Handle conflicts appropriately
613
                conflict_count = merger.do_merge()
428 by Aaron Bentley
Get merging working
614
                merger.set_pending()
430 by Aaron Bentley
Handle conflicts appropriately
615
                if conflict_count == 0:
487.2.5 by Aaron Bentley
Test successful merge
616
                    self.window._merge_successful()
430 by Aaron Bentley
Handle conflicts appropriately
617
                else:
487.2.7 by Aaron Bentley
Add more merge tests
618
                    self.window._conflicts()
430 by Aaron Bentley
Handle conflicts appropriately
619
                    # There are conflicts to be resolved.
487.2.1 by Aaron Bentley
Refactor merge directive window into MergeController
620
                self.window.destroy()
428 by Aaron Bentley
Get merging working
621
            except Exception, e:
487.2.8 by Aaron Bentley
Update error handling to use window
622
                self.window._handle_error(e)
428 by Aaron Bentley
Get merging working
623
        finally:
624
            tree.unlock()
625
427 by Aaron Bentley
Add merge button when displaying merge directives
626
450 by Aaron Bentley
Update to use new Tree.iter_changes
627
def iter_changes_to_status(source, target):
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
628
    """Determine the differences between trees.
629
450 by Aaron Bentley
Update to use new Tree.iter_changes
630
    This is a wrapper around iter_changes which just yields more
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
631
    understandable results.
632
633
    :param source: The source tree (basis tree)
634
    :param target: The target tree
635
    :return: A list of (file_id, real_path, change_type, display_path)
636
    """
637
    added = 'added'
638
    removed = 'removed'
639
    renamed = 'renamed'
640
    renamed_and_modified = 'renamed and modified'
641
    modified = 'modified'
642
    kind_changed = 'kind changed'
613.1.1 by Vincent Ladeuil
Fix bug #286834 bu handling 'missing' files corner case.
643
    missing = 'missing'
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
644
645
    # TODO: Handle metadata changes
646
647
    status = []
648
    target.lock_read()
649
    try:
650
        source.lock_read()
651
        try:
652
            for (file_id, paths, changed_content, versioned, parent_ids, names,
450 by Aaron Bentley
Update to use new Tree.iter_changes
653
                 kinds, executables) in target.iter_changes(source):
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
654
655
                # Skip the root entry if it isn't very interesting
656
                if parent_ids == (None, None):
657
                    continue
658
659
                change_type = None
660
                if kinds[0] is None:
661
                    source_marker = ''
662
                else:
663
                    source_marker = osutils.kind_marker(kinds[0])
613.1.1 by Vincent Ladeuil
Fix bug #286834 bu handling 'missing' files corner case.
664
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
665
                if kinds[1] is None:
613.1.1 by Vincent Ladeuil
Fix bug #286834 bu handling 'missing' files corner case.
666
                    if kinds[0] is None:
667
                        # We assume bzr will flag only files in that case,
668
                        # there may be a bzr bug there as only files seems to
669
                        # not receive any kind.
670
                        marker = osutils.kind_marker('file')
671
                    else:
672
                        marker = osutils.kind_marker(kinds[0])
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
673
                else:
674
                    marker = osutils.kind_marker(kinds[1])
675
676
                real_path = paths[1]
677
                if real_path is None:
678
                    real_path = paths[0]
679
                assert real_path is not None
680
681
                present_source = versioned[0] and kinds[0] is not None
682
                present_target = versioned[1] and kinds[1] is not None
683
613.1.1 by Vincent Ladeuil
Fix bug #286834 bu handling 'missing' files corner case.
684
                if kinds[0] is None and kinds[1] is None:
685
                    change_type = missing
686
                    display_path = real_path + marker
687
                elif present_source != present_target:
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
688
                    if present_target:
689
                        change_type = added
690
                    else:
691
                        assert present_source
692
                        change_type = removed
613.1.1 by Vincent Ladeuil
Fix bug #286834 bu handling 'missing' files corner case.
693
                    display_path = real_path + marker
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
694
                elif names[0] != names[1] or parent_ids[0] != parent_ids[1]:
695
                    # Renamed
696
                    if changed_content or executables[0] != executables[1]:
697
                        # and modified
698
                        change_type = renamed_and_modified
699
                    else:
700
                        change_type = renamed
701
                    display_path = (paths[0] + source_marker
702
                                    + ' => ' + paths[1] + marker)
703
                elif kinds[0] != kinds[1]:
704
                    change_type = kind_changed
705
                    display_path = (paths[0] + source_marker
706
                                    + ' => ' + paths[1] + marker)
605.1.1 by John Arbash Meinel
just use changed_content as a truth value, rather than checking 'is True'
707
                elif changed_content or executables[0] != executables[1]:
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
708
                    change_type = modified
613.1.1 by Vincent Ladeuil
Fix bug #286834 bu handling 'missing' files corner case.
709
                    display_path = real_path + marker
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
710
                else:
711
                    assert False, "How did we get here?"
712
713
                status.append((file_id, real_path, change_type, display_path))
714
        finally:
715
            source.unlock()
716
    finally:
717
        target.unlock()
718
719
    return status