sequenceeditor: keep the Summary column elided
[git-cola.git] / cola / sequenceeditor.py
blobe8ad0f9df3d25d07eec959bdff40c20f534889f6
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"""
622 NUMBER_COLUMN = 0
623 ENABLED_COLUMN = 1
624 COMMAND_COLUMN = 2
625 COMMIT_COLUMN = 3
626 REMARKS_COLUMN = 4
627 SUMMARY_COLUMN = 5
628 COLUMN_COUNT = 6
629 OID_LENGTH = 7
631 def __init__(
632 self,
633 idx,
634 enabled,
635 command,
636 oid='',
637 summary='',
638 cmdexec='',
639 branch='',
640 comment_char='#',
641 remarks=tuple(),
642 parent=None,
644 QtWidgets.QTreeWidgetItem.__init__(self, parent)
645 self.combo = None
646 self.command = command
647 self.idx = idx
648 self.oid = oid
649 self.summary = summary
650 self.cmdexec = cmdexec
651 self.branch = branch
652 self.comment_char = comment_char
654 # if core.abbrev is set to a higher value then we will notice by
655 # simply tracking the longest oid we've seen
656 oid_len = self.OID_LENGTH
657 self.__class__.OID_LENGTH = max(len(oid), oid_len)
659 self.setText(self.NUMBER_COLUMN, '%02d' % idx)
660 self.set_enabled(enabled)
661 # checkbox on 1
662 # combo box on 2
663 if self.is_exec():
664 self.setText(self.COMMIT_COLUMN, '')
665 self.setText(self.SUMMARY_COLUMN, cmdexec)
666 elif self.is_update_ref():
667 self.setText(self.COMMIT_COLUMN, '')
668 self.setText(self.SUMMARY_COLUMN, branch)
669 else:
670 self.setText(self.COMMIT_COLUMN, oid)
671 self.setText(self.SUMMARY_COLUMN, summary)
673 self.set_remarks(remarks)
675 flags = self.flags() | Qt.ItemIsUserCheckable
676 flags = flags | Qt.ItemIsDragEnabled
677 flags = flags & ~Qt.ItemIsDropEnabled
678 self.setFlags(flags)
680 def __eq__(self, other):
681 return self is other
683 def __hash__(self):
684 return self.oid
686 def copy(self):
687 return self.__class__(
688 self.idx,
689 self.is_enabled(),
690 self.command,
691 oid=self.oid,
692 summary=self.summary,
693 cmdexec=self.cmdexec,
694 branch=self.branch,
695 comment_char=self.comment_char,
696 remarks=self.remarks,
699 def decorate(self, parent):
700 if self.is_exec():
701 items = [EXEC]
702 idx = 0
703 elif self.is_update_ref():
704 items = [UPDATE_REF]
705 idx = 0
706 else:
707 items = COMMANDS
708 idx = COMMAND_IDX[self.command]
709 combo = self.combo = ComboBox()
710 combo.setEditable(False)
711 combo.addItems(items)
712 combo.setCurrentIndex(idx)
713 combo.setEnabled(self.is_commit())
715 signal = combo.currentIndexChanged
716 # pylint: disable=no-member
717 signal.connect(lambda x: self.set_command_and_validate(combo))
718 combo.validate.connect(parent.validate)
720 parent.setItemWidget(self, self.COMMAND_COLUMN, combo)
722 def is_exec(self):
723 return self.command == EXEC
725 def is_update_ref(self):
726 return self.command == UPDATE_REF
728 def is_commit(self):
729 return bool(
730 not (self.is_exec() or self.is_update_ref()) and self.oid and self.summary
733 def value(self):
734 """Return the serialized representation of an item"""
735 if self.is_enabled():
736 comment = ''
737 else:
738 comment = self.comment_char + ' '
739 if self.is_exec():
740 return f'{comment}{self.command} {self.cmdexec}'
741 if self.is_update_ref():
742 return f'{comment}{self.command} {self.branch}'
743 return f'{comment}{self.command} {self.oid} {self.summary}'
745 def is_enabled(self):
746 """Is the item enabled?"""
747 return self.checkState(self.ENABLED_COLUMN) == Qt.Checked
749 def set_enabled(self, enabled):
750 """Enable the item by checking its enabled checkbox"""
751 self.setCheckState(self.ENABLED_COLUMN, enabled and Qt.Checked or Qt.Unchecked)
753 def toggle_enabled(self):
754 """Toggle the enabled state of the item"""
755 self.set_enabled(not self.is_enabled())
757 def add_remark(self, remark):
758 """Add a remark to the item"""
759 self.set_remarks(tuple(sorted(set(self.remarks + (remark,)))))
761 def remove_remark(self, remark):
762 """Remove a remark from the item"""
763 self.set_remarks(tuple(r for r in self.remarks if r != remark))
765 def set_remarks(self, remarks):
766 """Set the remarks and update the remark text display"""
767 self.remarks = remarks
768 self.setText(self.REMARKS_COLUMN, ''.join(remarks))
770 def set_command(self, command):
771 """Set the item to a different command, no-op for exec items"""
772 if self.is_exec():
773 return
774 self.command = command
776 def refresh(self):
777 """Update the view to match the updated state"""
778 if self.is_commit():
779 command = self.command
780 self.combo.setCurrentIndex(COMMAND_IDX[command])
782 def reset_command(self, command):
783 """Set and refresh the item in one shot"""
784 self.set_command(command)
785 self.refresh()
787 def set_command_and_validate(self, combo):
788 """Set the command and validate the command order"""
789 command = COMMANDS[combo.currentIndex()]
790 self.set_command(command)
791 self.combo.validate.emit()
794 def show_help(context):
795 help_text = N_(
797 Commands
798 --------
799 pick = use commit
800 reword = use commit, but edit the commit message
801 edit = use commit, but stop for amending
802 squash = use commit, but meld into previous commit
803 fixup = like "squash", but discard this commit's log message
804 exec = run command (the rest of the line) using shell
805 update-ref = update branches that point to commits
807 These lines can be re-ordered; they are executed from top to bottom.
809 If you disable a line here THAT COMMIT WILL BE LOST.
811 However, if you disable everything, the rebase will be aborted.
813 Keyboard Shortcuts
814 ------------------
815 ? = show help
816 j = move down
817 k = move up
818 J = shift row down
819 K = shift row up
821 1, p = pick
822 2, r = reword
823 3, e = edit
824 4, f = fixup
825 5, s = squash
826 spacebar = toggle enabled
828 ctrl+enter = accept changes and rebase
829 ctrl+q = cancel and abort the rebase
830 ctrl+d = launch difftool
833 title = N_('Help - git-cola-sequence-editor')
834 return text.text_dialog(context, help_text, title)