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
32
from bzrlib import version_info
34
if version_info < (0, 9):
35
# function deprecated after 0.9
36
from bzrlib.delta import compare_trees
38
import bzrlib.errors as errors
39
from bzrlib.workingtree import WorkingTree
42
""" Display Commit dialog and perform the needed actions. """
43
def __init__(self, gladefile, comm, dialog):
44
""" Initialize the Commit dialog. """
45
self.gladefile = gladefile
46
self.glade = gtk.glade.XML(self.gladefile, 'window_commit', 'olive-gtk')
48
# Communication object
53
# Get some important widgets
54
self.window = self.glade.get_widget('window_commit')
55
self.checkbutton_local = self.glade.get_widget('checkbutton_commit_local')
56
self.textview = self.glade.get_widget('textview_commit')
57
self.file_view = self.glade.get_widget('treeview_commit_select')
59
# Check if current location is a branch
61
(self.wt, path) = WorkingTree.open_containing(self.comm.get_path())
62
branch = self.wt.branch
63
except errors.NotBranchError:
69
file_id = self.wt.path2id(path)
71
self.notbranch = False
77
self.old_tree = self.wt.branch.repository.revision_tree(self.wt.branch.last_revision())
78
if version_info < (0, 9):
79
self.delta = compare_trees(self.old_tree, self.wt)
81
self.delta = self.wt.changes_from(self.old_tree)
83
# Dictionary for signal_autoconnect
84
dic = { "on_button_commit_commit_clicked": self.commit,
85
"on_button_commit_cancel_clicked": self.close }
87
# Connect the signals to the handlers
88
self.glade.signal_autoconnect(dic)
90
# Create the file list
91
self._create_file_view()
94
""" Display the Push dialog. """
96
self.dialog.error_dialog(_('Directory is not a branch'),
97
_('You can perform this action only in a branch.'))
100
from olive.backend.info import is_checkout
101
if is_checkout(self.comm.get_path()):
102
# we have a checkout, so the local commit checkbox must appear
103
self.checkbutton_local.show()
105
self.textview.modify_font(pango.FontDescription("Monospace"))
109
# This code is from Jelmer Vernooij's bzr-gtk branch
110
def _create_file_view(self):
111
self.file_store = gtk.ListStore(gobject.TYPE_BOOLEAN,
114
self.file_view.set_model(self.file_store)
115
crt = gtk.CellRendererToggle()
116
crt.set_property("activatable", True)
117
crt.connect("toggled", self._toggle_commit, self.file_store)
118
self.file_view.append_column(gtk.TreeViewColumn(_('Commit'),
120
self.file_view.append_column(gtk.TreeViewColumn(_('Path'),
121
gtk.CellRendererText(), text=1))
122
self.file_view.append_column(gtk.TreeViewColumn(_('Type'),
123
gtk.CellRendererText(), text=2))
125
for path, id, kind in self.delta.added:
126
self.file_store.append([ True, path, _('added') ])
128
for path, id, kind in self.delta.removed:
129
self.file_store.append([ True, path, _('removed') ])
131
for oldpath, newpath, id, kind, text_modified, meta_modified in self.delta.renamed:
132
self.file_store.append([ True, oldpath, _('renamed') ])
134
for path, id, kind, text_modified, meta_modified in self.delta.modified:
135
self.file_store.append([ True, path, _('modified') ])
19
from gi.repository import Gdk
20
from gi.repository import Gtk
21
from gi.repository import GObject
22
from gi.repository import Pango
25
from bzrlib import bencode
27
from bzrlib.util import bencode
33
from bzrlib.plugins.gtk.dialog import question_dialog
34
from bzrlib.plugins.gtk.errors import show_bzr_error
35
from bzrlib.plugins.gtk.i18n import _i18n
36
from bzrlib.plugins.gtk.commitmsgs import SavedCommitMessagesManager
46
def pending_revisions(wt):
47
"""Return a list of pending merges or None if there are none of them.
49
Arguably this should be a core function, and
50
``bzrlib.status.show_pending_merges`` should be built on top of it.
52
:return: [(rev, [children])]
54
parents = wt.get_parent_ids()
58
# The basic pending merge algorithm uses the same algorithm as
59
# bzrlib.status.show_pending_merges
62
last_revision = parents[0]
64
if last_revision is not None:
66
ignore = set(branch.repository.get_ancestry(last_revision,
68
except errors.NoSuchRevision:
69
# the last revision is a ghost : assume everything is new
71
ignore = set([None, last_revision])
79
rev = branch.repository.get_revision(merge)
81
pm.append((rev, children))
83
# This does need to be topo sorted, so we search backwards
84
inner_merges = branch.repository.get_ancestry(merge)
85
assert inner_merges[0] is None
87
for mmerge in reversed(inner_merges):
90
rev = branch.repository.get_revision(mmerge)
94
except errors.NoSuchRevision:
95
print "DEBUG: NoSuchRevision:", merge
100
_newline_variants_re = re.compile(r'\r\n?')
101
def _sanitize_and_decode_message(utf8_message):
102
"""Turn a utf-8 message into a sanitized Unicode message."""
103
fixed_newline = _newline_variants_re.sub('\n', utf8_message)
104
return fixed_newline.decode('utf-8')
107
class CommitDialog(Gtk.Dialog):
108
"""Implementation of Commit."""
110
def __init__(self, wt, selected=None, parent=None):
111
super(CommitDialog, self).__init__(
112
title="Commit to %s" % wt.basedir, parent=parent, flags=0)
113
self.connect('delete-event', self._on_delete_window)
114
self._question_dialog = question_dialog
116
self.set_type_hint(Gdk.WindowTypeHint.NORMAL)
119
# TODO: Do something with this value, it is used by Olive
120
# It used to set all changes but this one to False
121
self._selected = selected
122
self._enable_per_file_commits = True
123
self._commit_all_changes = True
124
self.committed_revision_id = None # Nothing has been committed yet
125
self._saved_commit_messages_manager = SavedCommitMessagesManager(
126
self._wt, self._wt.branch)
132
def setup_params(self):
133
"""Setup the member variables for state."""
134
self._basis_tree = self._wt.basis_tree()
136
self._pending = pending_revisions(self._wt)
138
self._is_checkout = (self._wt.branch.get_bound_location() is not None)
140
def fill_in_data(self):
141
# Now that we are built, handle changes to the view based on the state
142
self._fill_in_pending()
144
self._fill_in_files()
145
self._fill_in_checkout()
146
self._fill_in_per_file_info()
148
def _fill_in_pending(self):
149
if not self._pending:
150
self._pending_box.hide()
153
# TODO: We'd really prefer this to be a nested list
154
for rev, children in self._pending:
155
rev_info = self._rev_to_pending_info(rev)
156
self._pending_store.append([
157
rev_info['revision_id'],
159
rev_info['committer'],
162
for child in children:
163
rev_info = self._rev_to_pending_info(child)
164
self._pending_store.append([
165
rev_info['revision_id'],
167
rev_info['committer'],
170
self._pending_box.show()
172
def _fill_in_files(self):
173
# We should really use add a progress bar of some kind.
174
# While we fill in the view, hide the store
175
store = self._files_store
176
self._treeview_files.set_model(None)
178
added = _i18n('added')
179
removed = _i18n('removed')
180
renamed = _i18n('renamed')
181
renamed_and_modified = _i18n('renamed and modified')
182
modified = _i18n('modified')
183
kind_changed = _i18n('kind changed')
186
# [file_id, real path, checkbox, display path, changes type, message]
187
# iter_changes returns:
188
# (file_id, (path_in_source, path_in_target),
189
# changed_content, versioned, parent, name, kind,
192
all_enabled = (self._selected is None)
193
# The first entry is always the 'whole tree'
194
all_iter = store.append([None, None, all_enabled, 'All Files', '', ''])
195
initial_cursor = store.get_path(all_iter)
196
# should we pass specific_files?
198
self._basis_tree.lock_read()
200
from diff import iter_changes_to_status
201
saved_file_messages = self._saved_commit_messages_manager.get()[1]
202
for (file_id, real_path, change_type, display_path
203
) in iter_changes_to_status(self._basis_tree, self._wt):
204
if self._selected and real_path != self._selected:
209
default_message = saved_file_messages[file_id]
212
item_iter = store.append([
214
real_path.encode('UTF-8'),
216
display_path.encode('UTF-8'),
218
default_message, # Initial comment
220
if self._selected and enabled:
221
initial_cursor = store.get_path(item_iter)
223
self._basis_tree.unlock()
226
self._treeview_files.set_model(store)
227
self._last_selected_file = None
228
# This sets the cursor, which causes the expander to close, which
229
# causes the _file_message_text_view to never get realized. So we have
230
# to give it a little kick, or it warns when we try to grab the focus
231
self._treeview_files.set_cursor(initial_cursor, None, False)
233
def _realize_file_message_tree_view(*args):
234
self._file_message_text_view.realize()
235
self.connect_after('realize', _realize_file_message_tree_view)
237
def _fill_in_diff(self):
238
self._diff_view.set_trees(self._wt, self._basis_tree)
240
def _fill_in_checkout(self):
241
if not self._is_checkout:
242
self._check_local.hide()
245
bus = dbus.SystemBus()
247
proxy_obj = bus.get_object('org.freedesktop.NetworkManager',
248
'/org/freedesktop/NetworkManager')
249
except dbus.DBusException:
250
trace.mutter("networkmanager not available.")
251
self._check_local.show()
254
dbus_iface = dbus.Interface(proxy_obj,
255
'org.freedesktop.NetworkManager')
257
# 3 is the enum value for STATE_CONNECTED
258
self._check_local.set_active(dbus_iface.state() != 3)
259
except dbus.DBusException, e:
260
# Silently drop errors. While DBus may be
261
# available, NetworkManager doesn't necessarily have to be
262
trace.mutter("unable to get networkmanager state: %r" % e)
263
self._check_local.show()
265
def _fill_in_per_file_info(self):
266
config = self._wt.branch.get_config()
267
enable_per_file_commits = config.get_user_option('per_file_commits')
268
if (enable_per_file_commits is None
269
or enable_per_file_commits.lower()
270
not in ('y', 'yes', 'on', 'enable', '1', 't', 'true')):
271
self._enable_per_file_commits = False
273
self._enable_per_file_commits = True
274
if not self._enable_per_file_commits:
275
self._file_message_expander.hide()
276
self._global_message_label.set_markup(_i18n('<b>Commit Message</b>'))
278
def _compute_delta(self):
279
self._delta = self._wt.changes_from(self._basis_tree)
282
"""Build up the dialog widgets."""
283
# The primary pane which splits it into left and right (adjustable)
285
self._hpane = Gtk.HPaned()
287
self._construct_left_pane()
288
self._construct_right_pane()
289
self._construct_action_pane()
291
self.get_content_area().pack_start(self._hpane, True, True, 0)
293
self.set_focus(self._global_message_text_view)
295
self._construct_accelerators()
298
def _set_sizes(self):
299
# This seems like a reasonable default, we might like it to
300
# be a bit wider, so that by default we can fit an 80-line diff in the
302
# Alternatively, we should be saving the last position/size rather than
303
# setting it to a fixed value every time we start up.
304
screen = self.get_screen()
305
monitor = 0 # We would like it to be the monitor we are going to
306
# display on, but I don't know how to figure that out
307
# Only really useful for freaks like me that run dual
308
# monitor, with different sizes on the monitors
309
monitor_rect = screen.get_monitor_geometry(monitor)
310
width = int(monitor_rect.width * 0.66)
311
height = int(monitor_rect.height * 0.66)
312
self.set_default_size(width, height)
313
self._hpane.set_position(300)
315
def _construct_accelerators(self):
316
group = Gtk.AccelGroup()
317
group.connect(Gdk.keyval_from_name('N'),
318
Gdk.ModifierType.CONTROL_MASK, 0, self._on_accel_next)
319
self.add_accel_group(group)
321
# ignore the escape key (avoid closing the window)
322
self.connect_object('close', self.emit_stop_by_name, 'close')
324
def _construct_left_pane(self):
325
self._left_pane_box = Gtk.VBox(homogeneous=False, spacing=5)
326
self._construct_file_list()
327
self._construct_pending_list()
329
self._check_local = Gtk.CheckButton(_i18n("_Only commit locally"),
331
self._left_pane_box.pack_end(self._check_local, False, False, 0)
332
self._check_local.set_active(False)
334
self._hpane.pack1(self._left_pane_box, resize=False, shrink=False)
335
self._left_pane_box.show()
337
def _construct_right_pane(self):
338
# TODO: I really want to make it so the diff view gets more space than
339
# the global commit message, and the per-file commit message gets even
340
# less. When I did it with wxGlade, I set it to 4 for diff, 2 for
341
# commit, and 1 for file commit, and it looked good. But I don't seem
342
# to have a way to do that with the gtk boxes... :( (Which is extra
343
# weird since wx uses gtk on Linux...)
344
self._right_pane_table = Gtk.Table(rows=10, columns=1, homogeneous=False)
345
self._right_pane_table.set_row_spacings(5)
346
self._right_pane_table.set_col_spacings(5)
347
self._right_pane_table_row = 0
348
self._construct_diff_view()
349
self._construct_file_message()
350
self._construct_global_message()
352
self._right_pane_table.show()
353
self._hpane.pack2(self._right_pane_table, resize=True, shrink=True)
355
def _construct_action_pane(self):
356
self._button_cancel = Gtk.Button(stock=Gtk.STOCK_CANCEL)
357
self._button_cancel.connect('clicked', self._on_cancel_clicked)
358
self._button_cancel.show()
359
self.get_action_area().pack_end(
360
self._button_cancel, True, True, 0)
361
self._button_commit = Gtk.Button(_i18n("Comm_it"), use_underline=True)
362
self._button_commit.connect('clicked', self._on_commit_clicked)
363
self._button_commit.set_can_default(True)
364
self._button_commit.show()
365
self.get_action_area().pack_end(
366
self._button_commit, True, True, 0)
367
self._button_commit.grab_default()
369
def _add_to_right_table(self, widget, weight, expanding=False):
370
"""Add another widget to the table
372
:param widget: The object to add
373
:param weight: How many rows does this widget get to request
374
:param expanding: Should expand|fill|shrink be set?
376
end_row = self._right_pane_table_row + weight
378
expand_opts = Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL | Gtk.AttachOptions.SHRINK
380
options = expand_opts
381
self._right_pane_table.attach(widget, 0, 1,
382
self._right_pane_table_row, end_row,
383
xoptions=expand_opts, yoptions=options)
384
self._right_pane_table_row = end_row
386
def _construct_file_list(self):
387
self._files_box = Gtk.VBox(homogeneous=False, spacing=0)
388
file_label = Gtk.Label(label=_i18n('Files'))
390
self._files_box.pack_start(file_label, False, True, 0)
392
self._commit_all_files_radio = Gtk.RadioButton.new_with_label(
393
None, _i18n("Commit all changes"))
394
self._files_box.pack_start(self._commit_all_files_radio, False, True, 0)
395
self._commit_all_files_radio.show()
396
self._commit_all_files_radio.connect('toggled',
397
self._toggle_commit_selection)
398
self._commit_selected_radio = Gtk.RadioButton.new_with_label_from_widget(
399
self._commit_all_files_radio, _i18n("Only commit selected changes"))
400
self._files_box.pack_start(self._commit_selected_radio, False, True, 0)
401
self._commit_selected_radio.show()
402
self._commit_selected_radio.connect('toggled',
403
self._toggle_commit_selection)
405
self._commit_all_files_radio.set_label(_i18n('Commit all changes*'))
406
self._commit_all_files_radio.set_sensitive(False)
407
self._commit_selected_radio.set_sensitive(False)
409
scroller = Gtk.ScrolledWindow()
410
scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
411
self._treeview_files = Gtk.TreeView()
412
self._treeview_files.show()
413
scroller.add(self._treeview_files)
414
scroller.set_shadow_type(Gtk.ShadowType.IN)
416
self._files_box.pack_start(scroller, True, True, 0)
417
self._files_box.show()
418
self._left_pane_box.pack_start(self._files_box, True, True, 0)
420
# Keep note that all strings stored in a ListStore must be UTF-8
421
# strings. GTK does not support directly setting and restoring Unicode
423
liststore = Gtk.ListStore(
424
GObject.TYPE_STRING, # [0] file_id
425
GObject.TYPE_STRING, # [1] real path
426
GObject.TYPE_BOOLEAN, # [2] checkbox
427
GObject.TYPE_STRING, # [3] display path
428
GObject.TYPE_STRING, # [4] changes type
429
GObject.TYPE_STRING, # [5] commit message
431
self._files_store = liststore
432
self._treeview_files.set_model(liststore)
433
crt = Gtk.CellRendererToggle()
434
crt.set_property('activatable', not bool(self._pending))
435
crt.connect("toggled", self._toggle_commit, self._files_store)
437
name = _i18n('Commit*')
439
name = _i18n('Commit')
440
commit_col = Gtk.TreeViewColumn(name, crt, active=2)
441
commit_col.set_visible(False)
442
self._treeview_files.append_column(commit_col)
443
self._treeview_files.append_column(Gtk.TreeViewColumn(_i18n('Path'),
444
Gtk.CellRendererText(), text=3))
445
self._treeview_files.append_column(Gtk.TreeViewColumn(_i18n('Type'),
446
Gtk.CellRendererText(), text=4))
447
self._treeview_files.connect('cursor-changed',
448
self._on_treeview_files_cursor_changed)
450
def _toggle_commit(self, cell, path, model):
451
if model[path][0] is None: # No file_id means 'All Files'
452
new_val = not model[path][2]
456
model[path][2] = not model[path][2]
458
def _toggle_commit_selection(self, button):
459
all_files = self._commit_all_files_radio.get_active()
460
if self._commit_all_changes != all_files:
461
checked_col = self._treeview_files.get_column(0)
462
self._commit_all_changes = all_files
464
checked_col.set_visible(False)
466
checked_col.set_visible(True)
467
renderer = checked_col.get_cells()[0]
468
renderer.set_property('activatable', not all_files)
470
def _construct_pending_list(self):
471
# Pending information defaults to hidden, we put it all in 1 box, so
472
# that we can show/hide all of them at once
473
self._pending_box = Gtk.VBox()
474
self._pending_box.hide()
476
pending_message = Gtk.Label()
477
pending_message.set_markup(
478
_i18n('<i>* Cannot select specific files when merging</i>'))
479
self._pending_box.pack_start(pending_message, False, True, 5)
480
pending_message.show()
482
pending_label = Gtk.Label(label=_i18n('Pending Revisions'))
483
self._pending_box.pack_start(pending_label, False, True, 0)
486
scroller = Gtk.ScrolledWindow()
487
scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
488
self._treeview_pending = Gtk.TreeView()
489
scroller.add(self._treeview_pending)
490
scroller.set_shadow_type(Gtk.ShadowType.IN)
492
self._pending_box.pack_start(scroller, True, True, 5)
493
self._treeview_pending.show()
494
self._left_pane_box.pack_start(self._pending_box, True, True, 0)
496
liststore = Gtk.ListStore(GObject.TYPE_STRING, # revision_id
497
GObject.TYPE_STRING, # date
498
GObject.TYPE_STRING, # committer
499
GObject.TYPE_STRING, # summary
501
self._pending_store = liststore
502
self._treeview_pending.set_model(liststore)
503
self._treeview_pending.append_column(Gtk.TreeViewColumn(_i18n('Date'),
504
Gtk.CellRendererText(), text=1))
505
self._treeview_pending.append_column(Gtk.TreeViewColumn(_i18n('Committer'),
506
Gtk.CellRendererText(), text=2))
507
self._treeview_pending.append_column(Gtk.TreeViewColumn(_i18n('Summary'),
508
Gtk.CellRendererText(), text=3))
510
def _construct_diff_view(self):
511
from bzrlib.plugins.gtk.diff import DiffView
513
# TODO: jam 2007-10-30 The diff label is currently disabled. If we
514
# decide that we really don't ever want to display it, we should
515
# actually remove it, and other references to it, along with the
516
# tests that it is set properly.
517
self._diff_label = Gtk.Label(label=_i18n('Diff for whole tree'))
518
self._diff_label.set_alignment(0, 0)
519
self._right_pane_table.set_row_spacing(self._right_pane_table_row, 0)
520
self._add_to_right_table(self._diff_label, 1, False)
521
# self._diff_label.show()
523
self._diff_view = DiffView()
524
self._add_to_right_table(self._diff_view, 4, True)
525
self._diff_view.show()
527
def _construct_file_message(self):
528
scroller = Gtk.ScrolledWindow()
529
scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
531
self._file_message_text_view = Gtk.TextView()
532
scroller.add(self._file_message_text_view)
533
scroller.set_shadow_type(Gtk.ShadowType.IN)
536
self._file_message_text_view.modify_font(Pango.FontDescription("Monospace"))
537
self._file_message_text_view.set_wrap_mode(Gtk.WrapMode.WORD)
538
self._file_message_text_view.set_accepts_tab(False)
539
self._file_message_text_view.show()
541
self._file_message_expander = Gtk.Expander(
542
label=_i18n('File commit message'))
543
self._file_message_expander.set_expanded(True)
544
self._file_message_expander.add(scroller)
545
self._add_to_right_table(self._file_message_expander, 1, False)
546
self._file_message_expander.show()
548
def _construct_global_message(self):
549
self._global_message_label = Gtk.Label(label=_i18n('Global Commit Message'))
550
self._global_message_label.set_markup(
551
_i18n('<b>Global Commit Message</b>'))
552
self._global_message_label.set_alignment(0, 0)
553
self._right_pane_table.set_row_spacing(self._right_pane_table_row, 0)
554
self._add_to_right_table(self._global_message_label, 1, False)
555
# Can we remove the spacing between the label and the box?
556
self._global_message_label.show()
558
scroller = Gtk.ScrolledWindow()
559
scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
561
self._global_message_text_view = Gtk.TextView()
562
self._set_global_commit_message(self._saved_commit_messages_manager.get()[0])
563
self._global_message_text_view.modify_font(Pango.FontDescription("Monospace"))
564
scroller.add(self._global_message_text_view)
565
scroller.set_shadow_type(Gtk.ShadowType.IN)
567
self._add_to_right_table(scroller, 2, True)
568
self._file_message_text_view.set_wrap_mode(Gtk.WrapMode.WORD)
569
self._file_message_text_view.set_accepts_tab(False)
570
self._global_message_text_view.show()
572
def _on_treeview_files_cursor_changed(self, treeview):
573
treeselection = treeview.get_selection()
574
(model, selection) = treeselection.get_selected()
576
if selection is not None:
577
path, display_path = model.get(selection, 1, 3)
578
self._diff_label.set_text(_i18n('Diff for ') + display_path)
580
self._diff_view.show_diff(None)
582
self._diff_view.show_diff([path.decode('UTF-8')])
583
self._update_per_file_info(selection)
585
def _on_accel_next(self, accel_group, window, keyval, modifier):
586
# We don't really care about any of the parameters, because we know
587
# where this message came from
588
tree_selection = self._treeview_files.get_selection()
589
(model, selection) = tree_selection.get_selected()
590
if selection is None:
593
next = model.iter_next(selection)
596
# We have either made it to the end of the list, or nothing was
597
# selected. Either way, select All Files, and jump to the global
599
self._treeview_files.set_cursor(
600
Gtk.TreePath(path=0), None, False)
601
self._global_message_text_view.grab_focus()
603
# Set the cursor to this entry, and jump to the per-file commit
605
self._treeview_files.set_cursor(model.get_path(next), None, False)
606
self._file_message_text_view.grab_focus()
608
def _save_current_file_message(self):
609
if self._last_selected_file is None:
610
return # Nothing to save
611
text_buffer = self._file_message_text_view.get_buffer()
612
cur_text = text_buffer.get_text(text_buffer.get_start_iter(),
613
text_buffer.get_end_iter(), True)
614
last_selected = self._files_store.get_iter(self._last_selected_file)
615
self._files_store.set_value(last_selected, 5, cur_text)
617
def _update_per_file_info(self, selection):
618
# The node is changing, so cache the current message
619
if not self._enable_per_file_commits:
622
self._save_current_file_message()
623
text_buffer = self._file_message_text_view.get_buffer()
624
file_id, display_path, message = self._files_store.get(selection, 0, 3, 5)
625
if file_id is None: # Whole tree
626
self._file_message_expander.set_label(_i18n('File commit message'))
627
self._file_message_expander.set_expanded(False)
628
self._file_message_expander.set_sensitive(False)
629
text_buffer.set_text('')
630
self._last_selected_file = None
632
self._file_message_expander.set_label(_i18n('Commit message for ')
634
self._file_message_expander.set_expanded(True)
635
self._file_message_expander.set_sensitive(True)
636
text_buffer.set_text(message)
637
self._last_selected_file = self._files_store.get_path(selection)
137
639
def _get_specific_files(self):
139
it = self.file_store.get_iter_first()
141
if self.file_store.get_value(it, 0):
142
ret.append(self.file_store.get_value(it, 1))
143
it = self.file_store.iter_next(it)
146
# end of bzr-gtk code
148
def _toggle_commit(self, cell, path, model):
149
model[path][0] = not model[path][0]
152
def commit(self, widget):
153
textbuffer = self.textview.get_buffer()
154
start, end = textbuffer.get_bounds()
155
message = textbuffer.get_text(start, end)
157
checkbutton_strict = self.glade.get_widget('checkbutton_commit_strict')
158
checkbutton_force = self.glade.get_widget('checkbutton_commit_force')
160
specific_files = self._get_specific_files()
162
self.comm.set_busy(self.window)
163
# merged from Jelmer Vernooij's olive integration branch
640
"""Return the list of selected paths, and file info.
642
:return: ([unicode paths], [{utf-8 file info}]
644
self._save_current_file_message()
646
records = iter(self._files_store)
647
rec = records.next() # Skip the All Files record
648
assert rec[0] is None, "Are we skipping the wrong record?"
651
for record in records:
652
if self._commit_all_changes or record[2]:# [2] checkbox
653
file_id = record[0] # [0] file_id
654
path = record[1] # [1] real path
656
file_message = _sanitize_and_decode_message(record[5])
657
files.append(path.decode('UTF-8'))
658
if self._enable_per_file_commits and file_message:
659
# All of this needs to be utf-8 information
660
file_message = file_message.encode('UTF-8')
661
file_info.append({'path':path, 'file_id':file_id,
662
'message':file_message})
663
file_info.sort(key=lambda x:(x['path'], x['file_id']))
664
if self._enable_per_file_commits:
665
return files, file_info
670
def _on_cancel_clicked(self, button):
671
""" Cancel button clicked handler. """
675
def _on_delete_window(self, source, event):
676
""" Delete window handler. """
679
def _do_cancel(self):
680
"""If requested, saves commit messages when cancelling gcommit; they are re-used by a next gcommit"""
681
mgr = SavedCommitMessagesManager()
682
self._saved_commit_messages_manager = mgr
683
mgr.insert(self._get_global_commit_message(),
684
self._get_specific_files()[1])
685
if mgr.is_not_empty(): # maybe worth saving
686
response = self._question_dialog(
687
_i18n('Commit cancelled'),
688
_i18n('Do you want to save your commit messages ?'),
690
if response == Gtk.ResponseType.NO:
691
# save nothing and destroy old comments if any
692
mgr = SavedCommitMessagesManager()
693
mgr.save(self._wt, self._wt.branch)
694
self.response(Gtk.ResponseType.CANCEL) # close window
697
def _on_commit_clicked(self, button):
698
""" Commit button clicked handler. """
701
def _do_commit(self):
702
message = self._get_global_commit_message()
705
response = self._question_dialog(
706
_i18n('Commit with an empty message?'),
707
_i18n('You can describe your commit intent in the message.'),
709
if response == Gtk.ResponseType.NO:
710
# Kindly give focus to message area
711
self._global_message_text_view.grab_focus()
714
specific_files, file_info = self._get_specific_files()
716
specific_files = None
718
local = self._check_local.get_active()
720
# All we care about is if there is a single unknown, so if this loop is
721
# entered, then there are unknown files.
722
# TODO: jam 20071002 It seems like this should cancel the dialog
723
# entirely, since there isn't a way for them to add the unknown
724
# files at this point.
725
for path in self._wt.unknowns():
726
response = self._question_dialog(
727
_i18n("Commit with unknowns?"),
728
_i18n("Unknown files exist in the working tree. Commit anyway?"),
730
# Doesn't set a parent for the dialog..
731
if response == Gtk.ResponseType.NO:
738
revprops['file-info'] = bencode.bencode(file_info).decode('UTF-8')
165
self.wt.commit(message,
166
allow_pointless=checkbutton_force.get_active(),
167
strict=checkbutton_strict.get_active(),
168
local=self.checkbutton_local.get_active(),
169
specific_files=specific_files)
170
except errors.NotBranchError:
171
self.dialog.error_dialog(_('Directory is not a branch'),
172
_('You can perform this action only in a branch.'))
173
self.comm.set_busy(self.window, False)
175
except errors.LocalRequiresBoundBranch:
176
self.dialog.error_dialog(_('Directory is not a checkout'),
177
_('You can perform local commit only on checkouts.'))
178
self.comm.set_busy(self.window, False)
740
rev_id = self._wt.commit(message,
741
allow_pointless=False,
744
specific_files=specific_files,
180
746
except errors.PointlessCommit:
181
self.dialog.error_dialog(_('No changes to commit'),
182
_('Try force commit if you want to commit anyway.'))
183
self.comm.set_busy(self.window, False)
185
except errors.ConflictsInTree:
186
self.dialog.error_dialog(_('Conflicts in tree'),
187
_('You need to resolve the conflicts before committing.'))
188
self.comm.set_busy(self.window, False)
190
except errors.StrictCommitFailed:
191
self.dialog.error_dialog(_('Strict commit failed'),
192
_('There are unknown files in the working tree.\nPlease add or delete them.'))
193
self.comm.set_busy(self.window, False)
195
except errors.BoundBranchOutOfDate, errmsg:
196
self.dialog.error_dialog(_('Bound branch is out of date'),
198
self.comm.set_busy(self.window, False)
204
self.comm.refresh_right()
206
def close(self, widget=None):
207
self.window.destroy()
747
response = self._question_dialog(
748
_i18n('Commit with no changes?'),
749
_i18n('There are no changes in the working tree.'
750
' Do you want to commit anyway?'),
752
if response == Gtk.ResponseType.YES:
753
rev_id = self._wt.commit(message,
754
allow_pointless=True,
757
specific_files=specific_files,
759
self.committed_revision_id = rev_id
760
# destroy old comments if any
761
SavedCommitMessagesManager().save(self._wt, self._wt.branch)
762
self.response(Gtk.ResponseType.OK)
764
def _get_global_commit_message(self):
765
buf = self._global_message_text_view.get_buffer()
766
start, end = buf.get_bounds()
767
text = buf.get_text(start, end, True)
768
return _sanitize_and_decode_message(text)
770
def _set_global_commit_message(self, message):
771
"""Just a helper for the test suite."""
772
if isinstance(message, unicode):
773
message = message.encode('UTF-8')
774
self._global_message_text_view.get_buffer().set_text(message)
776
def _set_file_commit_message(self, message):
777
"""Helper for the test suite."""
778
if isinstance(message, unicode):
779
message = message.encode('UTF-8')
780
self._file_message_text_view.get_buffer().set_text(message)
783
def _rev_to_pending_info(rev):
784
"""Get the information from a pending merge."""
785
from bzrlib.osutils import format_date
787
rev_dict['committer'] = re.sub('<.*@.*>', '', rev.committer).strip(' ')
788
rev_dict['summary'] = rev.get_summary()
789
rev_dict['date'] = format_date(rev.timestamp,
791
'original', date_fmt="%Y-%m-%d",
793
rev_dict['revision_id'] = rev.revision_id