/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: Daniel Schierbeck
  • Date: 2007-11-02 14:49:57 UTC
  • mto: (330.6.1 trunk)
  • mto: This revision was merged to the branch mainline in revision 341.
  • Revision ID: daniel.schierbeck@gmail.com-20071102144957-8r4lp70ma5bpk8r8
Renamed logview 'revisionview'.

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 sys
18
 
 
19
17
try:
20
18
    import pygtk
21
19
    pygtk.require("2.0")
22
20
except:
23
21
    pass
 
22
 
 
23
import gtk
 
24
import gobject
 
25
import pango
 
26
 
 
27
import os.path
 
28
 
 
29
from bzrlib import errors, osutils
 
30
from bzrlib.trace import mutter
 
31
 
 
32
from dialog import error_dialog, question_dialog
 
33
from errors import show_bzr_error
 
34
 
24
35
try:
25
 
    import gtk
26
 
    import gtk.glade
27
 
    import gobject
28
 
    import pango
29
 
except:
30
 
    sys.exit(1)
31
 
 
32
 
from bzrlib import version_info
33
 
 
34
 
import bzrlib.errors as errors
35
 
from bzrlib.workingtree import WorkingTree
36
 
 
37
 
from dialog import error_dialog
38
 
 
39
 
class OliveCommit:
40
 
    """ Display Commit dialog and perform the needed actions. """
41
 
    def __init__(self, gladefile, wt, wtpath):
42
 
        """ Initialize the Commit dialog. """
43
 
        self.gladefile = gladefile
44
 
        self.glade = gtk.glade.XML(self.gladefile, 'window_commit', 'olive-gtk')
 
36
    import dbus
 
37
    import dbus.glib
 
38
    have_dbus = True
 
39
except ImportError:
 
40
    have_dbus = False
 
41
 
 
42
class CommitDialog(gtk.Dialog):
 
43
    """ New implementation of the Commit dialog. """
 
44
    def __init__(self, wt, wtpath, notbranch, selected=None, parent=None):
 
45
        """ Initialize the Commit Dialog. """
 
46
        gtk.Dialog.__init__(self, title="Commit - Olive",
 
47
                                  parent=parent,
 
48
                                  flags=0,
 
49
                                  buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
45
50
        
 
51
        # Get arguments
46
52
        self.wt = wt
47
53
        self.wtpath = wtpath
48
 
 
49
 
        # Get some important widgets
50
 
        self.window = self.glade.get_widget('window_commit')
51
 
        self.checkbutton_local = self.glade.get_widget('checkbutton_commit_local')
52
 
        self.textview = self.glade.get_widget('textview_commit')
53
 
        self.file_view = self.glade.get_widget('treeview_commit_select')
54
 
 
55
 
        file_id = self.wt.path2id(wtpath)
56
 
 
57
 
        self.notbranch = False
58
 
        if file_id is None:
59
 
            self.notbranch = True
60
 
            return
 
54
        self.notbranch = notbranch
 
55
        self.selected = selected
61
56
        
62
57
        # Set the delta
63
58
        self.old_tree = self.wt.branch.repository.revision_tree(self.wt.branch.last_revision())
64
 
        if version_info < (0, 9):
65
 
            self.delta = compare_trees(self.old_tree, self.wt)
66
 
        else:
67
 
            self.delta = self.wt.changes_from(self.old_tree)
68
 
        
69
 
        # Dictionary for signal_autoconnect
70
 
        dic = { "on_button_commit_commit_clicked": self.commit,
71
 
                "on_button_commit_cancel_clicked": self.close }
72
 
        
73
 
        # Connect the signals to the handlers
74
 
        self.glade.signal_autoconnect(dic)
75
 
        
76
 
        # Create the file list
77
 
        self._create_file_view()
78
 
    
79
 
    def display(self):
80
 
        """ Display the Push dialog. """
 
59
        self.delta = self.wt.changes_from(self.old_tree)
 
60
        
 
61
        # Get pending merges
 
62
        self.pending = self._pending_merges(self.wt)
 
63
        
 
64
        # Do some preliminary checks
 
65
        self._is_checkout = False
 
66
        self._is_pending = False
 
67
        if self.wt is None and not self.notbranch:
 
68
            error_dialog(_('Directory does not have a working tree'),
 
69
                         _('Operation aborted.'))
 
70
            self.close()
 
71
            return
 
72
 
81
73
        if self.notbranch:
82
74
            error_dialog(_('Directory is not a branch'),
83
 
                                     _('You can perform this action only in a branch.'))
 
75
                         _('You can perform this action only in a branch.'))
84
76
            self.close()
 
77
            return
85
78
        else:
86
79
            if self.wt.branch.get_bound_location() is not None:
87
80
                # we have a checkout, so the local commit checkbox must appear
88
 
                self.checkbutton_local.show()
89
 
            
90
 
            self.textview.modify_font(pango.FontDescription("Monospace"))
91
 
            self.window.show()
92
 
            
93
 
    
 
81
                self._is_checkout = True
 
82
            
 
83
            if self.pending:
 
84
                # There are pending merges, file selection not supported
 
85
                self._is_pending = True
 
86
        
 
87
        # Create the widgets
 
88
        self._button_commit = gtk.Button(_("Comm_it"), use_underline=True)
 
89
        self._expander_files = gtk.Expander(_("File(s) to commit"))
 
90
        self._vpaned_main = gtk.VPaned()
 
91
        self._scrolledwindow_files = gtk.ScrolledWindow()
 
92
        self._scrolledwindow_message = gtk.ScrolledWindow()
 
93
        self._treeview_files = gtk.TreeView()
 
94
        self._vbox_message = gtk.VBox()
 
95
        self._label_message = gtk.Label(_("Commit message:"))
 
96
        self._textview_message = gtk.TextView()
 
97
        
 
98
        if self._is_pending:
 
99
            self._expander_merges = gtk.Expander(_("Pending merges"))
 
100
            self._vpaned_list = gtk.VPaned()
 
101
            self._scrolledwindow_merges = gtk.ScrolledWindow()
 
102
            self._treeview_merges = gtk.TreeView()
 
103
            self._scrolledwindow_merges.set_policy(gtk.POLICY_AUTOMATIC,
 
104
                                                   gtk.POLICY_AUTOMATIC)
 
105
 
 
106
        # Set callbacks
 
107
        self._button_commit.connect('clicked', self._on_commit_clicked)
 
108
        self._treeview_files.connect('row_activated', self._on_treeview_files_row_activated)
 
109
        
 
110
        # Set properties
 
111
        self._scrolledwindow_files.set_policy(gtk.POLICY_AUTOMATIC,
 
112
                                              gtk.POLICY_AUTOMATIC)
 
113
        self._scrolledwindow_message.set_policy(gtk.POLICY_AUTOMATIC,
 
114
                                                gtk.POLICY_AUTOMATIC)
 
115
        self._textview_message.modify_font(pango.FontDescription("Monospace"))
 
116
        self.set_default_size(500, 500)
 
117
        self._vpaned_main.set_position(200)
 
118
        self._button_commit.set_flags(gtk.CAN_DEFAULT)
 
119
 
 
120
        # Construct the dialog
 
121
        self.action_area.pack_end(self._button_commit)
 
122
        
 
123
        self._scrolledwindow_files.add(self._treeview_files)
 
124
        self._scrolledwindow_message.add(self._textview_message)
 
125
        
 
126
        self._expander_files.add(self._scrolledwindow_files)
 
127
        
 
128
        self._vbox_message.pack_start(self._label_message, False, False)
 
129
        self._vbox_message.pack_start(self._scrolledwindow_message, True, True)
 
130
        
 
131
        if self._is_pending:        
 
132
            self._expander_merges.add(self._scrolledwindow_merges)
 
133
            self._scrolledwindow_merges.add(self._treeview_merges)
 
134
            self._vpaned_list.add1(self._expander_files)
 
135
            self._vpaned_list.add2(self._expander_merges)
 
136
            self._vpaned_main.add1(self._vpaned_list)
 
137
        else:
 
138
            self._vpaned_main.add1(self._expander_files)
 
139
 
 
140
        self._vpaned_main.add2(self._vbox_message)
 
141
        
 
142
        self.vbox.pack_start(self._vpaned_main, True, True)
 
143
        if self._is_checkout: 
 
144
            self._check_local = gtk.CheckButton(_("_Only commit locally"),
 
145
                                                use_underline=True)
 
146
            self.vbox.pack_start(self._check_local, False, False)
 
147
            if have_dbus:
 
148
                bus = dbus.SystemBus()
 
149
                proxy_obj = bus.get_object('org.freedesktop.NetworkManager', 
 
150
                              '/org/freedesktop/NetworkManager')
 
151
                dbus_iface = dbus.Interface(
 
152
                        proxy_obj, 'org.freedesktop.NetworkManager')
 
153
                try:
 
154
                    # 3 is the enum value for STATE_CONNECTED
 
155
                    self._check_local.set_active(dbus_iface.state() != 3)
 
156
                except dbus.DBusException, e:
 
157
                    # Silently drop errors. While DBus may be 
 
158
                    # available, NetworkManager doesn't necessarily have to be
 
159
                    mutter("unable to get networkmanager state: %r" % e)
 
160
                
 
161
        # Create the file list
 
162
        self._create_file_view()
 
163
        # Create the pending merges
 
164
        self._create_pending_merges()
 
165
        
 
166
        # Expand the corresponding expander
 
167
        if self._is_pending:
 
168
            self._expander_merges.set_expanded(True)
 
169
        else:
 
170
            self._expander_files.set_expanded(True)
 
171
        
 
172
        # Display dialog
 
173
        self.vbox.show_all()
 
174
        
 
175
        # Default to Commit button
 
176
        self._button_commit.grab_default()
 
177
    
 
178
    def _on_treeview_files_row_activated(self, treeview, path, view_column):
 
179
        # FIXME: the diff window freezes for some reason
 
180
        treeselection = treeview.get_selection()
 
181
        (model, iter) = treeselection.get_selected()
 
182
        
 
183
        if iter is not None:
 
184
            from diff import DiffWindow
 
185
            
 
186
            _selected = model.get_value(iter, 1)
 
187
            
 
188
            diff = DiffWindow()
 
189
            diff.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
 
190
            diff.set_modal(True)
 
191
            parent_tree = self.wt.branch.repository.revision_tree(self.wt.branch.last_revision())
 
192
            diff.set_diff(self.wt.branch.nick, self.wt, parent_tree)
 
193
            try:
 
194
                diff.set_file(_selected)
 
195
            except errors.NoSuchFile:
 
196
                pass
 
197
            diff.show()
 
198
    
 
199
    @show_bzr_error
 
200
    def _on_commit_clicked(self, button):
 
201
        """ Commit button clicked handler. """
 
202
        textbuffer = self._textview_message.get_buffer()
 
203
        start, end = textbuffer.get_bounds()
 
204
        message = textbuffer.get_text(start, end).decode('utf-8')
 
205
        
 
206
        if not self.pending:
 
207
            specific_files = self._get_specific_files()
 
208
        else:
 
209
            specific_files = None
 
210
 
 
211
        if message == '':
 
212
            response = question_dialog(_('Commit with an empty message?'),
 
213
                                       _('You can describe your commit intent in the message.'))
 
214
            if response == gtk.RESPONSE_NO:
 
215
                # Kindly give focus to message area
 
216
                self._textview_message.grab_focus()
 
217
                return
 
218
 
 
219
        if self._is_checkout:
 
220
            local = self._check_local.get_active()
 
221
        else:
 
222
            local = False
 
223
 
 
224
        if list(self.wt.unknowns()) != []:
 
225
            response = question_dialog(_("Commit with unknowns?"),
 
226
               _("Unknown files exist in the working tree. Commit anyway?"))
 
227
            if response == gtk.RESPONSE_NO:
 
228
                return
 
229
        
 
230
        try:
 
231
            self.wt.commit(message,
 
232
                       allow_pointless=False,
 
233
                       strict=False,
 
234
                       local=local,
 
235
                       specific_files=specific_files)
 
236
        except errors.PointlessCommit:
 
237
            response = question_dialog(_('Commit with no changes?'),
 
238
                                       _('There are no changes in the working tree.'))
 
239
            if response == gtk.RESPONSE_YES:
 
240
                self.wt.commit(message,
 
241
                               allow_pointless=True,
 
242
                               strict=False,
 
243
                               local=local,
 
244
                               specific_files=specific_files)
 
245
        self.response(gtk.RESPONSE_OK)
 
246
 
 
247
    def _pending_merges(self, wt):
 
248
        """ Return a list of pending merges or None if there are none of them. """
 
249
        parents = wt.get_parent_ids()
 
250
        if len(parents) < 2:
 
251
            return None
 
252
        
 
253
        import re
 
254
        from bzrlib.osutils import format_date
 
255
        
 
256
        pending = parents[1:]
 
257
        branch = wt.branch
 
258
        last_revision = parents[0]
 
259
        
 
260
        if last_revision is not None:
 
261
            try:
 
262
                ignore = set(branch.repository.get_ancestry(last_revision))
 
263
            except errors.NoSuchRevision:
 
264
                # the last revision is a ghost : assume everything is new 
 
265
                # except for it
 
266
                ignore = set([None, last_revision])
 
267
        else:
 
268
            ignore = set([None])
 
269
        
 
270
        pm = []
 
271
        for merge in pending:
 
272
            ignore.add(merge)
 
273
            try:
 
274
                m_revision = branch.repository.get_revision(merge)
 
275
                
 
276
                rev = {}
 
277
                rev['committer'] = re.sub('<.*@.*>', '', m_revision.committer).strip(' ')
 
278
                rev['summary'] = m_revision.get_summary()
 
279
                rev['date'] = format_date(m_revision.timestamp,
 
280
                                          m_revision.timezone or 0, 
 
281
                                          'original', date_fmt="%Y-%m-%d",
 
282
                                          show_offset=False)
 
283
                
 
284
                pm.append(rev)
 
285
                
 
286
                inner_merges = branch.repository.get_ancestry(merge)
 
287
                assert inner_merges[0] is None
 
288
                inner_merges.pop(0)
 
289
                inner_merges.reverse()
 
290
                for mmerge in inner_merges:
 
291
                    if mmerge in ignore:
 
292
                        continue
 
293
                    mm_revision = branch.repository.get_revision(mmerge)
 
294
                    
 
295
                    rev = {}
 
296
                    rev['committer'] = re.sub('<.*@.*>', '', mm_revision.committer).strip(' ')
 
297
                    rev['summary'] = mm_revision.get_summary()
 
298
                    rev['date'] = format_date(mm_revision.timestamp,
 
299
                                              mm_revision.timezone or 0, 
 
300
                                              'original', date_fmt="%Y-%m-%d",
 
301
                                              show_offset=False)
 
302
                
 
303
                    pm.append(rev)
 
304
                    
 
305
                    ignore.add(mmerge)
 
306
            except errors.NoSuchRevision:
 
307
                print "DEBUG: NoSuchRevision:", merge
 
308
        
 
309
        return pm
 
310
 
94
311
    def _create_file_view(self):
95
 
        self.file_store = gtk.ListStore(gobject.TYPE_BOOLEAN,
96
 
                                        gobject.TYPE_STRING,
97
 
                                        gobject.TYPE_STRING)
98
 
        self.file_view.set_model(self.file_store)
 
312
        self._file_store = gtk.ListStore(gobject.TYPE_BOOLEAN,   # [0] checkbox
 
313
                                         gobject.TYPE_STRING,    # [1] path to display
 
314
                                         gobject.TYPE_STRING,    # [2] changes type
 
315
                                         gobject.TYPE_STRING)    # [3] real path
 
316
        self._treeview_files.set_model(self._file_store)
99
317
        crt = gtk.CellRendererToggle()
100
318
        crt.set_property("activatable", True)
101
 
        crt.connect("toggled", self._toggle_commit, self.file_store)
102
 
        self.file_view.append_column(gtk.TreeViewColumn(_('Commit'),
103
 
                                     crt, active=0))
104
 
        self.file_view.append_column(gtk.TreeViewColumn(_('Path'),
 
319
        crt.connect("toggled", self._toggle_commit, self._file_store)
 
320
        commit_column = gtk.TreeViewColumn(_('Commit'), crt, active=0)
 
321
        if self._is_pending:
 
322
            commit_column.set_visible(False)
 
323
        self._treeview_files.append_column(commit_column)
 
324
        self._treeview_files.append_column(gtk.TreeViewColumn(_('Path'),
105
325
                                     gtk.CellRendererText(), text=1))
106
 
        self.file_view.append_column(gtk.TreeViewColumn(_('Type'),
 
326
        self._treeview_files.append_column(gtk.TreeViewColumn(_('Type'),
107
327
                                     gtk.CellRendererText(), text=2))
108
328
 
109
329
        for path, id, kind in self.delta.added:
110
 
            self.file_store.append([ True, path, _('added') ])
 
330
            marker = osutils.kind_marker(kind)
 
331
            if self.selected is not None:
 
332
                if path == os.path.join(self.wtpath, self.selected):
 
333
                    self._file_store.append([ True, path+marker, _('added'), path ])
 
334
                else:
 
335
                    self._file_store.append([ False, path+marker, _('added'), path ])
 
336
            else:
 
337
                self._file_store.append([ True, path+marker, _('added'), path ])
111
338
 
112
339
        for path, id, kind in self.delta.removed:
113
 
            self.file_store.append([ True, path, _('removed') ])
 
340
            marker = osutils.kind_marker(kind)
 
341
            if self.selected is not None:
 
342
                if path == os.path.join(self.wtpath, self.selected):
 
343
                    self._file_store.append([ True, path+marker, _('removed'), path ])
 
344
                else:
 
345
                    self._file_store.append([ False, path+marker, _('removed'), path ])
 
346
            else:
 
347
                self._file_store.append([ True, path+marker, _('removed'), path ])
114
348
 
115
349
        for oldpath, newpath, id, kind, text_modified, meta_modified in self.delta.renamed:
116
 
            self.file_store.append([ True, oldpath, _('renamed') ])
 
350
            marker = osutils.kind_marker(kind)
 
351
            if text_modified or meta_modified:
 
352
                changes = _('renamed and modified')
 
353
            else:
 
354
                changes = _('renamed')
 
355
            if self.selected is not None:
 
356
                if newpath == os.path.join(self.wtpath, self.selected):
 
357
                    self._file_store.append([ True,
 
358
                                              oldpath+marker + '  =>  ' + newpath+marker,
 
359
                                              changes,
 
360
                                              newpath
 
361
                                            ])
 
362
                else:
 
363
                    self._file_store.append([ False,
 
364
                                              oldpath+marker + '  =>  ' + newpath+marker,
 
365
                                              changes,
 
366
                                              newpath
 
367
                                            ])
 
368
            else:
 
369
                self._file_store.append([ True,
 
370
                                          oldpath+marker + '  =>  ' + newpath+marker,
 
371
                                          changes,
 
372
                                          newpath
 
373
                                        ])
117
374
 
118
375
        for path, id, kind, text_modified, meta_modified in self.delta.modified:
119
 
            self.file_store.append([ True, path, _('modified') ])
 
376
            marker = osutils.kind_marker(kind)
 
377
            if self.selected is not None:
 
378
                if path == os.path.join(self.wtpath, self.selected):
 
379
                    self._file_store.append([ True, path+marker, _('modified'), path ])
 
380
                else:
 
381
                    self._file_store.append([ False, path+marker, _('modified'), path ])
 
382
            else:
 
383
                self._file_store.append([ True, path+marker, _('modified'), path ])
 
384
    
 
385
    def _create_pending_merges(self):
 
386
        if not self.pending:
 
387
            return
 
388
        
 
389
        liststore = gtk.ListStore(gobject.TYPE_STRING,
 
390
                                  gobject.TYPE_STRING,
 
391
                                  gobject.TYPE_STRING)
 
392
        self._treeview_merges.set_model(liststore)
 
393
        
 
394
        self._treeview_merges.append_column(gtk.TreeViewColumn(_('Date'),
 
395
                                            gtk.CellRendererText(), text=0))
 
396
        self._treeview_merges.append_column(gtk.TreeViewColumn(_('Committer'),
 
397
                                            gtk.CellRendererText(), text=1))
 
398
        self._treeview_merges.append_column(gtk.TreeViewColumn(_('Summary'),
 
399
                                            gtk.CellRendererText(), text=2))
 
400
        
 
401
        for item in self.pending:
 
402
            liststore.append([ item['date'],
 
403
                               item['committer'],
 
404
                               item['summary'] ])
120
405
    
121
406
    def _get_specific_files(self):
122
407
        ret = []
123
 
        it = self.file_store.get_iter_first()
 
408
        it = self._file_store.get_iter_first()
124
409
        while it:
125
 
            if self.file_store.get_value(it, 0):
126
 
                ret.append(self.file_store.get_value(it, 1))
127
 
            it = self.file_store.iter_next(it)
 
410
            if self._file_store.get_value(it, 0):
 
411
                # get real path from hidden column 3
 
412
                ret.append(self._file_store.get_value(it, 3))
 
413
            it = self._file_store.iter_next(it)
128
414
 
129
415
        return ret
130
 
    # end of bzr-gtk code
131
416
    
132
417
    def _toggle_commit(self, cell, path, model):
133
418
        model[path][0] = not model[path][0]
134
419
        return
135
 
    
136
 
    def commit(self, widget):
137
 
        textbuffer = self.textview.get_buffer()
138
 
        start, end = textbuffer.get_bounds()
139
 
        message = textbuffer.get_text(start, end)
140
 
        
141
 
        checkbutton_strict = self.glade.get_widget('checkbutton_commit_strict')
142
 
        checkbutton_force = self.glade.get_widget('checkbutton_commit_force')
143
 
        
144
 
        specific_files = self._get_specific_files()
145
 
        
146
 
        try:
147
 
            self.wt.commit(message, 
148
 
                           allow_pointless=checkbutton_force.get_active(),
149
 
                           strict=checkbutton_strict.get_active(),
150
 
                           local=self.checkbutton_local.get_active(),
151
 
                           specific_files=specific_files)
152
 
        except errors.NotBranchError:
153
 
            error_dialog(_('Directory is not a branch'),
154
 
                                     _('You can perform this action only in a branch.'))
155
 
            return
156
 
        except errors.LocalRequiresBoundBranch:
157
 
            error_dialog(_('Directory is not a checkout'),
158
 
                                     _('You can perform local commit only on checkouts.'))
159
 
            return
160
 
        except errors.PointlessCommit:
161
 
            error_dialog(_('No changes to commit'),
162
 
                                     _('Try force commit if you want to commit anyway.'))
163
 
            return
164
 
        except errors.ConflictsInTree:
165
 
            error_dialog(_('Conflicts in tree'),
166
 
                                     _('You need to resolve the conflicts before committing.'))
167
 
            return
168
 
        except errors.StrictCommitFailed:
169
 
            error_dialog(_('Strict commit failed'),
170
 
                                     _('There are unknown files in the working tree.\nPlease add or delete them.'))
171
 
            return
172
 
        except errors.BoundBranchOutOfDate, errmsg:
173
 
            error_dialog(_('Bound branch is out of date'),
174
 
                                     _('%s') % errmsg)
175
 
            return
176
 
        except errors.BzrError, msg:
177
 
            error_dialog(_('Unknown bzr error'), str(msg))
178
 
            return
179
 
        except Exception, msg:
180
 
            error_dialog(_('Unknown error'), str(msg))
181
 
            return
182
 
        
183
 
        self.close()
184
 
        self.comm.refresh_right()
185
 
        
186
 
    def close(self, widget=None):
187
 
        self.window.destroy()