28
import bzrlib.errors as errors
29
from bzrlib import osutils
30
from bzrlib import errors, osutils
31
from bzrlib.trace import mutter
32
from bzrlib.util import bencode
31
34
from dialog import error_dialog, question_dialog
32
from guifiles import GLADEFILENAME
36
""" Display Commit dialog and perform the needed actions. """
37
def __init__(self, wt, wtpath, notbranch):
38
""" Initialize the Commit dialog.
39
:param wt: bzr working tree object
40
:param wtpath: path to working tree root
41
:param notbranch: flag that path is not a brach
44
self.glade = gtk.glade.XML(GLADEFILENAME, 'window_commit', 'olive-gtk')
48
self.notbranch = notbranch
50
# Get some important widgets
51
self.window = self.glade.get_widget('window_commit')
52
self.checkbutton_local = self.glade.get_widget('checkbutton_commit_local')
53
self.textview = self.glade.get_widget('textview_commit')
54
self.file_expander = self.glade.get_widget('expander_commit_select')
55
self.file_view = self.glade.get_widget('treeview_commit_select')
56
self.pending_expander = self.glade.get_widget('expander_commit_pending')
57
self.pending_label = self.glade.get_widget('label_commit_pending')
58
self.pending_view = self.glade.get_widget('treeview_commit_pending')
60
if wt is None or notbranch:
64
self.old_tree = self.wt.branch.repository.revision_tree(self.wt.branch.last_revision())
65
self.delta = self.wt.changes_from(self.old_tree)
68
self.pending = self._pending_merges(self.wt)
70
# Dictionary for signal_autoconnect
71
dic = { "on_button_commit_commit_clicked": self.commit,
72
"on_button_commit_cancel_clicked": self.close }
74
# Connect the signals to the handlers
75
self.glade.signal_autoconnect(dic)
77
# Create the file list
78
self._create_file_view()
79
# Create the pending merges
80
self._create_pending_merges()
83
""" Display the Push dialog.
84
@return: True if dialog is shown.
86
if self.wt is None and not self.notbranch:
87
error_dialog(_('Directory does not have a working tree'),
88
_('Operation aborted.'))
92
error_dialog(_('Directory is not a branch'),
93
_('You can perform this action only in a branch.'))
35
from errors import show_bzr_error
45
def pending_revisions(wt):
46
"""Return a list of pending merges or None if there are none of them.
48
Arguably this should be a core function, and
49
``bzrlib.status.show_pending_merges`` should be built on top of it.
51
:return: [(rev, [children])]
53
parents = wt.get_parent_ids()
57
# The basic pending merge algorithm uses the same algorithm as
58
# bzrlib.status.show_pending_merges
61
last_revision = parents[0]
63
if last_revision is not None:
65
ignore = set(branch.repository.get_ancestry(last_revision,
67
except errors.NoSuchRevision:
68
# the last revision is a ghost : assume everything is new
70
ignore = set([None, last_revision])
78
rev = branch.repository.get_revision(merge)
80
pm.append((rev, children))
82
# This does need to be topo sorted, so we search backwards
83
inner_merges = branch.repository.get_ancestry(merge)
84
assert inner_merges[0] is None
86
for mmerge in reversed(inner_merges):
89
rev = branch.repository.get_revision(mmerge)
93
except errors.NoSuchRevision:
94
print "DEBUG: NoSuchRevision:", merge
99
class CommitDialog(gtk.Dialog):
100
"""Implementation of Commit."""
102
def __init__(self, wt, selected=None, parent=None):
103
gtk.Dialog.__init__(self, title="Commit - Olive",
106
buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
107
self._question_dialog = question_dialog
110
# TODO: Do something with this value, it is used by Olive
111
# It used to set all changes but this one to False
112
self._selected = selected
113
self._enable_per_file_commits = True
114
self.committed_revision_id = None # Nothing has been committed yet
120
def setup_params(self):
121
"""Setup the member variables for state."""
122
self._basis_tree = self._wt.basis_tree()
124
self._pending = pending_revisions(self._wt)
126
self._is_checkout = (self._wt.branch.get_bound_location() is not None)
128
def fill_in_data(self):
129
# Now that we are built, handle changes to the view based on the state
130
self._fill_in_pending()
132
self._fill_in_files()
133
self._fill_in_checkout()
134
self._fill_in_per_file_info()
136
def _fill_in_pending(self):
137
if not self._pending:
138
self._pending_box.hide()
141
# TODO: We'd really prefer this to be a nested list
142
for rev, children in self._pending:
143
rev_info = self._rev_to_pending_info(rev)
144
self._pending_store.append([
145
rev_info['revision_id'],
147
rev_info['committer'],
150
for child in children:
151
rev_info = self._rev_to_pending_info(child)
152
self._pending_store.append([
153
rev_info['revision_id'],
155
rev_info['committer'],
158
self._pending_box.show()
160
def _fill_in_files(self):
161
# We should really use add a progress bar of some kind.
162
# While we fill in the view, hide the store
163
store = self._files_store
164
self._treeview_files.set_model(None)
167
removed = _('removed')
168
renamed = _('renamed')
169
renamed_and_modified = _('renamed and modified')
170
modified = _('modified')
171
kind_changed = _('kind changed')
174
# [file_id, real path, checkbox, display path, changes type, message]
175
# _iter_changes returns:
176
# (file_id, (path_in_source, path_in_target),
177
# changed_content, versioned, parent, name, kind,
180
all_enabled = (self._selected is None)
181
# The first entry is always the 'whole tree'
182
all_iter = store.append([None, None, all_enabled, 'All Files', '', ''])
183
initial_cursor = store.get_path(all_iter)
184
# should we pass specific_files?
186
self._basis_tree.lock_read()
188
from diff import _iter_changes_to_status
189
for (file_id, real_path, change_type, display_path
190
) in _iter_changes_to_status(self._basis_tree, self._wt):
191
if self._selected and real_path != self._selected:
195
item_iter = store.append([
197
real_path.encode('UTF-8'),
199
display_path.encode('UTF-8'),
201
'', # Initial comment
203
if self._selected and enabled:
204
initial_cursor = store.get_path(item_iter)
206
self._basis_tree.unlock()
209
self._treeview_files.set_model(store)
210
self._last_selected_file = None
211
# This sets the cursor, which causes the expander to close, which
212
# causes the _file_message_text_view to never get realized. So we have
213
# to give it a little kick, or it warns when we try to grab the focus
214
self._treeview_files.set_cursor(initial_cursor)
216
def _realize_file_message_tree_view(*args):
217
self._file_message_text_view.realize()
218
self.connect_after('realize', _realize_file_message_tree_view)
220
def _fill_in_diff(self):
221
self._diff_view.set_trees(self._wt, self._basis_tree)
223
def _fill_in_checkout(self):
224
if not self._is_checkout:
225
self._check_local.hide()
228
bus = dbus.SystemBus()
229
proxy_obj = bus.get_object('org.freedesktop.NetworkManager',
230
'/org/freedesktop/NetworkManager')
231
dbus_iface = dbus.Interface(proxy_obj,
232
'org.freedesktop.NetworkManager')
234
# 3 is the enum value for STATE_CONNECTED
235
self._check_local.set_active(dbus_iface.state() != 3)
236
except dbus.DBusException, e:
237
# Silently drop errors. While DBus may be
238
# available, NetworkManager doesn't necessarily have to be
239
mutter("unable to get networkmanager state: %r" % e)
240
self._check_local.show()
242
def _fill_in_per_file_info(self):
243
config = self._wt.branch.get_config()
244
enable_per_file_commits = config.get_user_option('per_file_commits')
245
if (enable_per_file_commits is None
246
or enable_per_file_commits.lower()
247
not in ('y', 'yes', 'on', 'enable', '1', 't', 'true')):
248
self._enable_per_file_commits = False
97
if self.wt.branch.get_bound_location() is not None:
98
# we have a checkout, so the local commit checkbox must appear
99
self.checkbutton_local.show()
102
# There are pending merges, file selection not supported
103
self.file_expander.set_expanded(False)
104
self.file_view.set_sensitive(False)
107
self.pending_expander.hide()
109
self.textview.modify_font(pango.FontDescription("Monospace"))
113
def _create_file_view(self):
114
self.file_store = gtk.ListStore(gobject.TYPE_BOOLEAN, # [0] checkbox
115
gobject.TYPE_STRING, # [1] path to display
116
gobject.TYPE_STRING, # [2] changes type
117
gobject.TYPE_STRING) # [3] real path
118
self.file_view.set_model(self.file_store)
250
self._enable_per_file_commits = True
251
if not self._enable_per_file_commits:
252
self._file_message_expander.hide()
254
def _compute_delta(self):
255
self._delta = self._wt.changes_from(self._basis_tree)
258
"""Build up the dialog widgets."""
259
# The primary pane which splits it into left and right (adjustable)
261
self._hpane = gtk.HPaned()
263
self._construct_left_pane()
264
self._construct_right_pane()
265
self._construct_action_pane()
267
self.vbox.pack_start(self._hpane)
269
self.set_focus(self._global_message_text_view)
271
self._construct_accelerators()
274
def _set_sizes(self):
275
# This seems like a reasonable default, we might like it to
276
# be a bit wider, so that by default we can fit an 80-line diff in the
278
# Alternatively, we should be saving the last position/size rather than
279
# setting it to a fixed value every time we start up.
280
screen = self.get_screen()
281
monitor = 0 # We would like it to be the monitor we are going to
282
# display on, but I don't know how to figure that out
283
# Only really useful for freaks like me that run dual
284
# monitor, with different sizes on the monitors
285
monitor_rect = screen.get_monitor_geometry(monitor)
286
width = int(monitor_rect.width * 0.66)
287
height = int(monitor_rect.height * 0.66)
288
self.set_default_size(width, height)
289
self._hpane.set_position(300)
291
def _construct_accelerators(self):
292
group = gtk.AccelGroup()
293
group.connect_group(gtk.gdk.keyval_from_name('N'),
294
gtk.gdk.CONTROL_MASK, 0, self._on_accel_next)
295
self.add_accel_group(group)
297
def _construct_left_pane(self):
298
self._left_pane_box = gtk.VBox(homogeneous=False, spacing=5)
299
self._construct_file_list()
300
self._construct_pending_list()
302
self._check_local = gtk.CheckButton(_("_Only commit locally"),
304
self._left_pane_box.pack_end(self._check_local, False, False)
305
self._check_local.set_active(False)
307
self._hpane.pack1(self._left_pane_box, resize=False, shrink=False)
308
self._left_pane_box.show()
310
def _construct_right_pane(self):
311
# TODO: I really want to make it so the diff view gets more space than
312
# the global commit message, and the per-file commit message gets even
313
# less. When I did it with wxGlade, I set it to 4 for diff, 2 for
314
# commit, and 1 for file commit, and it looked good. But I don't seem
315
# to have a way to do that with the gtk boxes... :( (Which is extra
316
# weird since wx uses gtk on Linux...)
317
self._right_pane_table = gtk.Table(rows=10, columns=1, homogeneous=False)
318
self._right_pane_table.set_row_spacings(5)
319
self._right_pane_table.set_col_spacings(5)
320
self._right_pane_table_row = 0
321
self._construct_diff_view()
322
self._construct_file_message()
323
self._construct_global_message()
325
self._right_pane_table.show()
326
self._hpane.pack2(self._right_pane_table, resize=True, shrink=True)
328
def _construct_action_pane(self):
329
self._button_commit = gtk.Button(_("Comm_it"), use_underline=True)
330
self._button_commit.connect('clicked', self._on_commit_clicked)
331
self._button_commit.set_flags(gtk.CAN_DEFAULT)
332
self._button_commit.show()
333
self.action_area.pack_end(self._button_commit)
334
self._button_commit.grab_default()
336
def _add_to_right_table(self, widget, weight, expanding=False):
337
"""Add another widget to the table
339
:param widget: The object to add
340
:param weight: How many rows does this widget get to request
341
:param expanding: Should expand|fill|shrink be set?
343
end_row = self._right_pane_table_row + weight
345
expand_opts = gtk.EXPAND | gtk.FILL | gtk.SHRINK
347
options = expand_opts
348
self._right_pane_table.attach(widget, 0, 1,
349
self._right_pane_table_row, end_row,
350
xoptions=expand_opts, yoptions=options)
351
self._right_pane_table_row = end_row
353
def _construct_file_list(self):
354
self._files_box = gtk.VBox(homogeneous=False, spacing=0)
355
file_label = gtk.Label(_('Files'))
357
self._files_box.pack_start(file_label, expand=False)
359
scroller = gtk.ScrolledWindow()
360
scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
361
self._treeview_files = gtk.TreeView()
362
self._treeview_files.show()
363
scroller.add(self._treeview_files)
364
scroller.set_shadow_type(gtk.SHADOW_IN)
366
self._files_box.pack_start(scroller,
367
expand=True, fill=True)
368
self._files_box.show()
369
self._left_pane_box.pack_start(self._files_box)
371
# Keep note that all strings stored in a ListStore must be UTF-8
372
# strings. GTK does not support directly setting and restoring Unicode
374
liststore = gtk.ListStore(
375
gobject.TYPE_STRING, # [0] file_id
376
gobject.TYPE_STRING, # [1] real path
377
gobject.TYPE_BOOLEAN, # [2] checkbox
378
gobject.TYPE_STRING, # [3] display path
379
gobject.TYPE_STRING, # [4] changes type
380
gobject.TYPE_STRING, # [5] commit message
382
self._files_store = liststore
383
self._treeview_files.set_model(liststore)
119
384
crt = gtk.CellRendererToggle()
120
crt.set_property("activatable", True)
121
crt.connect("toggled", self._toggle_commit, self.file_store)
122
self.file_view.append_column(gtk.TreeViewColumn(_('Commit'),
124
self.file_view.append_column(gtk.TreeViewColumn(_('Path'),
125
gtk.CellRendererText(), text=1))
126
self.file_view.append_column(gtk.TreeViewColumn(_('Type'),
127
gtk.CellRendererText(), text=2))
129
for path, id, kind in self.delta.added:
130
marker = osutils.kind_marker(kind)
131
self.file_store.append([ True, path+marker, _('added'), path ])
133
for path, id, kind in self.delta.removed:
134
marker = osutils.kind_marker(kind)
135
self.file_store.append([ True, path+marker, _('removed'), path ])
137
for oldpath, newpath, id, kind, text_modified, meta_modified in self.delta.renamed:
138
marker = osutils.kind_marker(kind)
139
if text_modified or meta_modified:
140
changes = _('renamed and modified')
385
crt.set_active(not bool(self._pending))
386
crt.connect("toggled", self._toggle_commit, self._files_store)
391
self._treeview_files.append_column(gtk.TreeViewColumn(name,
393
self._treeview_files.append_column(gtk.TreeViewColumn(_('Path'),
394
gtk.CellRendererText(), text=3))
395
self._treeview_files.append_column(gtk.TreeViewColumn(_('Type'),
396
gtk.CellRendererText(), text=4))
397
self._treeview_files.connect('cursor-changed',
398
self._on_treeview_files_cursor_changed)
400
def _toggle_commit(self, cell, path, model):
401
if model[path][0] is None: # No file_id means 'All Files'
402
new_val = not model[path][2]
406
model[path][2] = not model[path][2]
408
def _construct_pending_list(self):
409
# Pending information defaults to hidden, we put it all in 1 box, so
410
# that we can show/hide all of them at once
411
self._pending_box = gtk.VBox()
412
self._pending_box.hide()
414
pending_message = gtk.Label()
415
pending_message.set_markup(
416
_('<i>* Cannot select specific files when merging</i>'))
417
self._pending_box.pack_start(pending_message, expand=False, padding=5)
418
pending_message.show()
420
pending_label = gtk.Label(_('Pending Revisions'))
421
self._pending_box.pack_start(pending_label, expand=False, padding=0)
424
scroller = gtk.ScrolledWindow()
425
scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
426
self._treeview_pending = gtk.TreeView()
427
scroller.add(self._treeview_pending)
428
scroller.set_shadow_type(gtk.SHADOW_IN)
430
self._pending_box.pack_start(scroller,
431
expand=True, fill=True, padding=5)
432
self._treeview_pending.show()
433
self._left_pane_box.pack_start(self._pending_box)
435
liststore = gtk.ListStore(gobject.TYPE_STRING, # revision_id
436
gobject.TYPE_STRING, # date
437
gobject.TYPE_STRING, # committer
438
gobject.TYPE_STRING, # summary
440
self._pending_store = liststore
441
self._treeview_pending.set_model(liststore)
442
self._treeview_pending.append_column(gtk.TreeViewColumn(_('Date'),
443
gtk.CellRendererText(), text=1))
444
self._treeview_pending.append_column(gtk.TreeViewColumn(_('Committer'),
445
gtk.CellRendererText(), text=2))
446
self._treeview_pending.append_column(gtk.TreeViewColumn(_('Summary'),
447
gtk.CellRendererText(), text=3))
449
def _construct_diff_view(self):
450
from diff import DiffView
452
self._diff_label = gtk.Label(_('Diff for whole tree'))
453
self._diff_label.set_alignment(0, 0)
454
self._right_pane_table.set_row_spacing(self._right_pane_table_row, 0)
455
self._add_to_right_table(self._diff_label, 1, False)
456
self._diff_label.show()
458
self._diff_view = DiffView()
459
self._add_to_right_table(self._diff_view, 4, True)
460
self._diff_view.show()
462
def _construct_file_message(self):
463
scroller = gtk.ScrolledWindow()
464
scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
466
self._file_message_text_view = gtk.TextView()
467
scroller.add(self._file_message_text_view)
468
scroller.set_shadow_type(gtk.SHADOW_IN)
471
self._file_message_text_view.modify_font(pango.FontDescription("Monospace"))
472
self._file_message_text_view.set_wrap_mode(gtk.WRAP_WORD)
473
self._file_message_text_view.set_accepts_tab(False)
474
self._file_message_text_view.show()
476
self._file_message_expander = gtk.Expander(_('File commit message'))
477
self._file_message_expander.set_expanded(True)
478
self._file_message_expander.add(scroller)
479
self._add_to_right_table(self._file_message_expander, 1, False)
480
self._file_message_expander.show()
482
def _construct_global_message(self):
483
self._global_message_label = gtk.Label(_('Global Commit Message'))
484
self._global_message_label.set_alignment(0, 0)
485
self._right_pane_table.set_row_spacing(self._right_pane_table_row, 0)
486
self._add_to_right_table(self._global_message_label, 1, False)
487
# Can we remove the spacing between the label and the box?
488
self._global_message_label.show()
490
scroller = gtk.ScrolledWindow()
491
scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
493
self._global_message_text_view = gtk.TextView()
494
self._global_message_text_view.modify_font(pango.FontDescription("Monospace"))
495
scroller.add(self._global_message_text_view)
496
scroller.set_shadow_type(gtk.SHADOW_IN)
498
self._add_to_right_table(scroller, 2, True)
499
self._file_message_text_view.set_wrap_mode(gtk.WRAP_WORD)
500
self._file_message_text_view.set_accepts_tab(False)
501
self._global_message_text_view.show()
503
def _on_treeview_files_cursor_changed(self, treeview):
504
treeselection = treeview.get_selection()
505
(model, selection) = treeselection.get_selected()
507
if selection is not None:
508
path, display_path = model.get(selection, 1, 3)
509
self._diff_label.set_text(_('Diff for ') + display_path)
511
self._diff_view.show_diff(None)
142
changes = _('renamed')
143
self.file_store.append([ True,
144
oldpath+marker + ' => ' + newpath+marker,
149
for path, id, kind, text_modified, meta_modified in self.delta.modified:
150
marker = osutils.kind_marker(kind)
151
self.file_store.append([ True, path+marker, _('modified'), path ])
153
def _create_pending_merges(self):
155
# hide unused pending merge part
156
scrolled_window = self.glade.get_widget('scrolledwindow_commit_pending')
157
parent = scrolled_window.get_parent()
158
parent.remove(scrolled_window)
159
parent = self.pending_label.get_parent()
160
parent.remove(self.pending_label)
513
self._diff_view.show_diff([path.decode('UTF-8')])
514
self._update_per_file_info(selection)
516
def _on_accel_next(self, accel_group, window, keyval, modifier):
517
# We don't really care about any of the parameters, because we know
518
# where this message came from
519
tree_selection = self._treeview_files.get_selection()
520
(model, selection) = tree_selection.get_selected()
521
if selection is None:
524
next = model.iter_next(selection)
527
# We have either made it to the end of the list, or nothing was
528
# selected. Either way, select All Files, and jump to the global
530
self._treeview_files.set_cursor((0,))
531
self._global_message_text_view.grab_focus()
533
# Set the cursor to this entry, and jump to the per-file commit
535
self._treeview_files.set_cursor(model.get_path(next))
536
self._file_message_text_view.grab_focus()
538
def _save_current_file_message(self):
539
if self._last_selected_file is None:
540
return # Nothing to save
541
text_buffer = self._file_message_text_view.get_buffer()
542
cur_text = text_buffer.get_text(text_buffer.get_start_iter(),
543
text_buffer.get_end_iter())
544
last_selected = self._files_store.get_iter(self._last_selected_file)
545
self._files_store.set_value(last_selected, 5, cur_text)
547
def _update_per_file_info(self, selection):
548
# The node is changing, so cache the current message
549
if not self._enable_per_file_commits:
163
liststore = gtk.ListStore(gobject.TYPE_STRING,
166
self.pending_view.set_model(liststore)
168
self.pending_view.append_column(gtk.TreeViewColumn(_('Date'),
169
gtk.CellRendererText(), text=0))
170
self.pending_view.append_column(gtk.TreeViewColumn(_('Committer'),
171
gtk.CellRendererText(), text=1))
172
self.pending_view.append_column(gtk.TreeViewColumn(_('Summary'),
173
gtk.CellRendererText(), text=2))
175
for item in self.pending:
176
liststore.append([ item['date'],
552
self._save_current_file_message()
553
text_buffer = self._file_message_text_view.get_buffer()
554
file_id, display_path, message = self._files_store.get(selection, 0, 3, 5)
555
if file_id is None: # Whole tree
556
self._file_message_expander.set_label(_('File commit message'))
557
self._file_message_expander.set_expanded(False)
558
self._file_message_expander.set_sensitive(False)
559
text_buffer.set_text('')
560
self._last_selected_file = None
562
self._file_message_expander.set_label(_('Commit message for ')
564
self._file_message_expander.set_expanded(True)
565
self._file_message_expander.set_sensitive(True)
566
text_buffer.set_text(message)
567
self._last_selected_file = self._files_store.get_path(selection)
180
569
def _get_specific_files(self):
182
it = self.file_store.get_iter_first()
184
if self.file_store.get_value(it, 0):
185
# get real path from hidden column 3
186
ret.append(self.file_store.get_value(it, 3))
187
it = self.file_store.iter_next(it)
191
def _toggle_commit(self, cell, path, model):
192
model[path][0] = not model[path][0]
195
def _pending_merges(self, wt):
196
""" Return a list of pending merges or None if there are none of them. """
197
parents = wt.get_parent_ids()
202
from bzrlib.osutils import format_date
204
pending = parents[1:]
206
last_revision = parents[0]
208
if last_revision is not None:
210
ignore = set(branch.repository.get_ancestry(last_revision))
211
except errors.NoSuchRevision:
212
# the last revision is a ghost : assume everything is new
214
ignore = set([None, last_revision])
219
for merge in pending:
222
m_revision = branch.repository.get_revision(merge)
225
rev['committer'] = re.sub('<.*@.*>', '', m_revision.committer).strip(' ')
226
rev['summary'] = m_revision.get_summary()
227
rev['date'] = format_date(m_revision.timestamp,
228
m_revision.timezone or 0,
229
'original', date_fmt="%Y-%m-%d",
234
inner_merges = branch.repository.get_ancestry(merge)
235
assert inner_merges[0] is None
237
inner_merges.reverse()
238
for mmerge in inner_merges:
241
mm_revision = branch.repository.get_revision(mmerge)
244
rev['committer'] = re.sub('<.*@.*>', '', mm_revision.committer).strip(' ')
245
rev['summary'] = mm_revision.get_summary()
246
rev['date'] = format_date(mm_revision.timestamp,
247
mm_revision.timezone or 0,
248
'original', date_fmt="%Y-%m-%d",
254
except errors.NoSuchRevision:
255
print "DEBUG: NoSuchRevision:", merge
259
def commit(self, widget):
260
textbuffer = self.textview.get_buffer()
261
start, end = textbuffer.get_bounds()
262
message = textbuffer.get_text(start, end).decode('utf-8')
264
checkbutton_strict = self.glade.get_widget('checkbutton_commit_strict')
265
checkbutton_force = self.glade.get_widget('checkbutton_commit_force')
268
specific_files = self._get_specific_files()
270
specific_files = None
570
"""Return the list of selected paths, and file info.
572
:return: ([unicode paths], [{utf-8 file info}]
574
self._save_current_file_message()
576
records = iter(self._files_store)
577
rec = records.next() # Skip the All Files record
578
assert rec[0] is None, "Are we skipping the wrong record?"
581
for record in records:
582
if record[2]: # [2] checkbox
583
file_id = record[0] # [0] file_id
584
path = record[1] # [1] real path
585
file_message = record[5] # [5] commit message
586
files.append(path.decode('UTF-8'))
587
if self._enable_per_file_commits and file_message:
588
# All of this needs to be utf-8 information
589
file_info.append({'path':path, 'file_id':file_id,
590
'message':file_message})
591
file_info.sort(key=lambda x:(x['path'], x['file_id']))
592
if self._enable_per_file_commits:
593
return files, file_info
598
def _on_commit_clicked(self, button):
599
""" Commit button clicked handler. """
602
def _do_commit(self):
603
message = self._get_global_commit_message()
272
605
if message == '':
273
response = question_dialog('Commit with an empty message ?',
274
'You can describe your commit intent'
606
response = self._question_dialog(
607
_('Commit with an empty message?'),
608
_('You can describe your commit intent in the message.'))
276
609
if response == gtk.RESPONSE_NO:
277
610
# Kindly give focus to message area
278
self.textview.grab_focus()
611
self._global_message_text_view.grab_focus()
614
specific_files, file_info = self._get_specific_files()
616
specific_files = None
618
local = self._check_local.get_active()
620
# All we care about is if there is a single unknown, so if this loop is
621
# entered, then there are unknown files.
622
# TODO: jam 20071002 It seems like this should cancel the dialog
623
# entirely, since there isn't a way for them to add the unknown
624
# files at this point.
625
for path in self._wt.unknowns():
626
response = self._question_dialog(
627
_("Commit with unknowns?"),
628
_("Unknown files exist in the working tree. Commit anyway?"))
629
if response == gtk.RESPONSE_NO:
636
revprops['file-info'] = bencode.bencode(file_info).decode('UTF-8')
282
self.wt.commit(message,
283
allow_pointless=checkbutton_force.get_active(),
284
strict=checkbutton_strict.get_active(),
285
local=self.checkbutton_local.get_active(),
286
specific_files=specific_files)
287
except errors.NotBranchError:
288
error_dialog(_('Directory is not a branch'),
289
_('You can perform this action only in a branch.'))
291
except errors.LocalRequiresBoundBranch:
292
error_dialog(_('Directory is not a checkout'),
293
_('You can perform local commit only on checkouts.'))
638
rev_id = self._wt.commit(message,
639
allow_pointless=False,
642
specific_files=specific_files,
295
644
except errors.PointlessCommit:
296
error_dialog(_('No changes to commit'),
297
_('Try force commit if you want to commit anyway.'))
299
except errors.ConflictsInTree:
300
error_dialog(_('Conflicts in tree'),
301
_('You need to resolve the conflicts before committing.'))
303
except errors.StrictCommitFailed:
304
error_dialog(_('Strict commit failed'),
305
_('There are unknown files in the working tree.\nPlease add or delete them.'))
307
except errors.BoundBranchOutOfDate, errmsg:
308
error_dialog(_('Bound branch is out of date'),
311
except errors.BzrError, msg:
312
error_dialog(_('Unknown bzr error'), str(msg))
314
except Exception, msg:
315
error_dialog(_('Unknown error'), str(msg))
320
def close(self, widget=None):
321
self.window.destroy()
645
response = self._question_dialog(
646
_('Commit with no changes?'),
647
_('There are no changes in the working tree.'
648
' Do you want to commit anyway?'))
649
if response == gtk.RESPONSE_YES:
650
rev_id = self._wt.commit(message,
651
allow_pointless=True,
654
specific_files=specific_files,
656
self.committed_revision_id = rev_id
657
self.response(gtk.RESPONSE_OK)
659
def _get_global_commit_message(self):
660
buf = self._global_message_text_view.get_buffer()
661
start, end = buf.get_bounds()
662
return buf.get_text(start, end).decode('utf-8')
664
def _set_global_commit_message(self, message):
665
"""Just a helper for the test suite."""
666
if isinstance(message, unicode):
667
message = message.encode('UTF-8')
668
self._global_message_text_view.get_buffer().set_text(message)
670
def _set_file_commit_message(self, message):
671
"""Helper for the test suite."""
672
if isinstance(message, unicode):
673
message = message.encode('UTF-8')
674
self._file_message_text_view.get_buffer().set_text(message)
677
def _rev_to_pending_info(rev):
678
"""Get the information from a pending merge."""
679
from bzrlib.osutils import format_date
681
rev_dict['committer'] = re.sub('<.*@.*>', '', rev.committer).strip(' ')
682
rev_dict['summary'] = rev.get_summary()
683
rev_dict['date'] = format_date(rev.timestamp,
685
'original', date_fmt="%Y-%m-%d",
687
rev_dict['revision_id'] = rev.revision_id