/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: Edward Ari Bichetero
  • Date: 2011-02-24 09:49:08 UTC
  • mto: This revision was merged to the branch mainline in revision 715.
  • Revision ID: ebichete@yahoo.com-20110224094908-gm0b2phgcye3t32j
Stop crashes when bzr errors include markup-like text (that is not valid markup).

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