/b-gtk/fix-viz

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/b-gtk/fix-viz

« back to all changes in this revision

Viewing changes to commit.py

  • Committer: Jelmer Vernooij
  • Date: 2012-02-17 15:03:17 UTC
  • Revision ID: jelmer@samba.org-20120217150317-bz27z9fanucxel18
Avoid the use of Repository.get_ancestry().

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
 
try:
18
 
    import pygtk
19
 
    pygtk.require("2.0")
20
 
except:
21
 
    pass
22
 
 
23
 
import gtk
24
 
import gobject
25
 
import pango
26
 
 
27
 
import os.path
28
17
import re
29
18
 
30
 
from bzrlib import errors, osutils
31
 
from bzrlib.trace import mutter
32
 
from bzrlib.util import bencode
 
19
from gi.repository import Gdk
 
20
from gi.repository import Gtk
 
21
from gi.repository import GObject
 
22
from gi.repository import Pango
33
23
 
34
 
from bzrlib.plugins.gtk import _i18n
35
 
from dialog import error_dialog, question_dialog
36
 
from errors import show_bzr_error
 
24
from bzrlib import (
 
25
    bencode,
 
26
    errors,
 
27
    osutils,
 
28
    revision as _mod_revision,
 
29
    trace,
 
30
    tsort,
 
31
    )
 
32
from bzrlib.plugins.gtk.dialog import question_dialog
 
33
from bzrlib.plugins.gtk.errors import show_bzr_error
 
34
from bzrlib.plugins.gtk.i18n import _i18n
 
35
from bzrlib.plugins.gtk.commitmsgs import SavedCommitMessagesManager
37
36
 
38
37
try:
39
38
    import dbus
43
42
    have_dbus = False
44
43
 
45
44
 
 
45
def _get_sorted_revisions(tip_revision, revision_ids, parent_map):
 
46
    """Get an iterator which will return the revisions in merge sorted order.
 
47
 
 
48
    This will build up a list of all nodes, such that only nodes in the list
 
49
    are referenced. It then uses MergeSorter to return them in 'merge-sorted'
 
50
    order.
 
51
 
 
52
    :param revision_ids: A set of revision_ids
 
53
    :param parent_map: The parent information for each node. Revisions which
 
54
        are considered ghosts should not be present in the map.
 
55
    :return: iterator from MergeSorter.iter_topo_order()
 
56
    """
 
57
    # MergeSorter requires that all nodes be present in the graph, so get rid
 
58
    # of any references pointing outside of this graph.
 
59
    parent_graph = {}
 
60
    for revision_id in revision_ids:
 
61
        if revision_id not in parent_map: # ghost
 
62
            parent_graph[revision_id] = []
 
63
        else:
 
64
            # Only include parents which are in this sub-graph
 
65
            parent_graph[revision_id] = [p for p in parent_map[revision_id]
 
66
                                            if p in revision_ids]
 
67
    sorter = tsort.MergeSorter(parent_graph, tip_revision)
 
68
    return sorter.iter_topo_order()
 
69
 
 
70
 
46
71
def pending_revisions(wt):
47
72
    """Return a list of pending merges or None if there are none of them.
48
73
 
53
78
    """
54
79
    parents = wt.get_parent_ids()
55
80
    if len(parents) < 2:
56
 
        return None
 
81
        return
57
82
 
58
83
    # The basic pending merge algorithm uses the same algorithm as
59
84
    # bzrlib.status.show_pending_merges
61
86
    branch = wt.branch
62
87
    last_revision = parents[0]
63
88
 
64
 
    if last_revision is not None:
65
 
        try:
66
 
            ignore = set(branch.repository.get_ancestry(last_revision,
67
 
                                                        topo_sorted=False))
68
 
        except errors.NoSuchRevision:
69
 
            # the last revision is a ghost : assume everything is new
70
 
            # except for it
71
 
            ignore = set([None, last_revision])
72
 
    else:
73
 
        ignore = set([None])
 
89
    graph = branch.repository.get_graph()
 
90
    other_revisions = [last_revision]
74
91
 
75
92
    pm = []
76
93
    for merge in pending:
77
 
        ignore.add(merge)
78
 
        try:
79
 
            rev = branch.repository.get_revision(merge)
80
 
            children = []
81
 
            pm.append((rev, children))
82
 
 
83
 
            # This does need to be topo sorted, so we search backwards
84
 
            inner_merges = branch.repository.get_ancestry(merge)
85
 
            assert inner_merges[0] is None
86
 
            inner_merges.pop(0)
87
 
            for mmerge in reversed(inner_merges):
88
 
                if mmerge in ignore:
89
 
                    continue
90
 
                rev = branch.repository.get_revision(mmerge)
91
 
                children.append(rev)
92
 
 
93
 
                ignore.add(mmerge)
94
 
        except errors.NoSuchRevision:
95
 
            print "DEBUG: NoSuchRevision:", merge
96
 
 
97
 
    return pm
98
 
 
99
 
 
100
 
class CommitDialog(gtk.Dialog):
 
94
        try:
 
95
            merge_rev = branch.repository.get_revision(merge)
 
96
        except errors.NoSuchRevision:
 
97
            # If we are missing a revision, just print out the revision id
 
98
            trace.mutter("ghost: %r", merge)
 
99
            other_revisions.append(merge)
 
100
            continue
 
101
 
 
102
        # Find all of the revisions in the merge source, which are not in the
 
103
        # last committed revision.
 
104
        merge_extra = graph.find_unique_ancestors(merge, other_revisions)
 
105
        other_revisions.append(merge)
 
106
        merge_extra.discard(_mod_revision.NULL_REVISION)
 
107
 
 
108
        # Get a handle to all of the revisions we will need
 
109
        try:
 
110
            revisions = dict((rev.revision_id, rev) for rev in
 
111
                             branch.repository.get_revisions(merge_extra))
 
112
        except errors.NoSuchRevision:
 
113
            # One of the sub nodes is a ghost, check each one
 
114
            revisions = {}
 
115
            for revision_id in merge_extra:
 
116
                try:
 
117
                    rev = branch.repository.get_revisions([revision_id])[0]
 
118
                except errors.NoSuchRevision:
 
119
                    revisions[revision_id] = None
 
120
                else:
 
121
                    revisions[revision_id] = rev
 
122
 
 
123
         # Display the revisions brought in by this merge.
 
124
        rev_id_iterator = _get_sorted_revisions(merge, merge_extra,
 
125
                            branch.repository.get_parent_map(merge_extra))
 
126
        # Skip the first node
 
127
        num, first, depth, eom = rev_id_iterator.next()
 
128
        if first != merge:
 
129
            raise AssertionError('Somehow we misunderstood how'
 
130
                ' iter_topo_order works %s != %s' % (first, merge))
 
131
        children = []
 
132
        for num, sub_merge, depth, eom in rev_id_iterator:
 
133
            rev = revisions[sub_merge]
 
134
            if rev is None:
 
135
                trace.warning("ghost: %r", sub_merge)
 
136
                continue
 
137
            children.append(rev)
 
138
        yield (merge_rev, children)
 
139
 
 
140
 
 
141
_newline_variants_re = re.compile(r'\r\n?')
 
142
def _sanitize_and_decode_message(utf8_message):
 
143
    """Turn a utf-8 message into a sanitized Unicode message."""
 
144
    fixed_newline = _newline_variants_re.sub('\n', utf8_message)
 
145
    return osutils.safe_unicode(fixed_newline)
 
146
 
 
147
 
 
148
class CommitDialog(Gtk.Dialog):
101
149
    """Implementation of Commit."""
102
150
 
103
151
    def __init__(self, wt, selected=None, parent=None):
104
 
        gtk.Dialog.__init__(self, title="Commit - Olive",
105
 
                                  parent=parent,
106
 
                                  flags=0,
107
 
                                  buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
 
152
        super(CommitDialog, self).__init__(
 
153
            title="Commit to %s" % wt.basedir, parent=parent, flags=0)
 
154
        self.connect('delete-event', self._on_delete_window)
108
155
        self._question_dialog = question_dialog
109
156
 
 
157
        self.set_type_hint(Gdk.WindowTypeHint.NORMAL)
 
158
 
110
159
        self._wt = wt
111
160
        # TODO: Do something with this value, it is used by Olive
112
161
        #       It used to set all changes but this one to False
114
163
        self._enable_per_file_commits = True
115
164
        self._commit_all_changes = True
116
165
        self.committed_revision_id = None # Nothing has been committed yet
 
166
        self._last_selected_file = None
 
167
        self._saved_commit_messages_manager = SavedCommitMessagesManager(
 
168
            self._wt, self._wt.branch)
117
169
 
118
170
        self.setup_params()
119
171
        self.construct()
123
175
        """Setup the member variables for state."""
124
176
        self._basis_tree = self._wt.basis_tree()
125
177
        self._delta = None
126
 
        self._pending = pending_revisions(self._wt)
 
178
        self._wt.lock_read()
 
179
        try:
 
180
            self._pending = list(pending_revisions(self._wt))
 
181
        finally:
 
182
            self._wt.unlock()
127
183
 
128
184
        self._is_checkout = (self._wt.branch.get_bound_location() is not None)
129
185
 
181
237
 
182
238
        all_enabled = (self._selected is None)
183
239
        # The first entry is always the 'whole tree'
184
 
        all_iter = store.append([None, None, all_enabled, 'All Files', '', ''])
 
240
        all_iter = store.append(["", "", all_enabled, 'All Files', '', ''])
185
241
        initial_cursor = store.get_path(all_iter)
186
242
        # should we pass specific_files?
187
243
        self._wt.lock_read()
188
244
        self._basis_tree.lock_read()
189
245
        try:
190
246
            from diff import iter_changes_to_status
 
247
            saved_file_messages = self._saved_commit_messages_manager.get()[1]
191
248
            for (file_id, real_path, change_type, display_path
192
249
                ) in iter_changes_to_status(self._basis_tree, self._wt):
193
250
                if self._selected and real_path != self._selected:
194
251
                    enabled = False
195
252
                else:
196
253
                    enabled = True
 
254
                try:
 
255
                    default_message = saved_file_messages[file_id]
 
256
                except KeyError:
 
257
                    default_message = ''
197
258
                item_iter = store.append([
198
259
                    file_id,
199
260
                    real_path.encode('UTF-8'),
200
261
                    enabled,
201
262
                    display_path.encode('UTF-8'),
202
263
                    change_type,
203
 
                    '', # Initial comment
 
264
                    default_message, # Initial comment
204
265
                    ])
205
266
                if self._selected and enabled:
206
267
                    initial_cursor = store.get_path(item_iter)
213
274
        # This sets the cursor, which causes the expander to close, which
214
275
        # causes the _file_message_text_view to never get realized. So we have
215
276
        # to give it a little kick, or it warns when we try to grab the focus
216
 
        self._treeview_files.set_cursor(initial_cursor)
 
277
        self._treeview_files.set_cursor(initial_cursor, None, False)
217
278
 
218
279
        def _realize_file_message_tree_view(*args):
219
280
            self._file_message_text_view.realize()
232
293
                proxy_obj = bus.get_object('org.freedesktop.NetworkManager',
233
294
                                           '/org/freedesktop/NetworkManager')
234
295
            except dbus.DBusException:
235
 
                mutter("networkmanager not available.")
 
296
                trace.mutter("networkmanager not available.")
236
297
                self._check_local.show()
237
298
                return
238
 
            
 
299
 
239
300
            dbus_iface = dbus.Interface(proxy_obj,
240
301
                                        'org.freedesktop.NetworkManager')
241
302
            try:
244
305
            except dbus.DBusException, e:
245
306
                # Silently drop errors. While DBus may be
246
307
                # available, NetworkManager doesn't necessarily have to be
247
 
                mutter("unable to get networkmanager state: %r" % e)
 
308
                trace.mutter("unable to get networkmanager state: %r" % e)
248
309
        self._check_local.show()
249
310
 
250
311
    def _fill_in_per_file_info(self):
267
328
        """Build up the dialog widgets."""
268
329
        # The primary pane which splits it into left and right (adjustable)
269
330
        # sections.
270
 
        self._hpane = gtk.HPaned()
 
331
        self._hpane = Gtk.Paned.new(Gtk.Orientation.HORIZONTAL)
271
332
 
272
333
        self._construct_left_pane()
273
334
        self._construct_right_pane()
274
335
        self._construct_action_pane()
275
336
 
276
 
        self.vbox.pack_start(self._hpane)
 
337
        self.get_content_area().pack_start(self._hpane, True, True, 0)
277
338
        self._hpane.show()
278
339
        self.set_focus(self._global_message_text_view)
279
340
 
298
359
        self._hpane.set_position(300)
299
360
 
300
361
    def _construct_accelerators(self):
301
 
        group = gtk.AccelGroup()
302
 
        group.connect_group(gtk.gdk.keyval_from_name('N'),
303
 
                            gtk.gdk.CONTROL_MASK, 0, self._on_accel_next)
 
362
        group = Gtk.AccelGroup()
 
363
        group.connect(Gdk.keyval_from_name('N'),
 
364
                      Gdk.ModifierType.CONTROL_MASK, 0, self._on_accel_next)
304
365
        self.add_accel_group(group)
305
366
 
306
367
        # ignore the escape key (avoid closing the window)
307
368
        self.connect_object('close', self.emit_stop_by_name, 'close')
308
369
 
309
370
    def _construct_left_pane(self):
310
 
        self._left_pane_box = gtk.VBox(homogeneous=False, spacing=5)
 
371
        self._left_pane_box = Gtk.VBox(homogeneous=False, spacing=5)
311
372
        self._construct_file_list()
312
373
        self._construct_pending_list()
313
374
 
314
 
        self._check_local = gtk.CheckButton(_i18n("_Only commit locally"),
 
375
        self._check_local = Gtk.CheckButton(_i18n("_Only commit locally"),
315
376
                                            use_underline=True)
316
 
        self._left_pane_box.pack_end(self._check_local, False, False)
 
377
        self._left_pane_box.pack_end(self._check_local, False, False, 0)
317
378
        self._check_local.set_active(False)
318
379
 
319
380
        self._hpane.pack1(self._left_pane_box, resize=False, shrink=False)
326
387
        # commit, and 1 for file commit, and it looked good. But I don't seem
327
388
        # to have a way to do that with the gtk boxes... :( (Which is extra
328
389
        # weird since wx uses gtk on Linux...)
329
 
        self._right_pane_table = gtk.Table(rows=10, columns=1, homogeneous=False)
 
390
        self._right_pane_table = Gtk.Table(rows=10, columns=1, homogeneous=False)
330
391
        self._right_pane_table.set_row_spacings(5)
331
392
        self._right_pane_table.set_col_spacings(5)
332
393
        self._right_pane_table_row = 0
338
399
        self._hpane.pack2(self._right_pane_table, resize=True, shrink=True)
339
400
 
340
401
    def _construct_action_pane(self):
341
 
        self._button_commit = gtk.Button(_i18n("Comm_it"), use_underline=True)
 
402
        self._button_cancel = Gtk.Button(stock=Gtk.STOCK_CANCEL)
 
403
        self._button_cancel.connect('clicked', self._on_cancel_clicked)
 
404
        self._button_cancel.show()
 
405
        self.get_action_area().pack_end(
 
406
            self._button_cancel, True, True, 0)
 
407
        self._button_commit = Gtk.Button(_i18n("Comm_it"), use_underline=True)
342
408
        self._button_commit.connect('clicked', self._on_commit_clicked)
343
 
        self._button_commit.set_flags(gtk.CAN_DEFAULT)
 
409
        self._button_commit.set_can_default(True)
344
410
        self._button_commit.show()
345
 
        self.action_area.pack_end(self._button_commit)
 
411
        self.get_action_area().pack_end(
 
412
            self._button_commit, True, True, 0)
346
413
        self._button_commit.grab_default()
347
414
 
348
415
    def _add_to_right_table(self, widget, weight, expanding=False):
354
421
        """
355
422
        end_row = self._right_pane_table_row + weight
356
423
        options = 0
357
 
        expand_opts = gtk.EXPAND | gtk.FILL | gtk.SHRINK
 
424
        expand_opts = Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL | Gtk.AttachOptions.SHRINK
358
425
        if expanding:
359
426
            options = expand_opts
360
427
        self._right_pane_table.attach(widget, 0, 1,
363
430
        self._right_pane_table_row = end_row
364
431
 
365
432
    def _construct_file_list(self):
366
 
        self._files_box = gtk.VBox(homogeneous=False, spacing=0)
367
 
        file_label = gtk.Label(_i18n('Files'))
 
433
        self._files_box = Gtk.VBox(homogeneous=False, spacing=0)
 
434
        file_label = Gtk.Label(label=_i18n('Files'))
368
435
        # file_label.show()
369
 
        self._files_box.pack_start(file_label, expand=False)
 
436
        self._files_box.pack_start(file_label, False, True, 0)
370
437
 
371
 
        self._commit_all_files_radio = gtk.RadioButton(
 
438
        self._commit_all_files_radio = Gtk.RadioButton.new_with_label(
372
439
            None, _i18n("Commit all changes"))
373
 
        self._files_box.pack_start(self._commit_all_files_radio, expand=False)
 
440
        self._files_box.pack_start(self._commit_all_files_radio, False, True, 0)
374
441
        self._commit_all_files_radio.show()
375
442
        self._commit_all_files_radio.connect('toggled',
376
443
            self._toggle_commit_selection)
377
 
        self._commit_selected_radio = gtk.RadioButton(
 
444
        self._commit_selected_radio = Gtk.RadioButton.new_with_label_from_widget(
378
445
            self._commit_all_files_radio, _i18n("Only commit selected changes"))
379
 
        self._files_box.pack_start(self._commit_selected_radio, expand=False)
 
446
        self._files_box.pack_start(self._commit_selected_radio, False, True, 0)
380
447
        self._commit_selected_radio.show()
381
448
        self._commit_selected_radio.connect('toggled',
382
449
            self._toggle_commit_selection)
385
452
            self._commit_all_files_radio.set_sensitive(False)
386
453
            self._commit_selected_radio.set_sensitive(False)
387
454
 
388
 
        scroller = gtk.ScrolledWindow()
389
 
        scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
390
 
        self._treeview_files = gtk.TreeView()
 
455
        scroller = Gtk.ScrolledWindow()
 
456
        scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
 
457
        self._treeview_files = Gtk.TreeView()
391
458
        self._treeview_files.show()
392
459
        scroller.add(self._treeview_files)
393
 
        scroller.set_shadow_type(gtk.SHADOW_IN)
 
460
        scroller.set_shadow_type(Gtk.ShadowType.IN)
394
461
        scroller.show()
395
 
        self._files_box.pack_start(scroller,
396
 
                                   expand=True, fill=True)
 
462
        self._files_box.pack_start(scroller, True, True, 0)
397
463
        self._files_box.show()
398
 
        self._left_pane_box.pack_start(self._files_box)
 
464
        self._left_pane_box.pack_start(self._files_box, True, True, 0)
399
465
 
400
466
        # Keep note that all strings stored in a ListStore must be UTF-8
401
467
        # strings. GTK does not support directly setting and restoring Unicode
402
468
        # objects.
403
 
        liststore = gtk.ListStore(
404
 
            gobject.TYPE_STRING,  # [0] file_id
405
 
            gobject.TYPE_STRING,  # [1] real path
406
 
            gobject.TYPE_BOOLEAN, # [2] checkbox
407
 
            gobject.TYPE_STRING,  # [3] display path
408
 
            gobject.TYPE_STRING,  # [4] changes type
409
 
            gobject.TYPE_STRING,  # [5] commit message
 
469
        liststore = Gtk.ListStore(
 
470
            GObject.TYPE_STRING,  # [0] file_id
 
471
            GObject.TYPE_STRING,  # [1] real path
 
472
            GObject.TYPE_BOOLEAN, # [2] checkbox
 
473
            GObject.TYPE_STRING,  # [3] display path
 
474
            GObject.TYPE_STRING,  # [4] changes type
 
475
            GObject.TYPE_STRING,  # [5] commit message
410
476
            )
411
477
        self._files_store = liststore
412
478
        self._treeview_files.set_model(liststore)
413
 
        crt = gtk.CellRendererToggle()
 
479
        crt = Gtk.CellRendererToggle()
414
480
        crt.set_property('activatable', not bool(self._pending))
415
481
        crt.connect("toggled", self._toggle_commit, self._files_store)
416
482
        if self._pending:
417
483
            name = _i18n('Commit*')
418
484
        else:
419
485
            name = _i18n('Commit')
420
 
        commit_col = gtk.TreeViewColumn(name, crt, active=2)
 
486
        commit_col = Gtk.TreeViewColumn(name, crt, active=2)
421
487
        commit_col.set_visible(False)
422
488
        self._treeview_files.append_column(commit_col)
423
 
        self._treeview_files.append_column(gtk.TreeViewColumn(_i18n('Path'),
424
 
                                           gtk.CellRendererText(), text=3))
425
 
        self._treeview_files.append_column(gtk.TreeViewColumn(_i18n('Type'),
426
 
                                           gtk.CellRendererText(), text=4))
 
489
        self._treeview_files.append_column(Gtk.TreeViewColumn(_i18n('Path'),
 
490
                                           Gtk.CellRendererText(), text=3))
 
491
        self._treeview_files.append_column(Gtk.TreeViewColumn(_i18n('Type'),
 
492
                                           Gtk.CellRendererText(), text=4))
427
493
        self._treeview_files.connect('cursor-changed',
428
494
                                     self._on_treeview_files_cursor_changed)
429
495
 
430
496
    def _toggle_commit(self, cell, path, model):
431
 
        if model[path][0] is None: # No file_id means 'All Files'
 
497
        if model[path][0] == "": # No file_id means 'All Files'
432
498
            new_val = not model[path][2]
433
499
            for node in model:
434
500
                node[2] = new_val
444
510
                checked_col.set_visible(False)
445
511
            else:
446
512
                checked_col.set_visible(True)
447
 
            renderer = checked_col.get_cell_renderers()[0]
 
513
            renderer = checked_col.get_cells()[0]
448
514
            renderer.set_property('activatable', not all_files)
449
515
 
450
516
    def _construct_pending_list(self):
451
517
        # Pending information defaults to hidden, we put it all in 1 box, so
452
518
        # that we can show/hide all of them at once
453
 
        self._pending_box = gtk.VBox()
 
519
        self._pending_box = Gtk.VBox()
454
520
        self._pending_box.hide()
455
521
 
456
 
        pending_message = gtk.Label()
 
522
        pending_message = Gtk.Label()
457
523
        pending_message.set_markup(
458
524
            _i18n('<i>* Cannot select specific files when merging</i>'))
459
 
        self._pending_box.pack_start(pending_message, expand=False, padding=5)
 
525
        self._pending_box.pack_start(pending_message, False, True, 5)
460
526
        pending_message.show()
461
527
 
462
 
        pending_label = gtk.Label(_i18n('Pending Revisions'))
463
 
        self._pending_box.pack_start(pending_label, expand=False, padding=0)
 
528
        pending_label = Gtk.Label(label=_i18n('Pending Revisions'))
 
529
        self._pending_box.pack_start(pending_label, False, True, 0)
464
530
        pending_label.show()
465
531
 
466
 
        scroller = gtk.ScrolledWindow()
467
 
        scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
468
 
        self._treeview_pending = gtk.TreeView()
 
532
        scroller = Gtk.ScrolledWindow()
 
533
        scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
 
534
        self._treeview_pending = Gtk.TreeView()
469
535
        scroller.add(self._treeview_pending)
470
 
        scroller.set_shadow_type(gtk.SHADOW_IN)
 
536
        scroller.set_shadow_type(Gtk.ShadowType.IN)
471
537
        scroller.show()
472
 
        self._pending_box.pack_start(scroller,
473
 
                                     expand=True, fill=True, padding=5)
 
538
        self._pending_box.pack_start(scroller, True, True, 5)
474
539
        self._treeview_pending.show()
475
 
        self._left_pane_box.pack_start(self._pending_box)
 
540
        self._left_pane_box.pack_start(self._pending_box, True, True, 0)
476
541
 
477
 
        liststore = gtk.ListStore(gobject.TYPE_STRING, # revision_id
478
 
                                  gobject.TYPE_STRING, # date
479
 
                                  gobject.TYPE_STRING, # committer
480
 
                                  gobject.TYPE_STRING, # summary
 
542
        liststore = Gtk.ListStore(GObject.TYPE_STRING, # revision_id
 
543
                                  GObject.TYPE_STRING, # date
 
544
                                  GObject.TYPE_STRING, # committer
 
545
                                  GObject.TYPE_STRING, # summary
481
546
                                 )
482
547
        self._pending_store = liststore
483
548
        self._treeview_pending.set_model(liststore)
484
 
        self._treeview_pending.append_column(gtk.TreeViewColumn(_i18n('Date'),
485
 
                                             gtk.CellRendererText(), text=1))
486
 
        self._treeview_pending.append_column(gtk.TreeViewColumn(_i18n('Committer'),
487
 
                                             gtk.CellRendererText(), text=2))
488
 
        self._treeview_pending.append_column(gtk.TreeViewColumn(_i18n('Summary'),
489
 
                                             gtk.CellRendererText(), text=3))
 
549
        self._treeview_pending.append_column(Gtk.TreeViewColumn(_i18n('Date'),
 
550
                                             Gtk.CellRendererText(), text=1))
 
551
        self._treeview_pending.append_column(Gtk.TreeViewColumn(_i18n('Committer'),
 
552
                                             Gtk.CellRendererText(), text=2))
 
553
        self._treeview_pending.append_column(Gtk.TreeViewColumn(_i18n('Summary'),
 
554
                                             Gtk.CellRendererText(), text=3))
490
555
 
491
556
    def _construct_diff_view(self):
492
 
        from diff import DiffView
 
557
        from bzrlib.plugins.gtk.diff import DiffView
493
558
 
494
559
        # TODO: jam 2007-10-30 The diff label is currently disabled. If we
495
560
        #       decide that we really don't ever want to display it, we should
496
561
        #       actually remove it, and other references to it, along with the
497
562
        #       tests that it is set properly.
498
 
        self._diff_label = gtk.Label(_i18n('Diff for whole tree'))
 
563
        self._diff_label = Gtk.Label(label=_i18n('Diff for whole tree'))
499
564
        self._diff_label.set_alignment(0, 0)
500
565
        self._right_pane_table.set_row_spacing(self._right_pane_table_row, 0)
501
566
        self._add_to_right_table(self._diff_label, 1, False)
506
571
        self._diff_view.show()
507
572
 
508
573
    def _construct_file_message(self):
509
 
        scroller = gtk.ScrolledWindow()
510
 
        scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 
574
        scroller = Gtk.ScrolledWindow()
 
575
        scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
511
576
 
512
 
        self._file_message_text_view = gtk.TextView()
 
577
        self._file_message_text_view = Gtk.TextView()
513
578
        scroller.add(self._file_message_text_view)
514
 
        scroller.set_shadow_type(gtk.SHADOW_IN)
 
579
        scroller.set_shadow_type(Gtk.ShadowType.IN)
515
580
        scroller.show()
516
581
 
517
 
        self._file_message_text_view.modify_font(pango.FontDescription("Monospace"))
518
 
        self._file_message_text_view.set_wrap_mode(gtk.WRAP_WORD)
 
582
        self._file_message_text_view.modify_font(Pango.FontDescription("Monospace"))
 
583
        self._file_message_text_view.set_wrap_mode(Gtk.WrapMode.WORD)
519
584
        self._file_message_text_view.set_accepts_tab(False)
520
585
        self._file_message_text_view.show()
521
586
 
522
 
        self._file_message_expander = gtk.Expander(_i18n('File commit message'))
 
587
        self._file_message_expander = Gtk.Expander(
 
588
            label=_i18n('File commit message'))
523
589
        self._file_message_expander.set_expanded(True)
524
590
        self._file_message_expander.add(scroller)
525
591
        self._add_to_right_table(self._file_message_expander, 1, False)
526
592
        self._file_message_expander.show()
527
593
 
528
594
    def _construct_global_message(self):
529
 
        self._global_message_label = gtk.Label(_i18n('Global Commit Message'))
 
595
        self._global_message_label = Gtk.Label(label=_i18n('Global Commit Message'))
530
596
        self._global_message_label.set_markup(
531
597
            _i18n('<b>Global Commit Message</b>'))
532
598
        self._global_message_label.set_alignment(0, 0)
535
601
        # Can we remove the spacing between the label and the box?
536
602
        self._global_message_label.show()
537
603
 
538
 
        scroller = gtk.ScrolledWindow()
539
 
        scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 
604
        scroller = Gtk.ScrolledWindow()
 
605
        scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
540
606
 
541
 
        self._global_message_text_view = gtk.TextView()
542
 
        self._global_message_text_view.modify_font(pango.FontDescription("Monospace"))
 
607
        self._global_message_text_view = Gtk.TextView()
 
608
        self._set_global_commit_message(self._saved_commit_messages_manager.get()[0])
 
609
        self._global_message_text_view.modify_font(Pango.FontDescription("Monospace"))
543
610
        scroller.add(self._global_message_text_view)
544
 
        scroller.set_shadow_type(gtk.SHADOW_IN)
 
611
        scroller.set_shadow_type(Gtk.ShadowType.IN)
545
612
        scroller.show()
546
613
        self._add_to_right_table(scroller, 2, True)
547
 
        self._file_message_text_view.set_wrap_mode(gtk.WRAP_WORD)
 
614
        self._file_message_text_view.set_wrap_mode(Gtk.WrapMode.WORD)
548
615
        self._file_message_text_view.set_accepts_tab(False)
549
616
        self._global_message_text_view.show()
550
617
 
551
618
    def _on_treeview_files_cursor_changed(self, treeview):
552
619
        treeselection = treeview.get_selection()
 
620
        if treeselection is None:
 
621
            # The treeview was probably destroyed as the dialog closes.
 
622
            return
553
623
        (model, selection) = treeselection.get_selected()
554
624
 
555
625
        if selection is not None:
556
626
            path, display_path = model.get(selection, 1, 3)
557
627
            self._diff_label.set_text(_i18n('Diff for ') + display_path)
558
 
            if path is None:
 
628
            if path == "":
559
629
                self._diff_view.show_diff(None)
560
630
            else:
561
 
                self._diff_view.show_diff([path.decode('UTF-8')])
 
631
                self._diff_view.show_diff([osutils.safe_unicode(path)])
562
632
            self._update_per_file_info(selection)
563
633
 
564
634
    def _on_accel_next(self, accel_group, window, keyval, modifier):
575
645
            # We have either made it to the end of the list, or nothing was
576
646
            # selected. Either way, select All Files, and jump to the global
577
647
            # commit message.
578
 
            self._treeview_files.set_cursor((0,))
 
648
            self._treeview_files.set_cursor(
 
649
                Gtk.TreePath(path=0), "", False)
579
650
            self._global_message_text_view.grab_focus()
580
651
        else:
581
652
            # Set the cursor to this entry, and jump to the per-file commit
582
653
            # message
583
 
            self._treeview_files.set_cursor(model.get_path(next))
 
654
            self._treeview_files.set_cursor(model.get_path(next), None, False)
584
655
            self._file_message_text_view.grab_focus()
585
656
 
586
657
    def _save_current_file_message(self):
588
659
            return # Nothing to save
589
660
        text_buffer = self._file_message_text_view.get_buffer()
590
661
        cur_text = text_buffer.get_text(text_buffer.get_start_iter(),
591
 
                                        text_buffer.get_end_iter())
 
662
                                        text_buffer.get_end_iter(), True)
592
663
        last_selected = self._files_store.get_iter(self._last_selected_file)
593
664
        self._files_store.set_value(last_selected, 5, cur_text)
594
665
 
600
671
        self._save_current_file_message()
601
672
        text_buffer = self._file_message_text_view.get_buffer()
602
673
        file_id, display_path, message = self._files_store.get(selection, 0, 3, 5)
603
 
        if file_id is None: # Whole tree
 
674
        if file_id == "": # Whole tree
604
675
            self._file_message_expander.set_label(_i18n('File commit message'))
605
676
            self._file_message_expander.set_expanded(False)
606
677
            self._file_message_expander.set_sensitive(False)
623
694
        files = []
624
695
        records = iter(self._files_store)
625
696
        rec = records.next() # Skip the All Files record
626
 
        assert rec[0] is None, "Are we skipping the wrong record?"
 
697
        assert rec[0] == "", "Are we skipping the wrong record?"
627
698
 
628
699
        file_info = []
629
700
        for record in records:
630
701
            if self._commit_all_changes or record[2]:# [2] checkbox
631
 
                file_id = record[0] # [0] file_id
632
 
                path = record[1]    # [1] real path
633
 
                file_message = record[5] # [5] commit message
 
702
                file_id = osutils.safe_utf8(record[0]) # [0] file_id
 
703
                path = osutils.safe_utf8(record[1])    # [1] real path
 
704
                # [5] commit message
 
705
                file_message = _sanitize_and_decode_message(record[5])
634
706
                files.append(path.decode('UTF-8'))
635
707
                if self._enable_per_file_commits and file_message:
636
708
                    # All of this needs to be utf-8 information
 
709
                    file_message = file_message.encode('UTF-8')
637
710
                    file_info.append({'path':path, 'file_id':file_id,
638
711
                                     'message':file_message})
639
712
        file_info.sort(key=lambda x:(x['path'], x['file_id']))
643
716
            return files, []
644
717
 
645
718
    @show_bzr_error
 
719
    def _on_cancel_clicked(self, button):
 
720
        """ Cancel button clicked handler. """
 
721
        self._do_cancel()
 
722
 
 
723
    @show_bzr_error
 
724
    def _on_delete_window(self, source, event):
 
725
        """ Delete window handler. """
 
726
        self._do_cancel()
 
727
 
 
728
    def _do_cancel(self):
 
729
        """If requested, saves commit messages when cancelling gcommit; they are re-used by a next gcommit"""
 
730
        mgr = SavedCommitMessagesManager()
 
731
        self._saved_commit_messages_manager = mgr
 
732
        mgr.insert(self._get_global_commit_message(),
 
733
                   self._get_specific_files()[1])
 
734
        if mgr.is_not_empty(): # maybe worth saving
 
735
            response = self._question_dialog(
 
736
                _i18n('Commit cancelled'),
 
737
                _i18n('Do you want to save your commit messages ?'),
 
738
                parent=self)
 
739
            if response == Gtk.ResponseType.NO:
 
740
                 # save nothing and destroy old comments if any
 
741
                mgr = SavedCommitMessagesManager()
 
742
        mgr.save(self._wt, self._wt.branch)
 
743
        self.response(Gtk.ResponseType.CANCEL) # close window
 
744
 
 
745
    @show_bzr_error
646
746
    def _on_commit_clicked(self, button):
647
747
        """ Commit button clicked handler. """
648
748
        self._do_commit()
653
753
        if message == '':
654
754
            response = self._question_dialog(
655
755
                _i18n('Commit with an empty message?'),
656
 
                _i18n('You can describe your commit intent in the message.'))
657
 
            if response == gtk.RESPONSE_NO:
 
756
                _i18n('You can describe your commit intent in the message.'),
 
757
                parent=self)
 
758
            if response == Gtk.ResponseType.NO:
658
759
                # Kindly give focus to message area
659
760
                self._global_message_text_view.grab_focus()
660
761
                return
673
774
        for path in self._wt.unknowns():
674
775
            response = self._question_dialog(
675
776
                _i18n("Commit with unknowns?"),
676
 
                _i18n("Unknown files exist in the working tree. Commit anyway?"))
677
 
            if response == gtk.RESPONSE_NO:
 
777
                _i18n("Unknown files exist in the working tree. Commit anyway?"),
 
778
                parent=self)
 
779
                # Doesn't set a parent for the dialog..
 
780
            if response == Gtk.ResponseType.NO:
678
781
                return
679
782
            break
680
783
 
693
796
            response = self._question_dialog(
694
797
                _i18n('Commit with no changes?'),
695
798
                _i18n('There are no changes in the working tree.'
696
 
                      ' Do you want to commit anyway?'))
697
 
            if response == gtk.RESPONSE_YES:
 
799
                      ' Do you want to commit anyway?'),
 
800
                parent=self)
 
801
            if response == Gtk.ResponseType.YES:
698
802
                rev_id = self._wt.commit(message,
699
803
                               allow_pointless=True,
700
804
                               strict=False,
702
806
                               specific_files=specific_files,
703
807
                               revprops=revprops)
704
808
        self.committed_revision_id = rev_id
705
 
        self.response(gtk.RESPONSE_OK)
 
809
        # destroy old comments if any
 
810
        SavedCommitMessagesManager().save(self._wt, self._wt.branch)
 
811
        self.response(Gtk.ResponseType.OK)
706
812
 
707
813
    def _get_global_commit_message(self):
708
814
        buf = self._global_message_text_view.get_buffer()
709
815
        start, end = buf.get_bounds()
710
 
        return buf.get_text(start, end).decode('utf-8')
 
816
        text = buf.get_text(start, end, True)
 
817
        return _sanitize_and_decode_message(text)
711
818
 
712
819
    def _set_global_commit_message(self, message):
713
820
        """Just a helper for the test suite."""
734
841
                                       show_offset=False)
735
842
        rev_dict['revision_id'] = rev.revision_id
736
843
        return rev_dict
 
844