1
1
# Copyright (C) 2006 by Szilveszter Farkas (Phanatic) <szilveszter.farkas@gmail.com>
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
11
# GNU General Public License for more details.
13
13
# You should have received a copy of the GNU General Public License
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
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:
65
graph = branch.repository.get_graph()
66
ignore = set([r for r,ps in graph.iter_ancestry([last_revision])])
74
rev = branch.repository.get_revision(merge)
76
pm.append((rev, children))
78
# This does need to be topo sorted, so we search backwards
79
inner_merges = branch.repository.get_ancestry(merge)
80
assert inner_merges[0] is None
82
for mmerge in reversed(inner_merges):
85
rev = branch.repository.get_revision(mmerge)
89
except errors.NoSuchRevision:
90
print "DEBUG: NoSuchRevision:", merge
95
_newline_variants_re = re.compile(r'\r\n?')
96
def _sanitize_and_decode_message(utf8_message):
97
"""Turn a utf-8 message into a sanitized Unicode message."""
98
fixed_newline = _newline_variants_re.sub('\n', utf8_message)
99
return fixed_newline.decode('utf-8')
102
class CommitDialog(Gtk.Dialog):
103
"""Implementation of Commit."""
105
def __init__(self, wt, selected=None, parent=None):
106
super(CommitDialog, self).__init__(
107
title="Commit to %s" % wt.basedir, parent=parent, flags=0)
108
self.connect('delete-event', self._on_delete_window)
109
self._question_dialog = question_dialog
111
self.set_type_hint(Gdk.WindowTypeHint.NORMAL)
114
# TODO: Do something with this value, it is used by Olive
115
# It used to set all changes but this one to False
116
self._selected = selected
117
self._enable_per_file_commits = True
118
self._commit_all_changes = True
119
self.committed_revision_id = None # Nothing has been committed yet
120
self._saved_commit_messages_manager = SavedCommitMessagesManager(
121
self._wt, self._wt.branch)
127
def setup_params(self):
128
"""Setup the member variables for state."""
129
self._basis_tree = self._wt.basis_tree()
133
self._pending = pending_revisions(self._wt)
137
self._is_checkout = (self._wt.branch.get_bound_location() is not None)
139
def fill_in_data(self):
140
# Now that we are built, handle changes to the view based on the state
141
self._fill_in_pending()
143
self._fill_in_files()
144
self._fill_in_checkout()
145
self._fill_in_per_file_info()
147
def _fill_in_pending(self):
148
if not self._pending:
149
self._pending_box.hide()
152
# TODO: We'd really prefer this to be a nested list
153
for rev, children in self._pending:
154
rev_info = self._rev_to_pending_info(rev)
155
self._pending_store.append([
156
rev_info['revision_id'],
158
rev_info['committer'],
161
for child in children:
162
rev_info = self._rev_to_pending_info(child)
163
self._pending_store.append([
164
rev_info['revision_id'],
166
rev_info['committer'],
169
self._pending_box.show()
171
def _fill_in_files(self):
172
# We should really use add a progress bar of some kind.
173
# While we fill in the view, hide the store
174
store = self._files_store
175
self._treeview_files.set_model(None)
177
added = _i18n('added')
178
removed = _i18n('removed')
179
renamed = _i18n('renamed')
180
renamed_and_modified = _i18n('renamed and modified')
181
modified = _i18n('modified')
182
kind_changed = _i18n('kind changed')
185
# [file_id, real path, checkbox, display path, changes type, message]
186
# iter_changes returns:
187
# (file_id, (path_in_source, path_in_target),
188
# changed_content, versioned, parent, name, kind,
191
all_enabled = (self._selected is None)
192
# The first entry is always the 'whole tree'
193
all_iter = store.append([None, None, all_enabled, 'All Files', '', ''])
194
initial_cursor = store.get_path(all_iter)
195
# should we pass specific_files?
197
self._basis_tree.lock_read()
199
from diff import iter_changes_to_status
200
saved_file_messages = self._saved_commit_messages_manager.get()[1]
201
for (file_id, real_path, change_type, display_path
202
) in iter_changes_to_status(self._basis_tree, self._wt):
203
if self._selected and real_path != self._selected:
208
default_message = saved_file_messages[file_id]
211
item_iter = store.append([
213
real_path.encode('UTF-8'),
215
display_path.encode('UTF-8'),
217
default_message, # Initial comment
219
if self._selected and enabled:
220
initial_cursor = store.get_path(item_iter)
222
self._basis_tree.unlock()
225
self._treeview_files.set_model(store)
226
self._last_selected_file = None
227
# This sets the cursor, which causes the expander to close, which
228
# causes the _file_message_text_view to never get realized. So we have
229
# to give it a little kick, or it warns when we try to grab the focus
230
self._treeview_files.set_cursor(initial_cursor, None, False)
232
def _realize_file_message_tree_view(*args):
233
self._file_message_text_view.realize()
234
self.connect_after('realize', _realize_file_message_tree_view)
236
def _fill_in_diff(self):
237
self._diff_view.set_trees(self._wt, self._basis_tree)
239
def _fill_in_checkout(self):
240
if not self._is_checkout:
241
self._check_local.hide()
244
bus = dbus.SystemBus()
246
proxy_obj = bus.get_object('org.freedesktop.NetworkManager',
247
'/org/freedesktop/NetworkManager')
248
except dbus.DBusException:
249
trace.mutter("networkmanager not available.")
250
self._check_local.show()
253
dbus_iface = dbus.Interface(proxy_obj,
254
'org.freedesktop.NetworkManager')
256
# 3 is the enum value for STATE_CONNECTED
257
self._check_local.set_active(dbus_iface.state() != 3)
258
except dbus.DBusException, e:
259
# Silently drop errors. While DBus may be
260
# available, NetworkManager doesn't necessarily have to be
261
trace.mutter("unable to get networkmanager state: %r" % e)
262
self._check_local.show()
264
def _fill_in_per_file_info(self):
265
config = self._wt.branch.get_config()
266
enable_per_file_commits = config.get_user_option('per_file_commits')
267
if (enable_per_file_commits is None
268
or enable_per_file_commits.lower()
269
not in ('y', 'yes', 'on', 'enable', '1', 't', 'true')):
270
self._enable_per_file_commits = False
272
self._enable_per_file_commits = True
273
if not self._enable_per_file_commits:
274
self._file_message_expander.hide()
275
self._global_message_label.set_markup(_i18n('<b>Commit Message</b>'))
277
def _compute_delta(self):
278
self._delta = self._wt.changes_from(self._basis_tree)
281
"""Build up the dialog widgets."""
282
# The primary pane which splits it into left and right (adjustable)
284
self._hpane = Gtk.HPaned()
286
self._construct_left_pane()
287
self._construct_right_pane()
288
self._construct_action_pane()
290
self.get_content_area().pack_start(self._hpane, True, True, 0)
292
self.set_focus(self._global_message_text_view)
294
self._construct_accelerators()
297
def _set_sizes(self):
298
# This seems like a reasonable default, we might like it to
299
# be a bit wider, so that by default we can fit an 80-line diff in the
301
# Alternatively, we should be saving the last position/size rather than
302
# setting it to a fixed value every time we start up.
303
screen = self.get_screen()
304
monitor = 0 # We would like it to be the monitor we are going to
305
# display on, but I don't know how to figure that out
306
# Only really useful for freaks like me that run dual
307
# monitor, with different sizes on the monitors
308
monitor_rect = screen.get_monitor_geometry(monitor)
309
width = int(monitor_rect.width * 0.66)
310
height = int(monitor_rect.height * 0.66)
311
self.set_default_size(width, height)
312
self._hpane.set_position(300)
314
def _construct_accelerators(self):
315
group = Gtk.AccelGroup()
316
group.connect(Gdk.keyval_from_name('N'),
317
Gdk.ModifierType.CONTROL_MASK, 0, self._on_accel_next)
318
self.add_accel_group(group)
320
# ignore the escape key (avoid closing the window)
321
self.connect_object('close', self.emit_stop_by_name, 'close')
323
def _construct_left_pane(self):
324
self._left_pane_box = Gtk.VBox(homogeneous=False, spacing=5)
325
self._construct_file_list()
326
self._construct_pending_list()
328
self._check_local = Gtk.CheckButton(_i18n("_Only commit locally"),
330
self._left_pane_box.pack_end(self._check_local, False, False, 0)
331
self._check_local.set_active(False)
333
self._hpane.pack1(self._left_pane_box, resize=False, shrink=False)
334
self._left_pane_box.show()
336
def _construct_right_pane(self):
337
# TODO: I really want to make it so the diff view gets more space than
338
# the global commit message, and the per-file commit message gets even
339
# less. When I did it with wxGlade, I set it to 4 for diff, 2 for
340
# commit, and 1 for file commit, and it looked good. But I don't seem
341
# to have a way to do that with the gtk boxes... :( (Which is extra
342
# weird since wx uses gtk on Linux...)
343
self._right_pane_table = Gtk.Table(rows=10, columns=1, homogeneous=False)
344
self._right_pane_table.set_row_spacings(5)
345
self._right_pane_table.set_col_spacings(5)
346
self._right_pane_table_row = 0
347
self._construct_diff_view()
348
self._construct_file_message()
349
self._construct_global_message()
351
self._right_pane_table.show()
352
self._hpane.pack2(self._right_pane_table, resize=True, shrink=True)
354
def _construct_action_pane(self):
355
self._button_cancel = Gtk.Button(stock=Gtk.STOCK_CANCEL)
356
self._button_cancel.connect('clicked', self._on_cancel_clicked)
357
self._button_cancel.show()
358
self.get_action_area().pack_end(
359
self._button_cancel, True, True, 0)
360
self._button_commit = Gtk.Button(_i18n("Comm_it"), use_underline=True)
361
self._button_commit.connect('clicked', self._on_commit_clicked)
362
self._button_commit.set_can_default(True)
363
self._button_commit.show()
364
self.get_action_area().pack_end(
365
self._button_commit, True, True, 0)
366
self._button_commit.grab_default()
368
def _add_to_right_table(self, widget, weight, expanding=False):
369
"""Add another widget to the table
371
:param widget: The object to add
372
:param weight: How many rows does this widget get to request
373
:param expanding: Should expand|fill|shrink be set?
375
end_row = self._right_pane_table_row + weight
377
expand_opts = Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL | Gtk.AttachOptions.SHRINK
379
options = expand_opts
380
self._right_pane_table.attach(widget, 0, 1,
381
self._right_pane_table_row, end_row,
382
xoptions=expand_opts, yoptions=options)
383
self._right_pane_table_row = end_row
385
def _construct_file_list(self):
386
self._files_box = Gtk.VBox(homogeneous=False, spacing=0)
387
file_label = Gtk.Label(label=_i18n('Files'))
389
self._files_box.pack_start(file_label, False, True, 0)
391
self._commit_all_files_radio = Gtk.RadioButton.new_with_label(
392
None, _i18n("Commit all changes"))
393
self._files_box.pack_start(self._commit_all_files_radio, False, True, 0)
394
self._commit_all_files_radio.show()
395
self._commit_all_files_radio.connect('toggled',
396
self._toggle_commit_selection)
397
self._commit_selected_radio = Gtk.RadioButton.new_with_label_from_widget(
398
self._commit_all_files_radio, _i18n("Only commit selected changes"))
399
self._files_box.pack_start(self._commit_selected_radio, False, True, 0)
400
self._commit_selected_radio.show()
401
self._commit_selected_radio.connect('toggled',
402
self._toggle_commit_selection)
404
self._commit_all_files_radio.set_label(_i18n('Commit all changes*'))
405
self._commit_all_files_radio.set_sensitive(False)
406
self._commit_selected_radio.set_sensitive(False)
408
scroller = Gtk.ScrolledWindow()
409
scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
410
self._treeview_files = Gtk.TreeView()
411
self._treeview_files.show()
412
scroller.add(self._treeview_files)
413
scroller.set_shadow_type(Gtk.ShadowType.IN)
415
self._files_box.pack_start(scroller, True, True, 0)
416
self._files_box.show()
417
self._left_pane_box.pack_start(self._files_box, True, True, 0)
419
# Keep note that all strings stored in a ListStore must be UTF-8
420
# strings. GTK does not support directly setting and restoring Unicode
422
liststore = Gtk.ListStore(
423
GObject.TYPE_STRING, # [0] file_id
424
GObject.TYPE_STRING, # [1] real path
425
GObject.TYPE_BOOLEAN, # [2] checkbox
426
GObject.TYPE_STRING, # [3] display path
427
GObject.TYPE_STRING, # [4] changes type
428
GObject.TYPE_STRING, # [5] commit message
430
self._files_store = liststore
431
self._treeview_files.set_model(liststore)
432
crt = Gtk.CellRendererToggle()
433
crt.set_property('activatable', not bool(self._pending))
434
crt.connect("toggled", self._toggle_commit, self._files_store)
436
name = _i18n('Commit*')
438
name = _i18n('Commit')
439
commit_col = Gtk.TreeViewColumn(name, crt, active=2)
440
commit_col.set_visible(False)
441
self._treeview_files.append_column(commit_col)
442
self._treeview_files.append_column(Gtk.TreeViewColumn(_i18n('Path'),
443
Gtk.CellRendererText(), text=3))
444
self._treeview_files.append_column(Gtk.TreeViewColumn(_i18n('Type'),
445
Gtk.CellRendererText(), text=4))
446
self._treeview_files.connect('cursor-changed',
447
self._on_treeview_files_cursor_changed)
34
if bzrlib.version_info[1] < 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
41
from dialog import OliveDialog
44
""" Display Commit dialog and perform the needed actions. """
45
def __init__(self, gladefile, comm):
46
""" Initialize the Commit dialog. """
47
self.gladefile = gladefile
48
self.glade = gtk.glade.XML(self.gladefile, 'window_commit')
52
self.dialog = OliveDialog(self.gladefile)
54
# Check if current location is a branch
56
(self.wt, path) = WorkingTree.open_containing(self.comm.get_path())
57
branch = self.wt.branch
58
except errors.NotBranchError:
64
file_id = self.wt.path2id(path)
66
self.notbranch = False
72
self.old_tree = self.wt.branch.repository.revision_tree(self.wt.branch.last_revision())
73
if bzrlib.version_info[1] < 9:
74
self.delta = compare_trees(self.old_tree, self.wt)
76
self.delta = self.wt.changes_from(self.old_tree)
78
# Get the Commit dialog widget
79
self.window = self.glade.get_widget('window_commit')
81
# Dictionary for signal_autoconnect
82
dic = { "on_button_commit_commit_clicked": self.commit,
83
"on_button_commit_cancel_clicked": self.close }
85
# Connect the signals to the handlers
86
self.glade.signal_autoconnect(dic)
88
# Create the file list
89
self._create_file_view()
91
# Some additional widgets
92
self.checkbutton_local = self.glade.get_widget('checkbutton_commit_local')
93
self.textview = self.glade.get_widget('textview_commit')
96
""" Display the Push dialog. """
98
self.dialog.error_dialog('Directory is not 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 = self.glade.get_widget('treeview_commit_select')
115
self.file_view.set_model(self.file_store)
116
crt = gtk.CellRendererToggle()
117
crt.set_property("activatable", True)
118
crt.connect("toggled", self._toggle_commit, self.file_store)
119
self.file_view.append_column(gtk.TreeViewColumn("Commit",
121
self.file_view.append_column(gtk.TreeViewColumn("Path",
122
gtk.CellRendererText(), text=1))
123
self.file_view.append_column(gtk.TreeViewColumn("Type",
124
gtk.CellRendererText(), text=2))
126
for path, _, _ in self.delta.added:
127
self.file_store.append([ True, path, "added" ])
129
for path, _, _ in self.delta.removed:
130
self.file_store.append([ True, path, "removed" ])
132
for oldpath, _, _, _, _, _ in self.delta.renamed:
133
self.file_store.append([ True, oldpath, "renamed"])
135
for path, _, _, _, _ in self.delta.modified:
136
self.file_store.append([ True, path, "modified"])
138
def _get_specific_files(self):
140
it = self.file_store.get_iter_first()
142
if self.file_store.get_value(it, 0):
143
ret.append(self.file_store.get_value(it, 1))
144
it = self.file_store.iter_next(it)
147
# end of bzr-gtk code
449
149
def _toggle_commit(self, cell, path, model):
450
if model[path][0] is None: # No file_id means 'All Files'
451
new_val = not model[path][2]
455
model[path][2] = not model[path][2]
457
def _toggle_commit_selection(self, button):
458
all_files = self._commit_all_files_radio.get_active()
459
if self._commit_all_changes != all_files:
460
checked_col = self._treeview_files.get_column(0)
461
self._commit_all_changes = all_files
463
checked_col.set_visible(False)
465
checked_col.set_visible(True)
466
renderer = checked_col.get_cells()[0]
467
renderer.set_property('activatable', not all_files)
469
def _construct_pending_list(self):
470
# Pending information defaults to hidden, we put it all in 1 box, so
471
# that we can show/hide all of them at once
472
self._pending_box = Gtk.VBox()
473
self._pending_box.hide()
475
pending_message = Gtk.Label()
476
pending_message.set_markup(
477
_i18n('<i>* Cannot select specific files when merging</i>'))
478
self._pending_box.pack_start(pending_message, False, True, 5)
479
pending_message.show()
481
pending_label = Gtk.Label(label=_i18n('Pending Revisions'))
482
self._pending_box.pack_start(pending_label, False, True, 0)
485
scroller = Gtk.ScrolledWindow()
486
scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
487
self._treeview_pending = Gtk.TreeView()
488
scroller.add(self._treeview_pending)
489
scroller.set_shadow_type(Gtk.ShadowType.IN)
491
self._pending_box.pack_start(scroller, True, True, 5)
492
self._treeview_pending.show()
493
self._left_pane_box.pack_start(self._pending_box, True, True, 0)
495
liststore = Gtk.ListStore(GObject.TYPE_STRING, # revision_id
496
GObject.TYPE_STRING, # date
497
GObject.TYPE_STRING, # committer
498
GObject.TYPE_STRING, # summary
500
self._pending_store = liststore
501
self._treeview_pending.set_model(liststore)
502
self._treeview_pending.append_column(Gtk.TreeViewColumn(_i18n('Date'),
503
Gtk.CellRendererText(), text=1))
504
self._treeview_pending.append_column(Gtk.TreeViewColumn(_i18n('Committer'),
505
Gtk.CellRendererText(), text=2))
506
self._treeview_pending.append_column(Gtk.TreeViewColumn(_i18n('Summary'),
507
Gtk.CellRendererText(), text=3))
509
def _construct_diff_view(self):
510
from bzrlib.plugins.gtk.diff import DiffView
512
# TODO: jam 2007-10-30 The diff label is currently disabled. If we
513
# decide that we really don't ever want to display it, we should
514
# actually remove it, and other references to it, along with the
515
# tests that it is set properly.
516
self._diff_label = Gtk.Label(label=_i18n('Diff for whole tree'))
517
self._diff_label.set_alignment(0, 0)
518
self._right_pane_table.set_row_spacing(self._right_pane_table_row, 0)
519
self._add_to_right_table(self._diff_label, 1, False)
520
# self._diff_label.show()
522
self._diff_view = DiffView()
523
self._add_to_right_table(self._diff_view, 4, True)
524
self._diff_view.show()
526
def _construct_file_message(self):
527
scroller = Gtk.ScrolledWindow()
528
scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
530
self._file_message_text_view = Gtk.TextView()
531
scroller.add(self._file_message_text_view)
532
scroller.set_shadow_type(Gtk.ShadowType.IN)
535
self._file_message_text_view.modify_font(Pango.FontDescription("Monospace"))
536
self._file_message_text_view.set_wrap_mode(Gtk.WrapMode.WORD)
537
self._file_message_text_view.set_accepts_tab(False)
538
self._file_message_text_view.show()
540
self._file_message_expander = Gtk.Expander(
541
label=_i18n('File commit message'))
542
self._file_message_expander.set_expanded(True)
543
self._file_message_expander.add(scroller)
544
self._add_to_right_table(self._file_message_expander, 1, False)
545
self._file_message_expander.show()
547
def _construct_global_message(self):
548
self._global_message_label = Gtk.Label(label=_i18n('Global Commit Message'))
549
self._global_message_label.set_markup(
550
_i18n('<b>Global Commit Message</b>'))
551
self._global_message_label.set_alignment(0, 0)
552
self._right_pane_table.set_row_spacing(self._right_pane_table_row, 0)
553
self._add_to_right_table(self._global_message_label, 1, False)
554
# Can we remove the spacing between the label and the box?
555
self._global_message_label.show()
557
scroller = Gtk.ScrolledWindow()
558
scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
560
self._global_message_text_view = Gtk.TextView()
561
self._set_global_commit_message(self._saved_commit_messages_manager.get()[0])
562
self._global_message_text_view.modify_font(Pango.FontDescription("Monospace"))
563
scroller.add(self._global_message_text_view)
564
scroller.set_shadow_type(Gtk.ShadowType.IN)
566
self._add_to_right_table(scroller, 2, True)
567
self._file_message_text_view.set_wrap_mode(Gtk.WrapMode.WORD)
568
self._file_message_text_view.set_accepts_tab(False)
569
self._global_message_text_view.show()
571
def _on_treeview_files_cursor_changed(self, treeview):
572
treeselection = treeview.get_selection()
573
(model, selection) = treeselection.get_selected()
575
if selection is not None:
576
path, display_path = model.get(selection, 1, 3)
577
self._diff_label.set_text(_i18n('Diff for ') + display_path)
579
self._diff_view.show_diff(None)
581
self._diff_view.show_diff([path.decode('UTF-8')])
582
self._update_per_file_info(selection)
584
def _on_accel_next(self, accel_group, window, keyval, modifier):
585
# We don't really care about any of the parameters, because we know
586
# where this message came from
587
tree_selection = self._treeview_files.get_selection()
588
(model, selection) = tree_selection.get_selected()
589
if selection is None:
592
next = model.iter_next(selection)
595
# We have either made it to the end of the list, or nothing was
596
# selected. Either way, select All Files, and jump to the global
598
self._treeview_files.set_cursor(
599
Gtk.TreePath(path=0), None, False)
600
self._global_message_text_view.grab_focus()
602
# Set the cursor to this entry, and jump to the per-file commit
604
self._treeview_files.set_cursor(model.get_path(next), None, False)
605
self._file_message_text_view.grab_focus()
607
def _save_current_file_message(self):
608
if self._last_selected_file is None:
609
return # Nothing to save
610
text_buffer = self._file_message_text_view.get_buffer()
611
cur_text = text_buffer.get_text(text_buffer.get_start_iter(),
612
text_buffer.get_end_iter(), True)
613
last_selected = self._files_store.get_iter(self._last_selected_file)
614
self._files_store.set_value(last_selected, 5, cur_text)
616
def _update_per_file_info(self, selection):
617
# The node is changing, so cache the current message
618
if not self._enable_per_file_commits:
621
self._save_current_file_message()
622
text_buffer = self._file_message_text_view.get_buffer()
623
file_id, display_path, message = self._files_store.get(selection, 0, 3, 5)
624
if file_id is None: # Whole tree
625
self._file_message_expander.set_label(_i18n('File commit message'))
626
self._file_message_expander.set_expanded(False)
627
self._file_message_expander.set_sensitive(False)
628
text_buffer.set_text('')
629
self._last_selected_file = None
631
self._file_message_expander.set_label(_i18n('Commit message for ')
633
self._file_message_expander.set_expanded(True)
634
self._file_message_expander.set_sensitive(True)
635
text_buffer.set_text(message)
636
self._last_selected_file = self._files_store.get_path(selection)
638
def _get_specific_files(self):
639
"""Return the list of selected paths, and file info.
641
:return: ([unicode paths], [{utf-8 file info}]
643
self._save_current_file_message()
645
records = iter(self._files_store)
646
rec = records.next() # Skip the All Files record
647
assert rec[0] is None, "Are we skipping the wrong record?"
650
for record in records:
651
if self._commit_all_changes or record[2]:# [2] checkbox
652
file_id = record[0] # [0] file_id
653
path = record[1] # [1] real path
655
file_message = _sanitize_and_decode_message(record[5])
656
files.append(path.decode('UTF-8'))
657
if self._enable_per_file_commits and file_message:
658
# All of this needs to be utf-8 information
659
file_message = file_message.encode('UTF-8')
660
file_info.append({'path':path, 'file_id':file_id,
661
'message':file_message})
662
file_info.sort(key=lambda x:(x['path'], x['file_id']))
663
if self._enable_per_file_commits:
664
return files, file_info
669
def _on_cancel_clicked(self, button):
670
""" Cancel button clicked handler. """
674
def _on_delete_window(self, source, event):
675
""" Delete window handler. """
678
def _do_cancel(self):
679
"""If requested, saves commit messages when cancelling gcommit; they are re-used by a next gcommit"""
680
mgr = SavedCommitMessagesManager()
681
self._saved_commit_messages_manager = mgr
682
mgr.insert(self._get_global_commit_message(),
683
self._get_specific_files()[1])
684
if mgr.is_not_empty(): # maybe worth saving
685
response = self._question_dialog(
686
_i18n('Commit cancelled'),
687
_i18n('Do you want to save your commit messages ?'),
689
if response == Gtk.ResponseType.NO:
690
# save nothing and destroy old comments if any
691
mgr = SavedCommitMessagesManager()
692
mgr.save(self._wt, self._wt.branch)
693
self.response(Gtk.ResponseType.CANCEL) # close window
696
def _on_commit_clicked(self, button):
697
""" Commit button clicked handler. """
700
def _do_commit(self):
701
message = self._get_global_commit_message()
704
response = self._question_dialog(
705
_i18n('Commit with an empty message?'),
706
_i18n('You can describe your commit intent in the message.'),
708
if response == Gtk.ResponseType.NO:
709
# Kindly give focus to message area
710
self._global_message_text_view.grab_focus()
713
specific_files, file_info = self._get_specific_files()
715
specific_files = None
717
local = self._check_local.get_active()
719
# All we care about is if there is a single unknown, so if this loop is
720
# entered, then there are unknown files.
721
# TODO: jam 20071002 It seems like this should cancel the dialog
722
# entirely, since there isn't a way for them to add the unknown
723
# files at this point.
724
for path in self._wt.unknowns():
725
response = self._question_dialog(
726
_i18n("Commit with unknowns?"),
727
_i18n("Unknown files exist in the working tree. Commit anyway?"),
729
# Doesn't set a parent for the dialog..
730
if response == Gtk.ResponseType.NO:
737
revprops['file-info'] = bencode.bencode(file_info).decode('UTF-8')
150
model[path][0] = not model[path][0]
153
def commit(self, widget):
154
textbuffer = self.textview.get_buffer()
155
start, end = textbuffer.get_bounds()
156
message = textbuffer.get_text(start, end)
158
checkbutton_strict = self.glade.get_widget('checkbutton_commit_strict')
159
checkbutton_force = self.glade.get_widget('checkbutton_commit_force')
161
specific_files = self._get_specific_files()
163
self.comm.set_busy(self.window)
164
# merged from Jelmer Vernooij's olive integration branch
739
rev_id = self._wt.commit(message,
740
allow_pointless=False,
743
specific_files=specific_files,
166
self.wt.commit(message,
167
allow_pointless=checkbutton_force.get_active(),
168
strict=checkbutton_strict.get_active(),
169
local=self.checkbutton_local.get_active(),
170
specific_files=specific_files)
171
except errors.NotBranchError:
172
self.dialog.error_dialog('Directory is not a branch.')
173
self.comm.set_busy(self.window, False)
175
except errors.LocalRequiresBoundBranch:
176
self.dialog.error_dialog('Local commit requires a bound branch.')
177
self.comm.set_busy(self.window, False)
745
179
except errors.PointlessCommit:
746
response = self._question_dialog(
747
_i18n('Commit with no changes?'),
748
_i18n('There are no changes in the working tree.'
749
' Do you want to commit anyway?'),
751
if response == Gtk.ResponseType.YES:
752
rev_id = self._wt.commit(message,
753
allow_pointless=True,
756
specific_files=specific_files,
758
self.committed_revision_id = rev_id
759
# destroy old comments if any
760
SavedCommitMessagesManager().save(self._wt, self._wt.branch)
761
self.response(Gtk.ResponseType.OK)
763
def _get_global_commit_message(self):
764
buf = self._global_message_text_view.get_buffer()
765
start, end = buf.get_bounds()
766
text = buf.get_text(start, end, True)
767
return _sanitize_and_decode_message(text)
769
def _set_global_commit_message(self, message):
770
"""Just a helper for the test suite."""
771
if isinstance(message, unicode):
772
message = message.encode('UTF-8')
773
self._global_message_text_view.get_buffer().set_text(message)
775
def _set_file_commit_message(self, message):
776
"""Helper for the test suite."""
777
if isinstance(message, unicode):
778
message = message.encode('UTF-8')
779
self._file_message_text_view.get_buffer().set_text(message)
782
def _rev_to_pending_info(rev):
783
"""Get the information from a pending merge."""
784
from bzrlib.osutils import format_date
786
rev_dict['committer'] = re.sub('<.*@.*>', '', rev.committer).strip(' ')
787
rev_dict['summary'] = rev.get_summary()
788
rev_dict['date'] = format_date(rev.timestamp,
790
'original', date_fmt="%Y-%m-%d",
792
rev_dict['revision_id'] = rev.revision_id
180
self.dialog.error_dialog('No changes to commit. Try force commit.')
181
self.comm.set_busy(self.window, False)
183
except errors.ConflictsInTree:
184
self.dialog.error_dialog('Conflicts in tree. Please resolve them first.')
185
self.comm.set_busy(self.window, False)
187
except errors.StrictCommitFailed:
188
self.dialog.error_dialog('Strict commit failed. There are unknown files.')
189
self.comm.set_busy(self.window, False)
191
except errors.BoundBranchOutOfDate, errmsg:
192
self.dialog.error_dialog('Bound branch is out of date: %s' % errmsg)
193
self.comm.set_busy(self.window, False)
199
self.comm.refresh_right()
201
def close(self, widget=None):
202
self.window.destroy()