/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: 2009-06-04 21:10:21 UTC
  • Revision ID: jelmer@samba.org-20090604211021-o3ycs1uy97mwoime
Catch NoKeyringDaemonError.

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
16
16
 
 
17
import os.path
 
18
import re
 
19
 
17
20
try:
18
21
    import pygtk
19
22
    pygtk.require("2.0")
24
27
import gobject
25
28
import pango
26
29
 
27
 
import os.path
28
 
import re
29
 
 
30
 
from bzrlib import errors, osutils
31
 
from bzrlib.trace import mutter
 
30
from bzrlib import (
 
31
    branch,
 
32
    errors,
 
33
    osutils,
 
34
    trace,
 
35
    )
32
36
from bzrlib.util import bencode
33
37
 
34
38
from bzrlib.plugins.gtk import _i18n
35
 
from dialog import error_dialog, question_dialog
36
 
from errors import show_bzr_error
 
39
from bzrlib.plugins.gtk.dialog import question_dialog
 
40
from bzrlib.plugins.gtk.errors import show_bzr_error
37
41
 
38
42
try:
39
43
    import dbus
97
101
    return pm
98
102
 
99
103
 
 
104
_newline_variants_re = re.compile(r'\r\n?')
 
105
def _sanitize_and_decode_message(utf8_message):
 
106
    """Turn a utf-8 message into a sanitized Unicode message."""
 
107
    fixed_newline = _newline_variants_re.sub('\n', utf8_message)
 
108
    return fixed_newline.decode('utf-8')
 
109
 
 
110
 
100
111
class CommitDialog(gtk.Dialog):
101
112
    """Implementation of Commit."""
102
113
 
103
114
    def __init__(self, wt, selected=None, parent=None):
104
 
        gtk.Dialog.__init__(self, title="Commit - Olive",
105
 
                                  parent=parent,
106
 
                                  flags=0,
107
 
                                  buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
 
115
        gtk.Dialog.__init__(self, title="Commit to %s" % wt.basedir,
 
116
                            parent=parent, flags=0,)
 
117
        self.connect('delete-event', self._on_delete_window)
108
118
        self._question_dialog = question_dialog
109
119
 
 
120
        self.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_NORMAL)
 
121
 
110
122
        self._wt = wt
111
123
        # TODO: Do something with this value, it is used by Olive
112
124
        #       It used to set all changes but this one to False
114
126
        self._enable_per_file_commits = True
115
127
        self._commit_all_changes = True
116
128
        self.committed_revision_id = None # Nothing has been committed yet
 
129
        self._saved_commit_messages_manager = SavedCommitMessagesManager(self._wt, self._wt.branch)
117
130
 
118
131
        self.setup_params()
119
132
        self.construct()
188
201
        self._basis_tree.lock_read()
189
202
        try:
190
203
            from diff import iter_changes_to_status
 
204
            saved_file_messages = self._saved_commit_messages_manager.get()[1]
191
205
            for (file_id, real_path, change_type, display_path
192
206
                ) in iter_changes_to_status(self._basis_tree, self._wt):
193
207
                if self._selected and real_path != self._selected:
194
208
                    enabled = False
195
209
                else:
196
210
                    enabled = True
 
211
                try:
 
212
                    default_message = saved_file_messages[file_id]
 
213
                except KeyError:
 
214
                    default_message = ''
197
215
                item_iter = store.append([
198
216
                    file_id,
199
217
                    real_path.encode('UTF-8'),
200
218
                    enabled,
201
219
                    display_path.encode('UTF-8'),
202
220
                    change_type,
203
 
                    '', # Initial comment
 
221
                    default_message, # Initial comment
204
222
                    ])
205
223
                if self._selected and enabled:
206
224
                    initial_cursor = store.get_path(item_iter)
232
250
                proxy_obj = bus.get_object('org.freedesktop.NetworkManager',
233
251
                                           '/org/freedesktop/NetworkManager')
234
252
            except dbus.DBusException:
235
 
                mutter("networkmanager not available.")
 
253
                trace.mutter("networkmanager not available.")
236
254
                self._check_local.show()
237
255
                return
238
256
            
244
262
            except dbus.DBusException, e:
245
263
                # Silently drop errors. While DBus may be
246
264
                # available, NetworkManager doesn't necessarily have to be
247
 
                mutter("unable to get networkmanager state: %r" % e)
 
265
                trace.mutter("unable to get networkmanager state: %r" % e)
248
266
        self._check_local.show()
249
267
 
250
268
    def _fill_in_per_file_info(self):
338
356
        self._hpane.pack2(self._right_pane_table, resize=True, shrink=True)
339
357
 
340
358
    def _construct_action_pane(self):
 
359
        self._button_cancel = gtk.Button(stock=gtk.STOCK_CANCEL)
 
360
        self._button_cancel.connect('clicked', self._on_cancel_clicked)
 
361
        self._button_cancel.show()
 
362
        self.action_area.pack_end(self._button_cancel)
341
363
        self._button_commit = gtk.Button(_i18n("Comm_it"), use_underline=True)
342
364
        self._button_commit.connect('clicked', self._on_commit_clicked)
343
365
        self._button_commit.set_flags(gtk.CAN_DEFAULT)
539
561
        scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
540
562
 
541
563
        self._global_message_text_view = gtk.TextView()
 
564
        self._set_global_commit_message(self._saved_commit_messages_manager.get()[0])
542
565
        self._global_message_text_view.modify_font(pango.FontDescription("Monospace"))
543
566
        scroller.add(self._global_message_text_view)
544
567
        scroller.set_shadow_type(gtk.SHADOW_IN)
630
653
            if self._commit_all_changes or record[2]:# [2] checkbox
631
654
                file_id = record[0] # [0] file_id
632
655
                path = record[1]    # [1] real path
633
 
                file_message = record[5] # [5] commit message
 
656
                # [5] commit message
 
657
                file_message = _sanitize_and_decode_message(record[5])
634
658
                files.append(path.decode('UTF-8'))
635
659
                if self._enable_per_file_commits and file_message:
636
660
                    # All of this needs to be utf-8 information
 
661
                    file_message = file_message.encode('UTF-8')
637
662
                    file_info.append({'path':path, 'file_id':file_id,
638
663
                                     'message':file_message})
639
664
        file_info.sort(key=lambda x:(x['path'], x['file_id']))
643
668
            return files, []
644
669
 
645
670
    @show_bzr_error
 
671
    def _on_cancel_clicked(self, button):
 
672
        """ Cancel button clicked handler. """
 
673
        self._do_cancel()
 
674
 
 
675
    @show_bzr_error
 
676
    def _on_delete_window(self, source, event):
 
677
        """ Delete window handler. """
 
678
        self._do_cancel()
 
679
 
 
680
    def _do_cancel(self):
 
681
        """If requested, saves commit messages when cancelling gcommit; they are re-used by a next gcommit"""
 
682
        mgr = SavedCommitMessagesManager()
 
683
        self._saved_commit_messages_manager = mgr
 
684
        mgr.insert(self._get_global_commit_message(),
 
685
                   self._get_specific_files()[1])
 
686
        if mgr.is_not_empty(): # maybe worth saving
 
687
            response = self._question_dialog(
 
688
                _i18n('Commit cancelled'),
 
689
                _i18n('Do you want to save your commit messages ?'),
 
690
                parent=self)
 
691
            if response == gtk.RESPONSE_NO:
 
692
                 # save nothing and destroy old comments if any
 
693
                mgr = SavedCommitMessagesManager()
 
694
        mgr.save(self._wt, self._wt.branch)
 
695
        self.response(gtk.RESPONSE_CANCEL) # close window
 
696
 
 
697
    @show_bzr_error
646
698
    def _on_commit_clicked(self, button):
647
699
        """ Commit button clicked handler. """
648
700
        self._do_commit()
653
705
        if message == '':
654
706
            response = self._question_dialog(
655
707
                _i18n('Commit with an empty message?'),
656
 
                _i18n('You can describe your commit intent in the message.'))
 
708
                _i18n('You can describe your commit intent in the message.'),
 
709
                parent=self)
657
710
            if response == gtk.RESPONSE_NO:
658
711
                # Kindly give focus to message area
659
712
                self._global_message_text_view.grab_focus()
673
726
        for path in self._wt.unknowns():
674
727
            response = self._question_dialog(
675
728
                _i18n("Commit with unknowns?"),
676
 
                _i18n("Unknown files exist in the working tree. Commit anyway?"))
 
729
                _i18n("Unknown files exist in the working tree. Commit anyway?"),
 
730
                parent=self)
 
731
                # Doesn't set a parent for the dialog..
677
732
            if response == gtk.RESPONSE_NO:
678
733
                return
679
734
            break
693
748
            response = self._question_dialog(
694
749
                _i18n('Commit with no changes?'),
695
750
                _i18n('There are no changes in the working tree.'
696
 
                      ' Do you want to commit anyway?'))
 
751
                      ' Do you want to commit anyway?'),
 
752
                parent=self)
697
753
            if response == gtk.RESPONSE_YES:
698
754
                rev_id = self._wt.commit(message,
699
755
                               allow_pointless=True,
702
758
                               specific_files=specific_files,
703
759
                               revprops=revprops)
704
760
        self.committed_revision_id = rev_id
 
761
        # destroy old comments if any
 
762
        SavedCommitMessagesManager().save(self._wt, self._wt.branch)
705
763
        self.response(gtk.RESPONSE_OK)
706
764
 
707
765
    def _get_global_commit_message(self):
708
766
        buf = self._global_message_text_view.get_buffer()
709
767
        start, end = buf.get_bounds()
710
 
        return buf.get_text(start, end).decode('utf-8')
 
768
        text = buf.get_text(start, end)
 
769
        return _sanitize_and_decode_message(text)
711
770
 
712
771
    def _set_global_commit_message(self, message):
713
772
        """Just a helper for the test suite."""
734
793
                                       show_offset=False)
735
794
        rev_dict['revision_id'] = rev.revision_id
736
795
        return rev_dict
 
796
 
 
797
 
 
798
class SavedCommitMessagesManager:
 
799
    """Save glogal and per-file commit messages.
 
800
 
 
801
    Saves global commit message and utf-8 file_id->message dictionary
 
802
    of per-file commit messages on disk. Re-reads them later for re-using.
 
803
    """
 
804
 
 
805
    def __init__(self, tree=None, branch=None):
 
806
        """If branch is None, builds empty messages, otherwise reads them
 
807
        from branch's disk storage. 'tree' argument is for the future."""
 
808
        if branch is None:
 
809
            self.global_message = u''
 
810
            self.file_messages = {}
 
811
        else:
 
812
            config = branch.get_config()._get_branch_data_config()
 
813
            self.global_message = config.get_user_option(
 
814
                'gtk_global_commit_message')
 
815
            if self.global_message is None:
 
816
                self.global_message = u''
 
817
            file_messages = config.get_user_option('gtk_file_commit_messages')
 
818
            if file_messages: # unicode and B-encoded:
 
819
                self.file_messages = bencode.bdecode(
 
820
                    file_messages.encode('UTF-8'))
 
821
            else:
 
822
                self.file_messages = {}
 
823
 
 
824
    def get(self):
 
825
        return self.global_message, self.file_messages
 
826
 
 
827
    def is_not_empty(self):
 
828
        return bool(self.global_message or self.file_messages)
 
829
 
 
830
    def insert(self, global_message, file_info):
 
831
        """Formats per-file commit messages (list of dictionaries, one per file)
 
832
        into one utf-8 file_id->message dictionary and merges this with
 
833
        previously existing dictionary. Merges global commit message too."""
 
834
        file_messages = {}
 
835
        for fi in file_info:
 
836
            file_message = fi['message']
 
837
            if file_message:
 
838
                file_messages[fi['file_id']] = file_message # utf-8 strings
 
839
        for k,v in file_messages.iteritems():
 
840
            try:
 
841
                self.file_messages[k] = v + '\n******\n' + self.file_messages[k]
 
842
            except KeyError:
 
843
                self.file_messages[k] = v
 
844
        if self.global_message:
 
845
            self.global_message = global_message + '\n******\n' \
 
846
                + self.global_message
 
847
        else:
 
848
            self.global_message = global_message
 
849
 
 
850
    def save(self, tree, branch):
 
851
        # We store in branch's config, which can be a problem if two gcommit
 
852
        # are done in two checkouts of one single branch (comments overwrite
 
853
        # each other). Ideally should be in working tree. But uncommit does
 
854
        # not always have a working tree, though it always has a branch.
 
855
        # 'tree' argument is for the future
 
856
        config = branch.get_config()
 
857
        # should it be named "gtk_" or some more neutral name ("gui_" ?) to
 
858
        # be compatible with qbzr in the future?
 
859
        config.set_user_option('gtk_global_commit_message', self.global_message)
 
860
        # bencode() does not know unicode objects but set_user_option()
 
861
        # requires one:
 
862
        config.set_user_option(
 
863
            'gtk_file_commit_messages',
 
864
            bencode.bencode(self.file_messages).decode('UTF-8'))
 
865
 
 
866
 
 
867
def save_commit_messages(local, master, old_revno, old_revid,
 
868
                         new_revno, new_revid):
 
869
    b = local
 
870
    if b is None:
 
871
        b = master
 
872
    mgr = SavedCommitMessagesManager(None, b)
 
873
    revid_iterator = b.repository.iter_reverse_revision_history(old_revid)
 
874
    cur_revno = old_revno
 
875
    new_revision_id = old_revid
 
876
    graph = b.repository.get_graph()
 
877
    for rev_id in revid_iterator:
 
878
        if cur_revno == new_revno:
 
879
            break
 
880
        cur_revno -= 1
 
881
        rev = b.repository.get_revision(rev_id)
 
882
        file_info = rev.properties.get('file-info', None)
 
883
        if file_info is None:
 
884
            file_info = {}
 
885
        else:
 
886
            file_info = bencode.bdecode(file_info.encode('UTF-8'))
 
887
        global_message = osutils.safe_unicode(rev.message)
 
888
        # Concatenate comment of the uncommitted revision
 
889
        mgr.insert(global_message, file_info)
 
890
 
 
891
        parents = graph.get_parent_map([rev_id]).get(rev_id, None)
 
892
        if not parents:
 
893
            continue
 
894
    mgr.save(None, b)