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