From 9b76c068f97b200b814157f2aca2d880f7ad86f3 Mon Sep 17 00:00:00 2001
From: Friedemann Kleint <Friedemann.Kleint@nokia.com>
Date: Tue, 2 Feb 2010 12:27:05 +0100
Subject: [PATCH] VCS[git]: Implement add using '--intent-to-add' depending on
 version.

Implement IVersionControl::vcsAdd() using --intent-to-add with a cached
version check. On this occasion, implement vcsDelete() as well using
'git rm -f'.
---
 src/plugins/git/gitclient.cpp         | 85 +++++++++++++++++++++++++--
 src/plugins/git/gitclient.h           | 15 ++++-
 src/plugins/git/gitutils.h            |  6 ++
 src/plugins/git/gitversioncontrol.cpp | 28 +++++++--
 4 files changed, 124 insertions(+), 10 deletions(-)

diff --git a/src/plugins/git/gitclient.cpp b/src/plugins/git/gitclient.cpp
index b98462562ab..f2020b3a5ff 100644
--- a/src/plugins/git/gitclient.cpp
+++ b/src/plugins/git/gitclient.cpp
@@ -131,7 +131,8 @@ GitClient::GitClient(GitPlugin* plugin)
   : m_msgWait(tr("Waiting for data...")),
     m_plugin(plugin),
     m_core(Core::ICore::instance()),
-    m_repositoryChangedSignalMapper(0)
+    m_repositoryChangedSignalMapper(0),
+    m_cachedGitVersion(0)
 {
     if (QSettings *s = m_core->settings()) {
         m_settings.fromSettings(s);
@@ -463,14 +464,20 @@ void GitClient::addFile(const QString &workingDirectory, const QString &fileName
     executeGit(workingDirectory, arguments, 0, true);
 }
 
-bool GitClient::synchronousAdd(const QString &workingDirectory, const QStringList &files)
+// Warning: 'intendToAdd' works only from 1.6.1 onwards
+bool GitClient::synchronousAdd(const QString &workingDirectory,
+                               bool intendToAdd,
+                               const QStringList &files)
 {
     if (Git::Constants::debug)
         qDebug() << Q_FUNC_INFO << workingDirectory << files;
     QByteArray outputText;
     QByteArray errorText;
     QStringList arguments;
-    arguments << QLatin1String("add") << files;
+    arguments << QLatin1String("add");
+    if (intendToAdd)
+        arguments << QLatin1String("--intent-to-add");
+    arguments.append(files);
     const bool rc = synchronousGit(workingDirectory, arguments, &outputText, &errorText);
     if (!rc) {
         const QString errorMessage = tr("Unable to add %n file(s) to %1: %2", 0, files.size()).
@@ -480,6 +487,28 @@ bool GitClient::synchronousAdd(const QString &workingDirectory, const QStringLis
     return rc;
 }
 
+bool GitClient::synchronousDelete(const QString &workingDirectory,
+                                  bool force,
+                                  const QStringList &files)
+{
+    if (Git::Constants::debug)
+        qDebug() << Q_FUNC_INFO << workingDirectory << files;
+    QByteArray outputText;
+    QByteArray errorText;
+    QStringList arguments;
+    arguments << QLatin1String("rm");
+    if (force)
+        arguments << QLatin1String("--force");
+    arguments.append(files);
+    const bool rc = synchronousGit(workingDirectory, arguments, &outputText, &errorText);
+    if (!rc) {
+        const QString errorMessage = tr("Unable to remove %n file(s) from %1: %2", 0, files.size()).
+                                     arg(workingDirectory, commandOutputFromLocal8Bit(errorText));
+        VCSBase::VCSBaseOutputWindow::instance()->appendError(errorMessage);
+    }
+    return rc;
+}
+
 bool GitClient::synchronousReset(const QString &workingDirectory,
                                  const QStringList &files,
                                  QString *errorMessage)
@@ -1233,7 +1262,7 @@ bool GitClient::addAndCommit(const QString &repositoryDirectory,
     // for deletion
     QStringList addFiles = checkedFiles.toSet().subtract(origDeletedFiles.toSet()).toList();
     if (!addFiles.isEmpty())
-        if (!synchronousAdd(repositoryDirectory, addFiles))
+        if (!synchronousAdd(repositoryDirectory, false, addFiles))
             return false;
 
     // Do the final commit
@@ -1526,6 +1555,7 @@ void GitClient::setSettings(const GitSettings &s)
         if (QSettings *s = m_core->settings())
             m_settings.toSettings(s);
         m_binaryPath = m_settings.gitBinaryPath();
+        m_cachedGitVersion = 0u;
     }
 }
 
@@ -1542,5 +1572,52 @@ void GitClient::connectRepositoryChanged(const QString & repository, GitCommand
             Qt::QueuedConnection);
 }
 
+// determine version as '(major << 16) + (minor << 8) + patch' or 0.
+unsigned GitClient::gitVersion(QString *errorMessage  /* = 0 */)
+{
+    if (!m_cachedGitVersion)
+        m_cachedGitVersion = synchronousGitVersion(errorMessage);
+    return m_cachedGitVersion;
 }
+
+QString GitClient::gitVersionString(QString *errorMessage)
+{
+    if (const unsigned version = gitVersion(errorMessage)) {
+        QString rc;
+        QTextStream(&rc) << (version >> 16) << '.'
+                << (0xFF & (version >> 8)) << '.'
+                << (version & 0xFF);
+        return rc;
+    }
+    return QString();
+}
+
+// determine version as '(major << 16) + (minor << 8) + patch' or 0.
+unsigned GitClient::synchronousGitVersion(QString *errorMessage /* = 0 */)
+{
+    // run git --version
+    QByteArray outputText;
+    QByteArray errorText;
+    const bool rc = synchronousGit(QString(), QStringList("--version"), &outputText, &errorText);
+    if (!rc) {
+        const QString msg = tr("Unable to determine git version: %1").arg(commandOutputFromLocal8Bit(errorText));
+        if (errorMessage) {
+            *errorMessage = msg;
+        } else {
+            VCSBase::VCSBaseOutputWindow::instance()->appendError(msg);
+        }
+        return 0;
+    }
+    // cut 'git version 1.6.5.1.sha'
+    const QString output = commandOutputFromLocal8Bit(outputText);
+    const QRegExp versionPattern(QLatin1String("^[^\\d]+([\\d])\\.([\\d])\\.([\\d]).*$"));
+    QTC_ASSERT(versionPattern.isValid(), return 0);
+    QTC_ASSERT(versionPattern.exactMatch(output), return 0);
+    const unsigned major = versionPattern.cap(1).toUInt();
+    const unsigned minor = versionPattern.cap(2).toUInt();
+    const unsigned patch = versionPattern.cap(3).toUInt();
+    return version(major, minor, patch);
 }
+
+} // namespace Internal
+} // namespace Git
diff --git a/src/plugins/git/gitclient.h b/src/plugins/git/gitclient.h
index 55f6800d404..6b6ca1a655c 100644
--- a/src/plugins/git/gitclient.h
+++ b/src/plugins/git/gitclient.h
@@ -95,7 +95,13 @@ public:
     void checkoutBranch(const QString &workingDirectory, const QString &branch);
     void hardReset(const QString &workingDirectory, const QString &commit = QString());
     void addFile(const QString &workingDirectory, const QString &fileName);
-    bool synchronousAdd(const QString &workingDirectory, const QStringList &files);
+    bool synchronousAdd(const QString &workingDirectory,
+                        // Warning: Works only from 1.6.1 onwards
+                        bool intendToAdd,
+                        const QStringList &files);
+    bool synchronousDelete(const QString &workingDirectory,
+                           bool force,
+                           const QStringList &files);
     bool synchronousReset(const QString &workingDirectory,
                           const QStringList &files = QStringList(),
                           QString *errorMessage = 0);
@@ -139,6 +145,10 @@ public:
                                       QStringList *descriptions, QString *errorMessage);
     bool synchronousTopRevision(const QString &workingDirectory, QString *revision = 0,
                                 QString *branch = 0, QString *errorMessage = 0);
+    // determine version as '(major << 16) + (minor << 8) + patch' or 0
+    // with some smart caching.
+    unsigned gitVersion(QString *errorMessage = 0);
+    QString gitVersionString(QString *errorMessage = 0);
 
     void pull(const QString &workingDirectory);
     void push(const QString &workingDirectory);
@@ -223,6 +233,8 @@ private:
                         QByteArray* outputText = 0,
                         QByteArray* errorText = 0,
                         bool logCommandToWindow = true);
+    // determine version as '(major << 16) + (minor << 8) + patch' or 0.
+    unsigned synchronousGitVersion(QString *errorMessage = 0);
 
     enum RevertResult { RevertOk, RevertUnchanged, RevertCanceled, RevertFailed };
     RevertResult revertI(QStringList files, bool *isDirectory, QString *errorMessage);
@@ -234,6 +246,7 @@ private:
     GitSettings   m_settings;
     QString m_binaryPath;
     QSignalMapper *m_repositoryChangedSignalMapper;
+    unsigned      m_cachedGitVersion;
 };
 
 
diff --git a/src/plugins/git/gitutils.h b/src/plugins/git/gitutils.h
index 16d80ca656c..7dbefc91582 100644
--- a/src/plugins/git/gitutils.h
+++ b/src/plugins/git/gitutils.h
@@ -54,6 +54,12 @@ QDebug operator<<(QDebug d, const Stash &);
 // Make QInputDialog  play nicely
 bool inputText(QWidget *parent, const QString &title, const QString &prompt, QString *s);
 
+// Version information following Qt convention
+inline unsigned version(unsigned major, unsigned minor, unsigned patch)
+{
+    return (major << 16) + (minor << 8) + patch;
+}
+
 } // namespace Internal
 } // namespace Git
 
diff --git a/src/plugins/git/gitversioncontrol.cpp b/src/plugins/git/gitversioncontrol.cpp
index c98f0688921..49955873ab3 100644
--- a/src/plugins/git/gitversioncontrol.cpp
+++ b/src/plugins/git/gitversioncontrol.cpp
@@ -32,6 +32,11 @@
 #include "gitplugin.h"
 #include "gitutils.h"
 
+#include <utils/qtcassert.h>
+
+#include <QtCore/QDebug>
+#include <QtCore/QFileInfo>
+
 static const char stashMessageKeywordC[] = "IVersionControl@";
 static const char stashRevisionIdC[] = "revision";
 
@@ -54,12 +59,22 @@ QString GitVersionControl::displayName() const
     return QLatin1String("git");
 }
 
+// Add: Implement using "git add --intent-to-add" starting from 1.6.1
+static inline bool addOperationSupported()
+{
+    return gitClient()->gitVersion() >= version(1, 6, 1);
+}
+
 bool GitVersionControl::supportsOperation(Operation operation) const
 {
     bool rc = false;
     switch (operation) {
     case AddOperation:
+        rc = addOperationSupported();
+        break;
     case DeleteOperation:
+        rc = true;
+        break;
     case OpenOperation:
         break;
     case CreateRepositoryOperation:
@@ -75,15 +90,18 @@ bool GitVersionControl::vcsOpen(const QString & /*fileName*/)
     return false;
 }
 
-bool GitVersionControl::vcsAdd(const QString & /*fileName*/)
+bool GitVersionControl::vcsAdd(const QString & fileName)
 {
-    return false;
+    // Implement in terms of using "--intent-to-add"
+    QTC_ASSERT(addOperationSupported(), return false);
+    const QFileInfo fi(fileName);
+    return gitClient()->synchronousAdd(fi.absolutePath(), true, QStringList(fi.fileName()));
 }
 
-bool GitVersionControl::vcsDelete(const QString & /*fileName*/)
+bool GitVersionControl::vcsDelete(const QString & fileName)
 {
-    // TODO: implement using 'git rm'.
-    return false;
+    const QFileInfo fi(fileName);
+    return gitClient()->synchronousDelete(fi.absolutePath(), true, QStringList(fi.fileName()));
 }
 
 bool GitVersionControl::vcsCreateRepository(const QString &directory)
-- 
GitLab