def run(self): branch = self.opts.branch revision = self.opts.revision reset = self.opts.reset checkout = self.opts.checkout track = self.opts.track model = self.opts.model results = [] status = 0 if track and '/' in revision: remote = revision.split('/', 1)[0] status, out, err = model.git.fetch(remote) self.emit(SIGNAL(COMMAND_SIGNAL), status, out, err) results.append(('fetch', status, out, err)) if status == 0: status, out, err = model.create_branch(branch, revision, force=reset, track=track) self.emit(SIGNAL(COMMAND_SIGNAL), status, out, err) results.append(('branch', status, out, err)) if status == 0 and checkout: status, out, err = model.git.checkout(branch) self.emit(SIGNAL(COMMAND_SIGNAL), status, out, err) results.append(('checkout', status, out, err)) main.model().update_status() self.emit(SIGNAL('done(PyQt_PyObject)'), results)
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 __init__(self, parent, titlebar): DiffTextEdit.__init__(self, parent) self.model = model = main.model() # "Diff Options" tool menu self.diff_ignore_space_at_eol_action = add_action(self, N_('Ignore changes in whitespace at EOL'), self._update_diff_opts) self.diff_ignore_space_at_eol_action.setCheckable(True) self.diff_ignore_space_change_action = add_action(self, N_('Ignore changes in amount of whitespace'), self._update_diff_opts) self.diff_ignore_space_change_action.setCheckable(True) self.diff_ignore_all_space_action = add_action(self, N_('Ignore all whitespace'), self._update_diff_opts) self.diff_ignore_all_space_action.setCheckable(True) self.diff_function_context_action = add_action(self, N_('Show whole surrounding functions of changes'), self._update_diff_opts) self.diff_function_context_action.setCheckable(True) self.diffopts_button = create_action_button( tooltip=N_('Diff Options'), icon=options_icon()) self.diffopts_menu = create_menu(N_('Diff Options'), self.diffopts_button) self.diffopts_menu.addAction(self.diff_ignore_space_at_eol_action) self.diffopts_menu.addAction(self.diff_ignore_space_change_action) self.diffopts_menu.addAction(self.diff_ignore_all_space_action) self.diffopts_menu.addAction(self.diff_function_context_action) self.diffopts_button.setMenu(self.diffopts_menu) qtutils.hide_button_menu_indicator(self.diffopts_button) titlebar.add_corner_widget(self.diffopts_button) self.action_apply_selection = qtutils.add_action(self, '', self.apply_selection, Qt.Key_S) self.action_revert_selection = qtutils.add_action(self, '', self.revert_selection) self.action_revert_selection.setIcon(qtutils.icon('undo.svg')) self.launch_editor = qtutils.add_action(self, cmds.LaunchEditor.name(), run(cmds.LaunchEditor), cmds.LaunchEditor.SHORTCUT, 'Return', 'Enter') self.launch_editor.setIcon(qtutils.options_icon()) self.launch_difftool = qtutils.add_action(self, cmds.LaunchDifftool.name(), run(cmds.LaunchDifftool), cmds.LaunchDifftool.SHORTCUT) self.launch_difftool.setIcon(qtutils.icon('git.svg')) model.add_observer(model.message_diff_text_changed, self._emit_text) self.connect(self, SIGNAL('set_text'), self.setPlainText)
def __init__(self, timeout=333): """Set up the pyinotify thread""" QtCore.QThread.__init__(self) ## Git command object self._git = main.model().git ## pyinotify timeout self._timeout = timeout ## Path to monitor self._path = self._git.worktree() ## Signals thread termination self._running = True ## Directories to watching self._dirs_seen = set() ## The inotify watch manager instantiated in run() self._wmgr = None ## Has add_watch() failed? self._add_watch_failed = False ## Events to capture if utils.is_linux(): self._mask = (EventsCodes.ALL_FLAGS['IN_ATTRIB'] | EventsCodes.ALL_FLAGS['IN_CLOSE_WRITE'] | EventsCodes.ALL_FLAGS['IN_CREATE'] | EventsCodes.ALL_FLAGS['IN_DELETE'] | EventsCodes.ALL_FLAGS['IN_MODIFY'] | EventsCodes.ALL_FLAGS['IN_MOVED_TO'])
def sync_selection(self): """Push selection into the selection model.""" staged = [] unmerged = [] modified = [] untracked = [] state = State(staged, unmerged, modified, untracked) paths = self.selected_paths() model = main.model() model_staged = utils.add_parents(model.staged) model_modified = utils.add_parents(model.modified) model_unmerged = utils.add_parents(model.unmerged) model_untracked = utils.add_parents(model.untracked) for path in paths: if path in model_unmerged: unmerged.append(path) elif path in model_untracked: untracked.append(path) elif path in model_staged: staged.append(path) elif path in model_modified: modified.append(path) else: staged.append(path) # Push the new selection into the model. selection_model().set_selection(state) return paths
def selected_modified_paths(self, selection=None): """Return selected modified paths.""" if not selection: selection = self.selected_paths() model = main.model() modified = utils.add_parents(model.modified) return [p for p in selection if p in modified]
def run(): files = selection.selected_group() if not files: return s = selection.selection() model = main.model() launch_with_head(files, bool(s.staged), model.head)
def data(self, key): """ Return git data for a path. Supported keys are 'date', 'message', and 'author' """ if not self._data: log_line = main.model().git.log('-1', '--', self.path, M=True, all=False, no_color=True, pretty='format:%ar%x01%s%x01%an' )[STDOUT] if log_line: log_line = log_line date, message, author = log_line.split(chr(0x01), 2) self._data['date'] = date self._data['message'] = message self._data['author'] = author else: self._data['date'] = self.date() self._data['message'] = '-' self._data['author'] = self._cfg.get('user.name', 'unknown') return self._data[key]
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 new_model(app, repo, prompt=False, settings=None): model = main.model() valid = False if not prompt: valid = model.set_worktree(repo) if not valid: # We are not currently in a git repository so we need to find one. # Before prompting the user for a repostiory, check if they've # configured a default repository and attempt to use it. default_repo = gitcfg.current().get('cola.defaultrepo') if default_repo: valid = model.set_worktree(default_repo) while not valid: # If we've gotten into this loop then that means that neither the # current directory nor the default repository were available. # Prompt the user for a repository. startup_dlg = startup.StartupDialog(app.activeWindow(), settings=settings) gitdir = startup_dlg.find_git_repo() if not gitdir: sys.exit(EX_NOINPUT) valid = model.set_worktree(gitdir) # Finally, go to the root of the git repo os.chdir(model.git.worktree()) return model
def status(self): """Return the status for the entry's path.""" model = main.model() unmerged = utils.add_parents(model.unmerged) modified = utils.add_parents(model.modified) staged = utils.add_parents(model.staged) untracked = utils.add_parents(model.untracked) upstream_changed = utils.add_parents(model.upstream_changed) if self.path in unmerged: status = (icons.modified_name(), N_('Unmerged')) elif self.path in modified and self.path in staged: status = (icons.partial_name(), N_('Partially Staged')) elif self.path in modified: status = (icons.modified_name(), N_('Modified')) elif self.path in staged: status = (icons.staged_name(), N_('Staged')) elif self.path in upstream_changed: status = (icons.upstream_name(), N_('Changed Upstream')) elif self.path in untracked: status = (None, '?') else: status = (None, '') return status
def local_merge(): """Provides a dialog for merging branches""" model = main.model() view = MergeView(model, qtutils.active_window()) view.show() view.raise_() return view
def __init__(self, view=None): QtCore.QObject.__init__(self, view) self.model = main.model() self.view = view self.updated = set() self.connect(view, SIGNAL("history(QStringList)"), self.view_history) self.connect(view, SIGNAL("expanded(QModelIndex)"), self.query_model) self.connect(view, SIGNAL("difftool_predecessor"), self.difftool_predecessor)
def __init__(self, parent): standard.TreeView.__init__(self, parent) self.setRootIsDecorated(True) self.setSortingEnabled(False) self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) # Observe model updates model = main.model() model.add_observer(model.message_updated, self.update_actions) # The non-Qt cola application model self.connect(self, SIGNAL("expanded(QModelIndex)"), self.size_columns) self.connect(self, SIGNAL("collapsed(QModelIndex)"), self.size_columns) # Sync selection before the key press event changes the model index self.connect(self, SIGNAL("indexAboutToChange()"), self.sync_selection) self.action_history = self._create_action( N_("View History..."), N_("View history for selected path(s)."), self.view_history, "Shift+Ctrl+H" ) self.action_stage = self._create_action( N_("Stage Selected"), N_("Stage selected path(s) for commit."), self.stage_selected, cmds.Stage.SHORTCUT ) self.action_unstage = self._create_action( N_("Unstage Selected"), N_("Remove selected path(s) from the staging area."), self.unstage_selected, "Ctrl+U", ) self.action_untrack = self._create_action( N_("Untrack Selected"), N_("Stop tracking path(s)"), self.untrack_selected ) self.action_difftool = self._create_action( cmds.LaunchDifftool.name(), N_("Launch git-difftool on the current path."), cmds.run(cmds.LaunchDifftool), cmds.LaunchDifftool.SHORTCUT, ) self.action_difftool_predecessor = self._create_action( N_("Diff Against Predecessor..."), N_("Launch git-difftool against previous versions."), self.difftool_predecessor, "Shift+Ctrl+D", ) self.action_revert = self._create_action( N_("Revert Uncommitted Changes..."), N_("Revert changes to selected path(s)."), self.revert, "Ctrl+Z" ) self.action_editor = self._create_action( cmds.LaunchEditor.name(), N_("Edit selected path(s)."), cmds.run(cmds.LaunchEditor), cmds.LaunchEditor.SHORTCUT, )
def selected_unstaged_paths(self, selection=None): """Return selected unstaged paths.""" if not selection: selection = self.selected_paths() model = main.model() modified = utils.add_parents(model.modified) untracked = utils.add_parents(model.untracked) unstaged = modified.union(untracked) return [p for p in selection if p in unstaged]
def _update_callback(self): enabled = False s = self.selection_model.selection() if s.modified and self.model.stageable(): if s.modified[0] in self.model.submodules: pass elif s.modified[0] not in main.model().unstaged_deleted: enabled = True self.action_revert_selection.setEnabled(enabled)
def delete(self): remote = qtutils.selected_item(self.remotes, self.remote_list) if remote is None: return title = N_('Delete Remote') question = N_('Delete remote?') info = N_('Delete remote "%s"') % remote ok_btn = N_('Delete') if not qtutils.confirm(title, question, info, ok_btn): return status, out, err = git.remote('rm', remote) if status != 0: qtutils.critical(N_('Error deleting remote "%s"') % remote, out + err) main.model().update_status() self.refresh()
def __init__(self, view=None): QtCore.QObject.__init__(self, view) self.model = main.model() self.view = view self.connect(view, SIGNAL('history(PyQt_PyObject)'), self.view_history) self.connect(view, SIGNAL('expanded(QModelIndex)'), self.query_model) self.connect(view, SIGNAL('difftool_predecessor(PyQt_PyObject)'), self.difftool_predecessor)
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 main.model().staged cmds.do(cmds.Diff, paths, cached) return result
def selected_tracked_paths(self, selection=None): """Return selected tracked paths.""" if not selection: selection = self.selected_paths() model = main.model() staged = set(self.selected_staged_paths()) modified = set(self.selected_modified_paths()) untracked = utils.add_parents(set(model.untracked)) tracked = staged.union(modified) return [p for p in selection if p not in untracked or p in tracked]
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(N_('Path or URL to clone (Env. $VARS okay)')) url = os.path.expandvars(url) if not ok or not url: return None try: # Pick a suitable basename by parsing the URL newurl = url.replace('\\', '/').rstrip('/') 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(core.getcwd()) if not default: raise except: Interaction.information( N_('Error Cloning'), N_('Could not parse Git URL: "%s"') % url) Interaction.log(N_('Could not parse Git URL: "%s"') % url) return None # Prompt the user for a directory to use as the parent directory msg = N_('Select a parent directory for the new clone') dirname = qtutils.opendir_dialog(msg, main.model().getcwd()) if not dirname: return None count = 1 destdir = os.path.join(dirname, default) olddestdir = destdir if core.exists(destdir): # An existing path can be specified msg = (N_('"%s" already exists, cola will create a new directory') % destdir) Interaction.information('Directory Exists', msg) # Make sure the new destdir doesn't exist while core.exists(destdir): destdir = olddestdir + str(count) count += 1 if cmds.do(cmds.Clone, url, destdir, spawn=spawn): return destdir return None
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(N_('Path or URL to clone (Env. $VARS okay)')) url = os.path.expandvars(url) if not ok or not url: return None try: # Pick a suitable basename by parsing the URL newurl = url.replace('\\', '/').rstrip('/') 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(core.getcwd()) if not default: raise except: Interaction.information(N_('Error Cloning'), N_('Could not parse Git URL: "%s"') % url) Interaction.log(N_('Could not parse Git URL: "%s"') % url) return None # Prompt the user for a directory to use as the parent directory msg = N_('Select a parent directory for the new clone') dirname = qtutils.opendir_dialog(msg, main.model().getcwd()) if not dirname: return None count = 1 destdir = os.path.join(dirname, default) olddestdir = destdir if core.exists(destdir): # An existing path can be specified msg = (N_('"%s" already exists, cola will create a new directory') % destdir) Interaction.information('Directory Exists', msg) # Make sure the new destdir doesn't exist while core.exists(destdir): destdir = olddestdir + str(count) count += 1 if cmds.do(cmds.Clone, url, destdir, spawn=spawn): return destdir return None
def __init__(self, parent): DiffTextEdit.__init__(self, parent) self.model = model = main.model() self.mode = self.model.mode_none self.action_process_section = qtutils.add_action(self, N_('Process Section'), self.apply_section, Qt.Key_H) self.action_process_selection = qtutils.add_action(self, N_('Process Selection'), self.apply_selection, Qt.Key_S) self.launch_editor = qtutils.add_action(self, cmds.LaunchEditor.name(), run(cmds.LaunchEditor), cmds.LaunchEditor.SHORTCUT, 'Return', 'Enter') self.launch_editor.setIcon(qtutils.options_icon()) self.launch_difftool = qtutils.add_action(self, cmds.LaunchDifftool.name(), run(cmds.LaunchDifftool), cmds.LaunchDifftool.SHORTCUT) self.launch_difftool.setIcon(qtutils.icon('git.svg')) self.action_stage_selection = qtutils.add_action(self, N_('Stage &Selected Lines'), self.stage_selection) self.action_stage_selection.setIcon(qtutils.icon('add.svg')) self.action_stage_selection.setShortcut(Qt.Key_S) self.action_revert_selection = qtutils.add_action(self, N_('Revert Selected Lines...'), self.revert_selection) self.action_revert_selection.setIcon(qtutils.icon('undo.svg')) self.action_unstage_selection = qtutils.add_action(self, N_('Unstage &Selected Lines'), self.unstage_selection) self.action_unstage_selection.setIcon(qtutils.icon('remove.svg')) self.action_unstage_selection.setShortcut(Qt.Key_S) self.action_apply_selection = qtutils.add_action(self, N_('Apply Diff Selection to Work Tree'), self.stage_selection) self.action_apply_selection.setIcon(qtutils.apply_icon()) model.add_observer(model.message_mode_about_to_change, self._mode_about_to_change) model.add_observer(model.message_diff_text_changed, self._emit_text) self.connect(self, SIGNAL('copyAvailable(bool)'), self.enable_selection_actions) self.connect(self, SIGNAL('set_text'), self.setPlainText)
def __init__(self): """Initialize the command and stash away values for use in do()""" # These are commonly used so let's make it easier to write new commands. BaseCommand.__init__(self) self.model = main.model() self.old_diff_text = self.model.diff_text self.old_filename = self.model.filename self.old_mode = self.model.mode self.new_diff_text = self.old_diff_text self.new_filename = self.old_filename self.new_mode = self.old_mode
def new_model(app, repo, prompt=False): model = main.model() valid = model.set_worktree(repo) and not prompt while not valid: startup_dlg = startup.StartupDialog(app.activeWindow()) gitdir = startup_dlg.find_git_repo() if not gitdir: sys.exit(EX_NOINPUT) valid = model.set_worktree(gitdir) # Finally, go to the root of the git repo os.chdir(model.git.worktree()) return model
def __init__(self, parent, update=True): standard.Widget.__init__(self, parent) self.tree = RepoTreeView(self) self.mainlayout = qtutils.hbox(defs.no_margin, defs.spacing, self.tree) self.setLayout(self.mainlayout) self.resize(720, 420) self.connect(self, SIGNAL('updated'), self._updated_callback) self.model = main.model() self.model.add_observer(self.model.message_updated, self.model_updated) qtutils.add_close_action(self) if update: self.model_updated()
def new_model(app, repo, prompt=False): model = main.model() valid = model.set_worktree(repo) and not prompt while not valid: startup_dlg = startup.StartupDialog(app.activeWindow()) gitdir = startup_dlg.find_git_repo() if not gitdir: sys.exit(-1) valid = model.set_worktree(gitdir) # Finally, go to the root of the git repo os.chdir(model.git.worktree()) return model
def run(RemoteDialog): """Launches fetch/push/pull dialogs.""" # Copy global stuff over to speedup startup model = main.MainModel() global_model = main.model() model.currentbranch = global_model.currentbranch model.local_branches = global_model.local_branches model.remote_branches = global_model.remote_branches model.tags = global_model.tags model.remotes = global_model.remotes parent = qtutils.active_window() view = RemoteDialog(model, parent=parent) view.show() return view
def __init__(self, parent=None): QtGui.QWidget.__init__(self, parent) self.main_model = main.model() hint = N_('Filter paths...') self.text = completion.GitStatusFilterLineEdit(hint=hint, parent=self) self.text.setToolTip(hint) self.text.enable_hint(True) self.setFocusProxy(self.text) self.main_layout = qtutils.hbox(defs.no_margin, defs.spacing, self.text) self.setLayout(self.main_layout) self.connect(self.text, SIGNAL('changed()'), self.apply_filter) self.connect(self.text, SIGNAL('returnPressed()'), self.apply_filter)
def __init__(self, parent): QtGui.QStandardItemModel.__init__(self, parent) self._interesting_paths = self._get_paths() self._known_paths = set() self.connect(self, SIGNAL('updated'), self._updated_callback) model = main.model() model.add_observer(model.message_updated, self._model_updated) self._dir_rows = collections.defaultdict(int) self.setColumnCount(len(Columns.ALL)) for idx, header in enumerate(Columns.ALL): text = Columns.text(header) self.setHeaderData(idx, Qt.Horizontal, QtCore.QVariant(text)) self._direntries = {'': self.invisibleRootItem()} self._initialize()
def __init__(self, parent=None, update=True): standard.StandardDialog.__init__(self, parent) self.setObjectName('classic') self.tree = RepoTreeView(parent) self.setLayout(QtGui.QHBoxLayout()) self.layout().setMargin(1) self.layout().addWidget(self.tree) self.resize(720, 420) self.connect(self, SIGNAL('updated'), self._updated_callback) self.model = main.model() self.model.add_message_observer(self.model.message_updated, self._model_updated) cola.qtutils.add_close_action(self) if update: self._model_updated()
def __init__(self, parent): QtGui.QStandardItemModel.__init__(self, parent) self._interesting_paths = self._get_paths() self._known_paths = set() self.connect(self, SIGNAL('updated'), self._updated_callback) model = main.model() model.add_observer(model.message_updated, self._model_updated) self._dir_rows = {} self.setColumnCount(len(Columns.ALL)) for idx, header in enumerate(Columns.ALL): text = Columns.text(header) self.setHeaderData(idx, Qt.Horizontal, QtCore.QVariant(text)) self._direntries = {'': self.invisibleRootItem()} self._initialize()
def __init__(self, parent, update=True): standard.Widget.__init__(self, parent) self.tree = RepoTreeView(self) self.mainlayout = QtGui.QHBoxLayout() self.setLayout(self.mainlayout) self.mainlayout.setMargin(0) self.mainlayout.setSpacing(defs.spacing) self.mainlayout.addWidget(self.tree) self.resize(720, 420) self.connect(self, SIGNAL("updated"), self._updated_callback) self.model = main.model() self.model.add_observer(self.model.message_updated, self.model_updated) qtutils.add_close_action(self) if update: self.model_updated()
def __init__(self, parent): QtGui.QStandardItemModel.__init__(self, parent) self.entries = {} self._interesting_paths = set() self._interesting_files = set() self._known_paths = set() self._dir_entries = {} self._dir_rows = collections.defaultdict(int) self.connect(self, SIGNAL('updated()'), self.refresh, Qt.QueuedConnection) model = main.model() model.add_observer(model.message_updated, self._model_updated) self.file_icon = qtutils.file_icon() self.dir_icon = qtutils.dir_icon()
def __init__(self, parent, update=True, settings=None): standard.Widget.__init__(self, parent) self.settings = settings self.tree = RepoTreeView(self) self.mainlayout = qtutils.hbox(defs.no_margin, defs.spacing, self.tree) self.setLayout(self.mainlayout) self.connect(self, SIGNAL('updated()'), self._updated_callback, Qt.QueuedConnection) self.model = main.model() self.model.add_observer(self.model.message_updated, self.model_updated) qtutils.add_close_action(self) if update: self.model_updated() # Restore saved settings if not self.restore_state(settings=settings): self.resize(720, 420)
def __init__(self, timeout=333): """Set up the pyinotify thread""" QtCore.QThread.__init__(self) ## Git command object self._git = main.model().git ## pyinotify timeout self._timeout = timeout ## Path to monitor self._path = self._git.worktree() ## Signals thread termination self._running = True ## Directories to watching self._dirs_seen = set() ## The inotify watch manager instantiated in run() self._wmgr = None ## Events to capture if utils.is_linux(): self._mask = (EventsCodes.ALL_FLAGS['IN_ATTRIB'] | EventsCodes.ALL_FLAGS['IN_CLOSE_WRITE'] | EventsCodes.ALL_FLAGS['IN_DELETE'] | EventsCodes.ALL_FLAGS['IN_MODIFY'] | EventsCodes.ALL_FLAGS['IN_MOVED_TO'])
def launch(left=None, right=None, paths=None, left_take_parent=False, staged=False): """Launches 'git difftool' with given parameters""" difftool_args = ['git', 'difftool', '--no-prompt'] if staged: difftool_args.append('--cached') if left: if left_take_parent: # Check root commit (no parents and thus cannot execute '~') model = main.model() git = model.git status, out, err = git.rev_list(left, parents=True, n=1) Interaction.log_status(status, out, err) if status: raise OSError('git rev-list command failed') if len(out.split()) >= 2: # Commit has a parent, so we can take its child as requested left += '~' else: # No parent, assume it's the root commit, so we have to diff # against the empty tree. The empty tree is a built-in # git constant SHA1. The empty tree is a built-in Git SHA1. left = '4b825dc642cb6eb9a060e54bf8d69288fbee4904' difftool_args.append(left) if right: difftool_args.append(right) if paths: difftool_args.append('--') difftool_args.extend(paths) core.fork(difftool_args)
def status(self): """Return the status for the entry's path.""" model = main.model() unmerged = utils.add_parents(set(model.unmerged)) modified = utils.add_parents(set(model.modified)) staged = utils.add_parents(set(model.staged)) untracked = utils.add_parents(set(model.untracked)) upstream_changed = utils.add_parents(set(model.upstream_changed)) if self.path in unmerged: return (resources.icon('modified.png'), N_('Unmerged')) if self.path in modified and self.path in staged: return (resources.icon('partial.png'), N_('Partially Staged')) if self.path in modified: return (resources.icon('modified.png'), N_('Modified')) if self.path in staged: return (resources.icon('staged.png'), N_('Staged')) if self.path in upstream_changed: return (resources.icon('upstream.png'), N_('Changed Upstream')) if self.path in untracked: return (None, '?') return (None, '')
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(): 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(): # 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 load_commitmsg(): """Load a commit message from a file.""" filename = qtutils.open_file(N_('Load Commit Message'), directory=main.model().getcwd()) if filename: cmds.do(cmds.LoadCommitMessageFromFile, filename)
def __init__(self, parent): DiffTextEdit.__init__(self, parent) self.model = model = main.model() # "Diff Options" tool menu self.diff_ignore_space_at_eol_action = add_action( self, N_('Ignore changes in whitespace at EOL'), self._update_diff_opts) self.diff_ignore_space_at_eol_action.setCheckable(True) self.diff_ignore_space_change_action = add_action( self, N_('Ignore changes in amount of whitespace'), self._update_diff_opts) self.diff_ignore_space_change_action.setCheckable(True) self.diff_ignore_all_space_action = add_action( self, N_('Ignore all whitespace'), self._update_diff_opts) self.diff_ignore_all_space_action.setCheckable(True) self.diff_function_context_action = add_action( self, N_('Show whole surrounding functions of changes'), self._update_diff_opts) self.diff_function_context_action.setCheckable(True) self.diffopts_button = create_action_button(tooltip=N_('Diff Options'), icon=options_icon()) self.diffopts_menu = create_menu(N_('Diff Options'), self.diffopts_button) self.diffopts_menu.addAction(self.diff_ignore_space_at_eol_action) self.diffopts_menu.addAction(self.diff_ignore_space_change_action) self.diffopts_menu.addAction(self.diff_ignore_all_space_action) self.diffopts_menu.addAction(self.diff_function_context_action) self.diffopts_button.setMenu(self.diffopts_menu) qtutils.hide_button_menu_indicator(self.diffopts_button) titlebar = parent.titleBarWidget() titlebar.add_corner_widget(self.diffopts_button) self.action_process_section = qtutils.add_action( self, N_('Process Section'), self.apply_section, Qt.Key_H) self.action_process_selection = qtutils.add_action( self, N_('Process Selection'), self.apply_selection, Qt.Key_S) self.launch_editor = qtutils.add_action(self, cmds.LaunchEditor.name(), run(cmds.LaunchEditor), cmds.LaunchEditor.SHORTCUT, 'Return', 'Enter') self.launch_editor.setIcon(qtutils.options_icon()) self.launch_difftool = qtutils.add_action(self, cmds.LaunchDifftool.name(), run(cmds.LaunchDifftool), cmds.LaunchDifftool.SHORTCUT) self.launch_difftool.setIcon(qtutils.icon('git.svg')) self.action_stage_selection = qtutils.add_action( self, N_('Stage &Selected Lines'), self.stage_selection) self.action_stage_selection.setIcon(qtutils.icon('add.svg')) self.action_stage_selection.setShortcut(Qt.Key_S) self.action_revert_selection = qtutils.add_action( self, N_('Revert Selected Lines...'), self.revert_selection) self.action_revert_selection.setIcon(qtutils.icon('undo.svg')) self.action_unstage_selection = qtutils.add_action( self, N_('Unstage &Selected Lines'), self.unstage_selection) self.action_unstage_selection.setIcon(qtutils.icon('remove.svg')) self.action_unstage_selection.setShortcut(Qt.Key_S) self.action_apply_selection = qtutils.add_action( self, N_('Apply Diff Selection to Work Tree'), self.stage_selection) self.action_apply_selection.setIcon(qtutils.apply_icon()) model.add_observer(model.message_diff_text_changed, self._emit_text) self.connect(self, SIGNAL('copyAvailable(bool)'), self.enable_selection_actions) self.connect(self, SIGNAL('set_text'), self.setPlainText)
def open_repo(): dirname = qtutils.opendir_dialog(N_('Open Git Repository...'), main.model().getcwd()) if not dirname: return cmds.do(cmds.OpenRepo, dirname)
def __init__(self, parent=None): QtGui.QTreeWidget.__init__(self, parent) self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) self.headerItem().setHidden(True) self.setAllColumnsShowFocus(True) self.setSortingEnabled(False) self.setUniformRowHeights(True) self.setAnimated(True) self.setRootIsDecorated(False) self.setIndentation(0) self.setDragEnabled(True) ok = icons.ok() compare = icons.compare() question = icons.question() self.add_toplevel_item(N_('Staged'), ok, hide=True) self.add_toplevel_item(N_('Unmerged'), compare, hide=True) self.add_toplevel_item(N_('Modified'), compare, hide=True) self.add_toplevel_item(N_('Untracked'), question, hide=True) # Used to restore the selection self.old_scroll = None self.old_selection = None self.old_contents = None self.old_current_item = None self.expanded_items = set() self.process_selection_action = qtutils.add_action( self, cmds.StageOrUnstage.name(), cmds.run(cmds.StageOrUnstage), hotkeys.STAGE_SELECTION) self.revert_unstaged_edits_action = qtutils.add_action( self, cmds.RevertUnstagedEdits.name(), cmds.run(cmds.RevertUnstagedEdits), hotkeys.REVERT) self.revert_unstaged_edits_action.setIcon(icons.undo()) self.launch_difftool_action = qtutils.add_action( self, cmds.LaunchDifftool.name(), cmds.run(cmds.LaunchDifftool), hotkeys.DIFF) self.launch_difftool_action.setIcon(icons.diff()) self.launch_editor_action = qtutils.add_action( self, cmds.LaunchEditor.name(), cmds.run(cmds.LaunchEditor), hotkeys.EDIT, *hotkeys.ACCEPT) self.launch_editor_action.setIcon(icons.edit()) if not utils.is_win32(): self.open_using_default_app = qtutils.add_action( self, cmds.OpenDefaultApp.name(), self._open_using_default_app, hotkeys.PRIMARY_ACTION) self.open_using_default_app.setIcon(icons.default_app()) self.open_parent_dir_action = qtutils.add_action( self, cmds.OpenParentDir.name(), self._open_parent_dir, hotkeys.SECONDARY_ACTION) self.open_parent_dir_action.setIcon(icons.folder()) self.up_action = qtutils.add_action(self, N_('Move Up'), self.move_up, hotkeys.MOVE_UP, hotkeys.MOVE_UP_SECONDARY) self.down_action = qtutils.add_action(self, N_('Move Down'), self.move_down, hotkeys.MOVE_DOWN, hotkeys.MOVE_DOWN_SECONDARY) self.copy_path_action = qtutils.add_action( self, N_('Copy Path to Clipboard'), self.copy_path, hotkeys.COPY) self.copy_path_action.setIcon(icons.copy()) self.copy_relpath_action = qtutils.add_action( self, N_('Copy Relative Path to Clipboard'), self.copy_relpath, hotkeys.CUT) self.copy_relpath_action.setIcon(icons.copy()) self.view_history_action = qtutils.add_action(self, N_('View History...'), self.view_history, hotkeys.HISTORY) # MoveToTrash and Delete use the same shortcut. # We will only bind one of them, depending on whether or not the # MoveToTrash command is avaialble. When available, the hotkey # is bound to MoveToTrash, otherwise it is bound to Delete. if cmds.MoveToTrash.AVAILABLE: self.move_to_trash_action = qtutils.add_action( self, N_('Move file(s) to trash'), self._trash_untracked_files, hotkeys.TRASH) self.move_to_trash_action.setIcon(icons.discard()) delete_shortcut = hotkeys.DELETE_FILE else: self.move_to_trash_action = None delete_shortcut = hotkeys.DELETE_FILE_SECONDARY self.delete_untracked_files_action = qtutils.add_action( self, N_('Delete File(s)...'), self._delete_untracked_files, delete_shortcut) self.delete_untracked_files_action.setIcon(icons.discard()) self.connect(self, SIGNAL('about_to_update()'), self._about_to_update, Qt.QueuedConnection) self.connect(self, SIGNAL('updated()'), self._updated, Qt.QueuedConnection) self.m = main.model() self.m.add_observer(self.m.message_about_to_update, self.about_to_update) self.m.add_observer(self.m.message_updated, self.updated) self.connect(self, SIGNAL('itemSelectionChanged()'), self.show_selection) self.connect(self, SIGNAL('itemDoubleClicked(QTreeWidgetItem*,int)'), self.double_clicked) self.connect(self, SIGNAL('itemCollapsed(QTreeWidgetItem*)'), lambda x: self.update_column_widths()) self.connect(self, SIGNAL('itemExpanded(QTreeWidgetItem*)'), lambda x: self.update_column_widths())
def __init__(self, remote): ConfirmAction.__init__(self) self.model = main.model() self.remote = remote
def __init__(self, parent): QtGui.QTreeWidget.__init__(self, parent) self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) self.headerItem().setHidden(True) self.setAllColumnsShowFocus(True) self.setSortingEnabled(False) self.setUniformRowHeights(True) self.setAnimated(True) self.setRootIsDecorated(False) self.setIndentation(0) self.add_item(N_('Staged'), hide=True) self.add_item(N_('Unmerged'), hide=True) self.add_item(N_('Modified'), hide=True) self.add_item(N_('Untracked'), hide=True) # Used to restore the selection self.old_scroll = None self.old_selection = None self.old_contents = None self.old_current_item = None self.expanded_items = set() self.process_selection = qtutils.add_action(self, N_('Stage / Unstage'), self._process_selection, cmds.Stage.SHORTCUT) self.revert_unstaged_edits_action = qtutils.add_action( self, N_('Revert Unstaged Edits...'), cmds.run(cmds.RevertUnstagedEdits), cmds.RevertUnstagedEdits.SHORTCUT) self.revert_unstaged_edits_action.setIcon(qtutils.icon('undo.svg')) self.launch_difftool = qtutils.add_action( self, cmds.LaunchDifftool.name(), cmds.run(cmds.LaunchDifftool), cmds.LaunchDifftool.SHORTCUT) self.launch_difftool.setIcon(qtutils.icon('git.svg')) self.launch_editor = qtutils.add_action(self, cmds.LaunchEditor.name(), cmds.run(cmds.LaunchEditor), cmds.LaunchEditor.SHORTCUT, 'Return', 'Enter') self.launch_editor.setIcon(qtutils.options_icon()) if not utils.is_win32(): self.open_using_default_app = qtutils.add_action( self, cmds.OpenDefaultApp.name(), self._open_using_default_app, cmds.OpenDefaultApp.SHORTCUT) self.open_using_default_app.setIcon(qtutils.file_icon()) self.open_parent_dir = qtutils.add_action( self, cmds.OpenParentDir.name(), self._open_parent_dir, cmds.OpenParentDir.SHORTCUT) self.open_parent_dir.setIcon(qtutils.open_file_icon()) self.up = qtutils.add_action(self, N_('Move Up'), self.move_up, Qt.Key_K) self.down = qtutils.add_action(self, N_('Move Down'), self.move_down, Qt.Key_J) self.copy_path_action = qtutils.add_action( self, N_('Copy Path to Clipboard'), self.copy_path, QtGui.QKeySequence.Copy) self.copy_path_action.setIcon(qtutils.theme_icon('edit-copy.svg')) self.connect(self, SIGNAL('about_to_update'), self._about_to_update) self.connect(self, SIGNAL('updated'), self._updated) self.m = main.model() self.m.add_observer(self.m.message_about_to_update, self.about_to_update) self.m.add_observer(self.m.message_updated, self.updated) self.connect(self, SIGNAL('itemSelectionChanged()'), self.show_selection) self.connect(self, SIGNAL('itemDoubleClicked(QTreeWidgetItem*,int)'), self.double_clicked) self.connect(self, SIGNAL('itemCollapsed(QTreeWidgetItem*)'), lambda x: self.update_column_widths()) self.connect(self, SIGNAL('itemExpanded(QTreeWidgetItem*)'), lambda x: self.update_column_widths())
def _initialize(self): """Iterate over the cola model and create GitRepoItems.""" for path in main.model().everything(): self.add_file(path)
def _get_paths(self): """Return paths of interest; e.g. paths with a status.""" model = main.model() paths = set(model.staged + model.unstaged) return utils.add_parents(paths)
def selected_staged_paths(self, selection=None): """Return selected staged paths.""" if not selection: selection = self.selected_paths() staged = utils.add_parents(set(main.model().staged)) return [p for p in selection if p in staged]
def __init__(self, parent): standard.TreeView.__init__(self, parent) self.setRootIsDecorated(True) self.setSortingEnabled(False) self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) # Observe model updates model = main.model() model.add_observer(model.message_updated, self.update_actions) # The non-Qt cola application model self.connect(self, SIGNAL('expanded(QModelIndex)'), self.size_columns) self.connect(self, SIGNAL('collapsed(QModelIndex)'), self.size_columns) # Sync selection before the key press event changes the model index self.connect(self, SIGNAL('indexAboutToChange()'), self.sync_selection) self.action_history =\ self._create_action( N_('View History...'), N_('View history for selected path(s).'), self.view_history, 'Shift+Ctrl+H') self.action_stage =\ self._create_action(N_('Stage Selected'), N_('Stage selected path(s) for commit.'), self.stage_selected, cmds.Stage.SHORTCUT) self.action_unstage =\ self._create_action( N_('Unstage Selected'), N_('Remove selected path(s) from the staging area.'), self.unstage_selected, 'Ctrl+U') self.action_untrack =\ self._create_action(N_('Untrack Selected'), N_('Stop tracking path(s)'), self.untrack_selected) self.action_difftool =\ self._create_action(cmds.LaunchDifftool.name(), N_('Launch git-difftool on the current path.'), cmds.run(cmds.LaunchDifftool), cmds.LaunchDifftool.SHORTCUT) self.action_difftool_predecessor =\ self._create_action(N_('Diff Against Predecessor...'), N_('Launch git-difftool against previous versions.'), self.difftool_predecessor, 'Shift+Ctrl+D') self.action_revert =\ self._create_action(N_('Revert Uncommitted Changes...'), N_('Revert changes to selected path(s).'), self.revert, 'Ctrl+Z') self.action_editor =\ self._create_action(cmds.LaunchEditor.name(), N_('Edit selected path(s).'), cmds.run(cmds.LaunchEditor), cmds.LaunchEditor.SHORTCUT)
def get_files(self): model = main.model() return set(model.staged + model.unstaged)
def __init__(self, parent): CompletionModel.__init__(self, parent) self.main_model = model = main.model() msg = model.message_updated model.add_observer(msg, self.emit_update)
def __init__(self): BaseCommand.__init__(self) self.model = main.model()
def __init__(self, action_name): Command.__init__(self) self.action_name = action_name self.model = main.model()
def __init__(self): ConfirmAction.__init__(self) self.model = main.model() self.icon = resources.icon('undo.svg')
def has_stashable_changes(self): model = main.model() return bool(model.modified + model.staged)