/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 olive/commit.py

  • Committer: Jelmer Vernooij
  • Date: 2007-02-01 15:50:40 UTC
  • Revision ID: jelmer@samba.org-20070201155040-3hq4mfbxs99kzazy
add framework for tests.

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