Example #1
0
    def action_completed(self, task, status, output):
        # Grab the results of the action and finish up
        self.progress_thread.stop()
        self.progress_thread.wait()
        if task in self.tasks:
            self.tasks.remove(task)

        already_up_to_date = self.tr('Already up-to-date.')

        if not output: # git fetch --tags --verbose doesn't print anything...
            output = already_up_to_date

        self.setEnabled(True)
        self.progress.close()
        QtGui.QApplication.restoreOverrideCursor()

        result = 'returned exit status %d' % status
        message = '"git %s" %s' % (self.action.lower(), result)
        if output:
            message += '\n\n' + output

        # Force the status to 1 so that we always display the log
        qtutils.log(1, message)

        if status == 0:
            self.accept()
            return

        if self.action == PUSH:
            message += '\n\nHave you rebased/pulled lately?'

        qtutils.critical(self.windowTitle(),
                         message=message, details=output)
Example #2
0
 def export_patch(self):
     widget = self.commit_list
     row, selected = qtutils.selected_row(widget)
     if not selected or len(self.results) < row:
         return
     revision = self.results[row][0]
     qtutils.log(*self.model.export_patchset(revision, revision))
Example #3
0
    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.'))
Example #4
0
    def _action_completed(self, task, status, output):
        # Grab the results of the action and finish up
        if task in self._tasks:
            self._tasks.remove(task)

        if not output: # git fetch --tags --verbose doesn't print anything...
            output = self.tr('Already up-to-date.')
        # Force the status to 1 so that we always display the log
        qtutils.log(1, output)

        self.progress.close()
        QtGui.QApplication.restoreOverrideCursor()

        if status != 0 and self.action == 'push':
            message = 'Error pushing to "%s".\n\nPull first?' % self.model.remotename
            qtutils.critical('Push Error',
                             message=message, details=output)
        else:
            title = self.view.windowTitle()
            if status == 0:
                result = 'succeeded'
            else:
                result = 'returned exit status %d' % status

            message = '"git %s" %s' % (self.action, result)
            qtutils.information(title,
                                message=message, details=output)
        self.view.accept()
Example #5
0
    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)
Example #6
0
    def action_completed(self, task, status, output):
        # Grab the results of the action and finish up
        self.progress_thread.stop()
        self.progress_thread.wait()
        if task in self.tasks:
            self.tasks.remove(task)

        already_up_to_date = self.tr('Already up-to-date.')

        if not output:  # git fetch --tags --verbose doesn't print anything...
            output = already_up_to_date

        self.setEnabled(True)
        self.progress.close()
        QtGui.QApplication.restoreOverrideCursor()

        result = 'returned exit status %d' % status
        message = '"git %s" %s' % (self.action.lower(), result)
        if output:
            message += '\n\n' + output

        # Force the status to 1 so that we always display the log
        qtutils.log(1, message)

        if status == 0:
            self.accept()
            return

        if self.action == PUSH:
            message += '\n\nHave you rebased/pulled lately?'

        qtutils.critical(self.windowTitle(), message=message, details=output)
Example #7
0
 def export_patch(self):
     widget = self.view.commit_list
     row, selected = qtutils.selected_row(widget)
     if not selected or len(self.results) < row:
         return
     revision = self.results[row][0]
     qtutils.log(*self.model.export_patchset(revision, revision))
Example #8
0
 def cherry_pick(self):
     widget = self.commit_list
     row, selected = qtutils.selected_row(widget)
     if not selected or len(self.results) < row:
         return
     revision = self.results[row][0]
     qtutils.log(
         *git.cherry_pick(revision, with_stderr=True, with_status=True))
Example #9
0
def rebase():
    """Rebase onto a branch."""
    branch = choose_from_combo('Rebase Branch', cola.model().all_branches())
    if not branch:
        return
    #TODO cmd
    status, output = git.rebase(branch, with_stderr=True, with_status=True)
    qtutils.log(status, output)
Example #10
0
 def cherry_pick(self):
     row, selected = qtutils.selected_row(self.view.commit_list)
     if not selected:
         return
     sha1 = self.model.revision_sha1(row)
     qtutils.log(*self.model.git.cherry_pick(sha1,
                                             with_stderr=True,
                                             with_status=True))
Example #11
0
 def cherry_pick(self):
     widget = self.view.commit_list
     row, selected = qtutils.selected_row(widget)
     if not selected or len(self.results) < row:
         return
     revision = self.results[row][0]
     qtutils.log(*self.model.git.cherry_pick(revision,
                                             with_stderr=True,
                                             with_status=True))
Example #12
0
def rebase():
    """Rebase onto a branch."""
    branch = choose_from_combo('Rebase Branch',
                               cola.model().all_branches())
    if not branch:
        return
    #TODO cmd
    status, output = git.rebase(branch, with_stderr=True, with_status=True)
    qtutils.log(status, output)
Example #13
0
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
    parent = QtGui.QApplication.instance().activeWindow()
    msg = 'Select a parent directory for the new clone'
    dirname = qtutils.opendir_dialog(parent, 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
Example #14
0
        def remote_callback():
            if not self.model.remotename:
                errmsg = self.tr('No repository selected.')
                qtutils.log(1, errmsg)
                return
            remote, kwargs = self.common_args()
            action = self.action

            # Check if we're about to create a new branch and warn.
            if action == 'push' and not self.model.remote_branch:
                branch = self.model.local_branch
                candidate = '%s/%s' % (remote, branch)
                if candidate not in self.model.remote_branches:
                    title = 'Push'
                    msg = 'Branch "%s" does not exist in %s.' % (branch, remote)
                    msg += '\nA new remote branch will be published.'
                    info_txt= 'Create a new remote branch?'
                    ok_text = 'Create Remote Branch'
                    if not qtutils.confirm(title, msg, info_txt, ok_text,
                                           default=False,
                                           icon=qtutils.git_icon()):
                        return

            if not self.model.ffwd_only_checkbox:
                title = 'Force %s?' % action.title()
                ok_text = 'Force %s' % action.title()

                if action == 'fetch':
                    msg = 'Non-fast-forward fetch overwrites local history!'
                    info_txt = 'Force fetching from %s?' % remote
                elif action == 'push':
                    msg = ('Non-fast-forward push overwrites published '
                           'history!\n(Did you pull first?)')
                    info_txt = 'Force push to %s?' % remote
                else: # pull: shouldn't happen since the controls are hidden
                    msg = "You probably don't want to do this.\n\tContinue?"
                    return

                if not qtutils.confirm(title, msg, info_txt, ok_text,
                                       default=False,
                                       icon=qtutils.discard_icon()):
                    return

            # Disable the GUI by default
            self.view.setEnabled(False)
            self.progress.setEnabled(True)
            QtGui.QApplication.setOverrideCursor(Qt.WaitCursor)

            # Show a nice progress bar
            self.progress.setLabelText('Updating...')
            self.progress.show()

            # Use a thread to update in the background
            task = ActionTask(self.view, modelaction, remote, kwargs)
            self._tasks.append(task)
            QtCore.QThreadPool.globalInstance().start(task)
Example #15
0
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
Example #16
0
 def stash_apply(self):
     """Applies the currently selected stash
     """
     selection = self.selected_stash()
     if not selection:
         return
     qtutils.log(*self.model.git.stash('apply', '--index', selection,
                                       with_stderr=True,
                                       with_status=True))
     self.view.accept()
Example #17
0
 def select_commits(self):
     summaries = self.model.summaries
     if not summaries:
         msg = self.tr('No commits exist in this branch.')
         qtutils.log(1, msg)
         return []
     qtutils.set_items(self.commit_list, summaries)
     self.show()
     if self.exec_() != QtGui.QDialog.Accepted:
         return []
     revs = self.model.revisions
     return qtutils.selection_list(self.commit_list, revs)
Example #18
0
 def select_commits(self):
     summaries = self.model.summaries
     if not summaries:
         msg = self.tr('No commits exist in this branch.')
         qtutils.log(1, msg)
         return []
     qtutils.set_items(self.commit_list, summaries)
     self.show()
     if self.exec_() != QtGui.QDialog.Accepted:
         return []
     revs = self.model.revisions
     return qtutils.selection_list(self.commit_list, revs)
Example #19
0
    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)
Example #20
0
    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()

        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)
Example #21
0
def clone_repo(parent, 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(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(parent, msg, cola.model().getcwd())
    if not dirname:
        return None
    count = 1
    destdir = os.path.join(dirname, 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, url, destdir, spawn=spawn)
    return destdir
Example #22
0
    def commit(self):
        """Attempt to create a commit from the index and commit message."""
        msg = unicode(self.commitmsg.toPlainText())
        if not msg:
            # Describe a good commit message
            error_msg = self.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')
            qtutils.log(1, error_msg)
            cola.notifier().broadcast(signals.information,
                                      'Missing Commit Message',
                                      error_msg)
            return

        if not self.model.staged:
            error_msg = self.tr(''
                'No changes to commit.\n\n'
                'You must stage at least 1 file before you can commit.')
            if self.model.modified:
                informative_text = self.tr('Would you like to stage '
                                           'and commit all modified files?')
                if not qtutils.confirm(self, 'Stage and commit?',
                                       error_msg,
                                       informative_text,
                                       ok_text='Stage and Commit'):
                    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_checkbox.isChecked()
        if (amend and self.model.is_commit_published() and
            not qtutils.question(self,
                                 'Rewrite Published Commit?',
                                 'This commit has already been published.\n'
                                 'You are rewriting published history.\n'
                                 'You probably don\'t want to do this.\n\n'
                                 'Continue?',
                                 default=False)):
            return
        # Perform the commit
        cola.notifier().broadcast(signals.commit, amend, msg)
Example #23
0
 def _remove_uncommitted_edits(self):
     if not self.model.undoable():
         return
     items_to_undo = self.modified()
     if items_to_undo:
         if not qtutils.question(
             self,
             "Remove Uncommitted edits?",
             "This operation removes " "uncommitted edits.\n" "There's no going back.  Continue?",
             default=False,
         ):
             return
         cola.notifier().broadcast(signals.checkout, ["HEAD", "--"] + items_to_undo)
     else:
         qtutils.log(1, self.tr("No files selected for " "checkout from HEAD."))
Example #24
0
 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.'))
Example #25
0
 def _revert_uncommitted_edits(self):
     if not self.model.undoable():
         return
     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=False,
             icon=qtutils.icon("undo.svg"),
         ):
             return
         cola.notifier().broadcast(signals.checkout, ["HEAD", "--"] + items_to_undo)
     else:
         qtutils.log(1, self.tr("No files selected for " "checkout from HEAD."))
Example #26
0
 def _remove_uncommitted_edits(self):
     if not self.model.undoable():
         return
     items_to_undo = self.modified()
     if items_to_undo:
         if not qtutils.question(self,
                                 'Remove Uncommitted edits?',
                                 'This operation removes '
                                 'uncommitted edits.\n'
                                 'There\'s no going back.  Continue?',
                                 default=False):
             return
         cola.notifier().broadcast(signals.checkout,
                                   ['HEAD', '--'] + items_to_undo)
     else:
         qtutils.log(1, self.tr('No files selected for '
                                'checkout from HEAD.'))
Example #27
0
        def remote_callback():
            if not self.model.remotename:
                errmsg = self.tr('No repository selected.')
                qtutils.log(1, errmsg)
                return
            remote, kwargs = self.common_args()
            action = self.action

            # Check if we're about to create a new branch and warn.
            if action == 'push' and not self.model.remote_branch:
                branch = self.model.local_branch
                candidate = '%s/%s' % (remote, branch)
                if candidate not in self.model.remote_branches:
                    msg = ('Branch "' + branch + '" does not exist in ' +
                           remote + '.\n\nCreate a new branch?')
                    if not qtutils.question(self.view, 'Create New Branch?',
                                            msg, default=False):
                        return

            if not self.model.ffwd_only_checkbox:
                if action == 'fetch':
                    msg = ('Non-fast-forward fetch overwrites local '
                           'history!\n\tContinue?')
                elif action == 'push':
                    msg = ('Non-fast-forward push overwrites published '
                           'history!\nAre you sure you want to do this?  '
                           '(Did you pull first?)\n\tContinue?')
                else: # pull: shouldn't happen since the controls are hidden
                    msg = "You probably don't want to do this.\n\tContinue?"
                if not qtutils.question(self.view,
                        'Force %s?' % action.title(), msg, default=False):
                    return

            # Disable the GUI by default
            self.view.setEnabled(False)
            self.progress.setEnabled(True)
            QtGui.QApplication.setOverrideCursor(Qt.WaitCursor)

            # Show a nice progress bar
            self.progress.setLabelText('Updating...')
            self.progress.show()

            # Use a thread to update in the background
            task = ActionTask(self.view, modelaction, remote, kwargs)
            self._tasks.append(task)
            QtCore.QThreadPool.globalInstance().start(task)
Example #28
0
 def stash_remove(self):
     """Drops the currently selected stash
     """
     selection = self.selected_stash()
     name = self.selected_name()
     if not selection:
         return
     if not qtutils.confirm(self.view,
                            'Remove Stash?',
                            'Remove "%s"?' % name,
                            'Recovering these changes may not be possible.',
                            'Remove',
                            icon=qtutils.discard_icon()):
         return
     qtutils.log(*self.model.git.stash('drop', selection,
                                       with_stderr=True,
                                       with_status=True))
     self.update_model()
Example #29
0
 def stash_drop(self):
     """Drops the currently selected stash
     """
     selection = self.selected_stash()
     if not selection:
         return
     if not qtutils.question(self.view,
                             'Drop Stash?',
                             'This will permanently remove the '
                             'selected stash.\n'
                             'Recovering these changes may not '
                             'be possible.\n\n'
                             'Continue?'):
         return
     qtutils.log(*self.model.git.stash('drop', selection,
                                       with_stderr=True,
                                       with_status=True))
     self.update_model()
Example #30
0
    def merge_revision(self):
        """Merge the selected revision/branch"""
        revision = self.model.revision
        if not revision:
            qtutils.information('No Revision Specified',
                                'You must specify a revision to merge')
            return

        no_commit = not(self.view.checkbox_commit.isChecked())
        squash = self.view.checkbox_squash.isChecked()
        msg = gitcmds.merge_message()
        qtutils.log(*self.model.git.merge('-m'+msg,
                                         revision,
                                         strategy='recursive',
                                         no_commit=no_commit,
                                         squash=squash,
                                         with_stderr=True,
                                         with_status=True))
        self.view.accept()
Example #31
0
    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.
        """
        if not qtutils.question(self.view,
                                'Stash Changes?',
                                'This will stash your current '
                                'changes away for later use.\n'
                                'Continue?'):
            return

        stash_name, ok = qtutils.prompt('Enter a name for this stash')
        if not ok:
            return
        while stash_name in self.model.stash_list:
            qtutils.information('Oops!',
                                'That name already exists.  '
                                'Please enter another name.')
            stash_name, ok = qtutils.prompt('Enter a name for this stash')
            if not ok:
                return

        if not stash_name:
            return

        # Sanitize the stash name
        stash_name = utils.sanitize(stash_name)
        args = []
        if self.model.keep_index:
            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()
Example #32
0
        def remote_callback():
            if not self.model.remotename:
                errmsg = self.tr('No repository selected.')
                qtutils.log(1, errmsg)
                return
            remote, kwargs = self.common_args()
            action = self.action

            # Check if we're about to create a new branch and warn.
            if action == 'push' and not self.model.remote_branch:
                branch = self.model.local_branch
                candidate = '%s/%s' % (remote, branch)
                if candidate not in self.model.remote_branches:
                    msg = ('Branch "' + branch + '" does not exist in ' +
                           remote + '.\n\nCreate a new branch?')
                    if not qtutils.question(self.view, 'Create New Branch?',
                                            msg, default=False):
                        return

            if not self.model.ffwd_only_checkbox:
                if action == 'fetch':
                    msg = ('Non-fast-forward fetch overwrites local '
                           'history!\n\tContinue?')
                elif action == 'push':
                    msg = ('Non-fast-forward push overwrites published '
                           'history!\nAre you sure you want to do this?  '
                           '(Did you pull first?)\n\tContinue?')
                else: # pull: shouldn't happen since the controls are hidden
                    msg = "You probably don't want to do this.\n\tContinue?"
                if not qtutils.question(self.view,
                        'Force %s?' % action.title(), msg, default=False):
                    return
            status, output = modelaction(remote, **kwargs)
            if not output: # git fetch --tags --verbose doesn't print anything...
                output = self.tr('Already up-to-date.')
            # Force the status to 1 so that we always display the log
            qtutils.log(1, output)
            self.view.accept()
Example #33
0
    def create_branch(self):
        """Creates a branch; called by the "Create Branch" button"""

        revision = self.model.revision
        branch = self.model.local_branch
        existing_branches = self.model.local_branches

        if not branch or not revision:
            qtutils.information('Missing Data',
                                'Please provide both a branch '
                                'name and revision expression.')
            return

        check_branch = False
        if branch in existing_branches:
            if self.view.no_update_radio.isChecked():
                msg = self.tr("Branch '%s' already exists.")
                msg = unicode(msg) % branch
                qtutils.information('warning', msg)
                return
            # Whether we should prompt the user for lost commits
            commits = gitcmds.rev_list_range(revision, branch)
            check_branch = bool(commits)

        if check_branch:
            msg = self.tr("Resetting '%s' to '%s' will "
                          "lose the following commits:")
            lines = [ unicode(msg) % (branch, revision) ]

            for idx, commit in enumerate(commits):
                subject = commit[1][0:min(len(commit[1]),16)]
                if len(subject) < len(commit[1]):
                    subject += '...'
                lines.append('\t' + commit[0][:8]
                        +'\t' + subject)
                if idx >= 5:
                    skip = len(commits) - 5
                    lines.append('\t(%d skipped)' % skip)
                    break

            lines.extend([
                unicode(self.tr('Recovering lost commits may not be easy.')),
                unicode(self.tr("Reset '%s'?")) % branch
                ])

            result = qtutils.question(self.view, 'warning', '\n'.join(lines))
            if not result:
                return

        # TODO handle fetch
        track = self.view.remote_radio.isChecked()
        fetch = self.view.fetch_checkbox.isChecked()
        ffwd = self.view.ffwd_only_radio.isChecked()
        reset = self.view.reset_radio.isChecked()
        chkout = self.view.checkout_checkbox.isChecked()

        status, output = self.model.create_branch(branch, revision, track=track)
        qtutils.log(status, output)
        if status == 0 and chkout:
            status, output = self.model.git.checkout(branch,
                                                     with_status=True,
                                                     with_stderr=True)
            qtutils.log(status, output)
        self.view.accept()
Example #34
0
 def _init_log_window(self):
     """Initialize the logging subwindow."""
     branch = self.model.currentbranch
     qtutils.log(0, self.model.git_version +
                 '\ncola version ' + version.version())
Example #35
0
 def thread_command(self, status, output):
     qtutils.log(status, output)
Example #36
0
 def thread_command(self, status, output):
     qtutils.log(status, output)
Example #37
0
    def __init__(self, model, parent):
        MainWindow.__init__(self, parent)
        # Default size; this is thrown out when save/restore is used
        self.resize(987, 610)
        self.model = model
        self.prefs_model = prefs_model = PreferencesModel()

        # Internal field used by import/export_state().
        # Change this whenever dockwidgets are removed.
        self.widget_version = 1

        # Keeps track of merge messages we've seen
        self.merge_message_hash = ''

        self.setAcceptDrops(True)
        self.setAttribute(Qt.WA_MacMetalStyle)

        # Dockwidget options
        qtcompat.set_common_dock_options(self)

        cfg = gitcfg.instance()
        self.classic_dockable = (cfg.get('cola.browserdockable') or
                                 cfg.get('cola.classicdockable'))
        if self.classic_dockable:
            self.classicdockwidget = create_dock('Browser', self)
            self.classicwidget = classic_widget(self)
            self.classicdockwidget.setWidget(self.classicwidget)

        # "Actions" widget
        self.actionsdockwidget = create_dock('Action', self)
        self.actionsdockwidgetcontents = qt.QFlowLayoutWidget(self)
        layout = self.actionsdockwidgetcontents.layout()
        self.stage_button = create_button(text='Stage', layout=layout)
        self.unstage_button = create_button(text='Unstage', layout=layout)
        self.rescan_button = create_button(text='Rescan', layout=layout)
        self.fetch_button = create_button(text='Fetch...', layout=layout)
        self.push_button = create_button(text='Push...', layout=layout)
        self.pull_button = create_button(text='Pull...', layout=layout)
        self.stash_button = create_button(text='Stash...', layout=layout)
        layout.addStretch()
        self.actionsdockwidget.setWidget(self.actionsdockwidgetcontents)

        # "Repository Status" widget
        self.statusdockwidget = create_dock('Status', self)
        self.statusdockwidget.setWidget(StatusWidget(self))

        # "Commit Message Editor" widget
        self.position_label = QtGui.QLabel()
        font = qtutils.default_monospace_font()
        font.setPointSize(int(font.pointSize() * 0.8))
        self.position_label.setFont(font)
        self.commitdockwidget = create_dock('Commit', self)
        titlebar = self.commitdockwidget.titleBarWidget()
        titlebar.add_corner_widget(self.position_label)

        self.commitmsgeditor = CommitMessageEditor(model, self)
        relay_signal(self, self.commitmsgeditor, SIGNAL(signals.amend_mode))
        relay_signal(self, self.commitmsgeditor, SIGNAL(signals.signoff))
        relay_signal(self, self.commitmsgeditor,
                     SIGNAL(signals.load_previous_message))
        self.commitdockwidget.setWidget(self.commitmsgeditor)

        # "Console" widget
        self.logwidget = LogWidget()
        self.logdockwidget = create_dock('Console', self)
        self.logdockwidget.setWidget(self.logwidget)
        cola.notifier().connect(signals.log_cmd, self.logwidget.log)

        # "Diff Viewer" widget
        self.diffdockwidget = create_dock('Diff', self)
        self.diffeditor = DiffEditor(self.diffdockwidget)
        self.diffdockwidget.setWidget(self.diffeditor)

        # All Actions
        self.menu_unstage_all = add_action(self,
                'Unstage All', emit(self, signals.unstage_all))
        self.menu_unstage_all.setIcon(qtutils.icon('remove.svg'))

        self.menu_unstage_selected = add_action(self,
                'Unstage From Commit', emit(self, signals.unstage_selected))
        self.menu_unstage_selected.setIcon(qtutils.icon('remove.svg'))

        self.menu_show_diffstat = add_action(self,
                'Diffstat', emit(self, signals.diffstat), 'Alt+D')

        self.menu_stage_modified = add_action(self,
                'Stage Changed Files To Commit',
                emit(self, signals.stage_modified), 'Alt+A')
        self.menu_stage_modified.setIcon(qtutils.icon('add.svg'))

        self.menu_stage_untracked = add_action(self,
                'Stage All Untracked', emit(self, signals.stage_untracked), 'Alt+U')
        self.menu_stage_untracked.setIcon(qtutils.icon('add.svg'))

        self.menu_export_patches = add_action(self,
                'Export Patches...', guicmds.export_patches, 'Alt+E')
        self.menu_preferences = add_action(self,
                'Preferences', lambda: preferences(model=prefs_model),
                QtGui.QKeySequence.Preferences, 'Ctrl+O')

        self.menu_edit_remotes = add_action(self,
                'Edit Remotes...', lambda: editremotes.edit().exec_())
        self.menu_rescan = add_action(self,
                'Rescan', emit(self, signals.rescan_and_refresh), 'Ctrl+R')
        self.menu_rescan.setIcon(qtutils.reload_icon())

        self.menu_browse_recent = add_action(self,
                'Recently Modified Files...', browse_recent, 'Shift+Ctrl+E')

        self.menu_cherry_pick = add_action(self,
                'Cherry-Pick...', guicmds.cherry_pick, 'Ctrl+P')

        self.menu_load_commitmsg = add_action(self,
                'Load Commit Message...', guicmds.load_commitmsg)

        self.menu_save_tarball = add_action(self,
                'Save As Tarball/Zip...', self.save_archive)

        self.menu_quit = add_action(self,
                'Quit', self.close, 'Ctrl+Q')
        self.menu_manage_bookmarks = add_action(self,
                'Bookmarks...', manage_bookmarks)
        self.menu_grep = add_action(self,
                'Grep', guicmds.grep)
        self.menu_merge_local = add_action(self,
                'Merge...', merge.local_merge)

        self.menu_merge_abort = add_action(self,
                'Abort Merge...', merge.abort_merge)

        self.menu_fetch = add_action(self,
                'Fetch...', remote.fetch)
        self.menu_push = add_action(self,
                'Push...', remote.push)
        self.menu_pull = add_action(self,
                'Pull...', remote.pull)

        self.menu_open_repo = add_action(self,
                'Open...', guicmds.open_repo)
        self.menu_open_repo.setIcon(qtutils.open_icon())

        self.menu_stash = add_action(self,
                'Stash...', stash.stash, 'Alt+Shift+S')

        self.menu_clone_repo = add_action(self,
                'Clone...', guicmds.clone_repo)
        self.menu_clone_repo.setIcon(qtutils.git_icon())

        self.menu_help_docs = add_action(self,
                'Documentation', resources.show_html_docs,
                QtGui.QKeySequence.HelpContents)

        self.menu_help_shortcuts = add_action(self,
                'Keyboard Shortcuts',
                show_shortcuts,
                QtCore.Qt.Key_Question)

        self.menu_visualize_current = add_action(self,
                'Visualize Current Branch...',
                emit(self, signals.visualize_current))
        self.menu_visualize_all = add_action(self,
                'Visualize All Branches...',
                emit(self, signals.visualize_all))
        self.menu_search_commits = add_action(self,
                'Search...', search)
        self.menu_browse_branch = add_action(self,
                'Browse Current Branch...', guicmds.browse_current)
        self.menu_browse_other_branch = add_action(self,
                'Browse Other Branch...', guicmds.browse_other)
        self.menu_load_commitmsg_template = add_action(self,
                'Get Commit Message Template',
                emit(self, signals.load_commit_template))
        self.menu_help_about = add_action(self,
                'About', launch_about_dialog)

        self.menu_branch_diff = add_action(self,
                'SHA-1...', guicmds.diff_revision)
        self.menu_diff_expression = add_action(self,
                'Expression...', guicmds.diff_expression)
        self.menu_branch_compare = add_action(self,
                'Branches...', compare_branches)

        self.menu_create_tag = add_action(self,
                'Create Tag...', create_tag)

        self.menu_create_branch = add_action(self,
                'Create...', create_new_branch, 'Ctrl+B')

        self.menu_delete_branch = add_action(self,
                'Delete...', guicmds.branch_delete)

        self.menu_checkout_branch = add_action(self,
                'Checkout...', guicmds.checkout_branch, 'Alt+B')
        self.menu_rebase_branch = add_action(self,
                'Rebase...', guicmds.rebase)
        self.menu_branch_review = add_action(self,
                'Review...', guicmds.review_branch)

        self.menu_classic = add_action(self,
                'Browser...', cola_classic)
        self.menu_classic.setIcon(qtutils.git_icon())

        self.menu_dag = add_action(self,
                'DAG...', lambda: git_dag(self.model))
        self.menu_dag.setIcon(qtutils.git_icon())

        # Relayed actions
        if not self.classic_dockable:
            # These shortcuts conflict with those from the
            # 'Browser' widget so don't register them when
            # the browser is a dockable tool.
            status_tree = self.statusdockwidget.widget().tree
            self.addAction(status_tree.up)
            self.addAction(status_tree.down)
            self.addAction(status_tree.process_selection)
            self.addAction(status_tree.launch_difftool)

        # Create the application menu
        self.menubar = QtGui.QMenuBar(self)

        # File Menu
        self.file_menu = create_menu('&File', self.menubar)
        self.file_menu.addAction(self.menu_preferences)
        self.file_menu.addSeparator()
        self.file_menu.addAction(self.menu_open_repo)
        self.menu_open_recent = self.file_menu.addMenu(tr('Open Recent'))
        self.file_menu.addSeparator()
        self.file_menu.addAction(self.menu_clone_repo)
        self.file_menu.addAction(self.menu_manage_bookmarks)
        self.file_menu.addSeparator()
        self.file_menu.addAction(self.menu_edit_remotes)
        self.file_menu.addAction(self.menu_rescan)
        self.file_menu.addSeparator()
        self.file_menu.addAction(self.menu_browse_recent)
        self.file_menu.addSeparator()
        self.file_menu.addAction(self.menu_load_commitmsg)
        self.file_menu.addAction(self.menu_load_commitmsg_template)
        self.file_menu.addSeparator()
        self.file_menu.addAction(self.menu_save_tarball)
        self.file_menu.addAction(self.menu_quit)
        # Add to menubar
        self.menubar.addAction(self.file_menu.menuAction())

        # Commit Menu
        self.commit_menu = create_menu('Co&mmit', self.menubar)
        self.commit_menu.setTitle(tr('Commit@@verb'))
        self.commit_menu.addAction(self.menu_stage_modified)
        self.commit_menu.addAction(self.menu_stage_untracked)
        self.commit_menu.addSeparator()
        self.commit_menu.addAction(self.menu_unstage_all)
        self.commit_menu.addAction(self.menu_unstage_selected)
        self.commit_menu.addSeparator()
        self.commit_menu.addAction(self.menu_search_commits)
        # Add to menubar
        self.menubar.addAction(self.commit_menu.menuAction())

        # Branch Menu
        self.branch_menu = create_menu('B&ranch', self.menubar)
        self.branch_menu.addAction(self.menu_branch_review)
        self.branch_menu.addSeparator()
        self.branch_menu.addAction(self.menu_create_branch)
        self.branch_menu.addAction(self.menu_checkout_branch)
        self.branch_menu.addAction(self.menu_rebase_branch)
        self.branch_menu.addAction(self.menu_delete_branch)
        self.branch_menu.addSeparator()
        self.branch_menu.addAction(self.menu_browse_branch)
        self.branch_menu.addAction(self.menu_browse_other_branch)
        self.branch_menu.addSeparator()
        self.branch_menu.addAction(self.menu_visualize_current)
        self.branch_menu.addAction(self.menu_visualize_all)
        # Add to menubar
        self.menubar.addAction(self.branch_menu.menuAction())

        # Actions menu
        self.actions_menu = create_menu('Act&ions', self.menubar)
        self.actions_menu.addAction(self.menu_merge_local)
        self.actions_menu.addAction(self.menu_stash)
        self.actions_menu.addSeparator()
        self.actions_menu.addAction(self.menu_fetch)
        self.actions_menu.addAction(self.menu_push)
        self.actions_menu.addAction(self.menu_pull)
        self.actions_menu.addSeparator()
        self.actions_menu.addAction(self.menu_create_tag)
        self.actions_menu.addSeparator()
        self.actions_menu.addAction(self.menu_export_patches)
        self.actions_menu.addAction(self.menu_cherry_pick)
        self.actions_menu.addSeparator()
        self.actions_menu.addAction(self.menu_merge_abort)
        self.actions_menu.addAction(self.menu_grep)
        # Add to menubar
        self.menubar.addAction(self.actions_menu.menuAction())

        # Diff Menu
        self.diff_menu = create_menu('&Diff', self.menubar)
        self.diff_menu.addAction(self.menu_branch_diff)
        self.diff_menu.addAction(self.menu_diff_expression)
        self.diff_menu.addAction(self.menu_branch_compare)
        self.diff_menu.addSeparator()
        self.diff_menu.addAction(self.menu_show_diffstat)
        # Add to menubar
        self.menubar.addAction(self.diff_menu.menuAction())

        # Tools Menu
        self.tools_menu = create_menu('&Tools', self.menubar)
        self.tools_menu.addAction(self.menu_classic)
        self.tools_menu.addAction(self.menu_dag)
        self.tools_menu.addSeparator()
        if self.classic_dockable:
            self.tools_menu.addAction(self.classicdockwidget.toggleViewAction())

        self.setup_dockwidget_tools_menu()
        self.menubar.addAction(self.tools_menu.menuAction())

        # Help Menu
        self.help_menu = create_menu('&Help', self.menubar)
        self.help_menu.addAction(self.menu_help_docs)
        self.help_menu.addAction(self.menu_help_shortcuts)
        self.help_menu.addAction(self.menu_help_about)
        # Add to menubar
        self.menubar.addAction(self.help_menu.menuAction())

        # Set main menu
        self.setMenuBar(self.menubar)

        # Arrange dock widgets
        top = Qt.TopDockWidgetArea
        bottom = Qt.BottomDockWidgetArea

        self.addDockWidget(top, self.commitdockwidget)
        if self.classic_dockable:
            self.addDockWidget(top, self.classicdockwidget)
        self.addDockWidget(top, self.statusdockwidget)
        self.addDockWidget(top, self.actionsdockwidget)
        self.addDockWidget(bottom, self.logdockwidget)
        if self.classic_dockable:
            self.tabifyDockWidget(self.classicdockwidget, self.commitdockwidget)
        self.tabifyDockWidget(self.logdockwidget, self.diffdockwidget)

        # Listen for model notifications
        model.add_observer(model.message_updated, self._update_view)

        prefs_model.add_observer(prefs_model.message_config_updated,
                                 self._config_updated)

        # Set a default value
        self.show_cursor_position(1, 0)

        # Add button callbacks
        connect_button(self.rescan_button,
                       emit(self, signals.rescan_and_refresh))
        connect_button(self.fetch_button, remote.fetch)
        connect_button(self.push_button, remote.push)
        connect_button(self.pull_button, remote.pull)
        connect_button(self.stash_button, stash.stash)

        connect_button(self.stage_button, self.stage)
        connect_button(self.unstage_button, self.unstage)

        self.connect(self.menu_open_recent, SIGNAL('aboutToShow()'),
                     self.build_recent_menu)

        self.connect(self.commitmsgeditor, SIGNAL('cursorPosition(int,int)'),
                     self.show_cursor_position)
        self.connect(self, SIGNAL('update'), self._update_callback)
        self.connect(self, SIGNAL('install_config_actions'),
                     self._install_config_actions)

        # Install .git-config-defined actions
        self._config_task = None
        self.install_config_actions()

        # Restore saved settings
        qtutils.apply_state(self)

        self.statusdockwidget.widget().setFocus()

        log(0, version.git_version_str() + '\ncola version ' + version.version())
Example #38
0
    def action_callback(self):
        action = self.action
        if action == FETCH:
            model_action = self.model.fetch
        elif action == PUSH:
            model_action = self.model.push
        else:  # if action == PULL:
            model_action = self.model.pull

        remote_name = unicode(self.remote_name.text())
        if not remote_name:
            errmsg = self.tr('No repository selected.')
            qtutils.log(1, errmsg)
            return
        remote, kwargs = self.common_args()

        # Check if we're about to create a new branch and warn.
        remote_branch = unicode(self.remote_branch.text())
        local_branch = unicode(self.local_branch.text())

        if action == PUSH and not remote_branch:
            branch = local_branch
            candidate = '%s/%s' % (remote, branch)
            if candidate not in self.model.remote_branches:
                title = self.tr(PUSH)
                msg = 'Branch "%s" does not exist in %s.' % (branch, remote)
                msg += '\nA new remote branch will be published.'
                info_txt = 'Create a new remote branch?'
                ok_text = 'Create Remote Branch'
                if not qtutils.confirm(title,
                                       msg,
                                       info_txt,
                                       ok_text,
                                       default=False,
                                       icon=qtutils.git_icon()):
                    return

        if not self.ffwd_only_checkbox.isChecked():
            title = 'Force %s?' % action.title()
            ok_text = 'Force %s' % action.title()

            if action == FETCH:
                msg = 'Non-fast-forward fetch overwrites local history!'
                info_txt = 'Force fetching from %s?' % remote
            elif action == PUSH:
                msg = ('Non-fast-forward push overwrites published '
                       'history!\n(Did you pull first?)')
                info_txt = 'Force push to %s?' % remote
            else:  # pull: shouldn't happen since the controls are hidden
                msg = "You probably don't want to do this.\n\tContinue?"
                return

            if not qtutils.confirm(title,
                                   msg,
                                   info_txt,
                                   ok_text,
                                   default=False,
                                   icon=qtutils.discard_icon()):
                return

        # Disable the GUI by default
        self.setEnabled(False)
        self.progress.setEnabled(True)
        QtGui.QApplication.setOverrideCursor(Qt.WaitCursor)

        # Show a nice progress bar
        self.progress_thread.start()
        self.progress.show()

        # Use a thread to update in the background
        task = ActionTask(self, model_action, remote, kwargs)
        self.tasks.append(task)
        QtCore.QThreadPool.globalInstance().start(task)
Example #39
0
    def __init__(self, model, parent):
        standard.MainWindow.__init__(self, parent)
        # Default size; this is thrown out when save/restore is used
        self.resize(987, 610)
        self.model = model
        self.prefs_model = prefs_model = PreferencesModel()

        # Internal field used by import/export_state().
        # Change this whenever dockwidgets are removed.
        self.widget_version = 1

        # Keeps track of merge messages we've seen
        self.merge_message_hash = ''

        self.setAcceptDrops(True)

        # Dockwidget options
        qtcompat.set_common_dock_options(self)

        self.classic_dockable = gitcfg.instance().get('cola.classicdockable')

        if self.classic_dockable:
            self.classicdockwidget = create_dock('Cola Classic', self)
            self.classicwidget = classic_widget(self)
            self.classicdockwidget.setWidget(self.classicwidget)

        # "Actions" widget
        self.actiondockwidget = create_dock('Actions', self)
        self.actiondockwidgetcontents = qt.QFlowLayoutWidget(self)
        layout = self.actiondockwidgetcontents.layout()
        self.stage_button = create_button('Stage', layout)
        self.unstage_button = create_button('Unstage', layout)
        self.rescan_button = create_button('Rescan', layout)
        self.fetch_button = create_button('Fetch...', layout)
        self.push_button = create_button('Push...', layout)
        self.pull_button = create_button('Pull...', layout)
        self.stash_button = create_button('Stash...', layout)
        self.alt_button = create_button('Exit Diff Mode', layout)
        self.alt_button.hide()
        layout.addStretch()
        self.actiondockwidget.setWidget(self.actiondockwidgetcontents)

        # "Repository Status" widget
        self.statusdockwidget = create_dock('Repository Status', self)
        self.statusdockwidget.setWidget(StatusWidget(self))

        # "Commit Message Editor" widget
        self.commitdockwidget = create_dock('Commit Message Editor', self)
        self.commitmsgeditor = CommitMessageEditor(model, self)
        relay_signal(self, self.commitmsgeditor, SIGNAL(signals.amend_mode))
        relay_signal(self, self.commitmsgeditor, SIGNAL(signals.signoff))
        relay_signal(self, self.commitmsgeditor,
                     SIGNAL(signals.load_previous_message))
        self.commitdockwidget.setWidget(self.commitmsgeditor)

        # "Command Output" widget
        logwidget = qtutils.logger()
        logwidget.setFont(diff_font())
        self.logdockwidget = create_dock('Command Output', self)
        self.logdockwidget.setWidget(logwidget)

        # "Diff Viewer" widget
        self.diffdockwidget = create_dock('Diff Viewer', self)
        self.diff_viewer = DiffTextEdit(self.diffdockwidget)
        self.diffdockwidget.setWidget(self.diff_viewer)

        # All Actions
        self.menu_unstage_all = add_action(self,
                'Unstage All', emit(self, signals.unstage_all))
        self.menu_unstage_all.setIcon(qtutils.icon('remove.svg'))

        self.menu_unstage_selected = add_action(self,
                'Unstage From Commit', emit(self, signals.unstage_selected))
        self.menu_unstage_selected.setIcon(qtutils.icon('remove.svg'))

        self.menu_show_diffstat = add_action(self,
                'Diffstat', emit(self, signals.diffstat), 'Ctrl+D')

        self.menu_stage_modified = add_action(self,
                'Stage Changed Files To Commit',
                emit(self, signals.stage_modified), 'Alt+A')
        self.menu_stage_modified.setIcon(qtutils.icon('add.svg'))

        self.menu_stage_untracked = add_action(self,
                'Stage All Untracked', emit(self, signals.stage_untracked), 'Alt+U')
        self.menu_stage_untracked.setIcon(qtutils.icon('add.svg'))

        self.menu_export_patches = add_action(self,
                'Export Patches...', guicmds.export_patches, 'Ctrl+E')
        self.menu_preferences = add_action(self,
                'Preferences', lambda: preferences(model=prefs_model),
                QtGui.QKeySequence.Preferences, 'Ctrl+O')

        self.menu_rescan = add_action(self,
                'Rescan', emit(self, signals.rescan), 'Ctrl+R')
        self.menu_rescan.setIcon(qtutils.reload_icon())

        self.menu_cherry_pick = add_action(self,
                'Cherry-Pick...', guicmds.cherry_pick, 'Ctrl+P')

        self.menu_load_commitmsg = add_action(self,
                'Load Commit Message...', guicmds.load_commitmsg)

        self.menu_quit = add_action(self,
                'Quit', self.close, 'Ctrl+Q')
        self.menu_manage_bookmarks = add_action(self,
                'Bookmarks...', manage_bookmarks)
        self.menu_grep = add_action(self,
                'Grep', guicmds.grep)
        self.menu_merge_local = add_action(self,
                'Merge...', merge.local_merge)

        self.menu_merge_abort = add_action(self,
                'Abort Merge...', merge.abort_merge)

        self.menu_fetch = add_action(self,
                'Fetch...', guicmds.fetch)
        self.menu_push = add_action(self,
                'Push...', guicmds.push)
        self.menu_pull = add_action(self,
                'Pull...', guicmds.pull)

        self.menu_open_repo = add_action(self,
                'Open...', guicmds.open_repo)
        self.menu_open_repo.setIcon(qtutils.open_icon())

        self.menu_stash = add_action(self,
                'Stash...', stash.stash, 'Alt+Shift+S')
        self.menu_diff_branch = add_action(self,
                'Apply Changes From Branch...', guicmds.diff_branch)
        self.menu_branch_compare = add_action(self,
                'Branches...', compare.branch_compare)

        self.menu_clone_repo = add_action(self,
                'Clone...', guicmds.clone_repo)
        self.menu_clone_repo.setIcon(qtutils.git_icon())

        self.menu_help_docs = add_action(self,
                'Documentation',
                lambda: self.model.git.web__browse(resources.html_docs()),
                QtGui.QKeySequence.HelpContents)
        self.menu_commit_compare = add_action(self,
                'Commits...', compare.compare)
        self.menu_commit_compare_file = add_action(self,
                'Commits Touching File...', compare.compare_file)
        self.menu_visualize_current = add_action(self,
                'Visualize Current Branch...',
                emit(self, signals.visualize_current))
        self.menu_visualize_all = add_action(self,
                'Visualize All Branches...',
                emit(self, signals.visualize_all))
        self.menu_search_commits = add_action(self,
                'Search...', search.search)
        self.menu_browse_branch = add_action(self,
                'Browse Current Branch...', guicmds.browse_current)
        self.menu_browse_other_branch = add_action(self,
                'Browse Other Branch...', guicmds.browse_other)
        self.menu_load_commitmsg_template = add_action(self,
                'Get Commit Message Template',
                emit(self, signals.load_commit_template))
        self.menu_help_about = add_action(self,
                'About', launch_about_dialog)
        self.menu_branch_diff = add_action(self,
                'SHA-1...', guicmds.branch_diff)
        self.menu_diff_expression = add_action(self,
                'Expression...', guicmds.diff_expression)
        self.menu_create_tag = add_action(self,
                'Create Tag...', createtag.create_tag)

        self.menu_create_branch = add_action(self,
                'Create...', create_new_branch, 'Ctrl+B')

        self.menu_delete_branch = add_action(self,
                'Delete...', guicmds.branch_delete)

        self.menu_checkout_branch = add_action(self,
                'Checkout...', guicmds.checkout_branch, 'Alt+B')
        self.menu_rebase_branch = add_action(self,
                'Rebase...', guicmds.rebase)
        self.menu_branch_review = add_action(self,
                'Review...', guicmds.review_branch)

        self.menu_classic = add_action(self,
                'Cola Classic...', cola_classic)
        self.menu_classic.setIcon(qtutils.git_icon())

        self.menu_dag = add_action(self,
                'DAG...', lambda: git_dag(self.model))
        self.menu_dag.setIcon(qtutils.git_icon())

        # Create the application menu
        self.menubar = QtGui.QMenuBar(self)

        # File Menu
        self.file_menu = create_menu('&File', self.menubar)
        self.file_menu.addAction(self.menu_preferences)
        self.file_menu.addSeparator()
        self.file_menu.addAction(self.menu_open_repo)
        self.file_menu.addAction(self.menu_clone_repo)
        self.file_menu.addAction(self.menu_manage_bookmarks)
        self.file_menu.addSeparator()
        self.file_menu.addAction(self.menu_rescan)
        self.file_menu.addSeparator()
        self.file_menu.addSeparator()
        self.file_menu.addAction(self.menu_load_commitmsg)
        self.file_menu.addAction(self.menu_load_commitmsg_template)
        self.file_menu.addSeparator()
        self.file_menu.addAction(self.menu_quit)
        # Add to menubar
        self.menubar.addAction(self.file_menu.menuAction())

        # Commit Menu
        self.commit_menu = create_menu('Co&mmit', self.menubar)
        self.commit_menu.setTitle(tr('Commit@@verb'))
        self.commit_menu.addAction(self.menu_stage_modified)
        self.commit_menu.addAction(self.menu_stage_untracked)
        self.commit_menu.addSeparator()
        self.commit_menu.addAction(self.menu_unstage_all)
        self.commit_menu.addAction(self.menu_unstage_selected)
        self.commit_menu.addSeparator()
        self.commit_menu.addAction(self.menu_search_commits)
        # Add to menubar
        self.menubar.addAction(self.commit_menu.menuAction())

        # Branch Menu
        self.branch_menu = create_menu('B&ranch', self.menubar)
        self.branch_menu.addAction(self.menu_branch_review)
        self.branch_menu.addSeparator()
        self.branch_menu.addAction(self.menu_create_branch)
        self.branch_menu.addAction(self.menu_checkout_branch)
        self.branch_menu.addAction(self.menu_rebase_branch)
        self.branch_menu.addAction(self.menu_delete_branch)
        self.branch_menu.addSeparator()
        self.branch_menu.addAction(self.menu_browse_branch)
        self.branch_menu.addAction(self.menu_browse_other_branch)
        self.branch_menu.addSeparator()
        self.branch_menu.addAction(self.menu_visualize_current)
        self.branch_menu.addAction(self.menu_visualize_all)
        self.branch_menu.addSeparator()
        self.branch_menu.addAction(self.menu_diff_branch)
        # Add to menubar
        self.menubar.addAction(self.branch_menu.menuAction())

        # Actions menu
        self.actions_menu = create_menu('Act&ions', self.menubar)
        self.actions_menu.addAction(self.menu_merge_local)
        self.actions_menu.addAction(self.menu_stash)
        self.actions_menu.addSeparator()
        self.actions_menu.addAction(self.menu_fetch)
        self.actions_menu.addAction(self.menu_push)
        self.actions_menu.addAction(self.menu_pull)
        self.actions_menu.addSeparator()
        self.actions_menu.addAction(self.menu_create_tag)
        self.actions_menu.addSeparator()
        self.actions_menu.addAction(self.menu_export_patches)
        self.actions_menu.addAction(self.menu_cherry_pick)
        self.actions_menu.addSeparator()
        self.actions_menu.addAction(self.menu_merge_abort)
        self.actions_menu.addAction(self.menu_grep)
        # Add to menubar
        self.menubar.addAction(self.actions_menu.menuAction())

        # Diff Menu
        self.diff_menu = create_menu('&Diff', self.menubar)
        self.diff_menu.addAction(self.menu_branch_diff)
        self.diff_menu.addAction(self.menu_diff_expression)
        self.diff_menu.addSeparator()
        self.diff_menu.addAction(self.menu_branch_compare)
        self.diff_menu.addAction(self.menu_commit_compare)
        self.diff_menu.addAction(self.menu_commit_compare_file)
        self.diff_menu.addSeparator()
        self.diff_menu.addAction(self.menu_show_diffstat)
        # Add to menubar
        self.menubar.addAction(self.diff_menu.menuAction())

        # Tools Menu
        self.tools_menu = create_menu('&Tools', self.menubar)
        self.tools_menu.addAction(self.menu_classic)
        self.tools_menu.addAction(self.menu_dag)
        self.tools_menu.addSeparator()
        if self.classic_dockable:
            self.tools_menu.addAction(self.classicdockwidget.toggleViewAction())
        self.tools_menu.addAction(self.diffdockwidget.toggleViewAction())
        self.tools_menu.addAction(self.actiondockwidget.toggleViewAction())
        self.tools_menu.addAction(self.commitdockwidget.toggleViewAction())
        self.tools_menu.addAction(self.statusdockwidget.toggleViewAction())
        self.tools_menu.addAction(self.logdockwidget.toggleViewAction())
        self.menubar.addAction(self.tools_menu.menuAction())

        # Help Menu
        self.help_menu = create_menu('&Help', self.menubar)
        self.help_menu.addAction(self.menu_help_docs)
        self.help_menu.addAction(self.menu_help_about)
        # Add to menubar
        self.menubar.addAction(self.help_menu.menuAction())

        # Set main menu
        self.setMenuBar(self.menubar)

        # Arrange dock widgets
        top = Qt.TopDockWidgetArea
        bottom = Qt.BottomDockWidgetArea

        self.addDockWidget(top, self.commitdockwidget)
        if self.classic_dockable:
            self.addDockWidget(top, self.classicdockwidget)
        self.addDockWidget(top, self.statusdockwidget)
        self.addDockWidget(top, self.actiondockwidget)
        self.addDockWidget(bottom, self.logdockwidget)
        if self.classic_dockable:
            self.tabifyDockWidget(self.classicdockwidget, self.commitdockwidget)
        self.tabifyDockWidget(self.logdockwidget, self.diffdockwidget)

        # Listen for model notifications
        model.add_message_observer(model.message_mode_changed,
                                   self._mode_changed)

        model.add_message_observer(model.message_updated,
                                   self._update_view)

        prefs_model.add_message_observer(prefs_model.message_config_updated,
                                         self._config_updated)


        # Add button callbacks
        connect_button(self.rescan_button, emit(self, signals.rescan))
        connect_button(self.alt_button, emit(self, signals.reset_mode))
        connect_button(self.fetch_button, guicmds.fetch)
        connect_button(self.push_button, guicmds.push)
        connect_button(self.pull_button, guicmds.pull)
        connect_button(self.stash_button, stash.stash)

        connect_button(self.stage_button, self.stage)
        connect_button(self.unstage_button, self.unstage)

        self.connect(self, SIGNAL('update'), self._update_callback)
        self.connect(self, SIGNAL('apply_state'), self.apply_state)
        self.connect(self, SIGNAL('install_config_actions'),
                     self._install_config_actions)

        # Install .git-config-defined actions
        self._config_task = None
        self.install_config_actions()

        # Restore saved settings
        self._gui_state_task = None
        self._load_gui_state()

        log(0, self.model.git_version + '\ncola version ' + version.version())