/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: 2012-02-03 19:49:55 UTC
  • mto: This revision was merged to the branch mainline in revision 773.
  • Revision ID: sinzui.is@verizon.net-20120203194955-69jvr0mgu8igea0i
Updated bzr-notify to gtk3.

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
import re
18
18
 
 
19
from gi.repository import Gdk
19
20
from gi.repository import Gtk
20
21
from gi.repository import GObject
21
22
from gi.repository import Pango
22
23
 
23
24
from bzrlib import (
 
25
    bencode,
24
26
    errors,
25
27
    osutils,
26
28
    trace,
27
29
    )
28
 
try:
29
 
    from bzrlib import bencode
30
 
except ImportError:
31
 
    from bzrlib.util import bencode
32
 
 
33
30
from bzrlib.plugins.gtk.dialog import question_dialog
34
31
from bzrlib.plugins.gtk.errors import show_bzr_error
35
32
from bzrlib.plugins.gtk.i18n import _i18n
 
33
from bzrlib.plugins.gtk.commitmsgs import SavedCommitMessagesManager
36
34
 
37
35
try:
38
36
    import dbus
61
59
    last_revision = parents[0]
62
60
 
63
61
    if last_revision is not None:
64
 
        try:
65
 
            ignore = set(branch.repository.get_ancestry(last_revision,
66
 
                                                        topo_sorted=False))
67
 
        except errors.NoSuchRevision:
68
 
            # the last revision is a ghost : assume everything is new
69
 
            # except for it
70
 
            ignore = set([None, last_revision])
 
62
        graph = branch.repository.get_graph()
 
63
        ignore = set([r for r,ps in graph.iter_ancestry([last_revision])])
71
64
    else:
72
 
        ignore = set([None])
 
65
        ignore = set([])
73
66
 
74
67
    pm = []
75
68
    for merge in pending:
100
93
def _sanitize_and_decode_message(utf8_message):
101
94
    """Turn a utf-8 message into a sanitized Unicode message."""
102
95
    fixed_newline = _newline_variants_re.sub('\n', utf8_message)
103
 
    return fixed_newline.decode('utf-8')
 
96
    return osutils.safe_unicode(fixed_newline)
104
97
 
105
98
 
106
99
class CommitDialog(Gtk.Dialog):
107
100
    """Implementation of Commit."""
108
101
 
109
102
    def __init__(self, wt, selected=None, parent=None):
110
 
        GObject.GObject.__init__(self, title="Commit to %s" % wt.basedir,
111
 
                            parent=parent, flags=0,)
 
103
        super(CommitDialog, self).__init__(
 
104
            title="Commit to %s" % wt.basedir, parent=parent, flags=0)
112
105
        self.connect('delete-event', self._on_delete_window)
113
106
        self._question_dialog = question_dialog
114
107
 
121
114
        self._enable_per_file_commits = True
122
115
        self._commit_all_changes = True
123
116
        self.committed_revision_id = None # Nothing has been committed yet
124
 
        self._saved_commit_messages_manager = SavedCommitMessagesManager(self._wt, self._wt.branch)
 
117
        self._last_selected_file = None
 
118
        self._saved_commit_messages_manager = SavedCommitMessagesManager(
 
119
            self._wt, self._wt.branch)
125
120
 
126
121
        self.setup_params()
127
122
        self.construct()
131
126
        """Setup the member variables for state."""
132
127
        self._basis_tree = self._wt.basis_tree()
133
128
        self._delta = None
134
 
        self._pending = pending_revisions(self._wt)
 
129
        self._wt.lock_read()
 
130
        try:
 
131
            self._pending = pending_revisions(self._wt)
 
132
        finally:
 
133
            self._wt.unlock()
135
134
 
136
135
        self._is_checkout = (self._wt.branch.get_bound_location() is not None)
137
136
 
189
188
 
190
189
        all_enabled = (self._selected is None)
191
190
        # The first entry is always the 'whole tree'
192
 
        all_iter = store.append([None, None, all_enabled, 'All Files', '', ''])
 
191
        all_iter = store.append(["", "", all_enabled, 'All Files', '', ''])
193
192
        initial_cursor = store.get_path(all_iter)
194
193
        # should we pass specific_files?
195
194
        self._wt.lock_read()
226
225
        # This sets the cursor, which causes the expander to close, which
227
226
        # causes the _file_message_text_view to never get realized. So we have
228
227
        # to give it a little kick, or it warns when we try to grab the focus
229
 
        self._treeview_files.set_cursor(initial_cursor)
 
228
        self._treeview_files.set_cursor(initial_cursor, None, False)
230
229
 
231
230
        def _realize_file_message_tree_view(*args):
232
231
            self._file_message_text_view.realize()
286
285
        self._construct_right_pane()
287
286
        self._construct_action_pane()
288
287
 
289
 
        self.vbox.pack_start(self._hpane, True, True, 0)
 
288
        self.get_content_area().pack_start(self._hpane, True, True, 0)
290
289
        self._hpane.show()
291
290
        self.set_focus(self._global_message_text_view)
292
291
 
312
311
 
313
312
    def _construct_accelerators(self):
314
313
        group = Gtk.AccelGroup()
315
 
        group.connect_group(Gdk.keyval_from_name('N'),
316
 
                            Gdk.EventMask.CONTROL_MASK, 0, self._on_accel_next)
 
314
        group.connect(Gdk.keyval_from_name('N'),
 
315
                      Gdk.ModifierType.CONTROL_MASK, 0, self._on_accel_next)
317
316
        self.add_accel_group(group)
318
317
 
319
318
        # ignore the escape key (avoid closing the window)
326
325
 
327
326
        self._check_local = Gtk.CheckButton(_i18n("_Only commit locally"),
328
327
                                            use_underline=True)
329
 
        self._left_pane_box.pack_end(self._check_local, False, False)
 
328
        self._left_pane_box.pack_end(self._check_local, False, False, 0)
330
329
        self._check_local.set_active(False)
331
330
 
332
331
        self._hpane.pack1(self._left_pane_box, resize=False, shrink=False)
354
353
        self._button_cancel = Gtk.Button(stock=Gtk.STOCK_CANCEL)
355
354
        self._button_cancel.connect('clicked', self._on_cancel_clicked)
356
355
        self._button_cancel.show()
357
 
        self.action_area.pack_end(self._button_cancel)
 
356
        self.get_action_area().pack_end(
 
357
            self._button_cancel, True, True, 0)
358
358
        self._button_commit = Gtk.Button(_i18n("Comm_it"), use_underline=True)
359
359
        self._button_commit.connect('clicked', self._on_commit_clicked)
360
360
        self._button_commit.set_can_default(True)
361
361
        self._button_commit.show()
362
 
        self.action_area.pack_end(self._button_commit)
 
362
        self.get_action_area().pack_end(
 
363
            self._button_commit, True, True, 0)
363
364
        self._button_commit.grab_default()
364
365
 
365
366
    def _add_to_right_table(self, widget, weight, expanding=False):
385
386
        # file_label.show()
386
387
        self._files_box.pack_start(file_label, False, True, 0)
387
388
 
388
 
        self._commit_all_files_radio = Gtk.RadioButton(
 
389
        self._commit_all_files_radio = Gtk.RadioButton.new_with_label(
389
390
            None, _i18n("Commit all changes"))
390
391
        self._files_box.pack_start(self._commit_all_files_radio, False, True, 0)
391
392
        self._commit_all_files_radio.show()
392
393
        self._commit_all_files_radio.connect('toggled',
393
394
            self._toggle_commit_selection)
394
 
        self._commit_selected_radio = Gtk.RadioButton(
 
395
        self._commit_selected_radio = Gtk.RadioButton.new_with_label_from_widget(
395
396
            self._commit_all_files_radio, _i18n("Only commit selected changes"))
396
397
        self._files_box.pack_start(self._commit_selected_radio, False, True, 0)
397
398
        self._commit_selected_radio.show()
409
410
        scroller.add(self._treeview_files)
410
411
        scroller.set_shadow_type(Gtk.ShadowType.IN)
411
412
        scroller.show()
412
 
        self._files_box.pack_start(scroller,
413
 
                                   expand=True, fill=True)
 
413
        self._files_box.pack_start(scroller, True, True, 0)
414
414
        self._files_box.show()
415
415
        self._left_pane_box.pack_start(self._files_box, True, True, 0)
416
416
 
445
445
                                     self._on_treeview_files_cursor_changed)
446
446
 
447
447
    def _toggle_commit(self, cell, path, model):
448
 
        if model[path][0] is None: # No file_id means 'All Files'
 
448
        if model[path][0] == "": # No file_id means 'All Files'
449
449
            new_val = not model[path][2]
450
450
            for node in model:
451
451
                node[2] = new_val
461
461
                checked_col.set_visible(False)
462
462
            else:
463
463
                checked_col.set_visible(True)
464
 
            renderer = checked_col.get_cell_renderers()[0]
 
464
            renderer = checked_col.get_cells()[0]
465
465
            renderer.set_property('activatable', not all_files)
466
466
 
467
467
    def _construct_pending_list(self):
473
473
        pending_message = Gtk.Label()
474
474
        pending_message.set_markup(
475
475
            _i18n('<i>* Cannot select specific files when merging</i>'))
476
 
        self._pending_box.pack_start(pending_message, expand=False, padding=5)
 
476
        self._pending_box.pack_start(pending_message, False, True, 5)
477
477
        pending_message.show()
478
478
 
479
479
        pending_label = Gtk.Label(label=_i18n('Pending Revisions'))
480
 
        self._pending_box.pack_start(pending_label, expand=False, padding=0)
 
480
        self._pending_box.pack_start(pending_label, False, True, 0)
481
481
        pending_label.show()
482
482
 
483
483
        scroller = Gtk.ScrolledWindow()
486
486
        scroller.add(self._treeview_pending)
487
487
        scroller.set_shadow_type(Gtk.ShadowType.IN)
488
488
        scroller.show()
489
 
        self._pending_box.pack_start(scroller,
490
 
                                     expand=True, fill=True, padding=5)
 
489
        self._pending_box.pack_start(scroller, True, True, 5)
491
490
        self._treeview_pending.show()
492
491
        self._left_pane_box.pack_start(self._pending_box, True, True, 0)
493
492
 
536
535
        self._file_message_text_view.set_accepts_tab(False)
537
536
        self._file_message_text_view.show()
538
537
 
539
 
        self._file_message_expander = Gtk.Expander(_i18n('File commit message'))
 
538
        self._file_message_expander = Gtk.Expander(
 
539
            label=_i18n('File commit message'))
540
540
        self._file_message_expander.set_expanded(True)
541
541
        self._file_message_expander.add(scroller)
542
542
        self._add_to_right_table(self._file_message_expander, 1, False)
568
568
 
569
569
    def _on_treeview_files_cursor_changed(self, treeview):
570
570
        treeselection = treeview.get_selection()
 
571
        if treeselection is None:
 
572
            # The treeview was probably destroyed as the dialog closes.
 
573
            return
571
574
        (model, selection) = treeselection.get_selected()
572
575
 
573
576
        if selection is not None:
574
577
            path, display_path = model.get(selection, 1, 3)
575
578
            self._diff_label.set_text(_i18n('Diff for ') + display_path)
576
 
            if path is None:
 
579
            if path == "":
577
580
                self._diff_view.show_diff(None)
578
581
            else:
579
 
                self._diff_view.show_diff([path.decode('UTF-8')])
 
582
                self._diff_view.show_diff([osutils.safe_unicode(path)])
580
583
            self._update_per_file_info(selection)
581
584
 
582
585
    def _on_accel_next(self, accel_group, window, keyval, modifier):
593
596
            # We have either made it to the end of the list, or nothing was
594
597
            # selected. Either way, select All Files, and jump to the global
595
598
            # commit message.
596
 
            self._treeview_files.set_cursor((0,))
 
599
            self._treeview_files.set_cursor(
 
600
                Gtk.TreePath(path=0), "", False)
597
601
            self._global_message_text_view.grab_focus()
598
602
        else:
599
603
            # Set the cursor to this entry, and jump to the per-file commit
600
604
            # message
601
 
            self._treeview_files.set_cursor(model.get_path(next))
 
605
            self._treeview_files.set_cursor(model.get_path(next), None, False)
602
606
            self._file_message_text_view.grab_focus()
603
607
 
604
608
    def _save_current_file_message(self):
606
610
            return # Nothing to save
607
611
        text_buffer = self._file_message_text_view.get_buffer()
608
612
        cur_text = text_buffer.get_text(text_buffer.get_start_iter(),
609
 
                                        text_buffer.get_end_iter())
 
613
                                        text_buffer.get_end_iter(), True)
610
614
        last_selected = self._files_store.get_iter(self._last_selected_file)
611
615
        self._files_store.set_value(last_selected, 5, cur_text)
612
616
 
618
622
        self._save_current_file_message()
619
623
        text_buffer = self._file_message_text_view.get_buffer()
620
624
        file_id, display_path, message = self._files_store.get(selection, 0, 3, 5)
621
 
        if file_id is None: # Whole tree
 
625
        if file_id == "": # Whole tree
622
626
            self._file_message_expander.set_label(_i18n('File commit message'))
623
627
            self._file_message_expander.set_expanded(False)
624
628
            self._file_message_expander.set_sensitive(False)
641
645
        files = []
642
646
        records = iter(self._files_store)
643
647
        rec = records.next() # Skip the All Files record
644
 
        assert rec[0] is None, "Are we skipping the wrong record?"
 
648
        assert rec[0] == "", "Are we skipping the wrong record?"
645
649
 
646
650
        file_info = []
647
651
        for record in records:
648
652
            if self._commit_all_changes or record[2]:# [2] checkbox
649
 
                file_id = record[0] # [0] file_id
650
 
                path = record[1]    # [1] real path
 
653
                file_id = osutils.safe_utf8(record[0]) # [0] file_id
 
654
                path = osutils.safe_utf8(record[1])    # [1] real path
651
655
                # [5] commit message
652
656
                file_message = _sanitize_and_decode_message(record[5])
653
657
                files.append(path.decode('UTF-8'))
760
764
    def _get_global_commit_message(self):
761
765
        buf = self._global_message_text_view.get_buffer()
762
766
        start, end = buf.get_bounds()
763
 
        text = buf.get_text(start, end)
 
767
        text = buf.get_text(start, end, True)
764
768
        return _sanitize_and_decode_message(text)
765
769
 
766
770
    def _set_global_commit_message(self, message):
789
793
        rev_dict['revision_id'] = rev.revision_id
790
794
        return rev_dict
791
795
 
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)