/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 16:50:29 UTC
  • mto: This revision was merged to the branch mainline in revision 741.
  • Revision ID: sinzui.is@verizon.net-20110731165029-9gixuqypi3lwapzm
Removed import_pygtk because gi does not impicitly call Main(). Inlined checks for gtk availablility.

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