/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."
771.1.7 by Curtis Hovey
PEP8 changes.
8
__author__ = "Scott James Remnant <scott@ubuntu.com>"
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
9
10
11
from cStringIO import StringIO
12
76 by Jelmer Vernooij
Replace non-UTF8 characters rather than generating an exception (fixes #44677).
13
import sys
688.3.1 by Toshio Kuratomi
Fix bzr-handle-patch to process patches with leading and trailing comments with
14
import inspect
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
15
771.1.7 by Curtis Hovey
PEP8 changes.
16
from gi.repository import Gtk
17
from gi.repository import Pango
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
18
try:
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
19
    from gi.repository import GtkSource
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
20
    have_gtksourceview = True
21
except ImportError:
22
    have_gtksourceview = False
23
434 by Aaron Bentley
Better errors, merge directive saving
24
from bzrlib import (
677 by Vincent Ladeuil
Fix 2.1.0 compatibility issue.
25
    errors,
434 by Aaron Bentley
Better errors, merge directive saving
26
    merge as _mod_merge,
27
    osutils,
28
    urlutils,
29
    workingtree,
771.1.7 by Curtis Hovey
PEP8 changes.
30
    )
724 by Jelmer Vernooij
Fix formatting, imports.
31
from bzrlib.diff import show_diff_trees
426 by Aaron Bentley
Start support for Merge Directives
32
from bzrlib.patches import parse_patches
724 by Jelmer Vernooij
Fix formatting, imports.
33
from bzrlib.plugins.gtk.dialog import (
34
    error_dialog,
35
    info_dialog,
36
    warning_dialog,
37
    )
729.1.1 by Jelmer Vernooij
Move i18n support to a separate file, so gettext files aren't loaded unless bzr-gtk is used.
38
from bzrlib.plugins.gtk.i18n import _i18n
39
from bzrlib.plugins.gtk.window import Window
428 by Aaron Bentley
Get merging working
40
41
657 by Jelmer Vernooij
Merge replacement for guess_language with small tweak: use the canonical implementation if we can.
42
def fallback_guess_language(slm, content_type):
43
    for lang_id in slm.get_language_ids():
44
        lang = slm.get_language(lang_id)
45
        if "text/x-patch" in lang.get_mime_types():
46
            return lang
47
    return None
48
49
428 by Aaron Bentley
Get merging working
50
class SelectCancelled(Exception):
51
52
    pass
298.2.1 by Daniel Schierbeck
Refactored the GTK window code, creating a single base window class that handles keyboard events.
53
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
54
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
55
class DiffFileView(Gtk.ScrolledWindow):
432 by Aaron Bentley
Misc updates
56
    """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
57
774.1.1 by Curtis Hovey
Remove unneeded gtksourceview1 support.
58
    SHOW_WIDGETS = True
59
51 by Jelmer Vernooij
Rework some of the parameters to DiffWindow.set_diff() to be
60
    def __init__(self):
734.1.51 by Curtis Hovey
Fix the initializer for many classes.
61
        super(DiffFileView, self).__init__()
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
62
        self.construct()
424 by Aaron Bentley
Add ghandle-patch
63
        self._diffs = {}
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
64
65
    def construct(self):
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
66
        self.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
67
        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
68
69
        if have_gtksourceview:
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
70
            self.buffer = GtkSource.Buffer()
734.1.3 by Curtis Hovey
Update diff to gtk3.
71
            lang_manager = GtkSource.LanguageManager.get_default()
72
            language = lang_manager.guess_language(None, "text/x-patch")
73
            self.buffer.set_language(language)
635.3.2 by Szilveszter Farkas
Support gedit color schemes for gtksourceview2.
74
            self.buffer.set_highlight_syntax(True)
734.1.3 by Curtis Hovey
Update diff to gtk3.
75
            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
76
        else:
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
77
            self.buffer = Gtk.TextBuffer()
78
            self.sourceview = Gtk.TextView(self.buffer)
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
79
555.2.1 by Jasper Groenewegen
Modify DiffWidget to include a checkbutton to wrap long lines in the diff view
80
        self.sourceview.set_editable(False)
774.1.1 by Curtis Hovey
Remove unneeded gtksourceview1 support.
81
        self.sourceview.override_font(Pango.FontDescription("Monospace"))
555.2.1 by Jasper Groenewegen
Modify DiffWidget to include a checkbutton to wrap long lines in the diff view
82
        self.add(self.sourceview)
774.1.1 by Curtis Hovey
Remove unneeded gtksourceview1 support.
83
        if self.SHOW_WIDGETS:
84
            self.sourceview.show()
232.1.4 by Adeodato Simó
Add a test for parse_colordiffrc, and fix the function to handle empty lines.
85
278.1.4 by John Arbash Meinel
Just playing around.
86
    def set_trees(self, rev_tree, parent_tree):
87
        self.rev_tree = rev_tree
88
        self.parent_tree = parent_tree
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
89
#        self._build_delta()
90
91
#    def _build_delta(self):
92
#        self.parent_tree.lock_read()
93
#        self.rev_tree.lock_read()
94
#        try:
771.1.7 by Curtis Hovey
PEP8 changes.
95
#            self.delta = iter_changes_to_status(
96
#               self.parent_tree, self.rev_tree)
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
97
#            self.path_to_status = {}
98
#            self.path_to_diff = {}
99
#            source_inv = self.parent_tree.inventory
100
#            target_inv = self.rev_tree.inventory
101
#            for (file_id, real_path, change_type, display_path) in self.delta:
771.1.7 by Curtis Hovey
PEP8 changes.
102
#                self.path_to_status[real_path] = u'=== %s %s' % (
103
#                    change_type, display_path)
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
104
#                if change_type in ('modified', 'renamed and modified'):
105
#                    source_ie = source_inv[file_id]
106
#                    target_ie = target_inv[file_id]
107
#                    sio = StringIO()
108
#                    source_ie.diff(internal_diff, *old path, *old_tree,
109
#                                   *new_path, target_ie, self.rev_tree,
110
#                                   sio)
771.1.7 by Curtis Hovey
PEP8 changes.
111
#                    self.path_to_diff[real_path] =
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
112
#
113
#        finally:
114
#            self.rev_tree.unlock()
115
#            self.parent_tree.unlock()
278.1.4 by John Arbash Meinel
Just playing around.
116
117
    def show_diff(self, specific_files):
426 by Aaron Bentley
Start support for Merge Directives
118
        sections = []
119
        if specific_files is None:
120
            self.buffer.set_text(self._diffs[None])
121
        else:
122
            for specific_file in specific_files:
123
                sections.append(self._diffs[specific_file])
124
            self.buffer.set_text(''.join(sections))
424 by Aaron Bentley
Add ghandle-patch
125
126
127
class DiffView(DiffFileView):
128
    """This is the soft and chewy filling for a DiffWindow."""
129
130
    def __init__(self):
734.1.51 by Curtis Hovey
Fix the initializer for many classes.
131
        super(DiffView, self).__init__()
424 by Aaron Bentley
Add ghandle-patch
132
        self.rev_tree = None
133
        self.parent_tree = None
134
135
    def show_diff(self, specific_files):
432 by Aaron Bentley
Misc updates
136
        """Show the diff for the specified files"""
278.1.4 by John Arbash Meinel
Just playing around.
137
        s = StringIO()
278.1.18 by John Arbash Meinel
Start checking the diff view is correct.
138
        show_diff_trees(self.parent_tree, self.rev_tree, s, specific_files,
139
                        old_label='', new_label='',
140
                        # path_encoding=sys.getdefaultencoding()
141
                        # The default is utf-8, but we interpret the file
142
                        # contents as getdefaultencoding(), so we should
143
                        # probably try to make the paths in the same encoding.
144
                        )
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
145
        # str.decode(encoding, 'replace') doesn't do anything. Because if a
146
        # character is not valid in 'encoding' there is nothing to replace, the
147
        # 'replace' is for 'str.encode()'
148
        try:
149
            decoded = s.getvalue().decode(sys.getdefaultencoding())
150
        except UnicodeDecodeError:
151
            try:
152
                decoded = s.getvalue().decode('UTF-8')
153
            except UnicodeDecodeError:
154
                decoded = s.getvalue().decode('iso-8859-1')
155
                # This always works, because every byte has a valid
156
                # mapping from iso-8859-1 to Unicode
157
        # TextBuffer must contain pure UTF-8 data
158
        self.buffer.set_text(decoded.encode('UTF-8'))
278.1.4 by John Arbash Meinel
Just playing around.
159
160
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
161
class DiffWidget(Gtk.HPaned):
423.8.1 by Jelmer Vernooij
Allow using the diff control as a widget
162
    """Diff widget
278.1.4 by John Arbash Meinel
Just playing around.
163
164
    """
771.1.1 by Curtis Hovey
Added hooks for testing.
165
166
    SHOW_WIDGETS = True
167
423.8.1 by Jelmer Vernooij
Allow using the diff control as a widget
168
    def __init__(self):
169
        super(DiffWidget, self).__init__()
278.1.4 by John Arbash Meinel
Just playing around.
170
171
        # The file hierarchy: a scrollable treeview
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
172
        scrollwin = Gtk.ScrolledWindow()
173
        scrollwin.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
174
        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
175
        self.pack1(scrollwin)
771.1.1 by Curtis Hovey
Added hooks for testing.
176
        if self.SHOW_WIDGETS:
177
            scrollwin.show()
771.1.7 by Curtis Hovey
PEP8 changes.
178
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
179
        self.model = Gtk.TreeStore(str, str)
734.1.3 by Curtis Hovey
Update diff to gtk3.
180
        self.treeview = Gtk.TreeView(model=self.model)
278.1.4 by John Arbash Meinel
Just playing around.
181
        self.treeview.set_headers_visible(False)
182
        self.treeview.set_search_column(1)
183
        self.treeview.connect("cursor-changed", self._treeview_cursor_cb)
184
        scrollwin.add(self.treeview)
771.1.1 by Curtis Hovey
Added hooks for testing.
185
        if self.SHOW_WIDGETS:
186
            self.treeview.show()
278.1.4 by John Arbash Meinel
Just playing around.
187
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
188
        cell = Gtk.CellRendererText()
278.1.4 by John Arbash Meinel
Just playing around.
189
        cell.set_property("width-chars", 20)
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
190
        column = Gtk.TreeViewColumn()
734.1.3 by Curtis Hovey
Update diff to gtk3.
191
        column.pack_start(cell, True)
278.1.4 by John Arbash Meinel
Just playing around.
192
        column.add_attribute(cell, "text", 0)
193
        self.treeview.append_column(column)
194
429 by Aaron Bentley
Merge from mainline
195
    def set_diff_text(self, lines):
432 by Aaron Bentley
Misc updates
196
        """Set the current diff from a list of lines
197
198
        :param lines: The diff to show, in unified diff format
199
        """
278.1.4 by John Arbash Meinel
Just playing around.
200
        # The diffs of the  selected file: a scrollable source or
201
        # text view
487.2.3 by Aaron Bentley
Much refactoring
202
203
    def set_diff_text_sections(self, sections):
531.7.13 by Scott Scriven
Removed exception-swallowing hasattr()s.
204
        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.
205
            self.diff_view = DiffFileView()
206
            self.pack2(self.diff_view)
771.1.3 by Curtis Hovey
Do not show widgets when not needed.
207
        if self.SHOW_WIDGETS:
208
            self.diff_view.show()
487.2.3 by Aaron Bentley
Much refactoring
209
        for oldname, newname, patch in sections:
210
            self.diff_view._diffs[newname] = str(patch)
211
            if newname is None:
212
                newname = ''
426 by Aaron Bentley
Start support for Merge Directives
213
            self.model.append(None, [oldname, newname])
427 by Aaron Bentley
Add merge button when displaying merge directives
214
        self.diff_view.show_diff(None)
278.1.4 by John Arbash Meinel
Just playing around.
215
429 by Aaron Bentley
Merge from mainline
216
    def set_diff(self, rev_tree, parent_tree):
278.1.4 by John Arbash Meinel
Just playing around.
217
        """Set the differences showed by this window.
218
219
        Compares the two trees and populates the window with the
220
        differences.
221
        """
531.7.13 by Scott Scriven
Removed exception-swallowing hasattr()s.
222
        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.
223
            self.diff_view = DiffView()
224
            self.pack2(self.diff_view)
771.1.1 by Curtis Hovey
Added hooks for testing.
225
        if self.SHOW_WIDGETS:
226
            self.diff_view.show()
278.1.12 by John Arbash Meinel
Delay computing the delta, and clean up some of the diff view names.
227
        self.diff_view.set_trees(rev_tree, parent_tree)
278.1.4 by John Arbash Meinel
Just playing around.
228
        self.rev_tree = rev_tree
229
        self.parent_tree = parent_tree
230
231
        self.model.clear()
232
        delta = self.rev_tree.changes_from(self.parent_tree)
233
771.1.7 by Curtis Hovey
PEP8 changes.
234
        self.model.append(None, ["Complete Diff", ""])
278.1.4 by John Arbash Meinel
Just playing around.
235
236
        if len(delta.added):
771.1.7 by Curtis Hovey
PEP8 changes.
237
            titer = self.model.append(None, ["Added", None])
278.1.4 by John Arbash Meinel
Just playing around.
238
            for path, id, kind in delta.added:
771.1.7 by Curtis Hovey
PEP8 changes.
239
                self.model.append(titer, [path, path])
278.1.4 by John Arbash Meinel
Just playing around.
240
241
        if len(delta.removed):
771.1.7 by Curtis Hovey
PEP8 changes.
242
            titer = self.model.append(None, ["Removed", None])
278.1.4 by John Arbash Meinel
Just playing around.
243
            for path, id, kind in delta.removed:
771.1.7 by Curtis Hovey
PEP8 changes.
244
                self.model.append(titer, [path, path])
278.1.4 by John Arbash Meinel
Just playing around.
245
246
        if len(delta.renamed):
771.1.7 by Curtis Hovey
PEP8 changes.
247
            titer = self.model.append(None, ["Renamed", None])
278.1.4 by John Arbash Meinel
Just playing around.
248
            for oldpath, newpath, id, kind, text_modified, meta_modified \
249
                    in delta.renamed:
771.1.7 by Curtis Hovey
PEP8 changes.
250
                self.model.append(titer, [oldpath, newpath])
278.1.4 by John Arbash Meinel
Just playing around.
251
252
        if len(delta.modified):
771.1.7 by Curtis Hovey
PEP8 changes.
253
            titer = self.model.append(None, ["Modified", None])
278.1.4 by John Arbash Meinel
Just playing around.
254
            for path, id, kind, text_modified, meta_modified in delta.modified:
771.1.7 by Curtis Hovey
PEP8 changes.
255
                self.model.append(titer, [path, path])
278.1.4 by John Arbash Meinel
Just playing around.
256
257
        self.treeview.expand_all()
531.7.5 by Scott Scriven
Fixed DiffWidget so more than one call to set_diff() will work.
258
        self.diff_view.show_diff(None)
278.1.4 by John Arbash Meinel
Just playing around.
259
260
    def set_file(self, file_path):
432 by Aaron Bentley
Misc updates
261
        """Select the current file to display"""
278.1.4 by John Arbash Meinel
Just playing around.
262
        tv_path = None
263
        for data in self.model:
264
            for child in data.iterchildren():
265
                if child[0] == file_path or child[1] == file_path:
266
                    tv_path = child.path
267
                    break
268
        if tv_path is None:
677 by Vincent Ladeuil
Fix 2.1.0 compatibility issue.
269
            raise errors.NoSuchFile(file_path)
734.1.12 by Curtis Hovey
Fixed row/path issues in gannoate.
270
        self.treeview.set_cursor(tv_path, None, False)
278.1.4 by John Arbash Meinel
Just playing around.
271
        self.treeview.scroll_to_cell(tv_path)
272
273
    def _treeview_cursor_cb(self, *args):
274
        """Callback for when the treeview cursor changes."""
275
        (path, col) = self.treeview.get_cursor()
771.1.4 by Curtis Hovey
Do not update diff_view when the treeview is being destroyed.
276
        if path is None:
277
            return
771.1.7 by Curtis Hovey
PEP8 changes.
278
        specific_files = [self.model[path][1]]
279
        if specific_files == [None]:
278.1.4 by John Arbash Meinel
Just playing around.
280
            return
771.1.7 by Curtis Hovey
PEP8 changes.
281
        elif specific_files == [""]:
278.1.4 by John Arbash Meinel
Just playing around.
282
            specific_files = None
771.1.7 by Curtis Hovey
PEP8 changes.
283
278.1.12 by John Arbash Meinel
Delay computing the delta, and clean up some of the diff view names.
284
        self.diff_view.show_diff(specific_files)
771.1.7 by Curtis Hovey
PEP8 changes.
285
555.2.2 by Jasper Groenewegen
Change from checkbox to menu item in bzr vis, add menu bar + item in gdiff
286
    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
287
        """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
288
        if wrap or widget.get_active():
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
289
            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
290
        else:
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
291
            self.diff_view.sourceview.set_wrap_mode(Gtk.WrapMode.NONE)
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
292
771.1.7 by Curtis Hovey
PEP8 changes.
293
423.8.1 by Jelmer Vernooij
Allow using the diff control as a widget
294
class DiffWindow(Window):
295
    """Diff window.
296
297
    This object represents and manages a single window containing the
298
    differences between two revisions on a branch.
299
    """
300
772.2.1 by Curtis Hovey
Added test for DiffWindow._get_button_bar.
301
    SHOW_WIDGETS = True
302
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
303
    def __init__(self, parent=None, operations=None):
734.1.51 by Curtis Hovey
Fix the initializer for many classes.
304
        super(DiffWindow, self).__init__(parent=parent)
423.8.1 by Jelmer Vernooij
Allow using the diff control as a widget
305
        self.set_border_width(0)
772.2.2 by Curtis Hovey
show_all() instead of individual calls.
306
        self.set_title("bzr diff")
423.8.1 by Jelmer Vernooij
Allow using the diff control as a widget
307
308
        # Use two thirds of the screen by default
309
        screen = self.get_screen()
310
        monitor = screen.get_monitor_geometry(0)
311
        width = int(monitor.width * 0.66)
312
        height = int(monitor.height * 0.66)
313
        self.set_default_size(width, height)
487.2.3 by Aaron Bentley
Much refactoring
314
        self.construct(operations)
423.8.1 by Jelmer Vernooij
Allow using the diff control as a widget
315
487.2.3 by Aaron Bentley
Much refactoring
316
    def construct(self, operations):
423.8.1 by Jelmer Vernooij
Allow using the diff control as a widget
317
        """Construct the window contents."""
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
318
        self.vbox = Gtk.VBox()
429 by Aaron Bentley
Merge from mainline
319
        self.add(self.vbox)
772.2.1 by Curtis Hovey
Added test for DiffWindow._get_button_bar.
320
        if self.SHOW_WIDGETS:
321
            self.vbox.show()
555.2.2 by Jasper Groenewegen
Change from checkbox to menu item in bzr vis, add menu bar + item in gdiff
322
        self.diff = DiffWidget()
323
        self.vbox.pack_end(self.diff, True, True, 0)
772.2.1 by Curtis Hovey
Added test for DiffWindow._get_button_bar.
324
        if self.SHOW_WIDGETS:
325
            self.diff.show_all()
555.2.2 by Jasper Groenewegen
Change from checkbox to menu item in bzr vis, add menu bar + item in gdiff
326
        # Build after DiffWidget to connect signals
327
        menubar = self._get_menu_bar()
328
        self.vbox.pack_start(menubar, False, False, 0)
487.2.3 by Aaron Bentley
Much refactoring
329
        hbox = self._get_button_bar(operations)
429 by Aaron Bentley
Merge from mainline
330
        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
331
            self.vbox.pack_start(hbox, False, True, 0)
771.1.7 by Curtis Hovey
PEP8 changes.
332
555.2.2 by Jasper Groenewegen
Change from checkbox to menu item in bzr vis, add menu bar + item in gdiff
333
    def _get_menu_bar(self):
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
334
        menubar = Gtk.MenuBar()
555.2.2 by Jasper Groenewegen
Change from checkbox to menu item in bzr vis, add menu bar + item in gdiff
335
        # View menu
734.1.38 by Curtis Hovey
Menu fixes.
336
        mb_view = Gtk.MenuItem.new_with_mnemonic(_i18n("_View"))
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
337
        mb_view_menu = Gtk.Menu()
734.1.38 by Curtis Hovey
Menu fixes.
338
        mb_view_wrapsource = Gtk.CheckMenuItem.new_with_mnemonic(
339
            _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
340
        mb_view_wrapsource.connect('activate', self.diff._on_wraplines_toggled)
341
        mb_view_menu.append(mb_view_wrapsource)
342
        mb_view.set_submenu(mb_view_menu)
343
        menubar.append(mb_view)
772.2.1 by Curtis Hovey
Added test for DiffWindow._get_button_bar.
344
        if self.SHOW_WIDGETS:
772.2.2 by Curtis Hovey
show_all() instead of individual calls.
345
            menubar.show_all()
555.2.2 by Jasper Groenewegen
Change from checkbox to menu item in bzr vis, add menu bar + item in gdiff
346
        return menubar
771.1.7 by Curtis Hovey
PEP8 changes.
347
487.2.3 by Aaron Bentley
Much refactoring
348
    def _get_button_bar(self, operations):
432 by Aaron Bentley
Misc updates
349
        """Return a button bar to use.
350
351
        :return: None, meaning that no button bar will be used.
352
        """
487.2.3 by Aaron Bentley
Much refactoring
353
        if operations is None:
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
354
            return None
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
355
        hbox = Gtk.HButtonBox()
356
        hbox.set_layout(Gtk.ButtonBoxStyle.START)
487.2.3 by Aaron Bentley
Much refactoring
357
        for title, method in operations:
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
358
            merge_button = Gtk.Button(title)
772.2.1 by Curtis Hovey
Added test for DiffWindow._get_button_bar.
359
            if self.SHOW_WIDGETS:
360
                merge_button.show()
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
361
            merge_button.set_relief(Gtk.ReliefStyle.NONE)
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
362
            merge_button.connect("clicked", method)
772.2.1 by Curtis Hovey
Added test for DiffWindow._get_button_bar.
363
            hbox.pack_start(merge_button, False, True, 0)
364
        if self.SHOW_WIDGETS:
365
            hbox.show()
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
366
        return hbox
367
368
    def _get_merge_target(self):
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
369
        d = Gtk.FileChooserDialog('Merge branch', self,
370
                                  Gtk.FileChooserAction.SELECT_FOLDER,
371
                                  buttons=(Gtk.STOCK_OK, Gtk.ResponseType.OK,
372
                                           Gtk.STOCK_CANCEL,
373
                                           Gtk.ResponseType.CANCEL,))
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
374
        try:
375
            result = d.run()
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
376
            if result != Gtk.ResponseType.OK:
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
377
                raise SelectCancelled()
378
            return d.get_current_folder_uri()
379
        finally:
380
            d.destroy()
381
487.2.5 by Aaron Bentley
Test successful merge
382
    def _merge_successful(self):
383
        # No conflicts found.
492 by Aaron Bentley
Allow saving from patch window, refactoring, testing.
384
        info_dialog(_i18n('Merge successful'),
385
                    _i18n('All changes applied successfully.'))
487.2.5 by Aaron Bentley
Test successful merge
386
487.2.7 by Aaron Bentley
Add more merge tests
387
    def _conflicts(self):
492 by Aaron Bentley
Allow saving from patch window, refactoring, testing.
388
        warning_dialog(_i18n('Conflicts encountered'),
389
                       _i18n('Please resolve the conflicts manually'
390
                             ' before committing.'))
487.2.7 by Aaron Bentley
Add more merge tests
391
487.2.8 by Aaron Bentley
Update error handling to use window
392
    def _handle_error(self, e):
393
        error_dialog('Error', str(e))
487.2.7 by Aaron Bentley
Add more merge tests
394
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
395
    def _get_save_path(self, basename):
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
396
        d = Gtk.FileChooserDialog('Save As', self,
397
                                  Gtk.FileChooserAction.SAVE,
398
                                  buttons=(Gtk.STOCK_OK, Gtk.ResponseType.OK,
399
                                           Gtk.STOCK_CANCEL,
400
                                           Gtk.ResponseType.CANCEL,))
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
401
        d.set_current_name(basename)
402
        try:
403
            result = d.run()
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
404
            if result != Gtk.ResponseType.OK:
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
405
                raise SelectCancelled()
406
            return urlutils.local_path_from_url(d.get_uri())
407
        finally:
408
            d.destroy()
429 by Aaron Bentley
Merge from mainline
409
423.8.1 by Jelmer Vernooij
Allow using the diff control as a widget
410
    def set_diff(self, description, rev_tree, parent_tree):
411
        """Set the differences showed by this window.
412
413
        Compares the two trees and populates the window with the
414
        differences.
415
        """
416
        self.diff.set_diff(rev_tree, parent_tree)
417
        self.set_title(description + " - bzrk diff")
418
419
    def set_file(self, file_path):
420
        self.diff.set_file(file_path)
421
422
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
423
class DiffController(object):
424
688.3.1 by Toshio Kuratomi
Fix bzr-handle-patch to process patches with leading and trailing comments with
425
    def __init__(self, path, patch, window=None, allow_dirty=False):
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
426
        self.path = path
427
        self.patch = patch
688.3.1 by Toshio Kuratomi
Fix bzr-handle-patch to process patches with leading and trailing comments with
428
        self.allow_dirty = allow_dirty
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
429
        if window is None:
430
            window = DiffWindow(operations=self._provide_operations())
431
            self.initialize_window(window)
487.2.3 by Aaron Bentley
Much refactoring
432
        self.window = window
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
433
434
    def initialize_window(self, window):
487.2.3 by Aaron Bentley
Much refactoring
435
        window.diff.set_diff_text_sections(self.get_diff_sections())
436
        window.set_title(self.path + " - diff")
437
438
    def get_diff_sections(self):
439
        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
440
        # allow_dirty was added to parse_patches in bzrlib 2.2b1
441
        if 'allow_dirty' in inspect.getargspec(parse_patches).args:
442
            patches = parse_patches(self.patch, allow_dirty=self.allow_dirty)
443
        else:
444
            patches = parse_patches(self.patch)
445
        for patch in patches:
487.2.3 by Aaron Bentley
Much refactoring
446
            oldname = patch.oldname.split('\t')[0]
447
            newname = patch.newname.split('\t')[0]
448
            yield oldname, newname, str(patch)
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
449
450
    def perform_save(self, window):
451
        try:
452
            save_path = self.window._get_save_path(osutils.basename(self.path))
453
        except SelectCancelled:
454
            return
455
        source = open(self.path, 'rb')
456
        try:
457
            target = open(save_path, 'wb')
458
            try:
459
                osutils.pumpfile(source, target)
460
            finally:
461
                target.close()
462
        finally:
463
            source.close()
464
465
    def _provide_operations(self):
466
        return [('Save', self.perform_save)]
467
468
469
class MergeDirectiveController(DiffController):
470
487.2.8 by Aaron Bentley
Update error handling to use window
471
    def __init__(self, path, directive, window=None):
734.1.51 by Curtis Hovey
Fix the initializer for many classes.
472
        super(MergeDirectiveController, self).__init__(
473
            path, directive.patch.splitlines(True), window)
428 by Aaron Bentley
Get merging working
474
        self.directive = directive
487.2.1 by Aaron Bentley
Refactor merge directive window into MergeController
475
        self.merge_target = None
476
477
    def _provide_operations(self):
478
        return [('Merge', self.perform_merge), ('Save', self.perform_save)]
427 by Aaron Bentley
Add merge button when displaying merge directives
479
428 by Aaron Bentley
Get merging working
480
    def perform_merge(self, window):
487.2.1 by Aaron Bentley
Refactor merge directive window into MergeController
481
        if self.merge_target is None:
482
            try:
483
                self.merge_target = self.window._get_merge_target()
484
            except SelectCancelled:
485
                return
486
        tree = workingtree.WorkingTree.open(self.merge_target)
428 by Aaron Bentley
Get merging working
487
        tree.lock_write()
488
        try:
489
            try:
677 by Vincent Ladeuil
Fix 2.1.0 compatibility issue.
490
                if tree.has_changes():
491
                    raise errors.UncommittedChanges(tree)
492
                merger, verified = _mod_merge.Merger.from_mergeable(
493
                    tree, self.directive, pb=None)
428 by Aaron Bentley
Get merging working
494
                merger.merge_type = _mod_merge.Merge3Merger
430 by Aaron Bentley
Handle conflicts appropriately
495
                conflict_count = merger.do_merge()
428 by Aaron Bentley
Get merging working
496
                merger.set_pending()
430 by Aaron Bentley
Handle conflicts appropriately
497
                if conflict_count == 0:
487.2.5 by Aaron Bentley
Test successful merge
498
                    self.window._merge_successful()
430 by Aaron Bentley
Handle conflicts appropriately
499
                else:
487.2.7 by Aaron Bentley
Add more merge tests
500
                    self.window._conflicts()
430 by Aaron Bentley
Handle conflicts appropriately
501
                    # There are conflicts to be resolved.
487.2.1 by Aaron Bentley
Refactor merge directive window into MergeController
502
                self.window.destroy()
428 by Aaron Bentley
Get merging working
503
            except Exception, e:
487.2.8 by Aaron Bentley
Update error handling to use window
504
                self.window._handle_error(e)
428 by Aaron Bentley
Get merging working
505
        finally:
506
            tree.unlock()
507
427 by Aaron Bentley
Add merge button when displaying merge directives
508
450 by Aaron Bentley
Update to use new Tree.iter_changes
509
def iter_changes_to_status(source, target):
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
510
    """Determine the differences between trees.
511
450 by Aaron Bentley
Update to use new Tree.iter_changes
512
    This is a wrapper around iter_changes which just yields more
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
513
    understandable results.
514
515
    :param source: The source tree (basis tree)
516
    :param target: The target tree
517
    :return: A list of (file_id, real_path, change_type, display_path)
518
    """
519
    added = 'added'
520
    removed = 'removed'
521
    renamed = 'renamed'
522
    renamed_and_modified = 'renamed and modified'
523
    modified = 'modified'
524
    kind_changed = 'kind changed'
613.1.1 by Vincent Ladeuil
Fix bug #286834 bu handling 'missing' files corner case.
525
    missing = 'missing'
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
526
527
    # TODO: Handle metadata changes
528
529
    status = []
530
    target.lock_read()
531
    try:
532
        source.lock_read()
533
        try:
534
            for (file_id, paths, changed_content, versioned, parent_ids, names,
450 by Aaron Bentley
Update to use new Tree.iter_changes
535
                 kinds, executables) in target.iter_changes(source):
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
536
537
                # Skip the root entry if it isn't very interesting
538
                if parent_ids == (None, None):
539
                    continue
540
541
                change_type = None
542
                if kinds[0] is None:
543
                    source_marker = ''
544
                else:
545
                    source_marker = osutils.kind_marker(kinds[0])
613.1.1 by Vincent Ladeuil
Fix bug #286834 bu handling 'missing' files corner case.
546
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
547
                if kinds[1] is None:
613.1.1 by Vincent Ladeuil
Fix bug #286834 bu handling 'missing' files corner case.
548
                    if kinds[0] is None:
549
                        # We assume bzr will flag only files in that case,
550
                        # there may be a bzr bug there as only files seems to
551
                        # not receive any kind.
552
                        marker = osutils.kind_marker('file')
553
                    else:
554
                        marker = osutils.kind_marker(kinds[0])
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
555
                else:
556
                    marker = osutils.kind_marker(kinds[1])
557
558
                real_path = paths[1]
559
                if real_path is None:
560
                    real_path = paths[0]
561
                assert real_path is not None
562
563
                present_source = versioned[0] and kinds[0] is not None
564
                present_target = versioned[1] and kinds[1] is not None
565
613.1.1 by Vincent Ladeuil
Fix bug #286834 bu handling 'missing' files corner case.
566
                if kinds[0] is None and kinds[1] is None:
567
                    change_type = missing
568
                    display_path = real_path + marker
569
                elif present_source != present_target:
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
570
                    if present_target:
571
                        change_type = added
572
                    else:
573
                        assert present_source
574
                        change_type = removed
613.1.1 by Vincent Ladeuil
Fix bug #286834 bu handling 'missing' files corner case.
575
                    display_path = real_path + marker
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
576
                elif names[0] != names[1] or parent_ids[0] != parent_ids[1]:
577
                    # Renamed
578
                    if changed_content or executables[0] != executables[1]:
579
                        # and modified
580
                        change_type = renamed_and_modified
581
                    else:
582
                        change_type = renamed
583
                    display_path = (paths[0] + source_marker
584
                                    + ' => ' + paths[1] + marker)
585
                elif kinds[0] != kinds[1]:
586
                    change_type = kind_changed
587
                    display_path = (paths[0] + source_marker
588
                                    + ' => ' + paths[1] + marker)
605.1.1 by John Arbash Meinel
just use changed_content as a truth value, rather than checking 'is True'
589
                elif changed_content or executables[0] != executables[1]:
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
590
                    change_type = modified
613.1.1 by Vincent Ladeuil
Fix bug #286834 bu handling 'missing' files corner case.
591
                    display_path = real_path + marker
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
592
                else:
593
                    assert False, "How did we get here?"
594
595
                status.append((file_id, real_path, change_type, display_path))
596
        finally:
597
            source.unlock()
598
    finally:
599
        target.unlock()
600
601
    return status