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
# Gives the focus to the commit message area
114
self.textview.grab_focus()
117
def _create_file_view(self):
118
self.file_store = gtk.ListStore(gobject.TYPE_BOOLEAN, # [0] checkbox
119
gobject.TYPE_STRING, # [1] path to display
120
gobject.TYPE_STRING, # [2] changes type
121
gobject.TYPE_STRING) # [3] real path
122
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)
123
403
crt = gtk.CellRendererToggle()
124
crt.set_property("activatable", True)
125
crt.connect("toggled", self._toggle_commit, self.file_store)
126
self.file_view.append_column(gtk.TreeViewColumn(_('Commit'),
128
self.file_view.append_column(gtk.TreeViewColumn(_('Path'),
129
gtk.CellRendererText(), text=1))
130
self.file_view.append_column(gtk.TreeViewColumn(_('Type'),
131
gtk.CellRendererText(), text=2))
133
for path, id, kind in self.delta.added:
134
marker = osutils.kind_marker(kind)
135
self.file_store.append([ True, path+marker, _('added'), path ])
137
for path, id, kind in self.delta.removed:
138
marker = osutils.kind_marker(kind)
139
self.file_store.append([ True, path+marker, _('removed'), path ])
141
for oldpath, newpath, id, kind, text_modified, meta_modified in self.delta.renamed:
142
marker = osutils.kind_marker(kind)
143
if text_modified or meta_modified:
144
changes = _('renamed and modified')
146
changes = _('renamed')
147
self.file_store.append([ True,
148
oldpath+marker + ' => ' + newpath+marker,
153
for path, id, kind, text_modified, meta_modified in self.delta.modified:
154
marker = osutils.kind_marker(kind)
155
self.file_store.append([ True, path+marker, _('modified'), path ])
157
def _create_pending_merges(self):
158
liststore = gtk.ListStore(gobject.TYPE_STRING,
161
self.pending_view.set_model(liststore)
163
self.pending_view.append_column(gtk.TreeViewColumn(_('Date'),
164
gtk.CellRendererText(), text=0))
165
self.pending_view.append_column(gtk.TreeViewColumn(_('Committer'),
166
gtk.CellRendererText(), text=1))
167
self.pending_view.append_column(gtk.TreeViewColumn(_('Summary'),
168
gtk.CellRendererText(), text=2))
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:
173
for item in self.pending:
174
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)
178
606
def _get_specific_files(self):
180
it = self.file_store.get_iter_first()
182
if self.file_store.get_value(it, 0):
183
# get real path from hidden column 3
184
ret.append(self.file_store.get_value(it, 3))
185
it = self.file_store.iter_next(it)
189
def _toggle_commit(self, cell, path, model):
190
model[path][0] = not model[path][0]
193
def _pending_merges(self, wt):
194
""" Return a list of pending merges or None if there are none of them. """
195
parents = wt.get_parent_ids()
200
from bzrlib.osutils import format_date
202
pending = parents[1:]
204
last_revision = parents[0]
206
if last_revision is not None:
208
ignore = set(branch.repository.get_ancestry(last_revision))
209
except errors.NoSuchRevision:
210
# the last revision is a ghost : assume everything is new
212
ignore = set([None, last_revision])
217
for merge in pending:
220
m_revision = branch.repository.get_revision(merge)
223
rev['committer'] = re.sub('<.*@.*>', '', m_revision.committer).strip(' ')
224
rev['summary'] = m_revision.get_summary()
225
rev['date'] = format_date(m_revision.timestamp,
226
m_revision.timezone or 0,
227
'original', date_fmt="%Y-%m-%d",
232
inner_merges = branch.repository.get_ancestry(merge)
233
assert inner_merges[0] is None
235
inner_merges.reverse()
236
for mmerge in inner_merges:
239
mm_revision = branch.repository.get_revision(mmerge)
242
rev['committer'] = re.sub('<.*@.*>', '', mm_revision.committer).strip(' ')
243
rev['summary'] = mm_revision.get_summary()
244
rev['date'] = format_date(mm_revision.timestamp,
245
mm_revision.timezone or 0,
246
'original', date_fmt="%Y-%m-%d",
252
except errors.NoSuchRevision:
253
print "DEBUG: NoSuchRevision:", merge
257
def commit(self, widget):
258
textbuffer = self.textview.get_buffer()
259
start, end = textbuffer.get_bounds()
260
message = textbuffer.get_text(start, end).decode('utf-8')
262
checkbutton_strict = self.glade.get_widget('checkbutton_commit_strict')
263
checkbutton_force = self.glade.get_widget('checkbutton_commit_force')
266
specific_files = self._get_specific_files()
268
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()
270
642
if message == '':
271
response = question_dialog('Commit with an empty message ?',
272
'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.'))
274
646
if response == gtk.RESPONSE_NO:
275
647
# Kindly give focus to message area
276
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')
280
self.wt.commit(message,
281
allow_pointless=checkbutton_force.get_active(),
282
strict=checkbutton_strict.get_active(),
283
local=self.checkbutton_local.get_active(),
284
specific_files=specific_files)
285
except errors.NotBranchError:
286
error_dialog(_('Directory is not a branch'),
287
_('You can perform this action only in a branch.'))
289
except errors.LocalRequiresBoundBranch:
290
error_dialog(_('Directory is not a checkout'),
291
_('You can perform local commit only on checkouts.'))
675
rev_id = self._wt.commit(message,
676
allow_pointless=False,
679
specific_files=specific_files,
293
681
except errors.PointlessCommit:
294
error_dialog(_('No changes to commit'),
295
_('Try force commit if you want to commit anyway.'))
297
except errors.ConflictsInTree:
298
error_dialog(_('Conflicts in tree'),
299
_('You need to resolve the conflicts before committing.'))
301
except errors.StrictCommitFailed:
302
error_dialog(_('Strict commit failed'),
303
_('There are unknown files in the working tree.\nPlease add or delete them.'))
305
except errors.BoundBranchOutOfDate, errmsg:
306
error_dialog(_('Bound branch is out of date'),
309
except errors.BzrError, msg:
310
error_dialog(_('Unknown bzr error'), str(msg))
312
except Exception, msg:
313
error_dialog(_('Unknown error'), str(msg))
318
def close(self, widget=None):
319
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