/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: Curtis Hovey
  • Date: 2011-07-31 16:50:29 UTC
  • mto: This revision was merged to the branch mainline in revision 741.
  • Revision ID: sinzui.is@verizon.net-20110731165029-9gixuqypi3lwapzm
Removed import_pygtk because gi does not impicitly call Main(). Inlined checks for gtk availablility.

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
33
 
 
34
 
from bzrlib.plugins.gtk import _i18n
35
 
from dialog import error_dialog, question_dialog
36
 
from errors import show_bzr_error
 
19
from gi.repository import Gtk
 
20
from gi.repository import GObject
 
21
from gi.repository import Pango
 
22
 
 
23
from bzrlib import (
 
24
    errors,
 
25
    osutils,
 
26
    trace,
 
27
    )
 
28
try:
 
29
    from bzrlib import bencode
 
30
except ImportError:
 
31
    from bzrlib.util import bencode
 
32
 
 
33
from bzrlib.plugins.gtk.dialog import question_dialog
 
34
from bzrlib.plugins.gtk.errors import show_bzr_error
 
35
from bzrlib.plugins.gtk.i18n import _i18n
37
36
 
38
37
try:
39
38
    import dbus
97
96
    return pm
98
97
 
99
98
 
100
 
class CommitDialog(gtk.Dialog):
 
99
_newline_variants_re = re.compile(r'\r\n?')
 
100
def _sanitize_and_decode_message(utf8_message):
 
101
    """Turn a utf-8 message into a sanitized Unicode message."""
 
102
    fixed_newline = _newline_variants_re.sub('\n', utf8_message)
 
103
    return fixed_newline.decode('utf-8')
 
104
 
 
105
 
 
106
class CommitDialog(Gtk.Dialog):
101
107
    """Implementation of Commit."""
102
108
 
103
109
    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))
 
110
        GObject.GObject.__init__(self, title="Commit to %s" % wt.basedir,
 
111
                            parent=parent, flags=0,)
 
112
        self.connect('delete-event', self._on_delete_window)
108
113
        self._question_dialog = question_dialog
109
114
 
 
115
        self.set_type_hint(Gdk.WindowTypeHint.NORMAL)
 
116
 
110
117
        self._wt = wt
111
118
        # TODO: Do something with this value, it is used by Olive
112
119
        #       It used to set all changes but this one to False
114
121
        self._enable_per_file_commits = True
115
122
        self._commit_all_changes = True
116
123
        self.committed_revision_id = None # Nothing has been committed yet
 
124
        self._saved_commit_messages_manager = SavedCommitMessagesManager(self._wt, self._wt.branch)
117
125
 
118
126
        self.setup_params()
119
127
        self.construct()
188
196
        self._basis_tree.lock_read()
189
197
        try:
190
198
            from diff import iter_changes_to_status
 
199
            saved_file_messages = self._saved_commit_messages_manager.get()[1]
191
200
            for (file_id, real_path, change_type, display_path
192
201
                ) in iter_changes_to_status(self._basis_tree, self._wt):
193
202
                if self._selected and real_path != self._selected:
194
203
                    enabled = False
195
204
                else:
196
205
                    enabled = True
 
206
                try:
 
207
                    default_message = saved_file_messages[file_id]
 
208
                except KeyError:
 
209
                    default_message = ''
197
210
                item_iter = store.append([
198
211
                    file_id,
199
212
                    real_path.encode('UTF-8'),
200
213
                    enabled,
201
214
                    display_path.encode('UTF-8'),
202
215
                    change_type,
203
 
                    '', # Initial comment
 
216
                    default_message, # Initial comment
204
217
                    ])
205
218
                if self._selected and enabled:
206
219
                    initial_cursor = store.get_path(item_iter)
232
245
                proxy_obj = bus.get_object('org.freedesktop.NetworkManager',
233
246
                                           '/org/freedesktop/NetworkManager')
234
247
            except dbus.DBusException:
235
 
                mutter("networkmanager not available.")
 
248
                trace.mutter("networkmanager not available.")
236
249
                self._check_local.show()
237
250
                return
238
251
            
244
257
            except dbus.DBusException, e:
245
258
                # Silently drop errors. While DBus may be
246
259
                # available, NetworkManager doesn't necessarily have to be
247
 
                mutter("unable to get networkmanager state: %r" % e)
 
260
                trace.mutter("unable to get networkmanager state: %r" % e)
248
261
        self._check_local.show()
249
262
 
250
263
    def _fill_in_per_file_info(self):
267
280
        """Build up the dialog widgets."""
268
281
        # The primary pane which splits it into left and right (adjustable)
269
282
        # sections.
270
 
        self._hpane = gtk.HPaned()
 
283
        self._hpane = Gtk.HPaned()
271
284
 
272
285
        self._construct_left_pane()
273
286
        self._construct_right_pane()
274
287
        self._construct_action_pane()
275
288
 
276
 
        self.vbox.pack_start(self._hpane)
 
289
        self.vbox.pack_start(self._hpane, True, True, 0)
277
290
        self._hpane.show()
278
291
        self.set_focus(self._global_message_text_view)
279
292
 
298
311
        self._hpane.set_position(300)
299
312
 
300
313
    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)
 
314
        group = Gtk.AccelGroup()
 
315
        group.connect_group(Gdk.keyval_from_name('N'),
 
316
                            Gdk.EventMask.CONTROL_MASK, 0, self._on_accel_next)
304
317
        self.add_accel_group(group)
305
318
 
306
319
        # ignore the escape key (avoid closing the window)
307
320
        self.connect_object('close', self.emit_stop_by_name, 'close')
308
321
 
309
322
    def _construct_left_pane(self):
310
 
        self._left_pane_box = gtk.VBox(homogeneous=False, spacing=5)
 
323
        self._left_pane_box = Gtk.VBox(homogeneous=False, spacing=5)
311
324
        self._construct_file_list()
312
325
        self._construct_pending_list()
313
326
 
314
 
        self._check_local = gtk.CheckButton(_i18n("_Only commit locally"),
 
327
        self._check_local = Gtk.CheckButton(_i18n("_Only commit locally"),
315
328
                                            use_underline=True)
316
329
        self._left_pane_box.pack_end(self._check_local, False, False)
317
330
        self._check_local.set_active(False)
326
339
        # commit, and 1 for file commit, and it looked good. But I don't seem
327
340
        # to have a way to do that with the gtk boxes... :( (Which is extra
328
341
        # weird since wx uses gtk on Linux...)
329
 
        self._right_pane_table = gtk.Table(rows=10, columns=1, homogeneous=False)
 
342
        self._right_pane_table = Gtk.Table(rows=10, columns=1, homogeneous=False)
330
343
        self._right_pane_table.set_row_spacings(5)
331
344
        self._right_pane_table.set_col_spacings(5)
332
345
        self._right_pane_table_row = 0
338
351
        self._hpane.pack2(self._right_pane_table, resize=True, shrink=True)
339
352
 
340
353
    def _construct_action_pane(self):
341
 
        self._button_commit = gtk.Button(_i18n("Comm_it"), use_underline=True)
 
354
        self._button_cancel = Gtk.Button(stock=Gtk.STOCK_CANCEL)
 
355
        self._button_cancel.connect('clicked', self._on_cancel_clicked)
 
356
        self._button_cancel.show()
 
357
        self.action_area.pack_end(self._button_cancel)
 
358
        self._button_commit = Gtk.Button(_i18n("Comm_it"), use_underline=True)
342
359
        self._button_commit.connect('clicked', self._on_commit_clicked)
343
 
        self._button_commit.set_flags(gtk.CAN_DEFAULT)
 
360
        self._button_commit.set_can_default(True)
344
361
        self._button_commit.show()
345
362
        self.action_area.pack_end(self._button_commit)
346
363
        self._button_commit.grab_default()
354
371
        """
355
372
        end_row = self._right_pane_table_row + weight
356
373
        options = 0
357
 
        expand_opts = gtk.EXPAND | gtk.FILL | gtk.SHRINK
 
374
        expand_opts = Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL | Gtk.AttachOptions.SHRINK
358
375
        if expanding:
359
376
            options = expand_opts
360
377
        self._right_pane_table.attach(widget, 0, 1,
363
380
        self._right_pane_table_row = end_row
364
381
 
365
382
    def _construct_file_list(self):
366
 
        self._files_box = gtk.VBox(homogeneous=False, spacing=0)
367
 
        file_label = gtk.Label(_i18n('Files'))
 
383
        self._files_box = Gtk.VBox(homogeneous=False, spacing=0)
 
384
        file_label = Gtk.Label(label=_i18n('Files'))
368
385
        # file_label.show()
369
 
        self._files_box.pack_start(file_label, expand=False)
 
386
        self._files_box.pack_start(file_label, False, True, 0)
370
387
 
371
 
        self._commit_all_files_radio = gtk.RadioButton(
 
388
        self._commit_all_files_radio = Gtk.RadioButton(
372
389
            None, _i18n("Commit all changes"))
373
 
        self._files_box.pack_start(self._commit_all_files_radio, expand=False)
 
390
        self._files_box.pack_start(self._commit_all_files_radio, False, True, 0)
374
391
        self._commit_all_files_radio.show()
375
392
        self._commit_all_files_radio.connect('toggled',
376
393
            self._toggle_commit_selection)
377
 
        self._commit_selected_radio = gtk.RadioButton(
 
394
        self._commit_selected_radio = Gtk.RadioButton(
378
395
            self._commit_all_files_radio, _i18n("Only commit selected changes"))
379
 
        self._files_box.pack_start(self._commit_selected_radio, expand=False)
 
396
        self._files_box.pack_start(self._commit_selected_radio, False, True, 0)
380
397
        self._commit_selected_radio.show()
381
398
        self._commit_selected_radio.connect('toggled',
382
399
            self._toggle_commit_selection)
385
402
            self._commit_all_files_radio.set_sensitive(False)
386
403
            self._commit_selected_radio.set_sensitive(False)
387
404
 
388
 
        scroller = gtk.ScrolledWindow()
389
 
        scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
390
 
        self._treeview_files = gtk.TreeView()
 
405
        scroller = Gtk.ScrolledWindow()
 
406
        scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
 
407
        self._treeview_files = Gtk.TreeView()
391
408
        self._treeview_files.show()
392
409
        scroller.add(self._treeview_files)
393
 
        scroller.set_shadow_type(gtk.SHADOW_IN)
 
410
        scroller.set_shadow_type(Gtk.ShadowType.IN)
394
411
        scroller.show()
395
412
        self._files_box.pack_start(scroller,
396
413
                                   expand=True, fill=True)
397
414
        self._files_box.show()
398
 
        self._left_pane_box.pack_start(self._files_box)
 
415
        self._left_pane_box.pack_start(self._files_box, True, True, 0)
399
416
 
400
417
        # Keep note that all strings stored in a ListStore must be UTF-8
401
418
        # strings. GTK does not support directly setting and restoring Unicode
402
419
        # 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
 
420
        liststore = Gtk.ListStore(
 
421
            GObject.TYPE_STRING,  # [0] file_id
 
422
            GObject.TYPE_STRING,  # [1] real path
 
423
            GObject.TYPE_BOOLEAN, # [2] checkbox
 
424
            GObject.TYPE_STRING,  # [3] display path
 
425
            GObject.TYPE_STRING,  # [4] changes type
 
426
            GObject.TYPE_STRING,  # [5] commit message
410
427
            )
411
428
        self._files_store = liststore
412
429
        self._treeview_files.set_model(liststore)
413
 
        crt = gtk.CellRendererToggle()
 
430
        crt = Gtk.CellRendererToggle()
414
431
        crt.set_property('activatable', not bool(self._pending))
415
432
        crt.connect("toggled", self._toggle_commit, self._files_store)
416
433
        if self._pending:
417
434
            name = _i18n('Commit*')
418
435
        else:
419
436
            name = _i18n('Commit')
420
 
        commit_col = gtk.TreeViewColumn(name, crt, active=2)
 
437
        commit_col = Gtk.TreeViewColumn(name, crt, active=2)
421
438
        commit_col.set_visible(False)
422
439
        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))
 
440
        self._treeview_files.append_column(Gtk.TreeViewColumn(_i18n('Path'),
 
441
                                           Gtk.CellRendererText(), text=3))
 
442
        self._treeview_files.append_column(Gtk.TreeViewColumn(_i18n('Type'),
 
443
                                           Gtk.CellRendererText(), text=4))
427
444
        self._treeview_files.connect('cursor-changed',
428
445
                                     self._on_treeview_files_cursor_changed)
429
446
 
450
467
    def _construct_pending_list(self):
451
468
        # Pending information defaults to hidden, we put it all in 1 box, so
452
469
        # that we can show/hide all of them at once
453
 
        self._pending_box = gtk.VBox()
 
470
        self._pending_box = Gtk.VBox()
454
471
        self._pending_box.hide()
455
472
 
456
 
        pending_message = gtk.Label()
 
473
        pending_message = Gtk.Label()
457
474
        pending_message.set_markup(
458
475
            _i18n('<i>* Cannot select specific files when merging</i>'))
459
476
        self._pending_box.pack_start(pending_message, expand=False, padding=5)
460
477
        pending_message.show()
461
478
 
462
 
        pending_label = gtk.Label(_i18n('Pending Revisions'))
 
479
        pending_label = Gtk.Label(label=_i18n('Pending Revisions'))
463
480
        self._pending_box.pack_start(pending_label, expand=False, padding=0)
464
481
        pending_label.show()
465
482
 
466
 
        scroller = gtk.ScrolledWindow()
467
 
        scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
468
 
        self._treeview_pending = gtk.TreeView()
 
483
        scroller = Gtk.ScrolledWindow()
 
484
        scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
 
485
        self._treeview_pending = Gtk.TreeView()
469
486
        scroller.add(self._treeview_pending)
470
 
        scroller.set_shadow_type(gtk.SHADOW_IN)
 
487
        scroller.set_shadow_type(Gtk.ShadowType.IN)
471
488
        scroller.show()
472
489
        self._pending_box.pack_start(scroller,
473
490
                                     expand=True, fill=True, padding=5)
474
491
        self._treeview_pending.show()
475
 
        self._left_pane_box.pack_start(self._pending_box)
 
492
        self._left_pane_box.pack_start(self._pending_box, True, True, 0)
476
493
 
477
 
        liststore = gtk.ListStore(gobject.TYPE_STRING, # revision_id
478
 
                                  gobject.TYPE_STRING, # date
479
 
                                  gobject.TYPE_STRING, # committer
480
 
                                  gobject.TYPE_STRING, # summary
 
494
        liststore = Gtk.ListStore(GObject.TYPE_STRING, # revision_id
 
495
                                  GObject.TYPE_STRING, # date
 
496
                                  GObject.TYPE_STRING, # committer
 
497
                                  GObject.TYPE_STRING, # summary
481
498
                                 )
482
499
        self._pending_store = liststore
483
500
        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))
 
501
        self._treeview_pending.append_column(Gtk.TreeViewColumn(_i18n('Date'),
 
502
                                             Gtk.CellRendererText(), text=1))
 
503
        self._treeview_pending.append_column(Gtk.TreeViewColumn(_i18n('Committer'),
 
504
                                             Gtk.CellRendererText(), text=2))
 
505
        self._treeview_pending.append_column(Gtk.TreeViewColumn(_i18n('Summary'),
 
506
                                             Gtk.CellRendererText(), text=3))
490
507
 
491
508
    def _construct_diff_view(self):
492
 
        from diff import DiffView
 
509
        from bzrlib.plugins.gtk.diff import DiffView
493
510
 
494
511
        # TODO: jam 2007-10-30 The diff label is currently disabled. If we
495
512
        #       decide that we really don't ever want to display it, we should
496
513
        #       actually remove it, and other references to it, along with the
497
514
        #       tests that it is set properly.
498
 
        self._diff_label = gtk.Label(_i18n('Diff for whole tree'))
 
515
        self._diff_label = Gtk.Label(label=_i18n('Diff for whole tree'))
499
516
        self._diff_label.set_alignment(0, 0)
500
517
        self._right_pane_table.set_row_spacing(self._right_pane_table_row, 0)
501
518
        self._add_to_right_table(self._diff_label, 1, False)
506
523
        self._diff_view.show()
507
524
 
508
525
    def _construct_file_message(self):
509
 
        scroller = gtk.ScrolledWindow()
510
 
        scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 
526
        scroller = Gtk.ScrolledWindow()
 
527
        scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
511
528
 
512
 
        self._file_message_text_view = gtk.TextView()
 
529
        self._file_message_text_view = Gtk.TextView()
513
530
        scroller.add(self._file_message_text_view)
514
 
        scroller.set_shadow_type(gtk.SHADOW_IN)
 
531
        scroller.set_shadow_type(Gtk.ShadowType.IN)
515
532
        scroller.show()
516
533
 
517
 
        self._file_message_text_view.modify_font(pango.FontDescription("Monospace"))
518
 
        self._file_message_text_view.set_wrap_mode(gtk.WRAP_WORD)
 
534
        self._file_message_text_view.modify_font(Pango.FontDescription("Monospace"))
 
535
        self._file_message_text_view.set_wrap_mode(Gtk.WrapMode.WORD)
519
536
        self._file_message_text_view.set_accepts_tab(False)
520
537
        self._file_message_text_view.show()
521
538
 
522
 
        self._file_message_expander = gtk.Expander(_i18n('File commit message'))
 
539
        self._file_message_expander = Gtk.Expander(_i18n('File commit message'))
523
540
        self._file_message_expander.set_expanded(True)
524
541
        self._file_message_expander.add(scroller)
525
542
        self._add_to_right_table(self._file_message_expander, 1, False)
526
543
        self._file_message_expander.show()
527
544
 
528
545
    def _construct_global_message(self):
529
 
        self._global_message_label = gtk.Label(_i18n('Global Commit Message'))
 
546
        self._global_message_label = Gtk.Label(label=_i18n('Global Commit Message'))
530
547
        self._global_message_label.set_markup(
531
548
            _i18n('<b>Global Commit Message</b>'))
532
549
        self._global_message_label.set_alignment(0, 0)
535
552
        # Can we remove the spacing between the label and the box?
536
553
        self._global_message_label.show()
537
554
 
538
 
        scroller = gtk.ScrolledWindow()
539
 
        scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 
555
        scroller = Gtk.ScrolledWindow()
 
556
        scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
540
557
 
541
 
        self._global_message_text_view = gtk.TextView()
542
 
        self._global_message_text_view.modify_font(pango.FontDescription("Monospace"))
 
558
        self._global_message_text_view = Gtk.TextView()
 
559
        self._set_global_commit_message(self._saved_commit_messages_manager.get()[0])
 
560
        self._global_message_text_view.modify_font(Pango.FontDescription("Monospace"))
543
561
        scroller.add(self._global_message_text_view)
544
 
        scroller.set_shadow_type(gtk.SHADOW_IN)
 
562
        scroller.set_shadow_type(Gtk.ShadowType.IN)
545
563
        scroller.show()
546
564
        self._add_to_right_table(scroller, 2, True)
547
 
        self._file_message_text_view.set_wrap_mode(gtk.WRAP_WORD)
 
565
        self._file_message_text_view.set_wrap_mode(Gtk.WrapMode.WORD)
548
566
        self._file_message_text_view.set_accepts_tab(False)
549
567
        self._global_message_text_view.show()
550
568
 
630
648
            if self._commit_all_changes or record[2]:# [2] checkbox
631
649
                file_id = record[0] # [0] file_id
632
650
                path = record[1]    # [1] real path
633
 
                file_message = record[5] # [5] commit message
 
651
                # [5] commit message
 
652
                file_message = _sanitize_and_decode_message(record[5])
634
653
                files.append(path.decode('UTF-8'))
635
654
                if self._enable_per_file_commits and file_message:
636
655
                    # All of this needs to be utf-8 information
 
656
                    file_message = file_message.encode('UTF-8')
637
657
                    file_info.append({'path':path, 'file_id':file_id,
638
658
                                     'message':file_message})
639
659
        file_info.sort(key=lambda x:(x['path'], x['file_id']))
643
663
            return files, []
644
664
 
645
665
    @show_bzr_error
 
666
    def _on_cancel_clicked(self, button):
 
667
        """ Cancel button clicked handler. """
 
668
        self._do_cancel()
 
669
 
 
670
    @show_bzr_error
 
671
    def _on_delete_window(self, source, event):
 
672
        """ Delete window handler. """
 
673
        self._do_cancel()
 
674
 
 
675
    def _do_cancel(self):
 
676
        """If requested, saves commit messages when cancelling gcommit; they are re-used by a next gcommit"""
 
677
        mgr = SavedCommitMessagesManager()
 
678
        self._saved_commit_messages_manager = mgr
 
679
        mgr.insert(self._get_global_commit_message(),
 
680
                   self._get_specific_files()[1])
 
681
        if mgr.is_not_empty(): # maybe worth saving
 
682
            response = self._question_dialog(
 
683
                _i18n('Commit cancelled'),
 
684
                _i18n('Do you want to save your commit messages ?'),
 
685
                parent=self)
 
686
            if response == Gtk.ResponseType.NO:
 
687
                 # save nothing and destroy old comments if any
 
688
                mgr = SavedCommitMessagesManager()
 
689
        mgr.save(self._wt, self._wt.branch)
 
690
        self.response(Gtk.ResponseType.CANCEL) # close window
 
691
 
 
692
    @show_bzr_error
646
693
    def _on_commit_clicked(self, button):
647
694
        """ Commit button clicked handler. """
648
695
        self._do_commit()
653
700
        if message == '':
654
701
            response = self._question_dialog(
655
702
                _i18n('Commit with an empty message?'),
656
 
                _i18n('You can describe your commit intent in the message.'))
657
 
            if response == gtk.RESPONSE_NO:
 
703
                _i18n('You can describe your commit intent in the message.'),
 
704
                parent=self)
 
705
            if response == Gtk.ResponseType.NO:
658
706
                # Kindly give focus to message area
659
707
                self._global_message_text_view.grab_focus()
660
708
                return
673
721
        for path in self._wt.unknowns():
674
722
            response = self._question_dialog(
675
723
                _i18n("Commit with unknowns?"),
676
 
                _i18n("Unknown files exist in the working tree. Commit anyway?"))
677
 
            if response == gtk.RESPONSE_NO:
 
724
                _i18n("Unknown files exist in the working tree. Commit anyway?"),
 
725
                parent=self)
 
726
                # Doesn't set a parent for the dialog..
 
727
            if response == Gtk.ResponseType.NO:
678
728
                return
679
729
            break
680
730
 
693
743
            response = self._question_dialog(
694
744
                _i18n('Commit with no changes?'),
695
745
                _i18n('There are no changes in the working tree.'
696
 
                      ' Do you want to commit anyway?'))
697
 
            if response == gtk.RESPONSE_YES:
 
746
                      ' Do you want to commit anyway?'),
 
747
                parent=self)
 
748
            if response == Gtk.ResponseType.YES:
698
749
                rev_id = self._wt.commit(message,
699
750
                               allow_pointless=True,
700
751
                               strict=False,
702
753
                               specific_files=specific_files,
703
754
                               revprops=revprops)
704
755
        self.committed_revision_id = rev_id
705
 
        self.response(gtk.RESPONSE_OK)
 
756
        # destroy old comments if any
 
757
        SavedCommitMessagesManager().save(self._wt, self._wt.branch)
 
758
        self.response(Gtk.ResponseType.OK)
706
759
 
707
760
    def _get_global_commit_message(self):
708
761
        buf = self._global_message_text_view.get_buffer()
709
762
        start, end = buf.get_bounds()
710
 
        return buf.get_text(start, end).decode('utf-8')
 
763
        text = buf.get_text(start, end)
 
764
        return _sanitize_and_decode_message(text)
711
765
 
712
766
    def _set_global_commit_message(self, message):
713
767
        """Just a helper for the test suite."""
734
788
                                       show_offset=False)
735
789
        rev_dict['revision_id'] = rev.revision_id
736
790
        return rev_dict
 
791
 
 
792
 
 
793
class SavedCommitMessagesManager:
 
794
    """Save glogal and per-file commit messages.
 
795
 
 
796
    Saves global commit message and utf-8 file_id->message dictionary
 
797
    of per-file commit messages on disk. Re-reads them later for re-using.
 
798
    """
 
799
 
 
800
    def __init__(self, tree=None, branch=None):
 
801
        """If branch is None, builds empty messages, otherwise reads them
 
802
        from branch's disk storage. 'tree' argument is for the future."""
 
803
        if branch is None:
 
804
            self.global_message = u''
 
805
            self.file_messages = {}
 
806
        else:
 
807
            config = branch.get_config()
 
808
            self.global_message = config.get_user_option(
 
809
                'gtk_global_commit_message')
 
810
            if self.global_message is None:
 
811
                self.global_message = u''
 
812
            file_messages = config.get_user_option('gtk_file_commit_messages')
 
813
            if file_messages: # unicode and B-encoded:
 
814
                self.file_messages = bencode.bdecode(
 
815
                    file_messages.encode('UTF-8'))
 
816
            else:
 
817
                self.file_messages = {}
 
818
 
 
819
    def get(self):
 
820
        return self.global_message, self.file_messages
 
821
 
 
822
    def is_not_empty(self):
 
823
        return bool(self.global_message or self.file_messages)
 
824
 
 
825
    def insert(self, global_message, file_info):
 
826
        """Formats per-file commit messages (list of dictionaries, one per file)
 
827
        into one utf-8 file_id->message dictionary and merges this with
 
828
        previously existing dictionary. Merges global commit message too."""
 
829
        file_messages = {}
 
830
        for fi in file_info:
 
831
            file_message = fi['message']
 
832
            if file_message:
 
833
                file_messages[fi['file_id']] = file_message # utf-8 strings
 
834
        for k,v in file_messages.iteritems():
 
835
            try:
 
836
                self.file_messages[k] = v + '\n******\n' + self.file_messages[k]
 
837
            except KeyError:
 
838
                self.file_messages[k] = v
 
839
        if self.global_message:
 
840
            self.global_message = global_message + '\n******\n' \
 
841
                + self.global_message
 
842
        else:
 
843
            self.global_message = global_message
 
844
 
 
845
    def save(self, tree, branch):
 
846
        # We store in branch's config, which can be a problem if two gcommit
 
847
        # are done in two checkouts of one single branch (comments overwrite
 
848
        # each other). Ideally should be in working tree. But uncommit does
 
849
        # not always have a working tree, though it always has a branch.
 
850
        # 'tree' argument is for the future
 
851
        config = branch.get_config()
 
852
        # should it be named "gtk_" or some more neutral name ("gui_" ?) to
 
853
        # be compatible with qbzr in the future?
 
854
        config.set_user_option('gtk_global_commit_message', self.global_message)
 
855
        # bencode() does not know unicode objects but set_user_option()
 
856
        # requires one:
 
857
        config.set_user_option(
 
858
            'gtk_file_commit_messages',
 
859
            bencode.bencode(self.file_messages).decode('UTF-8'))
 
860
 
 
861
 
 
862
def save_commit_messages(local, master, old_revno, old_revid,
 
863
                         new_revno, new_revid):
 
864
    b = local
 
865
    if b is None:
 
866
        b = master
 
867
    mgr = SavedCommitMessagesManager(None, b)
 
868
    revid_iterator = b.repository.iter_reverse_revision_history(old_revid)
 
869
    cur_revno = old_revno
 
870
    new_revision_id = old_revid
 
871
    graph = b.repository.get_graph()
 
872
    for rev_id in revid_iterator:
 
873
        if cur_revno == new_revno:
 
874
            break
 
875
        cur_revno -= 1
 
876
        rev = b.repository.get_revision(rev_id)
 
877
        file_info = rev.properties.get('file-info', None)
 
878
        if file_info is None:
 
879
            file_info = {}
 
880
        else:
 
881
            file_info = bencode.bdecode(file_info.encode('UTF-8'))
 
882
        global_message = osutils.safe_unicode(rev.message)
 
883
        # Concatenate comment of the uncommitted revision
 
884
        mgr.insert(global_message, file_info)
 
885
 
 
886
        parents = graph.get_parent_map([rev_id]).get(rev_id, None)
 
887
        if not parents:
 
888
            continue
 
889
    mgr.save(None, b)