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