/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: Vincent Ladeuil
  • Date: 2009-12-10 16:13:32 UTC
  • Revision ID: v.ladeuil+lp@free.fr-20091210161332-d9vlfd6jy6zolxa1
Thanks to lamont, we know spell Prettifying with the right number of 't'.

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
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
 
from dialog import error_dialog, question_dialog
36
 
from errors import show_bzr_error
 
42
from bzrlib.plugins.gtk.dialog import question_dialog
 
43
from bzrlib.plugins.gtk.errors import show_bzr_error
37
44
 
38
45
try:
39
46
    import dbus
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
 
        gtk.Dialog.__init__(self, title="Commit - Olive",
105
 
                                  parent=parent,
106
 
                                  flags=0,
107
 
                                  buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
 
118
        gtk.Dialog.__init__(self, title="Commit to %s" % wt.basedir,
 
119
                            parent=parent, flags=0,)
 
120
        self.connect('delete-event', self._on_delete_window)
108
121
        self._question_dialog = question_dialog
109
122
 
 
123
        self.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_NORMAL)
 
124
 
110
125
        self._wt = wt
111
126
        # TODO: Do something with this value, it is used by Olive
112
127
        #       It used to set all changes but this one to False
114
129
        self._enable_per_file_commits = True
115
130
        self._commit_all_changes = True
116
131
        self.committed_revision_id = None # Nothing has been committed yet
 
132
        self._saved_commit_messages_manager = SavedCommitMessagesManager(self._wt, self._wt.branch)
117
133
 
118
134
        self.setup_params()
119
135
        self.construct()
188
204
        self._basis_tree.lock_read()
189
205
        try:
190
206
            from diff import iter_changes_to_status
 
207
            saved_file_messages = self._saved_commit_messages_manager.get()[1]
191
208
            for (file_id, real_path, change_type, display_path
192
209
                ) in iter_changes_to_status(self._basis_tree, self._wt):
193
210
                if self._selected and real_path != self._selected:
194
211
                    enabled = False
195
212
                else:
196
213
                    enabled = True
 
214
                try:
 
215
                    default_message = saved_file_messages[file_id]
 
216
                except KeyError:
 
217
                    default_message = ''
197
218
                item_iter = store.append([
198
219
                    file_id,
199
220
                    real_path.encode('UTF-8'),
200
221
                    enabled,
201
222
                    display_path.encode('UTF-8'),
202
223
                    change_type,
203
 
                    '', # Initial comment
 
224
                    default_message, # Initial comment
204
225
                    ])
205
226
                if self._selected and enabled:
206
227
                    initial_cursor = store.get_path(item_iter)
232
253
                proxy_obj = bus.get_object('org.freedesktop.NetworkManager',
233
254
                                           '/org/freedesktop/NetworkManager')
234
255
            except dbus.DBusException:
235
 
                mutter("networkmanager not available.")
 
256
                trace.mutter("networkmanager not available.")
236
257
                self._check_local.show()
237
258
                return
238
259
            
244
265
            except dbus.DBusException, e:
245
266
                # Silently drop errors. While DBus may be
246
267
                # available, NetworkManager doesn't necessarily have to be
247
 
                mutter("unable to get networkmanager state: %r" % e)
 
268
                trace.mutter("unable to get networkmanager state: %r" % e)
248
269
        self._check_local.show()
249
270
 
250
271
    def _fill_in_per_file_info(self):
338
359
        self._hpane.pack2(self._right_pane_table, resize=True, shrink=True)
339
360
 
340
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)
341
366
        self._button_commit = gtk.Button(_i18n("Comm_it"), use_underline=True)
342
367
        self._button_commit.connect('clicked', self._on_commit_clicked)
343
368
        self._button_commit.set_flags(gtk.CAN_DEFAULT)
539
564
        scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
540
565
 
541
566
        self._global_message_text_view = gtk.TextView()
 
567
        self._set_global_commit_message(self._saved_commit_messages_manager.get()[0])
542
568
        self._global_message_text_view.modify_font(pango.FontDescription("Monospace"))
543
569
        scroller.add(self._global_message_text_view)
544
570
        scroller.set_shadow_type(gtk.SHADOW_IN)
630
656
            if self._commit_all_changes or record[2]:# [2] checkbox
631
657
                file_id = record[0] # [0] file_id
632
658
                path = record[1]    # [1] real path
633
 
                file_message = record[5] # [5] commit message
 
659
                # [5] commit message
 
660
                file_message = _sanitize_and_decode_message(record[5])
634
661
                files.append(path.decode('UTF-8'))
635
662
                if self._enable_per_file_commits and file_message:
636
663
                    # All of this needs to be utf-8 information
 
664
                    file_message = file_message.encode('UTF-8')
637
665
                    file_info.append({'path':path, 'file_id':file_id,
638
666
                                     'message':file_message})
639
667
        file_info.sort(key=lambda x:(x['path'], x['file_id']))
643
671
            return files, []
644
672
 
645
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
646
701
    def _on_commit_clicked(self, button):
647
702
        """ Commit button clicked handler. """
648
703
        self._do_commit()
653
708
        if message == '':
654
709
            response = self._question_dialog(
655
710
                _i18n('Commit with an empty message?'),
656
 
                _i18n('You can describe your commit intent in the message.'))
 
711
                _i18n('You can describe your commit intent in the message.'),
 
712
                parent=self)
657
713
            if response == gtk.RESPONSE_NO:
658
714
                # Kindly give focus to message area
659
715
                self._global_message_text_view.grab_focus()
673
729
        for path in self._wt.unknowns():
674
730
            response = self._question_dialog(
675
731
                _i18n("Commit with unknowns?"),
676
 
                _i18n("Unknown files exist in the working tree. Commit anyway?"))
 
732
                _i18n("Unknown files exist in the working tree. Commit anyway?"),
 
733
                parent=self)
 
734
                # Doesn't set a parent for the dialog..
677
735
            if response == gtk.RESPONSE_NO:
678
736
                return
679
737
            break
693
751
            response = self._question_dialog(
694
752
                _i18n('Commit with no changes?'),
695
753
                _i18n('There are no changes in the working tree.'
696
 
                      ' Do you want to commit anyway?'))
 
754
                      ' Do you want to commit anyway?'),
 
755
                parent=self)
697
756
            if response == gtk.RESPONSE_YES:
698
757
                rev_id = self._wt.commit(message,
699
758
                               allow_pointless=True,
702
761
                               specific_files=specific_files,
703
762
                               revprops=revprops)
704
763
        self.committed_revision_id = rev_id
 
764
        # destroy old comments if any
 
765
        SavedCommitMessagesManager().save(self._wt, self._wt.branch)
705
766
        self.response(gtk.RESPONSE_OK)
706
767
 
707
768
    def _get_global_commit_message(self):
708
769
        buf = self._global_message_text_view.get_buffer()
709
770
        start, end = buf.get_bounds()
710
 
        return buf.get_text(start, end).decode('utf-8')
 
771
        text = buf.get_text(start, end)
 
772
        return _sanitize_and_decode_message(text)
711
773
 
712
774
    def _set_global_commit_message(self, message):
713
775
        """Just a helper for the test suite."""
734
796
                                       show_offset=False)
735
797
        rev_dict['revision_id'] = rev.revision_id
736
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)