/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: Martin Pool
  • Date: 2010-05-27 03:07:30 UTC
  • mfrom: (688.1.5 201956-help)
  • Revision ID: mbp@canonical.com-20100527030730-os0opv1xroetccm9
Make find/goto more discoverable

Show diffs side-by-side

added added

removed removed

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