def delete_branch(): """Launch the 'Delete Branch' dialog.""" icon = icons.discard() branch = choose_branch(N_('Delete Branch'), N_('Delete'), icon=icon) if not branch: return cmds.do(cmds.DeleteBranch, branch)
def apply_patches(self): items = self.tree.items() if not items: return patches = [ustr(i.data(0, Qt.UserRole).toPyObject()) for i in items] cmds.do(cmds.ApplyPatches, patches) self.accept()
def stage(self): """Stage selected files, or all files if no selection exists.""" paths = cola.selection_model().unstaged if not paths: cmds.do(cmds.StageModified) else: cmds.do(cmds.Stage, paths)
def unstage(self): """Unstage selected files, or all files if no selection exists.""" paths = cola.selection_model().staged if not paths: cmds.do(cmds.UnstageAll) else: cmds.do(cmds.Unstage, paths)
def create_patch(self): items = self.selectedItems() if not items: return sha1s = [item.commit.sha1 for item in reversed(items)] all_sha1s = [c.sha1 for c in self.commits] cmds.do(cmds.FormatPatch, sha1s, all_sha1s)
def choose_commit(self): revs, summaries = gitcmds.log_helper() sha1s = select_commits("Select Commit Message", revs, summaries, multiselect=False) if not sha1s: return sha1 = sha1s[0] cmds.do(cmds.LoadPreviousMessage, sha1)
def open_repo(): """Spawn a new cola session.""" dirname = qtutils.opendir_dialog('Open Git Repository...', cola.model().getcwd()) if not dirname: return cmds.do(cmds.OpenRepo, dirname)
def branch_delete(): """Launch the 'Delete Branch' dialog.""" branch = choose_from_combo(N_('Delete Branch'), cola.model().local_branches) if not branch: return cmds.do(cmds.DeleteBranch, branch)
def process_diff_selection(self, staged=True, apply_to_worktree=False): """Implement un/staging of selected lines or sections.""" if selection.selection_model().is_empty(): return offset, selection_text = self.offset_and_selection() cmds.do(cmds.ApplyDiffSelection, staged, offset, selection_text, apply_to_worktree)
def choose_commit(self, cmd): revs, summaries = gitcmds.log_helper() sha1s = select_commits(N_("Select Commit"), revs, summaries, multiselect=False) if not sha1s: return sha1 = sha1s[0] cmds.do(cmd, sha1)
def create_tag(self): """Verifies inputs and emits a notifier tag message.""" revision = self.revision.value() tag_name = self.tag_name.value() tag_msg = self.tag_msg.value() sign_tag = self.sign_tag.isChecked() if not revision: critical(N_('Missing Revision'), N_('Please specify a revision to tag.')) return elif not tag_name: critical(N_('Missing Name'), N_('Please specify a name for the new tag.')) return elif (sign_tag and not tag_msg and not qtutils.confirm(N_('Missing Tag Message'), N_('Tag-signing was requested but the tag ' 'message is empty.'), N_('An unsigned, lightweight tag will be ' 'created instead.\n' 'Create an unsigned tag?'), N_('Create Unsigned Tag'), default=False, icon=qtutils.save_icon())): return cmds.do(cmds.Tag, tag_name, revision, sign=sign_tag, message=tag_msg) information(N_('Tag Created'), N_('Created a new tag named "%s"') % tag_name, details=tag_msg or None) self.accept()
def export_patches(): """Run 'git format-patch' on a list of commits.""" revs, summaries = gitcmds.log_helper() to_export = select_commits(N_('Export Patches'), revs, summaries) if not to_export: return cmds.do(cmds.FormatPatch, reversed(to_export), reversed(revs))
def checkout_branch(): """Launch the 'Checkout Branch' dialog.""" branch = choose_from_combo(N_('Checkout Branch'), cola.model().local_branches) if not branch: return cmds.do(cmds.CheckoutBranch, branch)
def _revert_unstaged_edits(self, staged=False): if not self.m.undoable(): return if staged: items_to_undo = self.staged() else: items_to_undo = self.modified() if items_to_undo: if not qtutils.confirm( N_('Revert Unstaged Changes?'), N_('This operation drops unstaged changes.\n' 'These changes cannot be recovered.'), N_('Revert the unstaged changes?'), N_('Revert Unstaged Changes'), default=True, icon=qtutils.icon('undo.svg')): return args = [] if not staged and self.m.amending(): args.append(self.m.head) cmds.do(cmds.Checkout, args + ['--'] + items_to_undo) else: msg = N_('No files selected for checkout from HEAD.') Interaction.log(msg)
def _delete_files(self): files = self.untracked() count = len(files) if count == 0: return title = N_('Delete Files?') msg = N_('The following files will be deleted:') + '\n\n' fileinfo = subprocess.list2cmdline(files) if len(fileinfo) > 2048: fileinfo = fileinfo[:2048].rstrip() + '...' msg += fileinfo info_txt = N_('Delete %d file(s)?') % count ok_txt = N_('Delete Files') if qtutils.confirm( title, msg, info_txt, ok_txt, default=True, icon=qtutils.discard_icon()): cmds.do(cmds.Delete, files)
def process_diff_selection(self, selected=False, staged=True, apply_to_worktree=False, reverse=False): """Implement un/staging of selected lines or sections.""" offset, selection = self.offset_and_selection() cmds.do(cmds.ApplyDiffSelection, staged, selected, offset, selection, apply_to_worktree)
def cherry_pick(): """Launch the 'Cherry-Pick' dialog.""" revs, summaries = gitcmds.log_helper(all=True) commits = select_commits(N_("Cherry-Pick Commit"), revs, summaries, multiselect=False) if not commits: return cmds.do(cmds.CherryPick, commits)
def rebase_start(self): branch = guicmds.choose_ref(N_("Select New Upstream"), N_("Interactive Rebase")) if not branch: return None self.model.is_rebasing = True self._update_callback() cmds.do(cmds.Rebase, branch)
def commit(self): """Attempt to create a commit from the index and commit message.""" if not bool(self.summary.value()): # Describe a good commit message error_msg = N_( "" "Please supply a commit message.\n\n" "A good commit message has the following format:\n\n" "- First line: Describe in one sentence what you did.\n" "- Second line: Blank\n" "- Remaining lines: Describe why this change is good.\n" ) Interaction.log(error_msg) Interaction.information(N_("Missing Commit Message"), error_msg) return msg = self.commit_message(raw=False) if not self.model.staged: error_msg = N_("" "No changes to commit.\n\n" "You must stage at least 1 file before you can commit.") if self.model.modified: informative_text = N_("Would you like to stage and " "commit all modified files?") if not qtutils.confirm( N_("Stage and commit?"), error_msg, informative_text, N_("Stage and Commit"), default=True, icon=qtutils.save_icon(), ): return else: Interaction.information(N_("Nothing to commit"), error_msg) return cmds.do(cmds.StageModified) # Warn that amending published commits is generally bad amend = self.amend_action.isChecked() if ( amend and self.model.is_commit_published() and not qtutils.confirm( N_("Rewrite Published Commit?"), N_( "This commit has already been published.\n" "This operation will rewrite published history.\n" "You probably don't want to do this." ), N_("Amend the published commit?"), N_("Amend Commit"), default=False, icon=qtutils.save_icon(), ) ): return no_verify = self.bypass_commit_hooks_action.isChecked() sign = self.sign_action.isChecked() status, out, err = cmds.do(cmds.Commit, amend, msg, sign, no_verify=no_verify) if status != 0: Interaction.critical(N_("Commit failed"), N_('"git commit" returned exit code %s') % (status,), out + err)
def viz_revision(self): """Launch a gitk-like viewer on the selection revision""" revision = ustr(self.revision.text()) if not revision: qtutils.information(N_("No Revision Specified"), N_("You must specify a revision to view.")) return cmds.do(cmds.VisualizeRevision, revision)
def process_diff_selection(self, reverse=False, apply_to_worktree=False): """Implement un/staging of the selected line(s) or hunk.""" if selection.selection_model().is_empty(): return first_line_idx, last_line_idx = self.selected_lines() cmds.do(cmds.ApplyDiffSelection, first_line_idx, last_line_idx, self.has_selection(), reverse, apply_to_worktree)
def open_repo_in_new_window(): """Spawn a new cola session.""" dirname = qtutils.opendir_dialog(N_('Open Git Repository...'), main.model().getcwd()) if not dirname: return cmds.do(cmds.OpenNewRepo, dirname)
def _update_callback(self): """Update the title with the current branch and directory name.""" branch = self.model.currentbranch curdir = core.decode(os.getcwd()) msg = N_('Repository: %s') % curdir msg += '\n' msg += N_('Branch: %s') % branch self.commitdockwidget.setToolTip(msg) title = '%s: %s (%s)' % (self.model.project, branch, self.model.git.worktree()) if self.mode == self.model.mode_amend: title += ' (%s)' % N_('Amending') self.setWindowTitle(title) self.commitmsgeditor.set_mode(self.mode) if not self.model.amending(): # Check if there's a message file in .git/ merge_msg_path = gitcmds.merge_message_path() if merge_msg_path is None: return merge_msg_hash = utils.checksum(core.decode(merge_msg_path)) if merge_msg_hash == self.merge_message_hash: return self.merge_message_hash = merge_msg_hash cmds.do(cmds.LoadCommitMessage, core.decode(merge_msg_path))
def create_patch(self): items = self.selected_items() if not items: return selected_commits = self.sort_by_generation([n.commit for n in items]) sha1s = [c.sha1 for c in selected_commits] all_sha1s = [c.sha1 for c in self.commits] cmds.do(cmds.FormatPatch, sha1s, all_sha1s)
def add(self): widget = AddRemoteWidget(self) if not widget.add_remote(): return name = widget.name.value() url = widget.url.value() cmds.do(cmds.RemoteAdd, name, url) self.refresh()
def viz_revision(self): """Launch a gitk-like viewer on the selection revision""" revision = unicode(self.revision.text()) if not revision: qtutils.information('No Revision Specified', 'You must specify a revision to view') return cmds.do(cmds.VisualizeRevision, revision)
def contextMenuEvent(self, event): """Create the context menu for the diff display.""" menu = QtGui.QMenu(self) s = selection.selection() if self.model.stageable(): if s.modified and s.modified[0] in main.model().submodules: action = menu.addAction(qtutils.icon('add.svg'), cmds.Stage.name(), cmds.run(cmds.Stage, s.modified)) action.setShortcut(cmds.Stage.SHORTCUT) menu.addAction(qtutils.git_icon(), N_('Launch git-cola'), cmds.run(cmds.OpenRepo, core.abspath(s.modified[0]))) elif s.modified: action = menu.addAction(qtutils.icon('add.svg'), N_('Stage Section'), self.stage_section) action.setShortcut(Qt.Key_H) menu.addAction(self.action_stage_selection) menu.addSeparator() menu.addAction(qtutils.icon('undo.svg'), N_('Revert Section...'), self.revert_section) menu.addAction(self.action_revert_selection) if self.model.unstageable(): if s.staged and s.staged[0] in main.model().submodules: action = menu.addAction(qtutils.icon('remove.svg'), cmds.Unstage.name(), cmds.do(cmds.Unstage, s.staged)) action.setShortcut(cmds.Unstage.SHORTCUT) menu.addAction(qtutils.git_icon(), N_('Launch git-cola'), cmds.do(cmds.OpenRepo, core.abspath(s.staged[0]))) elif s.staged: action = menu.addAction(qtutils.icon('remove.svg'), N_('Unstage Section'), self.unstage_section) action.setShortcut(Qt.Key_H) menu.addAction(self.action_unstage_selection) if self.model.stageable() or self.model.unstageable(): menu.addSeparator() menu.addAction(self.launch_editor) menu.addAction(self.launch_difftool) menu.addSeparator() action = menu.addAction(qtutils.icon('edit-copy.svg'), N_('Copy'), self.copy) action.setShortcut(QtGui.QKeySequence.Copy) action = menu.addAction(qtutils.icon('edit-select-all.svg'), N_('Select All'), self.selectAll) action.setShortcut(QtGui.QKeySequence.SelectAll) menu.exec_(self.mapToGlobal(event.pos()))
def rename_branch(): """Launch the 'Rename Branch' dialogs.""" branch = choose_branch(N_('Rename Existing Branch'), N_('Select')) if not branch: return new_branch = choose_branch(N_('Enter Branch New Name'), N_('Rename')) if not new_branch: return cmds.do(cmds.RenameBranch, branch, new_branch)
def export_patches(): """Run 'git format-patch' on a list of commits.""" revs, summaries = gitcmds.log_helper() to_export = select_commits(N_("Export Patches"), revs, summaries) if not to_export: return to_export.reverse() revs.reverse() cmds.do(cmds.FormatPatch, to_export, revs)
def stash_apply(self): """Applies the currently selected stash """ selection = self.selected_stash() if not selection: return index = self.keep_index.isChecked() cmds.do(ApplyStash, selection, index) self.accept() cmds.do(cmds.Rescan)
def open_default(self): paths = self.tree.selected_filenames() cmds.do(cmds.OpenDefaultApp, paths)
def rebase_skip(self): cmds.do(cmds.RebaseSkip)
def broadcast(self): """Broadcasts a list of all files touched since last broadcast""" with self._lock: cmds.do(cmds.UpdateFileStatus) self._timer = None
def _open_parent_dir(self): cmds.do(cmds.OpenParentDir, self.selected_group())
def commit(self): """Attempt to create a commit from the index and commit message.""" if not bool(self.summary.value()): # Describe a good commit message error_msg = N_( '' 'Please supply a commit message.\n\n' 'A good commit message has the following format:\n\n' '- First line: Describe in one sentence what you did.\n' '- Second line: Blank\n' '- Remaining lines: Describe why this change is good.\n') Interaction.log(error_msg) Interaction.information(N_('Missing Commit Message'), error_msg) return msg = self.commit_message(raw=False) if not self.model.staged: error_msg = N_( '' 'No changes to commit.\n\n' 'You must stage at least 1 file before you can commit.') if self.model.modified: informative_text = N_('Would you like to stage and ' 'commit all modified files?') if not qtutils.confirm(N_('Stage and commit?'), error_msg, informative_text, N_('Stage and Commit'), default=True, icon=qtutils.save_icon()): return else: Interaction.information(N_('Nothing to commit'), error_msg) return cmds.do(cmds.StageModified) # Warn that amending published commits is generally bad amend = self.amend_action.isChecked() if (amend and self.model.is_commit_published() and not qtutils.confirm( N_('Rewrite Published Commit?'), N_('This commit has already been published.\n' 'This operation will rewrite published history.\n' 'You probably don\'t want to do this.'), N_('Amend the published commit?'), N_('Amend Commit'), default=False, icon=qtutils.save_icon())): return no_verify = self.bypass_commit_hooks_action.isChecked() sign = self.sign_action.isChecked() status, out, err = cmds.do(cmds.Commit, amend, msg, sign, no_verify=no_verify) if status != 0: Interaction.critical( N_('Commit failed'), N_('"git commit" returned exit code %s') % (status, ), out + err)
def update_diff(self): paths = self.sync_selection() if paths and self.model().path_is_interesting(paths[0]): cached = paths[0] in main.model().staged cmds.do(cmds.Diff, paths[0], cached)
def untrack_selected(self): """untrack selected paths.""" cmds.do(cmds.Untrack, self.selected_tracked_paths())
def view_history(self, entries): """Launch the configured history browser path-limited to entries.""" entries = map(unicode, entries) cmds.do(cmds.VisualizePaths, entries)
def rebase_edit_todo(self): cmds.do(cmds.RebaseEditTodo)
def rebase_continue(self): cmds.do(cmds.RebaseContinue)
def _open_using_default_app(self): cmds.do(cmds.OpenDefaultApp, self.selected_group())
def double_clicked(self, item, idx): """Called when an item is double-clicked in the repo status tree.""" cmds.do(cmds.StageOrUnstage)
def runner(): value = unicode(self.sender().text()) cmds.do(SetConfig, self.model, self.source, config, value)
def edit(self): paths = self.tree.selected_filenames() cmds.do(cmds.Edit, paths)
def load_commitmsg(): """Load a commit message from a file.""" filename = qtutils.open_dialog('Load Commit Message...', cola.model().getcwd()) if filename: cmds.do(cmds.LoadCommitMessage, filename)
def unstage_selected(self): """Signal that we should stage selected paths.""" cmds.do(cmds.Unstage, self.selected_staged_paths())
def contextMenuEvent(self, event): """Create the context menu for the diff display.""" menu = QtGui.QMenu(self) s = selection.selection() filename = selection.filename() if self.model.stageable() and s.modified: if s.modified[0] in main.model().submodules: action = menu.addAction(qtutils.icon('add.svg'), cmds.Stage.name(), cmds.run(cmds.Stage, s.modified)) action.setShortcut(cmds.Stage.SHORTCUT) menu.addAction( qtutils.git_icon(), N_('Launch git-cola'), cmds.run(cmds.OpenRepo, core.abspath(s.modified[0]))) else: if self.has_selection(): apply_text = N_('Stage Selected Lines') revert_text = N_('Revert Selected Lines...') else: apply_text = N_('Stage Diff Hunk') revert_text = N_('Revert Diff Hunk...') self.action_apply_selection.setText(apply_text) self.action_apply_selection.setIcon(qtutils.icon('add.svg')) self.action_revert_selection.setText(revert_text) menu.addAction(self.action_apply_selection) menu.addAction(self.action_revert_selection) if self.model.unstageable(): if s.staged and s.staged[0] in main.model().submodules: action = menu.addAction(qtutils.icon('remove.svg'), cmds.Unstage.name(), cmds.do(cmds.Unstage, s.staged)) action.setShortcut(cmds.Unstage.SHORTCUT) menu.addAction( qtutils.git_icon(), N_('Launch git-cola'), cmds.do(cmds.OpenRepo, core.abspath(s.staged[0]))) elif s.staged: if self.has_selection(): apply_text = N_('Unstage Selected Lines') else: apply_text = N_('Unstage Diff Hunk') self.action_apply_selection.setText(apply_text) self.action_apply_selection.setIcon(qtutils.icon('remove.svg')) menu.addAction(self.action_apply_selection) if self.model.stageable() or self.model.unstageable(): # Do not show the "edit" action when the file does not exist. # Untracked files exist by definition. if filename and core.exists(filename): menu.addSeparator() menu.addAction(self.launch_editor) # Removed files can still be diffed. menu.addAction(self.launch_difftool) menu.addSeparator() action = menu.addAction(qtutils.icon('edit-copy.svg'), N_('Copy'), self.copy) action.setShortcut(QtGui.QKeySequence.Copy) action = menu.addAction(qtutils.icon('edit-select-all.svg'), N_('Select All'), self.selectAll) action.setShortcut(QtGui.QKeySequence.SelectAll) menu.exec_(self.mapToGlobal(event.pos()))
def contextMenuEvent(self, event): """Create the context menu for the diff display.""" menu = qtutils.create_menu(N_('Actions'), self) s = selection.selection() filename = selection.filename() if self.model.stageable() or self.model.unstageable(): if self.model.stageable(): self.stage_or_unstage.setText(N_('Stage')) else: self.stage_or_unstage.setText(N_('Unstage')) menu.addAction(self.stage_or_unstage) if s.modified and self.model.stageable(): if s.modified[0] in main.model().submodules: action = menu.addAction(icons.add(), cmds.Stage.name(), cmds.run(cmds.Stage, s.modified)) action.setShortcut(hotkeys.STAGE_SELECTION) menu.addAction(icons.cola(), N_('Launch git-cola'), cmds.run(cmds.OpenRepo, core.abspath(s.modified[0]))) elif s.modified[0] not in main.model().unstaged_deleted: if self.has_selection(): apply_text = N_('Stage Selected Lines') revert_text = N_('Revert Selected Lines...') else: apply_text = N_('Stage Diff Hunk') revert_text = N_('Revert Diff Hunk...') self.action_apply_selection.setText(apply_text) self.action_apply_selection.setIcon(icons.add()) self.action_revert_selection.setText(revert_text) menu.addAction(self.action_apply_selection) menu.addAction(self.action_revert_selection) if s.staged and self.model.unstageable(): if s.staged[0] in main.model().submodules: action = menu.addAction(icons.remove(), cmds.Unstage.name(), cmds.do(cmds.Unstage, s.staged)) action.setShortcut(hotkeys.STAGE_SELECTION) menu.addAction(icons.cola(), N_('Launch git-cola'), cmds.do(cmds.OpenRepo, core.abspath(s.staged[0]))) elif s.staged[0] not in main.model().staged_deleted: if self.has_selection(): apply_text = N_('Unstage Selected Lines') else: apply_text = N_('Unstage Diff Hunk') self.action_apply_selection.setText(apply_text) self.action_apply_selection.setIcon(icons.remove()) menu.addAction(self.action_apply_selection) if self.model.stageable() or self.model.unstageable(): # Do not show the "edit" action when the file does not exist. # Untracked files exist by definition. if filename and core.exists(filename): menu.addSeparator() menu.addAction(self.launch_editor) # Removed files can still be diffed. menu.addAction(self.launch_difftool) # Add the Previous/Next File actions, which improves discoverability # of their associated shortcuts menu.addSeparator() menu.addAction(self.move_up) menu.addAction(self.move_down) menu.addSeparator() action = menu.addAction(icons.copy(), N_('Copy'), self.copy) action.setShortcut(QtGui.QKeySequence.Copy) action = menu.addAction(icons.select_all(), N_('Select All'), self.selectAll) action.setShortcut(QtGui.QKeySequence.SelectAll) menu.exec_(self.mapToGlobal(event.pos()))
def show_selection(self): """Show the selected item.""" # Sync the selection model selected = self.selection() selection.selection_model().set_selection(selected) self.update_actions(selected=selected) selected_indexes = self.selected_indexes() if not selected_indexes: if self.m.amending(): cmds.do(cmds.SetDiffText, '') else: cmds.do(cmds.ResetMode) return category, idx = selected_indexes[0] # A header item e.g. 'Staged', 'Modified', etc. if category == self.idx_header: cls = { self.idx_staged: cmds.DiffStagedSummary, self.idx_modified: cmds.Diffstat, # TODO implement UnmergedSummary #self.idx_unmerged: cmds.UnmergedSummary, self.idx_untracked: cmds.UntrackedSummary, }.get(idx, cmds.Diffstat) cmds.do(cls) # A staged file elif category == self.idx_staged: item = self.staged_items()[0] cmds.do(cmds.DiffStaged, item.path, deleted=item.deleted) # A modified file elif category == self.idx_modified: item = self.modified_items()[0] cmds.do(cmds.Diff, item.path, deleted=item.deleted) elif category == self.idx_unmerged: item = self.unmerged_items()[0] cmds.do(cmds.Diff, item.path) elif category == self.idx_untracked: item = self.unstaged_items()[0] cmds.do(cmds.ShowUntracked, item.path)
def event(self, e): if e.type() == QtCore.QEvent.ApplicationActivate: cfg = gitcfg.current() if cfg.get('cola.refreshonfocus', False): cmds.do(cmds.Refresh) return QtGui.QApplication.event(self, e)
def archive_saved(self): cmds.do(cmds.Archive, self.ref, self.fmt, self.prefix, self.filename) qtutils.information(N_('File Saved'), N_('File saved to "%s"') % self.filename)
def _update_files(self): # Respond to file system updates cmds.do(cmds.Refresh)
def edit_files(self, filenames): cmds.do(cmds.Edit, filenames)
def cherry_pick(self): sha1 = self.selected_sha1() if sha1 is None: return cmds.do(cmds.CherryPick, [sha1])
def current_font_changed(self, font): cmds.do(SetConfig, self.model, 'user', 'cola.fontdiff', unicode(font.toString()))
def rebase_abort(self): cmds.do(cmds.RebaseAbort)
def _delete_untracked_files(self): cmds.do(cmds.Delete, self.untracked())
def runner(value): cmds.do(SetConfig, self.model, self.source, config, value)
def _trash_untracked_files(self): cmds.do(cmds.MoveToTrash, self.untracked())
def font_size_changed(self, size): font = self.fixed_font.currentFont() font.setPointSize(size) cmds.do(SetConfig, self.model, 'user', 'cola.fontdiff', unicode(font.toString()))