From 352cf143535605d7e5488b33e395ce82c8c413fb Mon Sep 17 00:00:00 2001 From: Friedemann Kleint <Friedemann.Kleint@nokia.com> Date: Wed, 7 Jan 2009 12:58:31 +0100 Subject: [PATCH] Fixes: Add way to create a new local branch in the git plugin Task: 205821 Details: Split up the branch model into remote branch base class and extended local branch class with <New Branch> row. --- src/plugins/git/branchdialog.cpp | 17 ++- src/plugins/git/branchdialog.h | 8 +- src/plugins/git/branchmodel.cpp | 192 +++++++++++++++++++++++++------ src/plugins/git/branchmodel.h | 79 ++++++++++--- 4 files changed, 237 insertions(+), 59 deletions(-) diff --git a/src/plugins/git/branchdialog.cpp b/src/plugins/git/branchdialog.cpp index 8d06c871f96..3eeee2347b6 100644 --- a/src/plugins/git/branchdialog.cpp +++ b/src/plugins/git/branchdialog.cpp @@ -58,8 +58,9 @@ bool BranchDialog::init(GitClient *client, const QString &workingDirectory, QStr } m_ui->repositoryFieldLabel->setText(m_repoDirectory); - m_localModel = new BranchModel(client, BranchModel::LocalBranches, this); - m_remoteModel = new BranchModel(client, BranchModel::RemoteBranches, this); + m_localModel = new LocalBranchModel(client, this); + connect(m_localModel, SIGNAL(newBranchCreated(QString)), this, SLOT(slotNewLocalBranchCreated(QString))); + m_remoteModel = new RemoteBranchModel(client, this); if (!m_localModel->refresh(workingDirectory, errorMessage) || !m_remoteModel->refresh(workingDirectory, errorMessage)) return false; @@ -93,13 +94,23 @@ void BranchDialog::slotEnableButtons() const int selectedLocalRow = selectedLocalBranchIndex(); const int currentLocalBranch = m_localModel->currentBranch(); - const bool hasSelection = selectedLocalRow != -1; + const bool hasSelection = selectedLocalRow != -1 && !m_localModel->isNewBranchRow(selectedLocalRow); const bool currentIsNotSelected = hasSelection && selectedLocalRow != currentLocalBranch; m_checkoutButton->setEnabled(currentIsNotSelected); m_deleteButton->setEnabled(currentIsNotSelected); } +void BranchDialog::slotNewLocalBranchCreated(const QString &b) +{ + // Select the newly created branch + const int row = m_localModel->findBranchByName(b); + if (row != -1) { + const QModelIndex index = m_localModel->index(row); + m_ui->localBranchListView->selectionModel()->select(index, QItemSelectionModel::Select); + } +} + bool BranchDialog::ask(const QString &title, const QString &what, bool defaultButton) { return QMessageBox::question(this, title, what, QMessageBox::Yes|QMessageBox::No, diff --git a/src/plugins/git/branchdialog.h b/src/plugins/git/branchdialog.h index 064719b8ff0..c803b57e833 100644 --- a/src/plugins/git/branchdialog.h +++ b/src/plugins/git/branchdialog.h @@ -14,7 +14,8 @@ namespace Git { } class GitClient; - class BranchModel; + class LocalBranchModel; + class RemoteBranchModel; /* Branch dialog: Display a list of local branches at the top * and remote branches below. Offers to checkout/delete local @@ -39,6 +40,7 @@ namespace Git { void slotCheckoutSelectedBranch(); void slotDeleteSelectedBranch(); void slotLocalBranchActivated(); + void slotNewLocalBranchCreated(const QString &); private: bool ask(const QString &title, const QString &what, bool defaultButton); @@ -51,8 +53,8 @@ namespace Git { QPushButton *m_checkoutButton; QPushButton *m_deleteButton; - BranchModel *m_localModel; - BranchModel *m_remoteModel; + LocalBranchModel *m_localModel; + RemoteBranchModel *m_remoteModel; QString m_repoDirectory; }; } // namespace Internal diff --git a/src/plugins/git/branchmodel.cpp b/src/plugins/git/branchmodel.cpp index b02d58dbd04..bb8ccf19510 100644 --- a/src/plugins/git/branchmodel.cpp +++ b/src/plugins/git/branchmodel.cpp @@ -2,6 +2,8 @@ #include "gitclient.h" #include <QtCore/QDebug> +#include <QtCore/QRegExp> +#include <QtCore/QTimer> enum { debug = 0 }; @@ -10,7 +12,7 @@ namespace Git { // Parse a branch line: " *name sha description". Return true if it is // the current one -bool BranchModel::Branch::parse(const QString &lineIn, bool *isCurrent) +bool RemoteBranchModel::Branch::parse(const QString &lineIn, bool *isCurrent) { if (debug) qDebug() << Q_FUNC_INFO << lineIn; @@ -28,40 +30,41 @@ bool BranchModel::Branch::parse(const QString &lineIn, bool *isCurrent) return true; } -static inline Qt::ItemFlags typeToModelFlags(BranchModel::Type t) +// ------ RemoteBranchModel +RemoteBranchModel::RemoteBranchModel(GitClient *client, QObject *parent) : + QAbstractListModel(parent), + m_flags(Qt::ItemIsSelectable|Qt::ItemIsEnabled), + m_client(client) { - Qt::ItemFlags rc = Qt::ItemIsSelectable|Qt::ItemIsEnabled; - if (t == BranchModel::LocalBranches) - rc |= Qt::ItemIsUserCheckable; - return rc; } -// --- BranchModel -BranchModel::BranchModel(GitClient *client, Type type, QObject *parent) : - QAbstractListModel(parent), - m_type(type), - m_flags(typeToModelFlags(type)), - m_client(client), - m_currentBranch(-1) +bool RemoteBranchModel::refresh(const QString &workingDirectory, QString *errorMessage) { + int currentBranch; + return refreshBranches(workingDirectory, true, ¤tBranch, errorMessage); } -int BranchModel::currentBranch() const +QString RemoteBranchModel::branchName(int row) const { - return m_currentBranch; + return m_branches.at(row).name; } -QString BranchModel::branchName(int row) const +QString RemoteBranchModel::workingDirectory() const { - return m_branches.at(row).name; + return m_workingDirectory; } -int BranchModel::rowCount(const QModelIndex & /* parent */) const +int RemoteBranchModel::branchCount() const { return m_branches.size(); } -QVariant BranchModel::data(const QModelIndex &index, int role) const +int RemoteBranchModel::rowCount(const QModelIndex & /* parent */) const +{ + return branchCount(); +} + +QVariant RemoteBranchModel::data(const QModelIndex &index, int role) const { const int row = index.row(); switch (role) { @@ -72,36 +75,52 @@ QVariant BranchModel::data(const QModelIndex &index, int role) const m_branches.at(row).toolTip = toolTip(m_branches.at(row).currentSHA); return m_branches.at(row).toolTip; break; - case Qt::CheckStateRole: - if (m_type == RemoteBranches) - return QVariant(); - return row == m_currentBranch ? Qt::Checked : Qt::Unchecked; default: break; } return QVariant(); } -Qt::ItemFlags BranchModel::flags(const QModelIndex & /*index */) const +Qt::ItemFlags RemoteBranchModel::flags(const QModelIndex & /* index */) const { return m_flags; } -bool BranchModel::refresh(const QString &workingDirectory, QString *errorMessage) +QString RemoteBranchModel::toolTip(const QString &sha) const +{ + // Show the sha description excluding diff as toolTip + QString output; + QString errorMessage; + if (!m_client->synchronousShow(m_workingDirectory, sha, &output, &errorMessage)) + return errorMessage; + // Remove 'diff' output + const int diffPos = output.indexOf(QLatin1String("\ndiff --")); + if (diffPos != -1) + output.remove(diffPos, output.size() - diffPos); + return output; +} + +bool RemoteBranchModel::runGitBranchCommand(const QString &workingDirectory, const QStringList &additionalArgs, QString *output, QString *errorMessage) +{ + return m_client->synchronousBranchCmd(workingDirectory, additionalArgs, output, errorMessage); +} + +bool RemoteBranchModel::refreshBranches(const QString &workingDirectory, bool remoteBranches, + int *currentBranch, QString *errorMessage) { // Run branch command with verbose. QStringList branchArgs(QLatin1String("-v")); QString output; - if (m_type == RemoteBranches) + *currentBranch = -1; + if (remoteBranches) branchArgs.push_back(QLatin1String("-r")); - if (!m_client->synchronousBranchCmd(workingDirectory, branchArgs, &output, errorMessage)) + if (!runGitBranchCommand(workingDirectory, branchArgs, &output, errorMessage)) return false; if (debug) qDebug() << Q_FUNC_INFO << workingDirectory << output; // Parse output m_workingDirectory = workingDirectory; m_branches.clear(); - m_currentBranch = -1; const QStringList branches = output.split(QLatin1Char('\n')); const int branchCount = branches.size(); bool isCurrent; @@ -110,25 +129,122 @@ bool BranchModel::refresh(const QString &workingDirectory, QString *errorMessage if (newBranch.parse(branches.at(b), &isCurrent)) { m_branches.push_back(newBranch); if (isCurrent) - m_currentBranch = b; + *currentBranch = b; } } reset(); return true; } -QString BranchModel::toolTip(const QString &sha) const +int RemoteBranchModel::findBranchByName(const QString &name) const { - // Show the sha description excluding diff as toolTip + const int count = branchCount(); + for (int i = 0; i < count; i++) + if (branchName(i) == name) + return i; + return -1; +} + +// --- LocalBranchModel +LocalBranchModel::LocalBranchModel(GitClient *client, QObject *parent) : + RemoteBranchModel(client, parent), + m_typeHere(tr("<New branch>")), + m_typeHereToolTip(tr("Type to create a new branch")), + m_currentBranch(-1) +{ +} + +int LocalBranchModel::currentBranch() const +{ + return m_currentBranch; +} + +bool LocalBranchModel::isNewBranchRow(int row) const +{ + return row >= branchCount(); +} + +Qt::ItemFlags LocalBranchModel::flags(const QModelIndex & index) const +{ + if (isNewBranchRow(index)) + return Qt::ItemIsEditable|Qt::ItemIsSelectable|Qt::ItemIsEnabled| Qt::ItemIsUserCheckable; + return RemoteBranchModel::flags(index) | Qt::ItemIsUserCheckable; +} + +int LocalBranchModel::rowCount(const QModelIndex & /* parent */) const +{ + return branchCount() + 1; +} + +QVariant LocalBranchModel::data(const QModelIndex &index, int role) const +{ + if (isNewBranchRow(index)) { + switch (role) { + case Qt::DisplayRole: + return m_typeHere; + case Qt::ToolTipRole: + return m_typeHereToolTip; + case Qt::CheckStateRole: + return QVariant(false); + } + return QVariant(); + } + + if (role == Qt::CheckStateRole) + return index.row() == m_currentBranch ? Qt::Checked : Qt::Unchecked; + return RemoteBranchModel::data(index, role); +} + +bool LocalBranchModel::refresh(const QString &workingDirectory, QString *errorMessage) +{ + return refreshBranches(workingDirectory, false, &m_currentBranch, errorMessage); +} + +bool LocalBranchModel::checkNewBranchName(const QString &name) const +{ + // Syntax + const QRegExp pattern(QLatin1String("[a-zA-Z0-9-_]+")); + if (!pattern.exactMatch(name)) + return false; + // existing + if (findBranchByName(name) != -1) + return false; + return true; +} + +bool LocalBranchModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + // Verify + if (role != Qt::EditRole || index.row() < branchCount()) + return false; + const QString branchName = value.toString(); + const bool ok = checkNewBranchName(branchName); + if (debug) + qDebug() << Q_FUNC_INFO << branchName << ok; + if (!ok) + return false; + // Create QString output; QString errorMessage; - if (!m_client->synchronousShow(m_workingDirectory, sha, &output, &errorMessage)) - return errorMessage; - // Remove 'diff' output - const int diffPos = output.indexOf(QLatin1String("\ndiff --")); - if (diffPos != -1) - output.remove(diffPos, output.size() - diffPos); - return output; + if (!runGitBranchCommand(workingDirectory(), QStringList(branchName), &output, &errorMessage)) + return false; + m_newBranch = branchName; + // Start a delayed complete refresh and return true for now. + QTimer::singleShot(0, this, SLOT(slotNewBranchDelayedRefresh())); + return true; +} + +void LocalBranchModel::slotNewBranchDelayedRefresh() +{ + if (debug) + qDebug() << Q_FUNC_INFO; + + QString errorMessage; + if (!refresh(workingDirectory(), &errorMessage)) { + qWarning("%s", qPrintable(errorMessage)); + return; + } + emit newBranchCreated(m_newBranch); } } diff --git a/src/plugins/git/branchmodel.h b/src/plugins/git/branchmodel.h index 5083a1c377c..91b1ca84dc0 100644 --- a/src/plugins/git/branchmodel.h +++ b/src/plugins/git/branchmodel.h @@ -3,27 +3,25 @@ #include <QtCore/QAbstractListModel> #include <QtCore/QList> +#include <QtCore/QVariant> namespace Git { namespace Internal { class GitClient; -/* A model to list git branches in a simple list of branch names. Local - * branches will have a read-only checkbox indicating the current one. The - * [delayed] tooltip describes the latest commit. */ +/* A read-only model to list git remote branches in a simple list of branch names. + * The tooltip describes the latest commit (delayed creation). + * Provides virtuals to be able to derive a local branch model with the notion + * of a "current branch". */ -class BranchModel : public QAbstractListModel { +class RemoteBranchModel : public QAbstractListModel { + Q_OBJECT public: - enum Type { LocalBranches, RemoteBranches }; + explicit RemoteBranchModel(GitClient *client, QObject *parent = 0); - explicit BranchModel(GitClient *client, - Type type = LocalBranches, - QObject *parent = 0); - - bool refresh(const QString &workingDirectory, QString *errorMessage); + virtual bool refresh(const QString &workingDirectory, QString *errorMessage); - int currentBranch() const; QString branchName(int row) const; // QAbstractListModel @@ -31,9 +29,12 @@ public: virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; virtual Qt::ItemFlags flags(const QModelIndex &index) const; -private: - QString toolTip(const QString &sha) const; + int branchCount() const; + QString workingDirectory() const; + int findBranchByName(const QString &name) const; + +protected: struct Branch { bool parse(const QString &line, bool *isCurrent); @@ -43,13 +44,61 @@ private: }; typedef QList<Branch> BranchList; - const Type m_type; + /* Parse git output and populate m_branches. */ + bool refreshBranches(const QString &workingDirectory, bool remoteBranches, + int *currentBranch, QString *errorMessage); + bool runGitBranchCommand(const QString &workingDirectory, const QStringList &additionalArgs, QString *output, QString *errorMessage); + +private: + QString toolTip(const QString &sha) const; + const Qt::ItemFlags m_flags; - GitClient *m_client; + GitClient *m_client; QString m_workingDirectory; BranchList m_branches; +}; + +/* LocalBranchModel: Extends RemoteBranchModel by a read-only + * checkable column indicating the current branch. Provides an + * editable "Type here" new-branch-row at the bottom to create + * a new branch. */ + +class LocalBranchModel : public RemoteBranchModel { + Q_OBJECT +public: + + explicit LocalBranchModel(GitClient *client, + QObject *parent = 0); + + virtual bool refresh(const QString &workingDirectory, QString *errorMessage); + + // is this the "type here" row? + bool isNewBranchRow(int row) const; + bool isNewBranchRow(const QModelIndex & index) const { return isNewBranchRow(index.row()); } + + int currentBranch() const; + + // QAbstractListModel + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + virtual Qt::ItemFlags flags(const QModelIndex &index) const; + +signals: + void newBranchCreated(const QString &); + +private slots: + void slotNewBranchDelayedRefresh(); + +private: + bool checkNewBranchName(const QString &name) const; + + const QVariant m_typeHere; + const QVariant m_typeHereToolTip; + int m_currentBranch; + QString m_newBranch; }; } -- GitLab