def _send_msg(): if git.GIT_COLA_TRACE == 'trace': msg = ('info: Trace enabled. ' 'Many of commands reported with "trace" use git\'s stable ' '"plumbing" API and are not intended for typical ' 'day-to-day use. Here be dragons') cola.notifier().broadcast(signals.log_cmd, 0, msg)
def stash_save(self): """Saves the worktree in a stash This prompts the user for a stash name and creates a git stash named accordingly. """ stash_name, ok = qtutils.prompt('Save Stash', 'Enter a name for the stash') if not ok or not stash_name: return # Sanitize the stash name stash_name = utils.sanitize(stash_name) if stash_name in self.model.stash_names: qtutils.critical('Oops!', 'A stash named "%s" already exists' % stash_name) return args = [] if self.view.keep_index.isChecked(): args.append('--keep-index') args.append(stash_name) qtutils.log(*self.model.git.stash('save', with_stderr=True, with_status=True, *args)) self.view.accept() cola.notifier().broadcast(signals.rescan)
def do(self): if self.keep_index: args = ["save", "--keep-index", self.stash_name] else: args = ["save", self.stash_name] status, output = git.stash(with_stderr=True, with_status=True, *args) cola.notifier().broadcast(signals.log_cmd, status, output)
def unstage(self): """Unstage selected files, or all files if no selection exists.""" paths = cola.selection_model().staged if not paths: cola.notifier().broadcast(signals.unstage_all) else: cola.notifier().broadcast(signals.unstage, paths)
def start(): global _thread if not AVAILABLE: if utils.is_win32(): msg = ('file notification: disabled\n' 'Note: install pywin32 to enable.\n') elif utils.is_linux(): msg = ('inotify: disabled\n' 'Note: install python-pyinotify to enable inotify.\n') else: return if utils.is_debian(): msg += ('On Debian systems ' 'try: sudo aptitude install python-pyinotify') cola.notifier().broadcast(signals.log_cmd, 0, msg) return # Start the notification thread _thread = GitNotifier() _thread.start() if utils.is_win32(): msg = 'file notification: enabled' else: msg = 'inotify support: enabled' cola.notifier().broadcast(signals.log_cmd, 0, msg)
def _update_view(self): """Update the title with the current branch and directory name.""" title = '%s [%s]' % (self.model.project, self.model.currentbranch) if self.mode in (self.model.mode_diff, self.model.mode_diff_expr): title += ' *** diff mode***' elif self.mode == self.model.mode_review: title += ' *** review mode***' elif self.mode == self.model.mode_amend: title += ' *** amending ***' self.setWindowTitle(title) if self.mode != self.model.mode_amend: self.amend_checkbox.blockSignals(True) self.amend_checkbox.setChecked(False) self.amend_checkbox.blockSignals(False) if not self.model.read_only() and self.mode != self.model.mode_amend: # 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(merge_msg_path) if merge_msg_hash == self.merge_message_hash: return self.merge_message_hash = merge_msg_hash cola.notifier().broadcast(signals.load_commit_message, merge_msg_path)
def do(self): if self.keep_index: args = ['save', '--keep-index', self.stash_name] else: args = ['save', self.stash_name] status, output = git.stash(with_stderr=True, with_status=True, *args) cola.notifier().broadcast(signals.log_cmd, status, output)
def open_repo(): """Spawn a new cola session.""" dirname = qtutils.opendir_dialog('Open Git Repository...', cola.model().getcwd()) if not dirname: return cola.notifier().broadcast(signals.open_repo, dirname)
def do(self): if self.index: args = ['apply', '--index', self.selection] else: args = ['apply', self.selection] status, output = git.stash(with_stderr=True, with_status=True, *args) cola.notifier().broadcast(signals.log_cmd, status, output)
def show_selection(self): """Show the selected item.""" # Sync the selection model cola.selection_model().set_selection(self.selection()) selection = self.selected_indexes() if not selection: return category, idx = selection[0] # A header item e.g. 'Staged', 'Modified', etc. if category == self.idx_header: signal = { self.idx_staged: signals.staged_summary, self.idx_modified: signals.modified_summary, self.idx_unmerged: signals.unmerged_summary, self.idx_untracked: signals.untracked_summary, }.get(idx, signals.diffstat) cola.notifier().broadcast(signal) # A staged file elif category == self.idx_staged: cola.notifier().broadcast(signals.diff_staged, self.staged()) # A modified file elif category == self.idx_modified: cola.notifier().broadcast(signals.diff, self.modified()) elif category == self.idx_unmerged: cola.notifier().broadcast(signals.diff, self.unmerged()) elif category == self.idx_untracked: cola.notifier().broadcast(signals.show_untracked, self.unstaged())
def do(self): if self.index: args = ["apply", "--index", self.selection] else: args = ["apply", self.selection] status, output = git.stash(with_stderr=True, with_status=True, *args) cola.notifier().broadcast(signals.log_cmd, status, output)
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('Revert Unstaged Changes?', 'This operation drops unstaged changes.' '\nThese changes cannot be recovered.', 'Revert the unstaged changes?', 'Revert Unstaged Changes', default=True, icon=qtutils.icon('undo.svg')): return args = [] if not staged and self.m.amending(): args.append(self.m.head) cola.notifier().broadcast(signals.checkout, args + ['--'] + items_to_undo) else: qtutils.log(1, self.tr('No files selected for ' 'checkout from HEAD.'))
def _update_callback(self): """Update the title with the current branch and directory name.""" branch = self.model.currentbranch curdir = core.decode(os.getcwd()) msg = 'Repository: %s\nBranch: %s' % (curdir, branch) self.commitdockwidget.setToolTip(msg) title = '%s: %s' % (self.model.project, branch) if self.mode == self.model.mode_amend: title += ' ** 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 cola.notifier().broadcast(signals.load_commit_message, core.decode(merge_msg_path))
def _update_callback(self): """Update the title with the current branch and directory name.""" branch = self.model.currentbranch curdir = core.decode(os.getcwd()) msg = 'Repository: %s\nBranch: %s' % (curdir, branch) self.commitdockwidget.setToolTip(msg) title = '%s [%s]' % (self.model.project, branch) if self.mode in (self.model.mode_diff, self.model.mode_diff_expr): title += ' *** diff mode***' elif self.mode == self.model.mode_review: title += ' *** review mode***' elif self.mode == self.model.mode_amend: title += ' *** amending ***' self.setWindowTitle(title) if self.mode != self.model.mode_amend: self.amend_checkbox.blockSignals(True) self.amend_checkbox.setChecked(False) self.amend_checkbox.blockSignals(False) if not self.model.read_only() and self.mode != self.model.mode_amend: # 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 cola.notifier().broadcast(signals.load_commit_message, core.decode(merge_msg_path))
def load_commitmsg(parent): """Load a commit message from a file.""" filename = qtutils.open_dialog(parent, 'Load Commit Message...', cola.model().getcwd()) if filename: cola.notifier().broadcast(signals.load_commit_message, filename)
def _initialize(self): """Iterate over git-ls-tree and create GitTreeItems.""" status, output = git.ls_tree('--full-tree', '-r', '-t', '-z', self.ref, with_status=True, with_stderr=True) if status != 0: cola.notifier().broadcast(signals.log_cmd, status, output) return if not output: return for line in core.decode(output[:-1]).split('\0'): # .....6 ...4 ......................................40 # 040000 tree c127cde9a0c644a3a8fef449a244f47d5272dfa6 relative # 100644 blob 139e42bf4acaa4927ec9be1ec55a252b97d3f1e2 relative/path objtype = line[7] relpath = line[6 + 1 + 4 + 1 + 40 + 1:] if objtype == 't': parent = self.dir_entries[utils.dirname(relpath)] self.add_directory(parent, relpath) elif objtype == 'b': self.add_file(relpath)
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('Missing Revision', 'Please specify a revision to tag.') return elif not tag_name: critical('Missing Name', 'Please specify a name for the new tag.') return elif (sign_tag and not tag_msg and not qtutils.confirm('Missing Tag Message', 'Tag-signing was requested but the tag ' 'message is empty.', 'An unsigned, lightweight tag will be ' 'created instead.\n' 'Create an unsigned tag?', 'Create Unsigned Tag', default=False, icon=qtutils.save_icon())): return cola.notifier().broadcast(signals.tag, tag_name, revision, sign=sign_tag, message=tag_msg) information('Tag Created', 'Created a new tag named "%s"' % tag_name, details=tag_msg or None) self.accept()
def clicked(self, item=None, idx=None): """Called when a repo status tree item is clicked. This handles the behavior where clicking on the icon invokes the a context-specific action. """ if self.m.read_only(): return # Sync the selection model staged, modified, unmerged, untracked = self.selection() cola.selection_model().set_selection(staged, modified, unmerged, untracked) # Clear the selection if an empty area was clicked selection = self.selected_indexes() if not selection: if self.mode == self.m.mode_amend: cola.notifier().broadcast(signals.set_diff_text, '') else: cola.notifier().broadcast(signals.reset_mode) self.blockSignals(True) self.clearSelection() self.blockSignals(False) return if staged: qtutils.set_clipboard(staged[0]) elif modified: qtutils.set_clipboard(modified[0]) elif unmerged: qtutils.set_clipboard(unmerged[0]) elif untracked: qtutils.set_clipboard(untracked[0])
def create_tag(self): """Verifies inputs and emits a notifier tag message.""" if not self.model.tag_name: cola.notifier().broadcast(signals.critical, 'Missing Name', 'You must name the tag.') return if (self.model.sign_tag and not self.model.tag_msg and not qtutils.confirm('Missing Tag Message', 'Tag-signing was requested but the tag ' 'message is empty.', 'An unsigned, lightweight tag will be ' 'created instead.\n' 'Create an unsigned tag?', 'Create Unsigned Tag', default=False, icon=qtutils.save_icon())): return cola.notifier().broadcast(signals.tag, self.model.tag_name, self.model.revision_item, sign=self.model.sign_tag, message=self.model.tag_msg) self.view.accept()
def branch_delete(): """Launch the 'Delete Branch' dialog.""" branch = choose_from_combo('Delete Branch', cola.model().local_branches) if not branch: return cola.notifier().broadcast(signals.delete_branch, branch)
def grep(): """Prompt and use 'git grep' to find the content.""" # This should be a command in cola.cmds. txt, ok = qtutils.prompt('grep') if not ok: return cola.notifier().broadcast(signals.grep, txt)
def checkout_branch(): """Launch the 'Checkout Branch' dialog.""" branch = choose_from_combo('Checkout Branch', cola.model().local_branches) if not branch: return cola.notifier().broadcast(signals.checkout_branch, branch)
def tree_click(self, event): """ Called when a repo status tree item is clicked. This handles the behavior where clicking on the icon invokes the same appropriate action. """ result = QtGui.QTreeWidget.mouseReleaseEvent(self.tree, event) # Sync the selection model s, m, um, ut = self.selection() cola.selection_model().set_selection(s, m, um, ut) # Get the item that was clicked item = self.tree.itemAt(event.pos()) if not item: # Nothing was clicked -- reset the display and return cola.notifier().broadcast(signals.reset_mode) items = self.tree.selectedItems() self.tree.blockSignals(True) for i in items: self.tree.setItemSelected(i, False) self.tree.blockSignals(False) return result
def cherry_pick(self): """Launch the 'Cherry-Pick' dialog.""" revs, summaries = gitcmds.log_helper(all=True) commits = select_commits('Cherry-Pick Commit', revs, summaries, multiselect=False) if not commits: return cola.notifier().broadcast(signals.cherry_pick, commits)
def stash_show(self): """Shows the current stash in the main view.""" selection = self.selected_stash() if not selection: return diffstat = self.model.git.stash('show', selection) diff = self.model.git.stash('show', '-p', selection) cola.notifier().broadcast(signals.diff_text, '%s\n\n%s' % (diffstat, diff))
def diff_expression(): """Diff using an arbitrary expression.""" expr = choose_from_combo('Enter Diff Expression', cola.model().all_branches() + cola.model().tags) if not expr: return cola.notifier().broadcast(signals.diff_expr_mode, expr)
def _create_patch(self): items = self.selectedItems() if not items: return items.reverse() sha1s = [item.commit.sha1 for item in items] all_sha1s = [c.sha1 for c in self._commits] cola.notifier().broadcast(signals.format_patch, sha1s, all_sha1s)
def _create_patch(self): items = self.selectedItems() if not items: return selected_commits = 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] cola.notifier().broadcast(signals.format_patch, sha1s, all_sha1s)
def review_branch(): """Diff against an arbitrary revision, branch, tag, etc.""" branch = choose_from_combo('Select Branch, Tag, or Commit-ish', cola.model().all_branches() + cola.model().tags) if not branch: return cola.notifier().broadcast(signals.review_branch_mode, branch)
def branch_diff(self): """Diff against an arbitrary revision, branch, tag, etc.""" branch = choose_from_combo('Select Branch, Tag, or Commit-ish', ['HEAD^'] + self.model.all_branches() + self.model.tags) if not branch: return cola.notifier().broadcast(signals.diff_mode, branch)
def export_patches(self): """Run 'git format-patch' on a list of commits.""" revs, summaries = gitcmds.log_helper() to_export = select_commits('Export Patches', revs, summaries) if not to_export: return to_export.reverse() revs.reverse() cola.notifier().broadcast(signals.format_patch, to_export, revs)
def export_patches(): """Run 'git format-patch' on a list of commits.""" revs, summaries = gitcmds.log_helper() to_export = select_commits('Export Patches', revs, summaries) if not to_export: return to_export.reverse() revs.reverse() cola.notifier().broadcast(signals.format_patch, to_export, revs)
def event(self, msg): """Overrides event() to handle custom inotify events.""" if not AVAILABLE: return if msg.type() == INOTIFY_EVENT: cola.notifier().broadcast(signals.rescan) return True else: return False
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() cola.notifier().broadcast(signals.apply_diff_selection, 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('Cherry-Pick Commit', revs, summaries, multiselect=False) if not commits: return cola.notifier().broadcast(signals.cherry_pick, commits)
def selectionChanged(self, old_selection, new_selection): """Override selectionChanged to update available actions.""" result = QtGui.QTreeView.selectionChanged(self, old_selection, new_selection) self.update_actions() paths = self.sync_selection() if paths and self.model().path_is_interesting(paths[0]): cached = paths[0] in cola.model().staged cola.notifier().broadcast(signals.diff, paths, cached) return result
def _launch_difftool(self): staged, modified, unmerged, untracked = self.selection() if staged: selection = staged elif unmerged: selection = unmerged elif modified: selection = modified else: return cola.notifier().broadcast(signals.difftool, bool(staged), selection)
def revert(self): """Signal that we should revert changes to a path.""" if not qtutils.confirm('Revert Uncommitted Changes?', 'This operation drops uncommitted changes.' '\nThese changes cannot be recovered.', 'Revert the uncommitted changes?', 'Revert Uncommitted Changes', default=True, icon=qtutils.icon('undo.svg')): return paths = self.selected_tracked_paths() cola.notifier().broadcast(signals.checkout, ['HEAD', '--'] + paths)
def _process_selection(self): s = self.selection() if s.staged: cola.notifier().broadcast(signals.unstage, s.staged) unstaged = [] if s.unmerged: unstaged.extend(s.unmerged) if s.modified: unstaged.extend(s.modified) if s.untracked: unstaged.extend(s.untracked) if unstaged: cola.notifier().broadcast(signals.stage, unstaged)
def dropEvent(self, event): """Apply dropped patches with git-am""" event.accept() urls = event.mimeData().urls() if not urls: return paths = map(lambda x: unicode(x.path()), urls) patches = [p for p in paths if p.endswith('.patch')] dirs = [p for p in paths if os.path.isdir(p)] dirs.sort() for d in dirs: patches.extend(self._gather_patches(d)) # Broadcast the patches to apply cola.notifier().broadcast(signals.apply_patches, patches)
def register_for_signals(): # Register globally with the notifier notifier = cola.notifier() notifier.connect(signals.confirm, confirm) notifier.connect(signals.critical, critical) notifier.connect(signals.information, information) notifier.connect(signals.question, question)
def _revert_uncommitted_edits(self): items_to_undo = self.modified() if items_to_undo: if not qtutils.confirm('Revert Uncommitted Changes?', 'This operation drops uncommitted changes.' '\nThese changes cannot be recovered.', 'Revert the uncommitted changes?', 'Revert Uncommitted Changes', default=True, icon=qtutils.icon('undo.svg')): return cola.notifier().broadcast(signals.checkout, [self.m.head, '--'] + items_to_undo) else: qtutils.log(1, self.tr('No files selected for ' 'checkout from HEAD.'))
def do(self): fp = open(core.encode(self.filename), 'wb') cmd = ['git', 'archive', '--format='+self.fmt] if self.fmt in ('tgz', 'tar.gz'): cmd.append('-9') if self.prefix: cmd.append('--prefix=' + self.prefix) cmd.append(self.ref) proc = utils.start_command(cmd, stdout=fp) out, err = proc.communicate() fp.close() if not out: out = '' if err: out += err status = proc.returncode cola.notifier().broadcast(signals.log_cmd, status, out)
def do(self): squash = self.squash revision = self.revision no_commit = self.no_commit status, output = self.context.merge(revision, no_commit, squash) notifier = cola.notifier() notifier.broadcast(signals.log_cmd, status, output) self.context.update_status()
def do(self): context = self.context ref = core.encode(context.ref) relpath = core.encode(context.relpath) cmd = ['git', 'show', '%s:%s' % (ref, relpath)] fp = open(core.encode(context.filename), 'wb') proc = utils.start_command(cmd, stdout=fp) out, err = proc.communicate() fp.close() status = proc.returncode msg = ('Saved "%s" from %s to "%s"' % (context.relpath, context.ref, context.filename)) cola.notifier().broadcast(signals.log_cmd, status, msg) self.factory.prompt_user(signals.information, 'File Saved', 'File saved to "%s"' % context.filename)
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 = tr( '' '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') log(1, error_msg) cola.notifier().broadcast(signals.information, 'Missing Commit Message', error_msg) return msg = self.commit_message(raw=False) if not self.model.staged: error_msg = tr( '' 'No changes to commit.\n\n' 'You must stage at least 1 file before you can commit.') if self.model.modified: informative_text = tr('Would you like to stage and ' 'commit all modified files?') if not confirm('Stage and commit?', error_msg, informative_text, 'Stage and Commit', default=False, icon=save_icon()): return else: cola.notifier().broadcast(signals.information, 'Nothing to commit', error_msg) return cola.notifier().broadcast(signals.stage_modified) # Warn that amending published commits is generally bad amend = self.amend_action.isChecked() if (amend and self.model.is_commit_published() and not confirm('Rewrite Published Commit?', 'This commit has already been published.\n' 'This operation will rewrite published history.\n' 'You probably don\'t want to do this.', 'Amend the published commit?', 'Amend Commit', default=False, icon=save_icon())): return # Perform the commit cola.notifier().broadcast(signals.commit, amend, msg)
def _delete_files(self): files = self.untracked() count = len(files) if count == 0: return title = 'Delete Files?' msg = self.tr('The following files will be deleted:\n\n') fileinfo = subprocess.list2cmdline(files) if len(fileinfo) > 2048: fileinfo = fileinfo[:2048].rstrip() + '...' msg += fileinfo info_txt = unicode(self.tr('Delete %d file(s)?')) % count ok_txt = 'Delete Files' if qtutils.confirm(title, msg, info_txt, ok_txt, default=True, icon=qtutils.discard_icon()): cola.notifier().broadcast(signals.delete, files)
def clicked(self, item=None, idx=None): """Called when a repo status tree item is clicked. This handles the behavior where clicking on the icon invokes the a context-specific action. """ # Sync the selection model s = self.selection() cola.selection_model().set_selection(s) # Clear the selection if an empty area was clicked selection = self.selected_indexes() if not selection: if self.m.amending(): cola.notifier().broadcast(signals.set_diff_text, '') else: cola.notifier().broadcast(signals.reset_mode) self.blockSignals(True) self.clearSelection() self.blockSignals(False) return
def clone_repo(spawn=True): """ Present GUI controls for cloning a repository A new cola session is invoked when 'spawn' is True. """ url, ok = qtutils.prompt('Path or URL to clone (Env. $VARS okay)') url = os.path.expandvars(core.encode(url)) if not ok or not url: return None try: # Pick a suitable basename by parsing the URL newurl = url.replace('\\', '/') default = newurl.rsplit('/', 1)[-1] if default == '.git': # The end of the URL is /.git, so assume it's a file path default = os.path.basename(os.path.dirname(newurl)) if default.endswith('.git'): # The URL points to a bare repo default = default[:-4] if url == '.': # The URL is the current repo default = os.path.basename(os.getcwd()) if not default: raise except: cola.notifier().broadcast(signals.information, 'Error Cloning', 'Could not parse: "%s"' % url) qtutils.log(1, 'Oops, could not parse git url: "%s"' % url) return None # Prompt the user for a directory to use as the parent directory msg = 'Select a parent directory for the new clone' dirname = qtutils.opendir_dialog(msg, cola.model().getcwd()) if not dirname: return None count = 1 dirname = core.decode(dirname) destdir = os.path.join(dirname, core.decode(default)) olddestdir = destdir if os.path.exists(destdir): # An existing path can be specified msg = ('"%s" already exists, cola will create a new directory' % destdir) cola.notifier().broadcast(signals.information, 'Directory Exists', msg) # Make sure the new destdir doesn't exist while os.path.exists(destdir): destdir = olddestdir + str(count) count += 1 cola.notifier().broadcast(signals.clone, core.decode(url), destdir, spawn=spawn) return destdir
def editor(self): """Signal that we should edit selected paths using an external editor.""" cola.notifier().broadcast(signals.edit, self.selected_paths())
def difftool(self): """Signal that we should launch difftool on a path.""" cola.notifier().broadcast(signals.difftool, False, self.selected_tracked_paths())
def untrack_selected(self): """Signal that we should stage selected paths.""" cola.notifier().broadcast(signals.untrack, self.selected_tracked_paths())