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._commit_all_changes = True
115
self.committed_revision_id = None # Nothing has been committed yet
121
def setup_params(self):
122
"""Setup the member variables for state."""
123
self._basis_tree = self._wt.basis_tree()
125
self._pending = pending_revisions(self._wt)
127
self._is_checkout = (self._wt.branch.get_bound_location() is not None)
129
def fill_in_data(self):
130
# Now that we are built, handle changes to the view based on the state
131
self._fill_in_pending()
133
self._fill_in_files()
134
self._fill_in_checkout()
135
self._fill_in_per_file_info()
137
def _fill_in_pending(self):
138
if not self._pending:
139
self._pending_box.hide()
142
# TODO: We'd really prefer this to be a nested list
143
for rev, children in self._pending:
144
rev_info = self._rev_to_pending_info(rev)
145
self._pending_store.append([
146
rev_info['revision_id'],
148
rev_info['committer'],
151
for child in children:
152
rev_info = self._rev_to_pending_info(child)
153
self._pending_store.append([
154
rev_info['revision_id'],
156
rev_info['committer'],
159
self._pending_box.show()
161
def _fill_in_files(self):
162
# We should really use add a progress bar of some kind.
163
# While we fill in the view, hide the store
164
store = self._files_store
165
self._treeview_files.set_model(None)
168
removed = _('removed')
169
renamed = _('renamed')
170
renamed_and_modified = _('renamed and modified')
171
modified = _('modified')
172
kind_changed = _('kind changed')
175
# [file_id, real path, checkbox, display path, changes type, message]
176
# _iter_changes returns:
177
# (file_id, (path_in_source, path_in_target),
178
# changed_content, versioned, parent, name, kind,
181
all_enabled = (self._selected is None)
182
# The first entry is always the 'whole tree'
183
all_iter = store.append([None, None, all_enabled, 'All Files', '', ''])
184
initial_cursor = store.get_path(all_iter)
185
# should we pass specific_files?
187
self._basis_tree.lock_read()
189
from diff import _iter_changes_to_status
190
for (file_id, real_path, change_type, display_path
191
) in _iter_changes_to_status(self._basis_tree, self._wt):
192
if self._selected and real_path != self._selected:
196
item_iter = store.append([
198
real_path.encode('UTF-8'),
200
display_path.encode('UTF-8'),
202
'', # Initial comment
204
if self._selected and enabled:
205
initial_cursor = store.get_path(item_iter)
207
self._basis_tree.unlock()
210
self._treeview_files.set_model(store)
211
self._last_selected_file = None
212
# This sets the cursor, which causes the expander to close, which
213
# causes the _file_message_text_view to never get realized. So we have
214
# to give it a little kick, or it warns when we try to grab the focus
215
self._treeview_files.set_cursor(initial_cursor)
217
def _realize_file_message_tree_view(*args):
218
self._file_message_text_view.realize()
219
self.connect_after('realize', _realize_file_message_tree_view)
221
def _fill_in_diff(self):
222
self._diff_view.set_trees(self._wt, self._basis_tree)
224
def _fill_in_checkout(self):
225
if not self._is_checkout:
226
self._check_local.hide()
229
bus = dbus.SystemBus()
230
proxy_obj = bus.get_object('org.freedesktop.NetworkManager',
231
'/org/freedesktop/NetworkManager')
232
dbus_iface = dbus.Interface(proxy_obj,
233
'org.freedesktop.NetworkManager')
235
# 3 is the enum value for STATE_CONNECTED
236
self._check_local.set_active(dbus_iface.state() != 3)
237
except dbus.DBusException, e:
238
# Silently drop errors. While DBus may be
239
# available, NetworkManager doesn't necessarily have to be
240
mutter("unable to get networkmanager state: %r" % e)
241
self._check_local.show()
243
def _fill_in_per_file_info(self):
244
config = self._wt.branch.get_config()
245
enable_per_file_commits = config.get_user_option('per_file_commits')
246
if (enable_per_file_commits is None
247
or enable_per_file_commits.lower()
248
not in ('y', 'yes', 'on', 'enable', '1', 't', 'true')):
249
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)
251
self._enable_per_file_commits = True
252
if not self._enable_per_file_commits:
253
self._file_message_expander.hide()
254
self._global_message_label.set_markup(_('<b>Commit Message</b>'))
256
def _compute_delta(self):
257
self._delta = self._wt.changes_from(self._basis_tree)
260
"""Build up the dialog widgets."""
261
# The primary pane which splits it into left and right (adjustable)
263
self._hpane = gtk.HPaned()
265
self._construct_left_pane()
266
self._construct_right_pane()
267
self._construct_action_pane()
269
self.vbox.pack_start(self._hpane)
271
self.set_focus(self._global_message_text_view)
273
self._construct_accelerators()
276
def _set_sizes(self):
277
# This seems like a reasonable default, we might like it to
278
# be a bit wider, so that by default we can fit an 80-line diff in the
280
# Alternatively, we should be saving the last position/size rather than
281
# setting it to a fixed value every time we start up.
282
screen = self.get_screen()
283
monitor = 0 # We would like it to be the monitor we are going to
284
# display on, but I don't know how to figure that out
285
# Only really useful for freaks like me that run dual
286
# monitor, with different sizes on the monitors
287
monitor_rect = screen.get_monitor_geometry(monitor)
288
width = int(monitor_rect.width * 0.66)
289
height = int(monitor_rect.height * 0.66)
290
self.set_default_size(width, height)
291
self._hpane.set_position(300)
293
def _construct_accelerators(self):
294
group = gtk.AccelGroup()
295
group.connect_group(gtk.gdk.keyval_from_name('N'),
296
gtk.gdk.CONTROL_MASK, 0, self._on_accel_next)
297
self.add_accel_group(group)
299
def _construct_left_pane(self):
300
self._left_pane_box = gtk.VBox(homogeneous=False, spacing=5)
301
self._construct_file_list()
302
self._construct_pending_list()
304
self._check_local = gtk.CheckButton(_("_Only commit locally"),
306
self._left_pane_box.pack_end(self._check_local, False, False)
307
self._check_local.set_active(False)
309
self._hpane.pack1(self._left_pane_box, resize=False, shrink=False)
310
self._left_pane_box.show()
312
def _construct_right_pane(self):
313
# TODO: I really want to make it so the diff view gets more space than
314
# the global commit message, and the per-file commit message gets even
315
# less. When I did it with wxGlade, I set it to 4 for diff, 2 for
316
# commit, and 1 for file commit, and it looked good. But I don't seem
317
# to have a way to do that with the gtk boxes... :( (Which is extra
318
# weird since wx uses gtk on Linux...)
319
self._right_pane_table = gtk.Table(rows=10, columns=1, homogeneous=False)
320
self._right_pane_table.set_row_spacings(5)
321
self._right_pane_table.set_col_spacings(5)
322
self._right_pane_table_row = 0
323
self._construct_diff_view()
324
self._construct_file_message()
325
self._construct_global_message()
327
self._right_pane_table.show()
328
self._hpane.pack2(self._right_pane_table, resize=True, shrink=True)
330
def _construct_action_pane(self):
331
self._button_commit = gtk.Button(_("Comm_it"), use_underline=True)
332
self._button_commit.connect('clicked', self._on_commit_clicked)
333
self._button_commit.set_flags(gtk.CAN_DEFAULT)
334
self._button_commit.show()
335
self.action_area.pack_end(self._button_commit)
336
self._button_commit.grab_default()
338
def _add_to_right_table(self, widget, weight, expanding=False):
339
"""Add another widget to the table
341
:param widget: The object to add
342
:param weight: How many rows does this widget get to request
343
:param expanding: Should expand|fill|shrink be set?
345
end_row = self._right_pane_table_row + weight
347
expand_opts = gtk.EXPAND | gtk.FILL | gtk.SHRINK
349
options = expand_opts
350
self._right_pane_table.attach(widget, 0, 1,
351
self._right_pane_table_row, end_row,
352
xoptions=expand_opts, yoptions=options)
353
self._right_pane_table_row = end_row
355
def _construct_file_list(self):
356
self._files_box = gtk.VBox(homogeneous=False, spacing=0)
357
file_label = gtk.Label(_('Files'))
359
self._files_box.pack_start(file_label, expand=False)
361
self._commit_all_files_radio = gtk.RadioButton(
362
None, _("Commit all changes"))
363
self._files_box.pack_start(self._commit_all_files_radio, expand=False)
364
self._commit_all_files_radio.show()
365
self._commit_all_files_radio.connect('toggled',
366
self._toggle_commit_selection)
367
self._commit_selected_radio = gtk.RadioButton(
368
self._commit_all_files_radio, _("Only commit selected changes"))
369
self._files_box.pack_start(self._commit_selected_radio, expand=False)
370
self._commit_selected_radio.show()
371
self._commit_selected_radio.connect('toggled',
372
self._toggle_commit_selection)
374
self._commit_all_files_radio.set_label(_('Commit all changes*'))
375
self._commit_all_files_radio.set_sensitive(False)
376
self._commit_selected_radio.set_sensitive(False)
378
scroller = gtk.ScrolledWindow()
379
scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
380
self._treeview_files = gtk.TreeView()
381
self._treeview_files.show()
382
scroller.add(self._treeview_files)
383
scroller.set_shadow_type(gtk.SHADOW_IN)
385
self._files_box.pack_start(scroller,
386
expand=True, fill=True)
387
self._files_box.show()
388
self._left_pane_box.pack_start(self._files_box)
390
# Keep note that all strings stored in a ListStore must be UTF-8
391
# strings. GTK does not support directly setting and restoring Unicode
393
liststore = gtk.ListStore(
394
gobject.TYPE_STRING, # [0] file_id
395
gobject.TYPE_STRING, # [1] real path
396
gobject.TYPE_BOOLEAN, # [2] checkbox
397
gobject.TYPE_STRING, # [3] display path
398
gobject.TYPE_STRING, # [4] changes type
399
gobject.TYPE_STRING, # [5] commit message
401
self._files_store = liststore
402
self._treeview_files.set_model(liststore)
119
403
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')
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)
404
crt.set_property('activatable', not bool(self._pending))
405
crt.connect("toggled", self._toggle_commit, self._files_store)
410
commit_col = gtk.TreeViewColumn(name, crt, active=2)
411
commit_col.set_visible(False)
412
self._treeview_files.append_column(commit_col)
413
self._treeview_files.append_column(gtk.TreeViewColumn(_('Path'),
414
gtk.CellRendererText(), text=3))
415
self._treeview_files.append_column(gtk.TreeViewColumn(_('Type'),
416
gtk.CellRendererText(), text=4))
417
self._treeview_files.connect('cursor-changed',
418
self._on_treeview_files_cursor_changed)
420
def _toggle_commit(self, cell, path, model):
421
if model[path][0] is None: # No file_id means 'All Files'
422
new_val = not model[path][2]
426
model[path][2] = not model[path][2]
428
def _toggle_commit_selection(self, button):
429
all_files = self._commit_all_files_radio.get_active()
430
if self._commit_all_changes != all_files:
431
checked_col = self._treeview_files.get_column(0)
432
self._commit_all_changes = all_files
434
checked_col.set_visible(False)
436
checked_col.set_visible(True)
437
renderer = checked_col.get_cell_renderers()[0]
438
renderer.set_property('activatable', not all_files)
440
def _construct_pending_list(self):
441
# Pending information defaults to hidden, we put it all in 1 box, so
442
# that we can show/hide all of them at once
443
self._pending_box = gtk.VBox()
444
self._pending_box.hide()
446
pending_message = gtk.Label()
447
pending_message.set_markup(
448
_('<i>* Cannot select specific files when merging</i>'))
449
self._pending_box.pack_start(pending_message, expand=False, padding=5)
450
pending_message.show()
452
pending_label = gtk.Label(_('Pending Revisions'))
453
self._pending_box.pack_start(pending_label, expand=False, padding=0)
456
scroller = gtk.ScrolledWindow()
457
scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
458
self._treeview_pending = gtk.TreeView()
459
scroller.add(self._treeview_pending)
460
scroller.set_shadow_type(gtk.SHADOW_IN)
462
self._pending_box.pack_start(scroller,
463
expand=True, fill=True, padding=5)
464
self._treeview_pending.show()
465
self._left_pane_box.pack_start(self._pending_box)
467
liststore = gtk.ListStore(gobject.TYPE_STRING, # revision_id
468
gobject.TYPE_STRING, # date
469
gobject.TYPE_STRING, # committer
470
gobject.TYPE_STRING, # summary
472
self._pending_store = liststore
473
self._treeview_pending.set_model(liststore)
474
self._treeview_pending.append_column(gtk.TreeViewColumn(_('Date'),
475
gtk.CellRendererText(), text=1))
476
self._treeview_pending.append_column(gtk.TreeViewColumn(_('Committer'),
477
gtk.CellRendererText(), text=2))
478
self._treeview_pending.append_column(gtk.TreeViewColumn(_('Summary'),
479
gtk.CellRendererText(), text=3))
481
def _construct_diff_view(self):
482
from diff import DiffView
484
# TODO: jam 2007-10-30 The diff label is currently disabled. If we
485
# decide that we really don't ever want to display it, we should
486
# actually remove it, and other references to it, along with the
487
# tests that it is set properly.
488
self._diff_label = gtk.Label(_('Diff for whole tree'))
489
self._diff_label.set_alignment(0, 0)
490
self._right_pane_table.set_row_spacing(self._right_pane_table_row, 0)
491
self._add_to_right_table(self._diff_label, 1, False)
492
# self._diff_label.show()
494
self._diff_view = DiffView()
495
self._add_to_right_table(self._diff_view, 4, True)
496
self._diff_view.show()
498
def _construct_file_message(self):
499
scroller = gtk.ScrolledWindow()
500
scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
502
self._file_message_text_view = gtk.TextView()
503
scroller.add(self._file_message_text_view)
504
scroller.set_shadow_type(gtk.SHADOW_IN)
507
self._file_message_text_view.modify_font(pango.FontDescription("Monospace"))
508
self._file_message_text_view.set_wrap_mode(gtk.WRAP_WORD)
509
self._file_message_text_view.set_accepts_tab(False)
510
self._file_message_text_view.show()
512
self._file_message_expander = gtk.Expander(_('File commit message'))
513
self._file_message_expander.set_expanded(True)
514
self._file_message_expander.add(scroller)
515
self._add_to_right_table(self._file_message_expander, 1, False)
516
self._file_message_expander.show()
518
def _construct_global_message(self):
519
self._global_message_label = gtk.Label(_('Global Commit Message'))
520
self._global_message_label.set_markup(_('<b>Global Commit Message</b>'))
521
self._global_message_label.set_alignment(0, 0)
522
self._right_pane_table.set_row_spacing(self._right_pane_table_row, 0)
523
self._add_to_right_table(self._global_message_label, 1, False)
524
# Can we remove the spacing between the label and the box?
525
self._global_message_label.show()
527
scroller = gtk.ScrolledWindow()
528
scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
530
self._global_message_text_view = gtk.TextView()
531
self._global_message_text_view.modify_font(pango.FontDescription("Monospace"))
532
scroller.add(self._global_message_text_view)
533
scroller.set_shadow_type(gtk.SHADOW_IN)
535
self._add_to_right_table(scroller, 2, True)
536
self._file_message_text_view.set_wrap_mode(gtk.WRAP_WORD)
537
self._file_message_text_view.set_accepts_tab(False)
538
self._global_message_text_view.show()
540
def _on_treeview_files_cursor_changed(self, treeview):
541
treeselection = treeview.get_selection()
542
(model, selection) = treeselection.get_selected()
544
if selection is not None:
545
path, display_path = model.get(selection, 1, 3)
546
self._diff_label.set_text(_('Diff for ') + display_path)
548
self._diff_view.show_diff(None)
550
self._diff_view.show_diff([path.decode('UTF-8')])
551
self._update_per_file_info(selection)
553
def _on_accel_next(self, accel_group, window, keyval, modifier):
554
# We don't really care about any of the parameters, because we know
555
# where this message came from
556
tree_selection = self._treeview_files.get_selection()
557
(model, selection) = tree_selection.get_selected()
558
if selection is None:
561
next = model.iter_next(selection)
564
# We have either made it to the end of the list, or nothing was
565
# selected. Either way, select All Files, and jump to the global
567
self._treeview_files.set_cursor((0,))
568
self._global_message_text_view.grab_focus()
570
# Set the cursor to this entry, and jump to the per-file commit
572
self._treeview_files.set_cursor(model.get_path(next))
573
self._file_message_text_view.grab_focus()
575
def _save_current_file_message(self):
576
if self._last_selected_file is None:
577
return # Nothing to save
578
text_buffer = self._file_message_text_view.get_buffer()
579
cur_text = text_buffer.get_text(text_buffer.get_start_iter(),
580
text_buffer.get_end_iter())
581
last_selected = self._files_store.get_iter(self._last_selected_file)
582
self._files_store.set_value(last_selected, 5, cur_text)
584
def _update_per_file_info(self, selection):
585
# The node is changing, so cache the current message
586
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'],
589
self._save_current_file_message()
590
text_buffer = self._file_message_text_view.get_buffer()
591
file_id, display_path, message = self._files_store.get(selection, 0, 3, 5)
592
if file_id is None: # Whole tree
593
self._file_message_expander.set_label(_('File commit message'))
594
self._file_message_expander.set_expanded(False)
595
self._file_message_expander.set_sensitive(False)
596
text_buffer.set_text('')
597
self._last_selected_file = None
599
self._file_message_expander.set_label(_('Commit message for ')
601
self._file_message_expander.set_expanded(True)
602
self._file_message_expander.set_sensitive(True)
603
text_buffer.set_text(message)
604
self._last_selected_file = self._files_store.get_path(selection)
180
606
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
607
"""Return the list of selected paths, and file info.
609
:return: ([unicode paths], [{utf-8 file info}]
611
self._save_current_file_message()
613
records = iter(self._files_store)
614
rec = records.next() # Skip the All Files record
615
assert rec[0] is None, "Are we skipping the wrong record?"
618
for record in records:
619
if self._commit_all_changes or record[2]:# [2] checkbox
620
file_id = record[0] # [0] file_id
621
path = record[1] # [1] real path
622
file_message = record[5] # [5] commit message
623
files.append(path.decode('UTF-8'))
624
if self._enable_per_file_commits and file_message:
625
# All of this needs to be utf-8 information
626
file_info.append({'path':path, 'file_id':file_id,
627
'message':file_message})
628
file_info.sort(key=lambda x:(x['path'], x['file_id']))
629
if self._enable_per_file_commits:
630
return files, file_info
635
def _on_commit_clicked(self, button):
636
""" Commit button clicked handler. """
639
def _do_commit(self):
640
message = self._get_global_commit_message()
272
642
if message == '':
273
response = question_dialog('Commit with an empty message ?',
274
'You can describe your commit intent'
643
response = self._question_dialog(
644
_('Commit with an empty message?'),
645
_('You can describe your commit intent in the message.'))
276
646
if response == gtk.RESPONSE_NO:
277
647
# Kindly give focus to message area
278
self.textview.grab_focus()
648
self._global_message_text_view.grab_focus()
651
specific_files, file_info = self._get_specific_files()
653
specific_files = None
655
local = self._check_local.get_active()
657
# All we care about is if there is a single unknown, so if this loop is
658
# entered, then there are unknown files.
659
# TODO: jam 20071002 It seems like this should cancel the dialog
660
# entirely, since there isn't a way for them to add the unknown
661
# files at this point.
662
for path in self._wt.unknowns():
663
response = self._question_dialog(
664
_("Commit with unknowns?"),
665
_("Unknown files exist in the working tree. Commit anyway?"))
666
if response == gtk.RESPONSE_NO:
673
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.'))
675
rev_id = self._wt.commit(message,
676
allow_pointless=False,
679
specific_files=specific_files,
295
681
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()
682
response = self._question_dialog(
683
_('Commit with no changes?'),
684
_('There are no changes in the working tree.'
685
' Do you want to commit anyway?'))
686
if response == gtk.RESPONSE_YES:
687
rev_id = self._wt.commit(message,
688
allow_pointless=True,
691
specific_files=specific_files,
693
self.committed_revision_id = rev_id
694
self.response(gtk.RESPONSE_OK)
696
def _get_global_commit_message(self):
697
buf = self._global_message_text_view.get_buffer()
698
start, end = buf.get_bounds()
699
return buf.get_text(start, end).decode('utf-8')
701
def _set_global_commit_message(self, message):
702
"""Just a helper for the test suite."""
703
if isinstance(message, unicode):
704
message = message.encode('UTF-8')
705
self._global_message_text_view.get_buffer().set_text(message)
707
def _set_file_commit_message(self, message):
708
"""Helper for the test suite."""
709
if isinstance(message, unicode):
710
message = message.encode('UTF-8')
711
self._file_message_text_view.get_buffer().set_text(message)
714
def _rev_to_pending_info(rev):
715
"""Get the information from a pending merge."""
716
from bzrlib.osutils import format_date
718
rev_dict['committer'] = re.sub('<.*@.*>', '', rev.committer).strip(' ')
719
rev_dict['summary'] = rev.get_summary()
720
rev_dict['date'] = format_date(rev.timestamp,
722
'original', date_fmt="%Y-%m-%d",
724
rev_dict['revision_id'] = rev.revision_id