From c4c2437dd4de2efe787e17c923e181fbf7cac879 Mon Sep 17 00:00:00 2001
From: Friedemann Kleint <qtc-committer@nokia.com>
Date: Tue, 2 Dec 2008 15:43:58 +0100
Subject: [PATCH] Fixes: Ability to uncheck files in git.

---
 src/libs/utils/submiteditorwidget.ui | 11 ++--
 src/plugins/git/gitclient.cpp        | 90 ++++++++++++++++++++++++----
 src/plugins/git/gitclient.h          |  4 +-
 src/plugins/git/gitplugin.cpp        |  6 +-
 src/plugins/git/gitplugin.h          |  3 +-
 src/plugins/git/gitsubmiteditor.cpp  | 15 ++---
 src/plugins/git/gitsubmiteditor.h    |  9 ++-
 7 files changed, 106 insertions(+), 32 deletions(-)

diff --git a/src/libs/utils/submiteditorwidget.ui b/src/libs/utils/submiteditorwidget.ui
index 1a30e8b7919..4971f7e72d0 100644
--- a/src/libs/utils/submiteditorwidget.ui
+++ b/src/libs/utils/submiteditorwidget.ui
@@ -22,10 +22,9 @@
      <property name="flat">
       <bool>true</bool>
      </property>
-     <layout class="QGridLayout">
-      <item row="0" column="0">
-       <widget class="QPlainTextEdit" name="description">
-       </widget>
+     <layout class="QVBoxLayout" name="verticalLayout">
+      <item>
+       <widget class="QPlainTextEdit" name="description"/>
       </item>
      </layout>
     </widget>
@@ -38,8 +37,8 @@
      <property name="flat">
       <bool>true</bool>
      </property>
-     <layout class="QGridLayout">
-      <item row="0" column="0">
+     <layout class="QVBoxLayout" name="verticalLayout_2">
+      <item>
        <widget class="QListWidget" name="fileList">
         <property name="font">
          <font/>
diff --git a/src/plugins/git/gitclient.cpp b/src/plugins/git/gitclient.cpp
index d021881dbb0..f7bba284410 100644
--- a/src/plugins/git/gitclient.cpp
+++ b/src/plugins/git/gitclient.cpp
@@ -59,6 +59,8 @@ const char* const kGitCommand = "git";
 const char* const kGitDirectoryC = ".git";
 const char* const kBranchIndicatorC = "# On branch";
 
+enum { untrackedFilesInCommit = 0 };
+
 static inline QString msgServerFailure()
 {
     return GitClient::tr(
@@ -197,7 +199,9 @@ void GitClient::diff(const QString &workingDirectory, const QString &fileName)
 
 void GitClient::status(const QString &workingDirectory)
 {
-    executeGit(workingDirectory, QStringList(QLatin1String("status")), m_plugin->m_outputWindow, 0,true);
+    QStringList statusArgs(QLatin1String("status"));
+    statusArgs << QLatin1String("-u");
+    executeGit(workingDirectory, statusArgs, m_plugin->m_outputWindow, 0,true);
 }
 
 void GitClient::log(const QString &workingDirectory, const QString &fileName)
@@ -286,10 +290,12 @@ void GitClient::addFile(const QString &workingDirectory, const QString &fileName
 
 bool GitClient::synchronousAdd(const QString &workingDirectory, const QStringList &files)
 {
+    if (Git::Constants::debug)
+        qDebug() << Q_FUNC_INFO << workingDirectory << files;
     QByteArray outputText;
     QByteArray errorText;
     QStringList arguments;
-    arguments << "add" << files;
+    arguments << QLatin1String("add") << 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()).
@@ -300,6 +306,30 @@ bool GitClient::synchronousAdd(const QString &workingDirectory, const QStringLis
     return rc;
 }
 
+bool GitClient::synchronousReset(const QString &workingDirectory,
+                                 const QStringList &files)
+{
+    if (Git::Constants::debug)
+        qDebug() << Q_FUNC_INFO << workingDirectory << files;
+    QByteArray outputText;
+    QByteArray errorText;
+    QStringList arguments;
+    arguments << QLatin1String("reset") << QLatin1String("HEAD") << files;
+    const bool rc = synchronousGit(workingDirectory, arguments, &outputText, &errorText);
+    const QString output = QString::fromLocal8Bit(outputText);
+    m_plugin->m_outputWindow->popup(false);
+    m_plugin->m_outputWindow->append(output);
+    // Note that git exits with 1 even if the operation is successful
+    // Assume real failure if the output does not contain "foo.cpp modified"
+    if (!rc && !output.contains(QLatin1String("modified"))) {
+        const QString errorMessage = tr("Unable to reset %n file(s) in %1: %2", 0, files.size()).
+                                     arg(workingDirectory, QString::fromLocal8Bit(errorText));
+        m_plugin->m_outputWindow->append(errorMessage);
+        return false;
+    }
+    return true;
+}
+
 void GitClient::executeGit(const QString &workingDirectory, const QStringList &arguments,
                            GitOutputWindow *outputWindow, VCSBase::VCSBaseEditor* editor,
                            bool outputToWindow)
@@ -365,6 +395,22 @@ bool GitClient::synchronousGit(const QString &workingDirectory
     return process.exitCode() == 0;
 }
 
+
+// Trim a git status file spec: "modified:    foo .cpp" -> "modified: foo .cpp"
+static inline QString trimFileSpecification(QString fileSpec)
+{
+    const int colonIndex = fileSpec.indexOf(QLatin1Char(':'));
+    if (colonIndex != -1) {
+        // Collapse the sequence of spaces
+        const int filePos = colonIndex + 2;
+        int nonBlankPos = filePos;
+        for ( ; fileSpec.at(nonBlankPos).isSpace(); nonBlankPos++);
+        if (nonBlankPos > filePos)
+            fileSpec.remove(filePos, nonBlankPos - filePos);
+    }
+    return fileSpec;
+}
+
 /* Parse a git status file list:
  * \code
     # Changes to be committed:
@@ -385,8 +431,8 @@ static bool parseFiles(const QStringList &lines, CommitData *d)
     const QString untrackedIndicator = QLatin1String("# Untracked files:");
 
     State s = None;
-
-    const QRegExp filesPattern(QLatin1String("#\\t[^:]+:\\s+[^ ]+"));
+    // Match added/changed-not-updated files: "#<tab>modified: foo.cpp"
+    QRegExp filesPattern(QLatin1String("#\\t[^:]+:\\s+.+"));
     Q_ASSERT(filesPattern.isValid());
 
     const QStringList::const_iterator cend = lines.constEnd();
@@ -402,19 +448,22 @@ static bool parseFiles(const QStringList &lines, CommitData *d)
                     s = NotUpdatedFiles;
                 } else {
                     if (line.startsWith(untrackedIndicator)) {
+                        // Now match untracked: "#<tab>foo.cpp"
                         s = UntrackedFiles;
+                        filesPattern = QRegExp(QLatin1String("#\\t.+"));
+                        Q_ASSERT(filesPattern.isValid());
                     } else {
                         if (filesPattern.exactMatch(line)) {
-                            const QString fileSpec = line.mid(2).simplified();
+                            const QString fileSpec = line.mid(2).trimmed();
                             switch (s) {
                             case CommitFiles:
-                                d->commitFiles.push_back(fileSpec);
+                                d->commitFiles.push_back(trimFileSpecification(fileSpec));
                             break;
                             case NotUpdatedFiles:
-                                d->notUpdatedFiles.push_back(fileSpec);
+                                d->notUpdatedFiles.push_back(trimFileSpecification(fileSpec));
                                 break;
                             case UntrackedFiles:
-                                d->untrackedFiles.push_back(fileSpec);
+                                d->untrackedFiles.push_back(QLatin1String("untracked: ") + fileSpec);
                                 break;
                             case None:
                                 break;
@@ -461,7 +510,10 @@ bool GitClient::getCommitData(const QString &workingDirectory,
     // Run status. Note that it has exitcode 1 if there are no added files.
     QByteArray outputText;
     QByteArray errorText;
-    const bool statusRc = synchronousGit(workingDirectory, QStringList(QLatin1String("status")), &outputText, &errorText);
+    QStringList statusArgs(QLatin1String("status"));
+    if (untrackedFilesInCommit)
+        statusArgs << QLatin1String("-u");
+    const bool statusRc = synchronousGit(workingDirectory, statusArgs, &outputText, &errorText);
     if (!statusRc) {
         // Something fatal
         if (!outputText.contains(kBranchIndicatorC)) {
@@ -517,13 +569,25 @@ bool GitClient::getCommitData(const QString &workingDirectory,
     return true;
 }
 
+// addAndCommit:
 bool GitClient::addAndCommit(const QString &workingDirectory,
                              const GitSubmitEditorPanelData &data,
                              const QString &messageFile,
-                             const QStringList &files)
+                             const QStringList &checkedFiles,
+                             const QStringList &origCommitFiles)
 {
+    if (Git::Constants::debug)
+        qDebug() << "GitClient::addAndCommit:" << workingDirectory << checkedFiles << origCommitFiles;
+
+    // Do we need to reset any files that had been added before
+    // (did the user uncheck any previously added files)
+    const QSet<QString> resetFiles = origCommitFiles.toSet().subtract(checkedFiles.toSet());
+    if (!resetFiles.empty())
+        if (!synchronousReset(workingDirectory, resetFiles.toList()))
+            return false;
+
     // Re-add all to make sure we have the latest changes
-    if (!synchronousAdd(workingDirectory, files))
+    if (!synchronousAdd(workingDirectory, checkedFiles))
         return false;
 
     // Do the final commit
@@ -536,8 +600,8 @@ bool GitClient::addAndCommit(const QString &workingDirectory,
     QByteArray errorText;
     const bool rc = synchronousGit(workingDirectory, args, &outputText, &errorText);
     const QString message = rc ?
-        tr("Committed %n file(s).", 0, files.size()) :
-        tr("Unable to commit %n file(s): %1", 0, files.size()).arg(QString::fromLocal8Bit(errorText));
+        tr("Committed %n file(s).", 0, checkedFiles.size()) :
+        tr("Unable to commit %n file(s): %1", 0, checkedFiles.size()).arg(QString::fromLocal8Bit(errorText));
 
     m_plugin->m_outputWindow->append(message);
     m_plugin->m_outputWindow->popup(false);
diff --git a/src/plugins/git/gitclient.h b/src/plugins/git/gitclient.h
index 944041afa3d..1a7ebe8c983 100644
--- a/src/plugins/git/gitclient.h
+++ b/src/plugins/git/gitclient.h
@@ -90,6 +90,7 @@ public:
     void hardReset(const QString &workingDirectory, const QString &commit);
     void addFile(const QString &workingDirectory, const QString &fileName);
     bool synchronousAdd(const QString &workingDirectory, const QStringList &files);
+    bool synchronousReset(const QString &workingDirectory, const QStringList &files);
     void pull(const QString &workingDirectory);
     void push(const QString &workingDirectory);
 
@@ -105,7 +106,8 @@ public:
     bool addAndCommit(const QString &workingDirectory,
                       const GitSubmitEditorPanelData &data,
                       const QString &messageFile,
-                      const QStringList &files);
+                      const QStringList &checkedFiles,
+                      const QStringList &origCommitFiles);
 
 public slots:
     void show(const QString &source, const QString &id);
diff --git a/src/plugins/git/gitplugin.cpp b/src/plugins/git/gitplugin.cpp
index 3b6342db8ee..6ee7bab39b0 100644
--- a/src/plugins/git/gitplugin.cpp
+++ b/src/plugins/git/gitplugin.cpp
@@ -524,7 +524,10 @@ void GitPlugin::startCommit()
         return;
     }
 
+    // Store repository for diff and the original list of
+    // files to be able to unstage files the user unchecks
     m_submitRepository = data.panelInfo.repository;
+    m_submitOrigCommitFiles = GitSubmitEditor::statusListToFileList(data.commitFiles);
 
     if (Git::Constants::debug)
         qDebug() << Q_FUNC_INFO << data << commitTemplate;
@@ -614,7 +617,8 @@ bool GitPlugin::editorAboutToClose(Core::IEditor *iEditor)
         m_gitClient->addAndCommit(m_submitRepository,
                                   editor->panelData(),
                                   m_changeTmpFile->fileName(),
-                                  fileList);
+                                  fileList,
+                                  m_submitOrigCommitFiles);
     }
     cleanChangeTmpFile();
     return true;
diff --git a/src/plugins/git/gitplugin.h b/src/plugins/git/gitplugin.h
index 7becc05e7dc..51846c71d66 100644
--- a/src/plugins/git/gitplugin.h
+++ b/src/plugins/git/gitplugin.h
@@ -44,7 +44,7 @@
 
 #include <QtCore/QObject>
 #include <QtCore/QProcess>
-#include <QtCore/QList>
+#include <QtCore/QStringList>
 
 QT_BEGIN_NAMESPACE
 class QFile;
@@ -155,6 +155,7 @@ private:
     CoreListener                *m_coreListener;
     Core::IEditorFactory        *m_submitEditorFactory;
     QString                     m_submitRepository;
+    QStringList                 m_submitOrigCommitFiles;
     QTemporaryFile              *m_changeTmpFile;
 };
 
diff --git a/src/plugins/git/gitsubmiteditor.cpp b/src/plugins/git/gitsubmiteditor.cpp
index 4f0e201d666..516dc6570b7 100644
--- a/src/plugins/git/gitsubmiteditor.cpp
+++ b/src/plugins/git/gitsubmiteditor.cpp
@@ -52,11 +52,13 @@ GitSubmitEditorWidget *GitSubmitEditor::submitEditorWidget()
     return static_cast<GitSubmitEditorWidget *>(widget());
 }
 
-QStringList GitSubmitEditor::vcsFileListToFileList(const QStringList &rawList) const
+QStringList GitSubmitEditor::statusListToFileList(const QStringList &rawList)
 {
+    if (rawList.empty())
+        return rawList;
     QStringList rc;
     foreach (const QString &rf, rawList)
-        rc.push_back(fileFromChangeLine(rf));
+        rc.push_back(fileFromStatusLine(rf));
     return rc;
 }
 
@@ -65,9 +67,8 @@ void GitSubmitEditor::setCommitData(const CommitData &d)
     submitEditorWidget()->setPanelData(d.panelData);
     submitEditorWidget()->setPanelInfo(d.panelInfo);
 
-    // Commited: Checked, user cannot uncheck
-    addFiles(d.commitFiles, true, false);
-    // Not Updated: User can check
+    addFiles(d.commitFiles, true, true);
+    // Not Updated: Initially unchecked
     addFiles(d.notUpdatedFiles, false, true);
     addFiles(d.untrackedFiles, false, true);
 }
@@ -77,10 +78,10 @@ GitSubmitEditorPanelData GitSubmitEditor::panelData() const
     return const_cast<GitSubmitEditor*>(this)->submitEditorWidget()->panelData();
 }
 
-QString GitSubmitEditor::fileFromChangeLine(const QString &line)
+QString GitSubmitEditor::fileFromStatusLine(const QString &line)
 {
     QString rc = line;
-    // "modified: mainwindow.cpp"
+    // "modified:   mainwindow.cpp"
     const int index = rc.indexOf(QLatin1Char(':'));
     if (index != -1)
         rc.remove(0, index + 1);
diff --git a/src/plugins/git/gitsubmiteditor.h b/src/plugins/git/gitsubmiteditor.h
index e0554758a29..4219f5ae3dd 100644
--- a/src/plugins/git/gitsubmiteditor.h
+++ b/src/plugins/git/gitsubmiteditor.h
@@ -36,6 +36,8 @@
 
 #include <vcsbase/vcsbasesubmiteditor.h>
 
+#include <QtCore/QStringList>
+
 namespace Git {
 namespace Internal {
 
@@ -43,7 +45,6 @@ class GitSubmitEditorWidget;
 struct CommitData;
 struct GitSubmitEditorPanelData;
 
-/*  */
 class GitSubmitEditor : public VCSBase::VCSBaseSubmitEditor
 {
     Q_OBJECT
@@ -53,10 +54,12 @@ public:
     void setCommitData(const CommitData &);
     GitSubmitEditorPanelData panelData() const;
 
-    static QString fileFromChangeLine(const QString &line);
+    static QString fileFromStatusLine(const QString &line);
+    static QStringList statusListToFileList(const QStringList &);
 
 protected:
-    virtual QStringList vcsFileListToFileList(const QStringList &) const;
+    virtual QStringList vcsFileListToFileList(const QStringList &l) const
+    { return statusListToFileList(l); }
 
 private:
     inline GitSubmitEditorWidget *submitEditorWidget();
-- 
GitLab