sequenceeditor: rename the toggle action to "Toggle Remark"
[git-cola.git] / cola / sequenceeditor.py
bloba195d82ff185e528491fad1b12b74114bea53b7e
1 import sys
2 import re
3 from argparse import ArgumentParser
4 from functools import partial
6 from cola import app # prints a message if Qt cannot be found
7 from qtpy import QtGui
8 from qtpy import QtWidgets
9 from qtpy.QtCore import Qt
10 from qtpy.QtCore import Signal
12 # pylint: disable=ungrouped-imports
13 from cola import core
14 from cola import difftool
15 from cola import gitcmds
16 from cola import hotkeys
17 from cola import icons
18 from cola import qtutils
19 from cola import utils
20 from cola.i18n import N_
21 from cola.models import dag
22 from cola.models import prefs
23 from cola.widgets import defs
24 from cola.widgets import filelist
25 from cola.widgets import diff
26 from cola.widgets import standard
27 from cola.widgets import text
30 PICK = 'pick'
31 REWORD = 'reword'
32 EDIT = 'edit'
33 FIXUP = 'fixup'
34 SQUASH = 'squash'
35 UPDATE_REF = 'update-ref'
36 EXEC = 'exec'
37 COMMANDS = (
38 PICK,
39 REWORD,
40 EDIT,
41 FIXUP,
42 SQUASH,
44 COMMAND_IDX = {cmd_: idx_ for idx_, cmd_ in enumerate(COMMANDS)}
45 ABBREV = {
46 'p': PICK,
47 'r': REWORD,
48 'e': EDIT,
49 'f': FIXUP,
50 's': SQUASH,
51 'x': EXEC,
52 'u': UPDATE_REF,
56 def main():
57 """Start a git-cola-sequence-editor session"""
58 args = parse_args()
59 context = app.application_init(args)
60 view = new_window(context, args.filename)
61 app.application_run(context, view, start=view.start, stop=stop)
62 return view.status
65 def stop(context, _view):
66 """All done, cleanup"""
67 context.view.stop()
68 context.runtask.wait()
71 def parse_args():
72 parser = ArgumentParser()
73 parser.add_argument(
74 'filename', metavar='<filename>', help='git-rebase-todo file to edit'
76 app.add_common_arguments(parser)
77 return parser.parse_args()
80 def new_window(context, filename):
81 window = MainWindow(context)
82 editor = Editor(context, filename, parent=window)
83 window.set_editor(editor)
84 return window
87 def unabbrev(cmd):
88 """Expand shorthand commands into their full name"""
89 return ABBREV.get(cmd, cmd)
92 class MainWindow(standard.MainWindow):
93 """The main git-cola application window"""
95 def __init__(self, context, parent=None):
96 super().__init__(parent)
97 self.context = context
98 self.status = 1
99 # If the user closes the window without confirmation it's considered cancelled.
100 self.cancelled = True
101 self.editor = None
102 default_title = '%s - git cola sequence editor' % core.getcwd()
103 title = core.getenv('GIT_COLA_SEQ_EDITOR_TITLE', default_title)
104 self.setWindowTitle(title)
105 self.show_help_action = qtutils.add_action(
106 self, N_('Show Help'), partial(show_help, context), hotkeys.QUESTION
108 self.menubar = QtWidgets.QMenuBar(self)
109 self.help_menu = self.menubar.addMenu(N_('Help'))
110 self.help_menu.addAction(self.show_help_action)
111 self.setMenuBar(self.menubar)
113 qtutils.add_close_action(self)
114 self.init_state(context.settings, self.init_window_size)
116 def init_window_size(self):
117 """Set the window size on the first initial view"""
118 if utils.is_darwin():
119 width, height = qtutils.desktop_size()
120 self.resize(width, height)
121 else:
122 self.showMaximized()
124 def set_editor(self, editor):
125 self.editor = editor
126 self.setCentralWidget(editor)
127 editor.cancel.connect(self.close)
128 editor.rebase.connect(self.rebase)
129 editor.setFocus()
131 def start(self, _context, _view):
132 """Start background tasks"""
133 self.editor.start()
135 def stop(self):
136 """Stop background tasks"""
137 self.editor.stop()
139 def rebase(self):
140 """Exit the editor and initiate a rebase"""
141 self.status = self.editor.save()
142 self.close()
145 class Editor(QtWidgets.QWidget):
146 cancel = Signal()
147 rebase = Signal()
149 def __init__(self, context, filename, parent=None):
150 super().__init__(parent)
152 self.widget_version = 1
153 self.context = context
154 self.filename = filename
155 self.comment_char = comment_char = prefs.comment_char(context)
157 self.diff = diff.DiffWidget(context, self)
158 self.tree = RebaseTreeWidget(context, comment_char, self)
159 self.filewidget = filelist.FileWidget(context, self, remarks=True)
160 self.setFocusProxy(self.tree)
162 self.rebase_button = qtutils.create_button(
163 text=core.getenv('GIT_COLA_SEQ_EDITOR_ACTION', N_('Rebase')),
164 tooltip=N_('Accept changes and rebase\nShortcut: Ctrl+Enter'),
165 icon=icons.ok(),
166 default=True,
169 self.extdiff_button = qtutils.create_button(
170 text=N_('Launch Diff Tool'),
171 tooltip=N_('Launch external diff tool\nShortcut: Ctrl+D'),
173 self.extdiff_button.setEnabled(False)
175 self.help_button = qtutils.create_button(
176 text=N_('Help'), tooltip=N_('Show help\nShortcut: ?'), icon=icons.question()
179 self.cancel_button = qtutils.create_button(
180 text=N_('Cancel'),
181 tooltip=N_('Cancel rebase\nShortcut: Ctrl+Q'),
182 icon=icons.close(),
185 top = qtutils.splitter(Qt.Horizontal, self.tree, self.filewidget)
186 top.setSizes([75, 25])
188 main_split = qtutils.splitter(Qt.Vertical, top, self.diff)
189 main_split.setSizes([25, 75])
191 controls_layout = qtutils.hbox(
192 defs.no_margin,
193 defs.button_spacing,
194 self.cancel_button,
195 qtutils.STRETCH,
196 self.help_button,
197 self.extdiff_button,
198 self.rebase_button,
200 layout = qtutils.vbox(defs.no_margin, defs.spacing, main_split, controls_layout)
201 self.setLayout(layout)
203 self.action_rebase = qtutils.add_action(
204 self,
205 N_('Rebase'),
206 self.rebase.emit,
207 hotkeys.CTRL_RETURN,
208 hotkeys.CTRL_ENTER,
211 self.tree.commits_selected.connect(self.commits_selected)
212 self.tree.commits_selected.connect(self.filewidget.commits_selected)
213 self.tree.commits_selected.connect(self.diff.commits_selected)
214 self.tree.external_diff.connect(self.external_diff)
216 self.filewidget.files_selected.connect(self.diff.files_selected)
217 self.filewidget.remark_toggled.connect(self.remark_toggled_for_files)
219 # `git` calls are expensive. When user toggles a remark of all commits touching
220 # selected paths the GUI freezes for a while on a big enough sequence. This
221 # cache is used (commit ID to paths tuple) to minimize calls to git.
222 self.oid_to_paths = {}
223 self.task = None # A task fills the cache in the background.
224 self.running = False # This flag stops it.
226 qtutils.connect_button(self.rebase_button, self.rebase.emit)
227 qtutils.connect_button(self.extdiff_button, self.external_diff)
228 qtutils.connect_button(self.help_button, partial(show_help, context))
229 qtutils.connect_button(self.cancel_button, self.cancel.emit)
231 def start(self):
232 insns = core.read(self.filename)
233 self.parse_sequencer_instructions(insns)
235 # Assume that the tree is filled at this point.
236 self.running = True
237 self.task = qtutils.SimpleTask(self.calculate_oid_to_paths)
238 self.context.runtask.start(self.task)
240 def stop(self):
241 self.running = False
243 # signal callbacks
244 def commits_selected(self, commits):
245 self.extdiff_button.setEnabled(bool(commits))
247 def remark_toggled_for_files(self, remark, filenames):
248 filenames = set(filenames)
250 items = self.tree.items()
251 touching_items = []
253 for item in items:
254 if not item.is_commit():
255 continue
256 oid = item.oid
257 paths = self.paths_touched_by_oid(oid)
258 if filenames.intersection(paths):
259 touching_items.append(item)
261 self.tree.toggle_remark_of_items(remark, touching_items)
263 def external_diff(self):
264 items = self.tree.selected_items()
265 if not items:
266 return
267 item = items[0]
268 difftool.diff_expression(self.context, self, item.oid + '^!', hide_expr=True)
270 # helpers
272 def paths_touched_by_oid(self, oid):
273 try:
274 return self.oid_to_paths[oid]
275 except KeyError:
276 pass
278 paths = gitcmds.changed_files(self.context, oid)
279 self.oid_to_paths[oid] = paths
281 return paths
283 def calculate_oid_to_paths(self):
284 """Fills the oid_to_paths cache in the background"""
285 for item in self.tree.items():
286 if not self.running:
287 return
288 self.paths_touched_by_oid(item.oid)
290 def parse_sequencer_instructions(self, insns):
291 idx = 1
292 re_comment_char = re.escape(self.comment_char)
293 exec_rgx = re.compile(r'^\s*(%s)?\s*(x|exec)\s+(.+)$' % re_comment_char)
294 update_ref_rgx = re.compile(
295 r'^\s*(%s)?\s*(u|update-ref)\s+(.+)$' % re_comment_char
297 # The upper bound of 40 below must match git.OID_LENGTH.
298 # We'll have to update this to the new hash length when that happens.
299 pick_rgx = re.compile(
301 r'^\s*(%s)?\s*'
302 + r'(p|pick|r|reword|e|edit|f|fixup|s|squash)'
303 + r'\s+([0-9a-f]{7,40})'
304 + r'\s+(.+)$'
306 % re_comment_char
308 for line in insns.splitlines():
309 match = pick_rgx.match(line)
310 if match:
311 enabled = match.group(1) is None
312 command = unabbrev(match.group(2))
313 oid = match.group(3)
314 summary = match.group(4)
315 self.tree.add_item(idx, enabled, command, oid=oid, summary=summary)
316 idx += 1
317 continue
318 match = exec_rgx.match(line)
319 if match:
320 enabled = match.group(1) is None
321 command = unabbrev(match.group(2))
322 cmdexec = match.group(3)
323 self.tree.add_item(idx, enabled, command, cmdexec=cmdexec)
324 idx += 1
325 continue
326 match = update_ref_rgx.match(line)
327 if match:
328 enabled = match.group(1) is None
329 command = unabbrev(match.group(2))
330 branch = match.group(3)
331 self.tree.add_item(idx, enabled, command, branch=branch)
332 idx += 1
333 continue
335 self.tree.decorate(self.tree.items())
336 self.tree.refit()
337 self.tree.select_first()
339 def save(self, string=None):
340 """Save the instruction sheet"""
342 if string is None:
343 lines = [item.value() for item in self.tree.items()]
344 # sequencer instructions
345 string = '\n'.join(lines) + '\n'
347 try:
348 core.write(self.filename, string)
349 status = 0
350 except (OSError, ValueError) as exc:
351 msg, details = utils.format_exception(exc)
352 sys.stderr.write(msg + '\n\n' + details)
353 status = 128
354 return status
357 # pylint: disable=too-many-ancestors
358 class RebaseTreeWidget(standard.DraggableTreeWidget):
359 commits_selected = Signal(object)
360 external_diff = Signal()
361 move_rows = Signal(object, object)
363 def __init__(self, context, comment_char, parent):
364 super().__init__(parent=parent)
365 self.context = context
366 self.comment_char = comment_char
367 # header
368 self.setHeaderLabels([
369 N_('#'),
370 N_('Enabled'),
371 N_('Command'),
372 N_('SHA-1'),
373 N_('Remarks'),
374 N_('Summary'),
376 self.header().setStretchLastSection(True)
377 self.setColumnCount(6)
378 self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
380 # actions
381 self.copy_oid_action = qtutils.add_action(
382 self, N_('Copy SHA-1'), self.copy_oid, QtGui.QKeySequence.Copy
385 self.external_diff_action = qtutils.add_action(
386 self, N_('Launch Diff Tool'), self.external_diff.emit, hotkeys.DIFF
389 self.toggle_enabled_action = qtutils.add_action(
390 self, N_('Toggle Enabled'), self.toggle_enabled, hotkeys.PRIMARY_ACTION
393 self.action_pick = qtutils.add_action(
394 self, N_('Pick'), lambda: self.set_selected_to(PICK), *hotkeys.REBASE_PICK
397 self.action_reword = qtutils.add_action(
398 self,
399 N_('Reword'),
400 lambda: self.set_selected_to(REWORD),
401 *hotkeys.REBASE_REWORD,
404 self.action_edit = qtutils.add_action(
405 self, N_('Edit'), lambda: self.set_selected_to(EDIT), *hotkeys.REBASE_EDIT
408 self.action_fixup = qtutils.add_action(
409 self,
410 N_('Fixup'),
411 lambda: self.set_selected_to(FIXUP),
412 *hotkeys.REBASE_FIXUP,
415 self.action_squash = qtutils.add_action(
416 self,
417 N_('Squash'),
418 lambda: self.set_selected_to(SQUASH),
419 *hotkeys.REBASE_SQUASH,
422 self.action_shift_down = qtutils.add_action(
423 self, N_('Shift Down'), self.shift_down, hotkeys.MOVE_DOWN_TERTIARY
426 self.action_shift_up = qtutils.add_action(
427 self, N_('Shift Up'), self.shift_up, hotkeys.MOVE_UP_TERTIARY
430 self.toggle_remark_actions = tuple(
431 qtutils.add_action(
432 self,
434 lambda remark=r: self.toggle_remark(remark),
435 hotkeys.hotkey(Qt.CTRL | getattr(Qt, 'Key_' + r)),
437 for r in map(str, range(10))
440 # pylint: disable=no-member
441 self.itemChanged.connect(self.item_changed)
442 self.itemSelectionChanged.connect(self.selection_changed)
443 self.move_rows.connect(self.move)
444 self.items_moved.connect(self.decorate)
446 def add_item(
447 self, idx, enabled, command, oid='', summary='', cmdexec='', branch=''
449 comment_char = self.comment_char
450 item = RebaseTreeWidgetItem(
451 idx,
452 enabled,
453 command,
454 oid=oid,
455 summary=summary,
456 cmdexec=cmdexec,
457 branch=branch,
458 comment_char=comment_char,
460 self.invisibleRootItem().addChild(item)
462 def decorate(self, items):
463 for item in items:
464 item.decorate(self)
466 def refit(self):
467 """Resize columns to fit content"""
468 for i in range(RebaseTreeWidgetItem.COLUMN_COUNT - 1):
469 self.resizeColumnToContents(i)
471 def item_changed(self, item, column):
472 """Validate item ordering when toggling their enabled state"""
473 if column == item.ENABLED_COLUMN:
474 self.validate()
476 def validate(self):
477 invalid_first_choice = {FIXUP, SQUASH}
478 for item in self.items():
479 if item.is_enabled() and item.is_commit():
480 if item.command in invalid_first_choice:
481 item.reset_command(PICK)
482 break
484 def set_selected_to(self, command):
485 for i in self.selected_items():
486 i.reset_command(command)
487 self.validate()
489 def set_command(self, item, command):
490 item.reset_command(command)
491 self.validate()
493 def copy_oid(self):
494 item = self.selected_item()
495 if item is None:
496 return
497 clipboard = item.oid or item.cmdexec
498 qtutils.set_clipboard(clipboard)
500 def selection_changed(self):
501 item = self.selected_item()
502 if item is None or not item.is_commit():
503 return
504 context = self.context
505 oid = item.oid
506 params = dag.DAG(oid, 2)
507 repo = dag.RepoReader(context, params)
508 commits = []
509 for commit in repo.get():
510 commits.append(commit)
511 if commits:
512 commits = commits[-1:]
513 self.commits_selected.emit(commits)
515 def toggle_enabled(self):
516 """Toggle the enabled state of each selected item"""
517 for item in self.selected_items():
518 item.toggle_enabled()
520 def select_first(self):
521 items = self.items()
522 if not items:
523 return
524 idx = self.model().index(0, 0)
525 if idx.isValid():
526 self.setCurrentIndex(idx)
528 def shift_down(self):
529 sel_items = self.selected_items()
530 all_items = self.items()
531 sel_idx = sorted([all_items.index(item) for item in sel_items])
532 if not sel_idx:
533 return
534 idx = sel_idx[0] + 1
535 if not (
536 idx > len(all_items) - len(sel_items)
537 or all_items[sel_idx[-1]] is all_items[-1]
539 self.move_rows.emit(sel_idx, idx)
541 def shift_up(self):
542 sel_items = self.selected_items()
543 all_items = self.items()
544 sel_idx = sorted([all_items.index(item) for item in sel_items])
545 if not sel_idx:
546 return
547 idx = sel_idx[0] - 1
548 if idx >= 0:
549 self.move_rows.emit(sel_idx, idx)
551 def toggle_remark(self, remark):
552 """Toggle remarks for all selected items"""
553 items = self.selected_items()
554 self.toggle_remark_of_items(remark, items)
556 def toggle_remark_of_items(self, remark, items):
557 """Toggle remarks for the specified items"""
558 for item in items:
559 if remark in item.remarks:
560 item.remove_remark(remark)
561 else:
562 item.add_remark(remark)
564 def move(self, src_idxs, dst_idx):
565 moved_items = []
566 src_base = sorted(src_idxs)[0]
567 for idx in reversed(sorted(src_idxs)):
568 item = self.invisibleRootItem().takeChild(idx)
569 moved_items.insert(0, [dst_idx + (idx - src_base), item])
571 for item in moved_items:
572 self.invisibleRootItem().insertChild(item[0], item[1])
573 self.setCurrentItem(item[1])
575 if moved_items:
576 moved_items = [item[1] for item in moved_items]
577 # If we've moved to the top then we need to re-decorate all items.
578 # Otherwise, we can decorate just the new items.
579 if dst_idx == 0:
580 self.decorate(self.items())
581 else:
582 self.decorate(moved_items)
584 for item in moved_items:
585 item.setSelected(True)
586 self.validate()
588 # Qt events
590 def dropEvent(self, event):
591 super().dropEvent(event)
592 self.validate()
594 def contextMenuEvent(self, event):
595 items = self.selected_items()
596 menu = qtutils.create_menu(N_('Actions'), self)
597 menu.addAction(self.action_pick)
598 menu.addAction(self.action_reword)
599 menu.addAction(self.action_edit)
600 menu.addAction(self.action_fixup)
601 menu.addAction(self.action_squash)
602 menu.addSeparator()
603 menu.addAction(self.toggle_enabled_action)
604 menu.addSeparator()
605 menu.addAction(self.copy_oid_action)
606 self.copy_oid_action.setDisabled(len(items) > 1)
607 menu.addAction(self.external_diff_action)
608 self.external_diff_action.setDisabled(len(items) > 1)
609 menu.addSeparator()
610 menu_toggle_remark = menu.addMenu(N_('Toggle Remark'))
611 for action in self.toggle_remark_actions:
612 menu_toggle_remark.addAction(action)
613 menu.exec_(self.mapToGlobal(event.pos()))
616 class ComboBox(QtWidgets.QComboBox):
617 validate = Signal()
620 class RebaseTreeWidgetItem(QtWidgets.QTreeWidgetItem):
621 """A single data row in the rebase tree widget"""
623 NUMBER_COLUMN = 0
624 ENABLED_COLUMN = 1
625 COMMAND_COLUMN = 2
626 COMMIT_COLUMN = 3
627 REMARKS_COLUMN = 4
628 SUMMARY_COLUMN = 5
629 COLUMN_COUNT = 6
630 OID_LENGTH = 7
632 def __init__(
633 self,
634 idx,
635 enabled,
636 command,
637 oid='',
638 summary='',
639 cmdexec='',
640 branch='',
641 comment_char='#',
642 remarks=tuple(),
643 parent=None,
645 QtWidgets.QTreeWidgetItem.__init__(self, parent)
646 self.combo = None
647 self.command = command
648 self.idx = idx
649 self.oid = oid
650 self.summary = summary
651 self.cmdexec = cmdexec
652 self.branch = branch
653 self.comment_char = comment_char
655 # if core.abbrev is set to a higher value then we will notice by
656 # simply tracking the longest oid we've seen
657 oid_len = self.OID_LENGTH
658 self.__class__.OID_LENGTH = max(len(oid), oid_len)
660 self.setText(self.NUMBER_COLUMN, '%02d' % idx)
661 self.set_enabled(enabled)
662 # checkbox on 1
663 # combo box on 2
664 if self.is_exec():
665 self.setText(self.COMMIT_COLUMN, '')
666 self.setText(self.SUMMARY_COLUMN, cmdexec)
667 elif self.is_update_ref():
668 self.setText(self.COMMIT_COLUMN, '')
669 self.setText(self.SUMMARY_COLUMN, branch)
670 else:
671 self.setText(self.COMMIT_COLUMN, oid)
672 self.setText(self.SUMMARY_COLUMN, summary)
674 self.set_remarks(remarks)
676 flags = self.flags() | Qt.ItemIsUserCheckable
677 flags = flags | Qt.ItemIsDragEnabled
678 flags = flags & ~Qt.ItemIsDropEnabled
679 self.setFlags(flags)
681 def __eq__(self, other):
682 return self is other
684 def __hash__(self):
685 return self.oid
687 def copy(self):
688 return self.__class__(
689 self.idx,
690 self.is_enabled(),
691 self.command,
692 oid=self.oid,
693 summary=self.summary,
694 cmdexec=self.cmdexec,
695 branch=self.branch,
696 comment_char=self.comment_char,
697 remarks=self.remarks,
700 def decorate(self, parent):
701 if self.is_exec():
702 items = [EXEC]
703 idx = 0
704 elif self.is_update_ref():
705 items = [UPDATE_REF]
706 idx = 0
707 else:
708 items = COMMANDS
709 idx = COMMAND_IDX[self.command]
710 combo = self.combo = ComboBox()
711 combo.setEditable(False)
712 combo.addItems(items)
713 combo.setCurrentIndex(idx)
714 combo.setEnabled(self.is_commit())
716 signal = combo.currentIndexChanged
717 # pylint: disable=no-member
718 signal.connect(lambda x: self.set_command_and_validate(combo))
719 combo.validate.connect(parent.validate)
721 parent.setItemWidget(self, self.COMMAND_COLUMN, combo)
723 def is_exec(self):
724 return self.command == EXEC
726 def is_update_ref(self):
727 return self.command == UPDATE_REF
729 def is_commit(self):
730 return bool(
731 not (self.is_exec() or self.is_update_ref()) and self.oid and self.summary
734 def value(self):
735 """Return the serialized representation of an item"""
736 if self.is_enabled():
737 comment = ''
738 else:
739 comment = self.comment_char + ' '
740 if self.is_exec():
741 return f'{comment}{self.command} {self.cmdexec}'
742 if self.is_update_ref():
743 return f'{comment}{self.command} {self.branch}'
744 return f'{comment}{self.command} {self.oid} {self.summary}'
746 def is_enabled(self):
747 """Is the item enabled?"""
748 return self.checkState(self.ENABLED_COLUMN) == Qt.Checked
750 def set_enabled(self, enabled):
751 """Enable the item by checking its enabled checkbox"""
752 self.setCheckState(self.ENABLED_COLUMN, enabled and Qt.Checked or Qt.Unchecked)
754 def toggle_enabled(self):
755 """Toggle the enabled state of the item"""
756 self.set_enabled(not self.is_enabled())
758 def add_remark(self, remark):
759 """Add a remark to the item"""
760 self.set_remarks(tuple(sorted(set(self.remarks + (remark,)))))
762 def remove_remark(self, remark):
763 """Remove a remark from the item"""
764 self.set_remarks(tuple(r for r in self.remarks if r != remark))
766 def set_remarks(self, remarks):
767 """Set the remarks and update the remark text display"""
768 self.remarks = remarks
769 self.setText(self.REMARKS_COLUMN, ''.join(remarks))
771 def set_command(self, command):
772 """Set the item to a different command, no-op for exec items"""
773 if self.is_exec():
774 return
775 self.command = command
777 def refresh(self):
778 """Update the view to match the updated state"""
779 if self.is_commit():
780 command = self.command
781 self.combo.setCurrentIndex(COMMAND_IDX[command])
783 def reset_command(self, command):
784 """Set and refresh the item in one shot"""
785 self.set_command(command)
786 self.refresh()
788 def set_command_and_validate(self, combo):
789 """Set the command and validate the command order"""
790 command = COMMANDS[combo.currentIndex()]
791 self.set_command(command)
792 self.combo.validate.emit()
795 def show_help(context):
796 help_text = N_(
798 Commands
799 --------
800 pick = use commit
801 reword = use commit, but edit the commit message
802 edit = use commit, but stop for amending
803 squash = use commit, but meld into previous commit
804 fixup = like "squash", but discard this commit's log message
805 exec = run command (the rest of the line) using shell
806 update-ref = update branches that point to commits
808 These lines can be re-ordered; they are executed from top to bottom.
810 If you disable a line here THAT COMMIT WILL BE LOST.
812 However, if you disable everything, the rebase will be aborted.
814 Keyboard Shortcuts
815 ------------------
816 ? = show help
817 j = move down
818 k = move up
819 J = shift row down
820 K = shift row up
822 1, p = pick
823 2, r = reword
824 3, e = edit
825 4, f = fixup
826 5, s = squash
827 spacebar = toggle enabled
829 ctrl+enter = accept changes and rebase
830 ctrl+q = cancel and abort the rebase
831 ctrl+d = launch difftool
834 title = N_('Help - git-cola-sequence-editor')
835 return text.text_dialog(context, help_text, title)