~bzr-gtk/bzr-gtk/gtk2

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