92
_newline_variants_re = re.compile(r'\r\n?')
93
def _sanitize_and_decode_message(utf8_message):
94
"""Turn a utf-8 message into a sanitized Unicode message."""
95
fixed_newline = _newline_variants_re.sub('\n', utf8_message)
96
return osutils.safe_unicode(fixed_newline)
99
class CommitDialog(Gtk.Dialog):
99
class CommitDialog(gtk.Dialog):
100
100
"""Implementation of Commit."""
102
102
def __init__(self, wt, selected=None, parent=None):
103
super(CommitDialog, self).__init__(
104
title="Commit to %s" % wt.basedir, parent=parent, flags=0)
105
self.connect('delete-event', self._on_delete_window)
103
gtk.Dialog.__init__(self, title="Commit - Olive",
106
buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
106
107
self._question_dialog = question_dialog
108
self.set_type_hint(Gdk.WindowTypeHint.NORMAL)
111
110
# TODO: Do something with this value, it is used by Olive
112
111
# It used to set all changes but this one to False
172
164
store = self._files_store
173
165
self._treeview_files.set_model(None)
175
added = _i18n('added')
176
removed = _i18n('removed')
177
renamed = _i18n('renamed')
178
renamed_and_modified = _i18n('renamed and modified')
179
modified = _i18n('modified')
180
kind_changed = _i18n('kind changed')
168
removed = _('removed')
169
renamed = _('renamed')
170
renamed_and_modified = _('renamed and modified')
171
modified = _('modified')
172
kind_changed = _('kind changed')
182
174
# The store holds:
183
175
# [file_id, real path, checkbox, display path, changes type, message]
184
# iter_changes returns:
176
# _iter_changes returns:
185
177
# (file_id, (path_in_source, path_in_target),
186
178
# changed_content, versioned, parent, name, kind,
189
181
all_enabled = (self._selected is None)
190
182
# The first entry is always the 'whole tree'
191
all_iter = store.append(["", "", all_enabled, 'All Files', '', ''])
183
all_iter = store.append([None, None, all_enabled, 'All Files', '', ''])
192
184
initial_cursor = store.get_path(all_iter)
193
185
# should we pass specific_files?
194
186
self._wt.lock_read()
195
187
self._basis_tree.lock_read()
197
from diff import iter_changes_to_status
198
saved_file_messages = self._saved_commit_messages_manager.get()[1]
189
from diff import _iter_changes_to_status
199
190
for (file_id, real_path, change_type, display_path
200
) in iter_changes_to_status(self._basis_tree, self._wt):
191
) in _iter_changes_to_status(self._basis_tree, self._wt):
201
192
if self._selected and real_path != self._selected:
206
default_message = saved_file_messages[file_id]
209
196
item_iter = store.append([
211
198
real_path.encode('UTF-8'),
213
200
display_path.encode('UTF-8'),
215
default_message, # Initial comment
202
'', # Initial comment
217
204
if self._selected and enabled:
218
205
initial_cursor = store.get_path(item_iter)
310
291
self._hpane.set_position(300)
312
293
def _construct_accelerators(self):
313
group = Gtk.AccelGroup()
314
group.connect(Gdk.keyval_from_name('N'),
315
Gdk.ModifierType.CONTROL_MASK, 0, self._on_accel_next)
294
group = gtk.AccelGroup()
295
group.connect_group(gtk.gdk.keyval_from_name('N'),
296
gtk.gdk.CONTROL_MASK, 0, self._on_accel_next)
316
297
self.add_accel_group(group)
318
# ignore the escape key (avoid closing the window)
319
self.connect_object('close', self.emit_stop_by_name, 'close')
321
299
def _construct_left_pane(self):
322
self._left_pane_box = Gtk.VBox(homogeneous=False, spacing=5)
300
self._left_pane_box = gtk.VBox(homogeneous=False, spacing=5)
323
301
self._construct_file_list()
324
302
self._construct_pending_list()
326
self._check_local = Gtk.CheckButton(_i18n("_Only commit locally"),
304
self._check_local = gtk.CheckButton(_("_Only commit locally"),
327
305
use_underline=True)
328
self._left_pane_box.pack_end(self._check_local, False, False, 0)
306
self._left_pane_box.pack_end(self._check_local, False, False)
329
307
self._check_local.set_active(False)
331
309
self._hpane.pack1(self._left_pane_box, resize=False, shrink=False)
350
328
self._hpane.pack2(self._right_pane_table, resize=True, shrink=True)
352
330
def _construct_action_pane(self):
353
self._button_cancel = Gtk.Button(stock=Gtk.STOCK_CANCEL)
354
self._button_cancel.connect('clicked', self._on_cancel_clicked)
355
self._button_cancel.show()
356
self.get_action_area().pack_end(
357
self._button_cancel, True, True, 0)
358
self._button_commit = Gtk.Button(_i18n("Comm_it"), use_underline=True)
331
self._button_commit = gtk.Button(_("Comm_it"), use_underline=True)
359
332
self._button_commit.connect('clicked', self._on_commit_clicked)
360
self._button_commit.set_can_default(True)
333
self._button_commit.set_flags(gtk.CAN_DEFAULT)
361
334
self._button_commit.show()
362
self.get_action_area().pack_end(
363
self._button_commit, True, True, 0)
335
self.action_area.pack_end(self._button_commit)
364
336
self._button_commit.grab_default()
366
338
def _add_to_right_table(self, widget, weight, expanding=False):
381
353
self._right_pane_table_row = end_row
383
355
def _construct_file_list(self):
384
self._files_box = Gtk.VBox(homogeneous=False, spacing=0)
385
file_label = Gtk.Label(label=_i18n('Files'))
356
self._files_box = gtk.VBox(homogeneous=False, spacing=0)
357
file_label = gtk.Label(_('Files'))
386
358
# file_label.show()
387
self._files_box.pack_start(file_label, False, True, 0)
359
self._files_box.pack_start(file_label, expand=False)
389
self._commit_all_files_radio = Gtk.RadioButton.new_with_label(
390
None, _i18n("Commit all changes"))
391
self._files_box.pack_start(self._commit_all_files_radio, False, True, 0)
361
self._commit_all_files_radio = gtk.RadioButton(
362
None, _("Commit all changes"))
363
self._files_box.pack_start(self._commit_all_files_radio, expand=False)
392
364
self._commit_all_files_radio.show()
393
365
self._commit_all_files_radio.connect('toggled',
394
366
self._toggle_commit_selection)
395
self._commit_selected_radio = Gtk.RadioButton.new_with_label_from_widget(
396
self._commit_all_files_radio, _i18n("Only commit selected changes"))
397
self._files_box.pack_start(self._commit_selected_radio, False, True, 0)
367
self._commit_selected_radio = gtk.RadioButton(
368
self._commit_all_files_radio, _("Only commit selected changes"))
369
self._files_box.pack_start(self._commit_selected_radio, expand=False)
398
370
self._commit_selected_radio.show()
399
371
self._commit_selected_radio.connect('toggled',
400
372
self._toggle_commit_selection)
401
373
if self._pending:
402
self._commit_all_files_radio.set_label(_i18n('Commit all changes*'))
374
self._commit_all_files_radio.set_label(_('Commit all changes*'))
403
375
self._commit_all_files_radio.set_sensitive(False)
404
376
self._commit_selected_radio.set_sensitive(False)
406
scroller = Gtk.ScrolledWindow()
407
scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
408
self._treeview_files = Gtk.TreeView()
378
scroller = gtk.ScrolledWindow()
379
scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
380
self._treeview_files = gtk.TreeView()
409
381
self._treeview_files.show()
410
382
scroller.add(self._treeview_files)
411
scroller.set_shadow_type(Gtk.ShadowType.IN)
383
scroller.set_shadow_type(gtk.SHADOW_IN)
413
self._files_box.pack_start(scroller, True, True, 0)
385
self._files_box.pack_start(scroller,
386
expand=True, fill=True)
414
387
self._files_box.show()
415
self._left_pane_box.pack_start(self._files_box, True, True, 0)
388
self._left_pane_box.pack_start(self._files_box)
417
390
# Keep note that all strings stored in a ListStore must be UTF-8
418
391
# strings. GTK does not support directly setting and restoring Unicode
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
393
liststore = gtk.ListStore(
394
gobject.TYPE_STRING, # [0] file_id
395
gobject.TYPE_STRING, # [1] real path
396
gobject.TYPE_BOOLEAN, # [2] checkbox
397
gobject.TYPE_STRING, # [3] display path
398
gobject.TYPE_STRING, # [4] changes type
399
gobject.TYPE_STRING, # [5] commit message
428
401
self._files_store = liststore
429
402
self._treeview_files.set_model(liststore)
430
crt = Gtk.CellRendererToggle()
403
crt = gtk.CellRendererToggle()
431
404
crt.set_property('activatable', not bool(self._pending))
432
405
crt.connect("toggled", self._toggle_commit, self._files_store)
433
406
if self._pending:
434
name = _i18n('Commit*')
436
name = _i18n('Commit')
437
commit_col = Gtk.TreeViewColumn(name, crt, active=2)
410
commit_col = gtk.TreeViewColumn(name, crt, active=2)
438
411
commit_col.set_visible(False)
439
412
self._treeview_files.append_column(commit_col)
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))
413
self._treeview_files.append_column(gtk.TreeViewColumn(_('Path'),
414
gtk.CellRendererText(), text=3))
415
self._treeview_files.append_column(gtk.TreeViewColumn(_('Type'),
416
gtk.CellRendererText(), text=4))
444
417
self._treeview_files.connect('cursor-changed',
445
418
self._on_treeview_files_cursor_changed)
447
420
def _toggle_commit(self, cell, path, model):
448
if model[path][0] == "": # No file_id means 'All Files'
421
if model[path][0] is None: # No file_id means 'All Files'
449
422
new_val = not model[path][2]
450
423
for node in model:
451
424
node[2] = new_val
461
434
checked_col.set_visible(False)
463
436
checked_col.set_visible(True)
464
renderer = checked_col.get_cells()[0]
437
renderer = checked_col.get_cell_renderers()[0]
465
438
renderer.set_property('activatable', not all_files)
467
440
def _construct_pending_list(self):
468
441
# Pending information defaults to hidden, we put it all in 1 box, so
469
442
# that we can show/hide all of them at once
470
self._pending_box = Gtk.VBox()
443
self._pending_box = gtk.VBox()
471
444
self._pending_box.hide()
473
pending_message = Gtk.Label()
446
pending_message = gtk.Label()
474
447
pending_message.set_markup(
475
_i18n('<i>* Cannot select specific files when merging</i>'))
476
self._pending_box.pack_start(pending_message, False, True, 5)
448
_('<i>* Cannot select specific files when merging</i>'))
449
self._pending_box.pack_start(pending_message, expand=False, padding=5)
477
450
pending_message.show()
479
pending_label = Gtk.Label(label=_i18n('Pending Revisions'))
480
self._pending_box.pack_start(pending_label, False, True, 0)
452
pending_label = gtk.Label(_('Pending Revisions'))
453
self._pending_box.pack_start(pending_label, expand=False, padding=0)
481
454
pending_label.show()
483
scroller = Gtk.ScrolledWindow()
484
scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
485
self._treeview_pending = Gtk.TreeView()
456
scroller = gtk.ScrolledWindow()
457
scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
458
self._treeview_pending = gtk.TreeView()
486
459
scroller.add(self._treeview_pending)
487
scroller.set_shadow_type(Gtk.ShadowType.IN)
460
scroller.set_shadow_type(gtk.SHADOW_IN)
489
self._pending_box.pack_start(scroller, True, True, 5)
462
self._pending_box.pack_start(scroller,
463
expand=True, fill=True, padding=5)
490
464
self._treeview_pending.show()
491
self._left_pane_box.pack_start(self._pending_box, True, True, 0)
465
self._left_pane_box.pack_start(self._pending_box)
493
liststore = Gtk.ListStore(GObject.TYPE_STRING, # revision_id
494
GObject.TYPE_STRING, # date
495
GObject.TYPE_STRING, # committer
496
GObject.TYPE_STRING, # summary
467
liststore = gtk.ListStore(gobject.TYPE_STRING, # revision_id
468
gobject.TYPE_STRING, # date
469
gobject.TYPE_STRING, # committer
470
gobject.TYPE_STRING, # summary
498
472
self._pending_store = liststore
499
473
self._treeview_pending.set_model(liststore)
500
self._treeview_pending.append_column(Gtk.TreeViewColumn(_i18n('Date'),
501
Gtk.CellRendererText(), text=1))
502
self._treeview_pending.append_column(Gtk.TreeViewColumn(_i18n('Committer'),
503
Gtk.CellRendererText(), text=2))
504
self._treeview_pending.append_column(Gtk.TreeViewColumn(_i18n('Summary'),
505
Gtk.CellRendererText(), text=3))
474
self._treeview_pending.append_column(gtk.TreeViewColumn(_('Date'),
475
gtk.CellRendererText(), text=1))
476
self._treeview_pending.append_column(gtk.TreeViewColumn(_('Committer'),
477
gtk.CellRendererText(), text=2))
478
self._treeview_pending.append_column(gtk.TreeViewColumn(_('Summary'),
479
gtk.CellRendererText(), text=3))
507
481
def _construct_diff_view(self):
508
from bzrlib.plugins.gtk.diff import DiffView
482
from diff import DiffView
510
484
# TODO: jam 2007-10-30 The diff label is currently disabled. If we
511
485
# decide that we really don't ever want to display it, we should
512
486
# actually remove it, and other references to it, along with the
513
487
# tests that it is set properly.
514
self._diff_label = Gtk.Label(label=_i18n('Diff for whole tree'))
488
self._diff_label = gtk.Label(_('Diff for whole tree'))
515
489
self._diff_label.set_alignment(0, 0)
516
490
self._right_pane_table.set_row_spacing(self._right_pane_table_row, 0)
517
491
self._add_to_right_table(self._diff_label, 1, False)
522
496
self._diff_view.show()
524
498
def _construct_file_message(self):
525
scroller = Gtk.ScrolledWindow()
526
scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
499
scroller = gtk.ScrolledWindow()
500
scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
528
self._file_message_text_view = Gtk.TextView()
502
self._file_message_text_view = gtk.TextView()
529
503
scroller.add(self._file_message_text_view)
530
scroller.set_shadow_type(Gtk.ShadowType.IN)
504
scroller.set_shadow_type(gtk.SHADOW_IN)
533
self._file_message_text_view.modify_font(Pango.FontDescription("Monospace"))
534
self._file_message_text_view.set_wrap_mode(Gtk.WrapMode.WORD)
507
self._file_message_text_view.modify_font(pango.FontDescription("Monospace"))
508
self._file_message_text_view.set_wrap_mode(gtk.WRAP_WORD)
535
509
self._file_message_text_view.set_accepts_tab(False)
536
510
self._file_message_text_view.show()
538
self._file_message_expander = Gtk.Expander(
539
label=_i18n('File commit message'))
512
self._file_message_expander = gtk.Expander(_('File commit message'))
540
513
self._file_message_expander.set_expanded(True)
541
514
self._file_message_expander.add(scroller)
542
515
self._add_to_right_table(self._file_message_expander, 1, False)
543
516
self._file_message_expander.show()
545
518
def _construct_global_message(self):
546
self._global_message_label = Gtk.Label(label=_i18n('Global Commit Message'))
547
self._global_message_label.set_markup(
548
_i18n('<b>Global Commit Message</b>'))
519
self._global_message_label = gtk.Label(_('Global Commit Message'))
520
self._global_message_label.set_markup(_('<b>Global Commit Message</b>'))
549
521
self._global_message_label.set_alignment(0, 0)
550
522
self._right_pane_table.set_row_spacing(self._right_pane_table_row, 0)
551
523
self._add_to_right_table(self._global_message_label, 1, False)
552
524
# Can we remove the spacing between the label and the box?
553
525
self._global_message_label.show()
555
scroller = Gtk.ScrolledWindow()
556
scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
527
scroller = gtk.ScrolledWindow()
528
scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
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"))
530
self._global_message_text_view = gtk.TextView()
531
self._global_message_text_view.modify_font(pango.FontDescription("Monospace"))
561
532
scroller.add(self._global_message_text_view)
562
scroller.set_shadow_type(Gtk.ShadowType.IN)
533
scroller.set_shadow_type(gtk.SHADOW_IN)
564
535
self._add_to_right_table(scroller, 2, True)
565
self._file_message_text_view.set_wrap_mode(Gtk.WrapMode.WORD)
536
self._file_message_text_view.set_wrap_mode(gtk.WRAP_WORD)
566
537
self._file_message_text_view.set_accepts_tab(False)
567
538
self._global_message_text_view.show()
569
540
def _on_treeview_files_cursor_changed(self, treeview):
570
541
treeselection = treeview.get_selection()
571
if treeselection is None:
572
# The treeview was probably destroyed as the dialog closes.
574
542
(model, selection) = treeselection.get_selected()
576
544
if selection is not None:
577
545
path, display_path = model.get(selection, 1, 3)
578
self._diff_label.set_text(_i18n('Diff for ') + display_path)
546
self._diff_label.set_text(_('Diff for ') + display_path)
580
548
self._diff_view.show_diff(None)
582
self._diff_view.show_diff([osutils.safe_unicode(path)])
550
self._diff_view.show_diff([path.decode('UTF-8')])
583
551
self._update_per_file_info(selection)
585
553
def _on_accel_next(self, accel_group, window, keyval, modifier):
646
613
records = iter(self._files_store)
647
614
rec = records.next() # Skip the All Files record
648
assert rec[0] == "", "Are we skipping the wrong record?"
615
assert rec[0] is None, "Are we skipping the wrong record?"
651
618
for record in records:
652
619
if self._commit_all_changes or record[2]:# [2] checkbox
653
file_id = osutils.safe_utf8(record[0]) # [0] file_id
654
path = osutils.safe_utf8(record[1]) # [1] real path
656
file_message = _sanitize_and_decode_message(record[5])
620
file_id = record[0] # [0] file_id
621
path = record[1] # [1] real path
622
file_message = record[5] # [5] commit message
657
623
files.append(path.decode('UTF-8'))
658
624
if self._enable_per_file_commits and file_message:
659
625
# All of this needs to be utf-8 information
660
file_message = file_message.encode('UTF-8')
661
626
file_info.append({'path':path, 'file_id':file_id,
662
627
'message':file_message})
663
628
file_info.sort(key=lambda x:(x['path'], x['file_id']))
670
def _on_cancel_clicked(self, button):
671
""" Cancel button clicked handler. """
675
def _on_delete_window(self, source, event):
676
""" Delete window handler. """
679
def _do_cancel(self):
680
"""If requested, saves commit messages when cancelling gcommit; they are re-used by a next gcommit"""
681
mgr = SavedCommitMessagesManager()
682
self._saved_commit_messages_manager = mgr
683
mgr.insert(self._get_global_commit_message(),
684
self._get_specific_files()[1])
685
if mgr.is_not_empty(): # maybe worth saving
686
response = self._question_dialog(
687
_i18n('Commit cancelled'),
688
_i18n('Do you want to save your commit messages ?'),
690
if response == Gtk.ResponseType.NO:
691
# save nothing and destroy old comments if any
692
mgr = SavedCommitMessagesManager()
693
mgr.save(self._wt, self._wt.branch)
694
self.response(Gtk.ResponseType.CANCEL) # close window
697
635
def _on_commit_clicked(self, button):
698
636
""" Commit button clicked handler. """
699
637
self._do_commit()