/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 15:52:43 UTC
  • mto: This revision was merged to the branch mainline in revision 741.
  • Revision ID: sinzui.is@verizon.net-20110731155243-ln8istmxbryhb4pz
Mechanical changes made by pygi.convert.sh.

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