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, &currentBranch, 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