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
19
from gi.repository import Gdk
20
from gi.repository import Gtk
21
from gi.repository import GObject
22
from gi.repository import Pango
25
from bzrlib import bencode
27
from bzrlib.util import bencode
30
29
from bzrlib import (
37
from bzrlib import bencode
39
from bzrlib.util import bencode
41
from bzrlib.plugins.gtk import _i18n
42
33
from bzrlib.plugins.gtk.dialog import question_dialog
43
34
from bzrlib.plugins.gtk.errors import show_bzr_error
35
from bzrlib.plugins.gtk.i18n import _i18n
36
from bzrlib.plugins.gtk.commitmsgs import SavedCommitMessagesManager
111
104
return fixed_newline.decode('utf-8')
114
class CommitDialog(gtk.Dialog):
107
class CommitDialog(Gtk.Dialog):
115
108
"""Implementation of Commit."""
117
110
def __init__(self, wt, selected=None, parent=None):
118
gtk.Dialog.__init__(self, title="Commit to %s" % wt.basedir,
119
parent=parent, flags=0,)
111
super(CommitDialog, self).__init__(
112
title="Commit to %s" % wt.basedir, parent=parent, flags=0)
120
113
self.connect('delete-event', self._on_delete_window)
121
114
self._question_dialog = question_dialog
123
self.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_NORMAL)
116
self.set_type_hint(Gdk.WindowTypeHint.NORMAL)
126
119
# TODO: Do something with this value, it is used by Olive
319
313
self._hpane.set_position(300)
321
315
def _construct_accelerators(self):
322
group = gtk.AccelGroup()
323
group.connect_group(gtk.gdk.keyval_from_name('N'),
324
gtk.gdk.CONTROL_MASK, 0, self._on_accel_next)
316
group = Gtk.AccelGroup()
317
group.connect(Gdk.keyval_from_name('N'),
318
Gdk.ModifierType.CONTROL_MASK, 0, self._on_accel_next)
325
319
self.add_accel_group(group)
327
321
# ignore the escape key (avoid closing the window)
328
322
self.connect_object('close', self.emit_stop_by_name, 'close')
330
324
def _construct_left_pane(self):
331
self._left_pane_box = gtk.VBox(homogeneous=False, spacing=5)
325
self._left_pane_box = Gtk.VBox(homogeneous=False, spacing=5)
332
326
self._construct_file_list()
333
327
self._construct_pending_list()
335
self._check_local = gtk.CheckButton(_i18n("_Only commit locally"),
329
self._check_local = Gtk.CheckButton(_i18n("_Only commit locally"),
336
330
use_underline=True)
337
self._left_pane_box.pack_end(self._check_local, False, False)
331
self._left_pane_box.pack_end(self._check_local, False, False, 0)
338
332
self._check_local.set_active(False)
340
334
self._hpane.pack1(self._left_pane_box, resize=False, shrink=False)
359
353
self._hpane.pack2(self._right_pane_table, resize=True, shrink=True)
361
355
def _construct_action_pane(self):
362
self._button_cancel = gtk.Button(stock=gtk.STOCK_CANCEL)
356
self._button_cancel = Gtk.Button(stock=Gtk.STOCK_CANCEL)
363
357
self._button_cancel.connect('clicked', self._on_cancel_clicked)
364
358
self._button_cancel.show()
365
self.action_area.pack_end(self._button_cancel)
366
self._button_commit = gtk.Button(_i18n("Comm_it"), use_underline=True)
359
self.get_action_area().pack_end(
360
self._button_cancel, True, True, 0)
361
self._button_commit = Gtk.Button(_i18n("Comm_it"), use_underline=True)
367
362
self._button_commit.connect('clicked', self._on_commit_clicked)
368
self._button_commit.set_flags(gtk.CAN_DEFAULT)
363
self._button_commit.set_can_default(True)
369
364
self._button_commit.show()
370
self.action_area.pack_end(self._button_commit)
365
self.get_action_area().pack_end(
366
self._button_commit, True, True, 0)
371
367
self._button_commit.grab_default()
373
369
def _add_to_right_table(self, widget, weight, expanding=False):
388
384
self._right_pane_table_row = end_row
390
386
def _construct_file_list(self):
391
self._files_box = gtk.VBox(homogeneous=False, spacing=0)
392
file_label = gtk.Label(_i18n('Files'))
387
self._files_box = Gtk.VBox(homogeneous=False, spacing=0)
388
file_label = Gtk.Label(label=_i18n('Files'))
393
389
# file_label.show()
394
self._files_box.pack_start(file_label, expand=False)
390
self._files_box.pack_start(file_label, False, True, 0)
396
self._commit_all_files_radio = gtk.RadioButton(
392
self._commit_all_files_radio = Gtk.RadioButton.new_with_label(
397
393
None, _i18n("Commit all changes"))
398
self._files_box.pack_start(self._commit_all_files_radio, expand=False)
394
self._files_box.pack_start(self._commit_all_files_radio, False, True, 0)
399
395
self._commit_all_files_radio.show()
400
396
self._commit_all_files_radio.connect('toggled',
401
397
self._toggle_commit_selection)
402
self._commit_selected_radio = gtk.RadioButton(
398
self._commit_selected_radio = Gtk.RadioButton.new_with_label_from_widget(
403
399
self._commit_all_files_radio, _i18n("Only commit selected changes"))
404
self._files_box.pack_start(self._commit_selected_radio, expand=False)
400
self._files_box.pack_start(self._commit_selected_radio, False, True, 0)
405
401
self._commit_selected_radio.show()
406
402
self._commit_selected_radio.connect('toggled',
407
403
self._toggle_commit_selection)
410
406
self._commit_all_files_radio.set_sensitive(False)
411
407
self._commit_selected_radio.set_sensitive(False)
413
scroller = gtk.ScrolledWindow()
414
scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
415
self._treeview_files = gtk.TreeView()
409
scroller = Gtk.ScrolledWindow()
410
scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
411
self._treeview_files = Gtk.TreeView()
416
412
self._treeview_files.show()
417
413
scroller.add(self._treeview_files)
418
scroller.set_shadow_type(gtk.SHADOW_IN)
414
scroller.set_shadow_type(Gtk.ShadowType.IN)
420
self._files_box.pack_start(scroller,
421
expand=True, fill=True)
416
self._files_box.pack_start(scroller, True, True, 0)
422
417
self._files_box.show()
423
self._left_pane_box.pack_start(self._files_box)
418
self._left_pane_box.pack_start(self._files_box, True, True, 0)
425
420
# Keep note that all strings stored in a ListStore must be UTF-8
426
421
# strings. GTK does not support directly setting and restoring Unicode
428
liststore = gtk.ListStore(
429
gobject.TYPE_STRING, # [0] file_id
430
gobject.TYPE_STRING, # [1] real path
431
gobject.TYPE_BOOLEAN, # [2] checkbox
432
gobject.TYPE_STRING, # [3] display path
433
gobject.TYPE_STRING, # [4] changes type
434
gobject.TYPE_STRING, # [5] commit message
423
liststore = Gtk.ListStore(
424
GObject.TYPE_STRING, # [0] file_id
425
GObject.TYPE_STRING, # [1] real path
426
GObject.TYPE_BOOLEAN, # [2] checkbox
427
GObject.TYPE_STRING, # [3] display path
428
GObject.TYPE_STRING, # [4] changes type
429
GObject.TYPE_STRING, # [5] commit message
436
431
self._files_store = liststore
437
432
self._treeview_files.set_model(liststore)
438
crt = gtk.CellRendererToggle()
433
crt = Gtk.CellRendererToggle()
439
434
crt.set_property('activatable', not bool(self._pending))
440
435
crt.connect("toggled", self._toggle_commit, self._files_store)
441
436
if self._pending:
442
437
name = _i18n('Commit*')
444
439
name = _i18n('Commit')
445
commit_col = gtk.TreeViewColumn(name, crt, active=2)
440
commit_col = Gtk.TreeViewColumn(name, crt, active=2)
446
441
commit_col.set_visible(False)
447
442
self._treeview_files.append_column(commit_col)
448
self._treeview_files.append_column(gtk.TreeViewColumn(_i18n('Path'),
449
gtk.CellRendererText(), text=3))
450
self._treeview_files.append_column(gtk.TreeViewColumn(_i18n('Type'),
451
gtk.CellRendererText(), text=4))
443
self._treeview_files.append_column(Gtk.TreeViewColumn(_i18n('Path'),
444
Gtk.CellRendererText(), text=3))
445
self._treeview_files.append_column(Gtk.TreeViewColumn(_i18n('Type'),
446
Gtk.CellRendererText(), text=4))
452
447
self._treeview_files.connect('cursor-changed',
453
448
self._on_treeview_files_cursor_changed)
469
464
checked_col.set_visible(False)
471
466
checked_col.set_visible(True)
472
renderer = checked_col.get_cell_renderers()[0]
467
renderer = checked_col.get_cells()[0]
473
468
renderer.set_property('activatable', not all_files)
475
470
def _construct_pending_list(self):
476
471
# Pending information defaults to hidden, we put it all in 1 box, so
477
472
# that we can show/hide all of them at once
478
self._pending_box = gtk.VBox()
473
self._pending_box = Gtk.VBox()
479
474
self._pending_box.hide()
481
pending_message = gtk.Label()
476
pending_message = Gtk.Label()
482
477
pending_message.set_markup(
483
478
_i18n('<i>* Cannot select specific files when merging</i>'))
484
self._pending_box.pack_start(pending_message, expand=False, padding=5)
479
self._pending_box.pack_start(pending_message, False, True, 5)
485
480
pending_message.show()
487
pending_label = gtk.Label(_i18n('Pending Revisions'))
488
self._pending_box.pack_start(pending_label, expand=False, padding=0)
482
pending_label = Gtk.Label(label=_i18n('Pending Revisions'))
483
self._pending_box.pack_start(pending_label, False, True, 0)
489
484
pending_label.show()
491
scroller = gtk.ScrolledWindow()
492
scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
493
self._treeview_pending = gtk.TreeView()
486
scroller = Gtk.ScrolledWindow()
487
scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
488
self._treeview_pending = Gtk.TreeView()
494
489
scroller.add(self._treeview_pending)
495
scroller.set_shadow_type(gtk.SHADOW_IN)
490
scroller.set_shadow_type(Gtk.ShadowType.IN)
497
self._pending_box.pack_start(scroller,
498
expand=True, fill=True, padding=5)
492
self._pending_box.pack_start(scroller, True, True, 5)
499
493
self._treeview_pending.show()
500
self._left_pane_box.pack_start(self._pending_box)
494
self._left_pane_box.pack_start(self._pending_box, True, True, 0)
502
liststore = gtk.ListStore(gobject.TYPE_STRING, # revision_id
503
gobject.TYPE_STRING, # date
504
gobject.TYPE_STRING, # committer
505
gobject.TYPE_STRING, # summary
496
liststore = Gtk.ListStore(GObject.TYPE_STRING, # revision_id
497
GObject.TYPE_STRING, # date
498
GObject.TYPE_STRING, # committer
499
GObject.TYPE_STRING, # summary
507
501
self._pending_store = liststore
508
502
self._treeview_pending.set_model(liststore)
509
self._treeview_pending.append_column(gtk.TreeViewColumn(_i18n('Date'),
510
gtk.CellRendererText(), text=1))
511
self._treeview_pending.append_column(gtk.TreeViewColumn(_i18n('Committer'),
512
gtk.CellRendererText(), text=2))
513
self._treeview_pending.append_column(gtk.TreeViewColumn(_i18n('Summary'),
514
gtk.CellRendererText(), text=3))
503
self._treeview_pending.append_column(Gtk.TreeViewColumn(_i18n('Date'),
504
Gtk.CellRendererText(), text=1))
505
self._treeview_pending.append_column(Gtk.TreeViewColumn(_i18n('Committer'),
506
Gtk.CellRendererText(), text=2))
507
self._treeview_pending.append_column(Gtk.TreeViewColumn(_i18n('Summary'),
508
Gtk.CellRendererText(), text=3))
516
510
def _construct_diff_view(self):
517
from diff import DiffView
511
from bzrlib.plugins.gtk.diff import DiffView
519
513
# TODO: jam 2007-10-30 The diff label is currently disabled. If we
520
514
# decide that we really don't ever want to display it, we should
521
515
# actually remove it, and other references to it, along with the
522
516
# tests that it is set properly.
523
self._diff_label = gtk.Label(_i18n('Diff for whole tree'))
517
self._diff_label = Gtk.Label(label=_i18n('Diff for whole tree'))
524
518
self._diff_label.set_alignment(0, 0)
525
519
self._right_pane_table.set_row_spacing(self._right_pane_table_row, 0)
526
520
self._add_to_right_table(self._diff_label, 1, False)
531
525
self._diff_view.show()
533
527
def _construct_file_message(self):
534
scroller = gtk.ScrolledWindow()
535
scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
528
scroller = Gtk.ScrolledWindow()
529
scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
537
self._file_message_text_view = gtk.TextView()
531
self._file_message_text_view = Gtk.TextView()
538
532
scroller.add(self._file_message_text_view)
539
scroller.set_shadow_type(gtk.SHADOW_IN)
533
scroller.set_shadow_type(Gtk.ShadowType.IN)
542
self._file_message_text_view.modify_font(pango.FontDescription("Monospace"))
543
self._file_message_text_view.set_wrap_mode(gtk.WRAP_WORD)
536
self._file_message_text_view.modify_font(Pango.FontDescription("Monospace"))
537
self._file_message_text_view.set_wrap_mode(Gtk.WrapMode.WORD)
544
538
self._file_message_text_view.set_accepts_tab(False)
545
539
self._file_message_text_view.show()
547
self._file_message_expander = gtk.Expander(_i18n('File commit message'))
541
self._file_message_expander = Gtk.Expander(
542
label=_i18n('File commit message'))
548
543
self._file_message_expander.set_expanded(True)
549
544
self._file_message_expander.add(scroller)
550
545
self._add_to_right_table(self._file_message_expander, 1, False)
551
546
self._file_message_expander.show()
553
548
def _construct_global_message(self):
554
self._global_message_label = gtk.Label(_i18n('Global Commit Message'))
549
self._global_message_label = Gtk.Label(label=_i18n('Global Commit Message'))
555
550
self._global_message_label.set_markup(
556
551
_i18n('<b>Global Commit Message</b>'))
557
552
self._global_message_label.set_alignment(0, 0)
560
555
# Can we remove the spacing between the label and the box?
561
556
self._global_message_label.show()
563
scroller = gtk.ScrolledWindow()
564
scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
558
scroller = Gtk.ScrolledWindow()
559
scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
566
self._global_message_text_view = gtk.TextView()
561
self._global_message_text_view = Gtk.TextView()
567
562
self._set_global_commit_message(self._saved_commit_messages_manager.get()[0])
568
self._global_message_text_view.modify_font(pango.FontDescription("Monospace"))
563
self._global_message_text_view.modify_font(Pango.FontDescription("Monospace"))
569
564
scroller.add(self._global_message_text_view)
570
scroller.set_shadow_type(gtk.SHADOW_IN)
565
scroller.set_shadow_type(Gtk.ShadowType.IN)
572
567
self._add_to_right_table(scroller, 2, True)
573
self._file_message_text_view.set_wrap_mode(gtk.WRAP_WORD)
568
self._file_message_text_view.set_wrap_mode(Gtk.WrapMode.WORD)
574
569
self._file_message_text_view.set_accepts_tab(False)
575
570
self._global_message_text_view.show()
601
596
# We have either made it to the end of the list, or nothing was
602
597
# selected. Either way, select All Files, and jump to the global
603
598
# commit message.
604
self._treeview_files.set_cursor((0,))
599
self._treeview_files.set_cursor(
600
Gtk.TreePath(path=0), None, False)
605
601
self._global_message_text_view.grab_focus()
607
603
# Set the cursor to this entry, and jump to the per-file commit
609
self._treeview_files.set_cursor(model.get_path(next))
605
self._treeview_files.set_cursor(model.get_path(next), None, False)
610
606
self._file_message_text_view.grab_focus()
612
608
def _save_current_file_message(self):
797
793
rev_dict['revision_id'] = rev.revision_id
801
class SavedCommitMessagesManager:
802
"""Save glogal and per-file commit messages.
804
Saves global commit message and utf-8 file_id->message dictionary
805
of per-file commit messages on disk. Re-reads them later for re-using.
808
def __init__(self, tree=None, branch=None):
809
"""If branch is None, builds empty messages, otherwise reads them
810
from branch's disk storage. 'tree' argument is for the future."""
812
self.global_message = u''
813
self.file_messages = {}
815
config = branch.get_config()._get_branch_data_config()
816
self.global_message = config.get_user_option(
817
'gtk_global_commit_message')
818
if self.global_message is None:
819
self.global_message = u''
820
file_messages = config.get_user_option('gtk_file_commit_messages')
821
if file_messages: # unicode and B-encoded:
822
self.file_messages = bencode.bdecode(
823
file_messages.encode('UTF-8'))
825
self.file_messages = {}
828
return self.global_message, self.file_messages
830
def is_not_empty(self):
831
return bool(self.global_message or self.file_messages)
833
def insert(self, global_message, file_info):
834
"""Formats per-file commit messages (list of dictionaries, one per file)
835
into one utf-8 file_id->message dictionary and merges this with
836
previously existing dictionary. Merges global commit message too."""
839
file_message = fi['message']
841
file_messages[fi['file_id']] = file_message # utf-8 strings
842
for k,v in file_messages.iteritems():
844
self.file_messages[k] = v + '\n******\n' + self.file_messages[k]
846
self.file_messages[k] = v
847
if self.global_message:
848
self.global_message = global_message + '\n******\n' \
849
+ self.global_message
851
self.global_message = global_message
853
def save(self, tree, branch):
854
# We store in branch's config, which can be a problem if two gcommit
855
# are done in two checkouts of one single branch (comments overwrite
856
# each other). Ideally should be in working tree. But uncommit does
857
# not always have a working tree, though it always has a branch.
858
# 'tree' argument is for the future
859
config = branch.get_config()
860
# should it be named "gtk_" or some more neutral name ("gui_" ?) to
861
# be compatible with qbzr in the future?
862
config.set_user_option('gtk_global_commit_message', self.global_message)
863
# bencode() does not know unicode objects but set_user_option()
865
config.set_user_option(
866
'gtk_file_commit_messages',
867
bencode.bencode(self.file_messages).decode('UTF-8'))
870
def save_commit_messages(local, master, old_revno, old_revid,
871
new_revno, new_revid):
875
mgr = SavedCommitMessagesManager(None, b)
876
revid_iterator = b.repository.iter_reverse_revision_history(old_revid)
877
cur_revno = old_revno
878
new_revision_id = old_revid
879
graph = b.repository.get_graph()
880
for rev_id in revid_iterator:
881
if cur_revno == new_revno:
884
rev = b.repository.get_revision(rev_id)
885
file_info = rev.properties.get('file-info', None)
886
if file_info is None:
889
file_info = bencode.bdecode(file_info.encode('UTF-8'))
890
global_message = osutils.safe_unicode(rev.message)
891
# Concatenate comment of the uncommitted revision
892
mgr.insert(global_message, file_info)
894
parents = graph.get_parent_map([rev_id]).get(rev_id, None)