/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: Jelmer Vernooij
  • Date: 2011-12-20 16:47:38 UTC
  • Revision ID: jelmer@canonical.com-20111220164738-l6tgnbttkxmq6877
Cope with some strings being unicode when returned by some versions of gtk.

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._saved_commit_messages_manager = SavedCommitMessagesManager(
 
118
            self._wt, self._wt.branch)
125
119
 
126
120
        self.setup_params()
127
121
        self.construct()
131
125
        """Setup the member variables for state."""
132
126
        self._basis_tree = self._wt.basis_tree()
133
127
        self._delta = None
134
 
        self._pending = pending_revisions(self._wt)
 
128
        self._wt.lock_read()
 
129
        try:
 
130
            self._pending = pending_revisions(self._wt)
 
131
        finally:
 
132
            self._wt.unlock()
135
133
 
136
134
        self._is_checkout = (self._wt.branch.get_bound_location() is not None)
137
135
 
189
187
 
190
188
        all_enabled = (self._selected is None)
191
189
        # The first entry is always the 'whole tree'
192
 
        all_iter = store.append([None, None, all_enabled, 'All Files', '', ''])
 
190
        all_iter = store.append(["", "", all_enabled, 'All Files', '', ''])
193
191
        initial_cursor = store.get_path(all_iter)
194
192
        # should we pass specific_files?
195
193
        self._wt.lock_read()
226
224
        # This sets the cursor, which causes the expander to close, which
227
225
        # causes the _file_message_text_view to never get realized. So we have
228
226
        # to give it a little kick, or it warns when we try to grab the focus
229
 
        self._treeview_files.set_cursor(initial_cursor)
 
227
        self._treeview_files.set_cursor(initial_cursor, None, False)
230
228
 
231
229
        def _realize_file_message_tree_view(*args):
232
230
            self._file_message_text_view.realize()
286
284
        self._construct_right_pane()
287
285
        self._construct_action_pane()
288
286
 
289
 
        self.vbox.pack_start(self._hpane, True, True, 0)
 
287
        self.get_content_area().pack_start(self._hpane, True, True, 0)
290
288
        self._hpane.show()
291
289
        self.set_focus(self._global_message_text_view)
292
290
 
312
310
 
313
311
    def _construct_accelerators(self):
314
312
        group = Gtk.AccelGroup()
315
 
        group.connect_group(Gdk.keyval_from_name('N'),
316
 
                            Gdk.EventMask.CONTROL_MASK, 0, self._on_accel_next)
 
313
        group.connect(Gdk.keyval_from_name('N'),
 
314
                      Gdk.ModifierType.CONTROL_MASK, 0, self._on_accel_next)
317
315
        self.add_accel_group(group)
318
316
 
319
317
        # ignore the escape key (avoid closing the window)
326
324
 
327
325
        self._check_local = Gtk.CheckButton(_i18n("_Only commit locally"),
328
326
                                            use_underline=True)
329
 
        self._left_pane_box.pack_end(self._check_local, False, False)
 
327
        self._left_pane_box.pack_end(self._check_local, False, False, 0)
330
328
        self._check_local.set_active(False)
331
329
 
332
330
        self._hpane.pack1(self._left_pane_box, resize=False, shrink=False)
354
352
        self._button_cancel = Gtk.Button(stock=Gtk.STOCK_CANCEL)
355
353
        self._button_cancel.connect('clicked', self._on_cancel_clicked)
356
354
        self._button_cancel.show()
357
 
        self.action_area.pack_end(self._button_cancel)
 
355
        self.get_action_area().pack_end(
 
356
            self._button_cancel, True, True, 0)
358
357
        self._button_commit = Gtk.Button(_i18n("Comm_it"), use_underline=True)
359
358
        self._button_commit.connect('clicked', self._on_commit_clicked)
360
359
        self._button_commit.set_can_default(True)
361
360
        self._button_commit.show()
362
 
        self.action_area.pack_end(self._button_commit)
 
361
        self.get_action_area().pack_end(
 
362
            self._button_commit, True, True, 0)
363
363
        self._button_commit.grab_default()
364
364
 
365
365
    def _add_to_right_table(self, widget, weight, expanding=False):
385
385
        # file_label.show()
386
386
        self._files_box.pack_start(file_label, False, True, 0)
387
387
 
388
 
        self._commit_all_files_radio = Gtk.RadioButton(
 
388
        self._commit_all_files_radio = Gtk.RadioButton.new_with_label(
389
389
            None, _i18n("Commit all changes"))
390
390
        self._files_box.pack_start(self._commit_all_files_radio, False, True, 0)
391
391
        self._commit_all_files_radio.show()
392
392
        self._commit_all_files_radio.connect('toggled',
393
393
            self._toggle_commit_selection)
394
 
        self._commit_selected_radio = Gtk.RadioButton(
 
394
        self._commit_selected_radio = Gtk.RadioButton.new_with_label_from_widget(
395
395
            self._commit_all_files_radio, _i18n("Only commit selected changes"))
396
396
        self._files_box.pack_start(self._commit_selected_radio, False, True, 0)
397
397
        self._commit_selected_radio.show()
409
409
        scroller.add(self._treeview_files)
410
410
        scroller.set_shadow_type(Gtk.ShadowType.IN)
411
411
        scroller.show()
412
 
        self._files_box.pack_start(scroller,
413
 
                                   expand=True, fill=True)
 
412
        self._files_box.pack_start(scroller, True, True, 0)
414
413
        self._files_box.show()
415
414
        self._left_pane_box.pack_start(self._files_box, True, True, 0)
416
415
 
445
444
                                     self._on_treeview_files_cursor_changed)
446
445
 
447
446
    def _toggle_commit(self, cell, path, model):
448
 
        if model[path][0] is None: # No file_id means 'All Files'
 
447
        if model[path][0] == "": # No file_id means 'All Files'
449
448
            new_val = not model[path][2]
450
449
            for node in model:
451
450
                node[2] = new_val
461
460
                checked_col.set_visible(False)
462
461
            else:
463
462
                checked_col.set_visible(True)
464
 
            renderer = checked_col.get_cell_renderers()[0]
 
463
            renderer = checked_col.get_cells()[0]
465
464
            renderer.set_property('activatable', not all_files)
466
465
 
467
466
    def _construct_pending_list(self):
473
472
        pending_message = Gtk.Label()
474
473
        pending_message.set_markup(
475
474
            _i18n('<i>* Cannot select specific files when merging</i>'))
476
 
        self._pending_box.pack_start(pending_message, expand=False, padding=5)
 
475
        self._pending_box.pack_start(pending_message, False, True, 5)
477
476
        pending_message.show()
478
477
 
479
478
        pending_label = Gtk.Label(label=_i18n('Pending Revisions'))
480
 
        self._pending_box.pack_start(pending_label, expand=False, padding=0)
 
479
        self._pending_box.pack_start(pending_label, False, True, 0)
481
480
        pending_label.show()
482
481
 
483
482
        scroller = Gtk.ScrolledWindow()
486
485
        scroller.add(self._treeview_pending)
487
486
        scroller.set_shadow_type(Gtk.ShadowType.IN)
488
487
        scroller.show()
489
 
        self._pending_box.pack_start(scroller,
490
 
                                     expand=True, fill=True, padding=5)
 
488
        self._pending_box.pack_start(scroller, True, True, 5)
491
489
        self._treeview_pending.show()
492
490
        self._left_pane_box.pack_start(self._pending_box, True, True, 0)
493
491
 
536
534
        self._file_message_text_view.set_accepts_tab(False)
537
535
        self._file_message_text_view.show()
538
536
 
539
 
        self._file_message_expander = Gtk.Expander(_i18n('File commit message'))
 
537
        self._file_message_expander = Gtk.Expander(
 
538
            label=_i18n('File commit message'))
540
539
        self._file_message_expander.set_expanded(True)
541
540
        self._file_message_expander.add(scroller)
542
541
        self._add_to_right_table(self._file_message_expander, 1, False)
573
572
        if selection is not None:
574
573
            path, display_path = model.get(selection, 1, 3)
575
574
            self._diff_label.set_text(_i18n('Diff for ') + display_path)
576
 
            if path is None:
 
575
            if path == "":
577
576
                self._diff_view.show_diff(None)
578
577
            else:
579
 
                self._diff_view.show_diff([path.decode('UTF-8')])
 
578
                self._diff_view.show_diff([osutils.safe_unicode(path)])
580
579
            self._update_per_file_info(selection)
581
580
 
582
581
    def _on_accel_next(self, accel_group, window, keyval, modifier):
593
592
            # We have either made it to the end of the list, or nothing was
594
593
            # selected. Either way, select All Files, and jump to the global
595
594
            # commit message.
596
 
            self._treeview_files.set_cursor((0,))
 
595
            self._treeview_files.set_cursor(
 
596
                Gtk.TreePath(path=0), "", False)
597
597
            self._global_message_text_view.grab_focus()
598
598
        else:
599
599
            # Set the cursor to this entry, and jump to the per-file commit
600
600
            # message
601
 
            self._treeview_files.set_cursor(model.get_path(next))
 
601
            self._treeview_files.set_cursor(model.get_path(next), None, False)
602
602
            self._file_message_text_view.grab_focus()
603
603
 
604
604
    def _save_current_file_message(self):
606
606
            return # Nothing to save
607
607
        text_buffer = self._file_message_text_view.get_buffer()
608
608
        cur_text = text_buffer.get_text(text_buffer.get_start_iter(),
609
 
                                        text_buffer.get_end_iter())
 
609
                                        text_buffer.get_end_iter(), True)
610
610
        last_selected = self._files_store.get_iter(self._last_selected_file)
611
611
        self._files_store.set_value(last_selected, 5, cur_text)
612
612
 
618
618
        self._save_current_file_message()
619
619
        text_buffer = self._file_message_text_view.get_buffer()
620
620
        file_id, display_path, message = self._files_store.get(selection, 0, 3, 5)
621
 
        if file_id is None: # Whole tree
 
621
        if file_id == "": # Whole tree
622
622
            self._file_message_expander.set_label(_i18n('File commit message'))
623
623
            self._file_message_expander.set_expanded(False)
624
624
            self._file_message_expander.set_sensitive(False)
641
641
        files = []
642
642
        records = iter(self._files_store)
643
643
        rec = records.next() # Skip the All Files record
644
 
        assert rec[0] is None, "Are we skipping the wrong record?"
 
644
        assert rec[0] == "", "Are we skipping the wrong record?"
645
645
 
646
646
        file_info = []
647
647
        for record in records:
648
648
            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
 
649
                file_id = osutils.safe_utf8(record[0]) # [0] file_id
 
650
                path = osutils.safe_utf8(record[1])    # [1] real path
651
651
                # [5] commit message
652
652
                file_message = _sanitize_and_decode_message(record[5])
653
653
                files.append(path.decode('UTF-8'))
760
760
    def _get_global_commit_message(self):
761
761
        buf = self._global_message_text_view.get_buffer()
762
762
        start, end = buf.get_bounds()
763
 
        text = buf.get_text(start, end)
 
763
        text = buf.get_text(start, end, True)
764
764
        return _sanitize_and_decode_message(text)
765
765
 
766
766
    def _set_global_commit_message(self, message):
789
789
        rev_dict['revision_id'] = rev.revision_id
790
790
        return rev_dict
791
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)