OSDN Git Service

Merge remote branch 'origin/2.0'
[qt-creator-jp/qt-creator-jp.git] / src / plugins / git / gitclient.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
8 **
9 ** Commercial Usage
10 **
11 ** Licensees holding valid Qt Commercial licenses may use this file in
12 ** accordance with the Qt Commercial License Agreement provided with the
13 ** Software or, alternatively, in accordance with the terms contained in
14 ** a written agreement between you and Nokia.
15 **
16 ** GNU Lesser General Public License Usage
17 **
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file.  Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 **
25 ** If you are unsure which license is appropriate for your use, please
26 ** contact the sales department at http://qt.nokia.com/contact.
27 **
28 **************************************************************************/
29
30 #include "gitclient.h"
31 #include "gitcommand.h"
32 #include "gitutils.h"
33
34 #include "commitdata.h"
35 #include "gitconstants.h"
36 #include "gitplugin.h"
37 #include "gitsubmiteditor.h"
38 #include "gitversioncontrol.h"
39
40 #include <coreplugin/actionmanager/actionmanager.h>
41 #include <coreplugin/coreconstants.h>
42 #include <coreplugin/editormanager/editormanager.h>
43 #include <coreplugin/icore.h>
44 #include <coreplugin/messagemanager.h>
45 #include <coreplugin/progressmanager/progressmanager.h>
46 #include <coreplugin/uniqueidmanager.h>
47 #include <coreplugin/filemanager.h>
48 #include <coreplugin/iversioncontrol.h>
49
50 #include <texteditor/itexteditor.h>
51 #include <utils/qtcassert.h>
52 #include <utils/synchronousprocess.h>
53 #include <vcsbase/vcsbaseeditor.h>
54 #include <vcsbase/vcsbaseoutputwindow.h>
55 #include <vcsbase/vcsbaseplugin.h>
56
57 #include <projectexplorer/environment.h>
58
59 #include <QtCore/QRegExp>
60 #include <QtCore/QTemporaryFile>
61 #include <QtCore/QTime>
62 #include <QtCore/QFileInfo>
63 #include <QtCore/QDir>
64 #include <QtCore/QSignalMapper>
65
66 #include <QtGui/QMainWindow> // for msg box parent
67 #include <QtGui/QMessageBox>
68 #include <QtGui/QPushButton>
69
70 static const char *const kGitDirectoryC = ".git";
71 static const char *const kBranchIndicatorC = "# On branch";
72
73 static inline QString msgServerFailure()
74 {
75     return Git::Internal::GitClient::tr(
76 "Note that the git plugin for QtCreator is not able to interact with the server "
77 "so far. Thus, manual ssh-identification etc. will not work.");
78 }
79
80 inline Core::IEditor* locateEditor(const Core::ICore *core, const char *property, const QString &entry)
81 {
82     foreach (Core::IEditor *ed, core->editorManager()->openedEditors())
83         if (ed->file()->property(property).toString() == entry)
84             return ed;
85     return 0;
86 }
87
88 // Return converted command output, remove '\r' read on Windows
89 static inline QString commandOutputFromLocal8Bit(const QByteArray &a)
90 {
91     QString output = QString::fromLocal8Bit(a);
92     output.remove(QLatin1Char('\r'));
93     return output;
94 }
95
96 // Return converted command output split into lines
97 static inline QStringList commandOutputLinesFromLocal8Bit(const QByteArray &a)
98 {
99     QString output = commandOutputFromLocal8Bit(a);
100     const QChar newLine = QLatin1Char('\n');
101     if (output.endsWith(newLine))
102         output.truncate(output.size() - 1);
103     if (output.isEmpty())
104         return QStringList();
105     return output.split(newLine);
106 }
107
108 static inline VCSBase::VCSBaseOutputWindow *outputWindow()
109 {
110     return VCSBase::VCSBaseOutputWindow::instance();
111 }
112
113 namespace Git {
114 namespace Internal {
115
116 static inline QString msgRepositoryNotFound(const QString &dir)
117 {
118     return GitClient::tr("Unable to determine the repository for %1.").arg(dir);
119 }
120
121 static inline QString msgParseFilesFailed()
122 {
123     return  GitClient::tr("Unable to parse the file output.");
124 }
125
126 // ---------------- GitClient
127
128 const char *GitClient::stashNamePrefix = "stash@{";
129
130 GitClient::GitClient(GitPlugin* plugin)
131   : m_msgWait(tr("Waiting for data...")),
132     m_plugin(plugin),
133     m_core(Core::ICore::instance()),
134     m_repositoryChangedSignalMapper(0),
135     m_cachedGitVersion(0),
136     m_hasCachedGitVersion(false)
137 {
138     if (QSettings *s = m_core->settings()) {
139         m_settings.fromSettings(s);
140         m_binaryPath = m_settings.gitBinaryPath();
141     }
142 }
143
144 GitClient::~GitClient()
145 {
146 }
147
148 const char *GitClient::noColorOption = "--no-color";
149
150 QString GitClient::findRepositoryForDirectory(const QString &dir)
151 {
152     // Check for ".git/config"
153     const QString checkFile = QLatin1String(kGitDirectoryC) + QLatin1String("/config");
154     return VCSBase::VCSBasePlugin::findRepositoryForDirectory(dir, checkFile);
155 }
156
157 /* Create an editor associated to VCS output of a source file/directory
158  * (using the file's codec). Makes use of a dynamic property to find an
159  * existing instance and to reuse it (in case, say, 'git diff foo' is
160  * already open). */
161 VCSBase::VCSBaseEditor
162     *GitClient::createVCSEditor(const QString &id,
163                                 QString title,
164                                 // Source file or directory
165                                 const QString &source,
166                                 bool setSourceCodec,
167                                 // Dynamic property and value to identify that editor
168                                 const char *registerDynamicProperty,
169                                 const QString &dynamicPropertyValue) const
170 {
171     VCSBase::VCSBaseEditor *rc = 0;
172     Core::IEditor* outputEditor = locateEditor(m_core, registerDynamicProperty, dynamicPropertyValue);
173     if (outputEditor) {
174          // Exists already
175         outputEditor->createNew(m_msgWait);
176         rc = VCSBase::VCSBaseEditor::getVcsBaseEditor(outputEditor);
177         QTC_ASSERT(rc, return 0);
178     } else {
179         // Create new, set wait message, set up with source and codec
180         outputEditor = m_core->editorManager()->openEditorWithContents(id, &title, m_msgWait);
181         outputEditor->file()->setProperty(registerDynamicProperty, dynamicPropertyValue);
182         rc = VCSBase::VCSBaseEditor::getVcsBaseEditor(outputEditor);
183         connect(rc, SIGNAL(annotateRevisionRequested(QString,QString,int)),
184                 this, SLOT(slotBlameRevisionRequested(QString,QString,int)));
185         QTC_ASSERT(rc, return 0);
186         rc->setSource(source);
187         if (setSourceCodec)
188             rc->setCodec(VCSBase::VCSBaseEditor::getCodec(source));
189     }
190     m_core->editorManager()->activateEditor(outputEditor);
191     rc->setForceReadOnly(true);
192     return rc;
193 }
194
195 void GitClient::diff(const QString &workingDirectory,
196                      const QStringList &diffArgs,
197                      const QStringList &unstagedFileNames,
198                      const QStringList &stagedFileNames)
199 {
200
201     if (Git::Constants::debug)
202         qDebug() << "diff" << workingDirectory << unstagedFileNames << stagedFileNames;
203
204     const QString binary = QLatin1String(Constants::GIT_BINARY);
205     const QString editorId = QLatin1String(Git::Constants::GIT_DIFF_EDITOR_ID);
206     const QString title = tr("Git Diff");
207
208     VCSBase::VCSBaseEditor *editor = createVCSEditor(editorId, title, workingDirectory, true, "originalFileName", workingDirectory);
209     editor->setDiffBaseDirectory(workingDirectory);
210
211     // Create a batch of 2 commands to be run after each other in case
212     // we have a mixture of staged/unstaged files as is the case
213     // when using the submit dialog.
214     GitCommand *command = createCommand(workingDirectory, editor);
215     // Directory diff?
216     QStringList commonDiffArgs;
217     commonDiffArgs << QLatin1String("diff") << QLatin1String(noColorOption);
218     if (m_settings.diffPatience)
219         commonDiffArgs << QLatin1String("--patience");
220     if (unstagedFileNames.empty() && stagedFileNames.empty()) {
221        QStringList arguments(commonDiffArgs);
222        arguments << diffArgs;
223        outputWindow()->appendCommand(workingDirectory, binary, arguments);
224        command->addJob(arguments, m_settings.timeoutSeconds);
225     } else {
226         // Files diff.
227         if (!unstagedFileNames.empty()) {
228            QStringList arguments(commonDiffArgs);
229            arguments << QLatin1String("--") << unstagedFileNames;
230            outputWindow()->appendCommand(workingDirectory, binary, arguments);
231            command->addJob(arguments, m_settings.timeoutSeconds);
232         }
233         if (!stagedFileNames.empty()) {
234            QStringList arguments(commonDiffArgs);
235            arguments << QLatin1String("--cached") << diffArgs << QLatin1String("--") << stagedFileNames;
236            outputWindow()->appendCommand(workingDirectory, binary, arguments);
237            command->addJob(arguments, m_settings.timeoutSeconds);
238         }
239     }
240     command->execute();
241 }
242
243 void GitClient::diff(const QString &workingDirectory,
244                      const QStringList &diffArgs,
245                      const QString &fileName)
246 {
247     if (Git::Constants::debug)
248         qDebug() << "diff" << workingDirectory << fileName;
249     QStringList arguments;
250     arguments << QLatin1String("diff") << QLatin1String(noColorOption)
251               << diffArgs;
252     if (!fileName.isEmpty())
253         arguments << QLatin1String("--") << fileName;
254     const QString editorId = QLatin1String(Git::Constants::GIT_DIFF_EDITOR_ID);
255     const QString title = tr("Git Diff %1").arg(fileName);
256     const QString sourceFile = VCSBase::VCSBaseEditor::getSource(workingDirectory, fileName);
257     VCSBase::VCSBaseEditor *editor = createVCSEditor(editorId, title, sourceFile, true, "originalFileName", sourceFile);
258     executeGit(workingDirectory, arguments, editor);
259 }
260
261 void GitClient::diffBranch(const QString &workingDirectory,
262                            const QStringList &diffArgs,
263                            const QString &branchName)
264 {
265     if (Git::Constants::debug)
266         qDebug() << "diffBranch" << workingDirectory << branchName;
267     QStringList arguments;
268     arguments << QLatin1String("diff") << QLatin1String(noColorOption)
269               << diffArgs  << branchName;
270
271     const QString editorId = QLatin1String(Git::Constants::GIT_DIFF_EDITOR_ID);
272     const QString title = tr("Git Diff Branch %1").arg(branchName);
273     const QString sourceFile = VCSBase::VCSBaseEditor::getSource(workingDirectory, QStringList());
274     VCSBase::VCSBaseEditor *editor = createVCSEditor(editorId, title, sourceFile, true,
275                                                      "BranchName", branchName);
276     executeGit(workingDirectory, arguments, editor);
277 }
278
279 void GitClient::status(const QString &workingDirectory)
280 {
281     // @TODO: Use "--no-color" once it is supported
282     QStringList statusArgs(QLatin1String("status"));
283     statusArgs << QLatin1String("-u");
284     VCSBase::VCSBaseOutputWindow *outwin = outputWindow();
285     outwin->setRepository(workingDirectory);
286     GitCommand *command = executeGit(workingDirectory, statusArgs, 0, true);
287     connect(command, SIGNAL(finished(bool,int,QVariant)), outwin, SLOT(clearRepository()),
288             Qt::QueuedConnection);
289 }
290
291 static const char graphLogFormatC[] = "%h %an %s %ci";
292
293 // Create a graphical log.
294 void GitClient::graphLog(const QString &workingDirectory)
295 {
296     if (Git::Constants::debug)
297         qDebug() << "log" << workingDirectory;
298
299     QStringList arguments;
300     arguments << QLatin1String("log") << QLatin1String(noColorOption);
301
302     if (m_settings.logCount > 0)
303          arguments << QLatin1String("-n") << QString::number(m_settings.logCount);
304     arguments << (QLatin1String("--pretty=format:") +  QLatin1String(graphLogFormatC))
305               << QLatin1String("--topo-order") <<  QLatin1String("--graph");
306
307     const QString title = tr("Git Log");
308     const QString editorId = QLatin1String(Git::Constants::GIT_LOG_EDITOR_ID);
309     const QString sourceFile = VCSBase::VCSBaseEditor::getSource(workingDirectory, QStringList());
310     VCSBase::VCSBaseEditor *editor = createVCSEditor(editorId, title, sourceFile, false, "logFileName", sourceFile);
311     executeGit(workingDirectory, arguments, editor);
312 }
313
314 void GitClient::log(const QString &workingDirectory, const QStringList &fileNames, bool enableAnnotationContextMenu)
315 {
316     if (Git::Constants::debug)
317         qDebug() << "log" << workingDirectory << fileNames;
318
319     QStringList arguments;
320     arguments << QLatin1String("log") << QLatin1String(noColorOption);
321
322     if (m_settings.logCount > 0)
323          arguments << QLatin1String("-n") << QString::number(m_settings.logCount);
324
325     if (!fileNames.isEmpty())
326         arguments.append(fileNames);
327
328     const QString msgArg = fileNames.empty() ? workingDirectory :
329                            fileNames.join(QString(", "));
330     const QString title = tr("Git Log %1").arg(msgArg);
331     const QString editorId = QLatin1String(Git::Constants::GIT_LOG_EDITOR_ID);
332     const QString sourceFile = VCSBase::VCSBaseEditor::getSource(workingDirectory, fileNames);
333     VCSBase::VCSBaseEditor *editor = createVCSEditor(editorId, title, sourceFile, false, "logFileName", sourceFile);
334     editor->setFileLogAnnotateEnabled(enableAnnotationContextMenu);
335     executeGit(workingDirectory, arguments, editor);
336 }
337
338 // Do not show "0000" or "^32ae4"
339 static inline bool canShow(const QString &sha)
340 {
341     if (sha.startsWith(QLatin1Char('^')))
342         return false;
343     if (sha.count(QLatin1Char('0')) == sha.size())
344         return false;
345     return true;
346 }
347
348 static inline QString msgCannotShow(const QString &sha)
349 {
350     return GitClient::tr("Cannot describe '%1'.").arg(sha);
351 }
352
353 void GitClient::show(const QString &source, const QString &id)
354 {
355     if (Git::Constants::debug)
356         qDebug() << "show" << source << id;
357     if (!canShow(id)) {
358         outputWindow()->append(msgCannotShow(id));
359         return;
360     }
361
362     QStringList arguments;
363     arguments << QLatin1String("show") << QLatin1String(noColorOption) << id;
364
365     const QString title =  tr("Git Show %1").arg(id);
366     const QString editorId = QLatin1String(Git::Constants::GIT_DIFF_EDITOR_ID);
367     VCSBase::VCSBaseEditor *editor = createVCSEditor(editorId, title, source, true, "show", id);
368
369     const QFileInfo sourceFi(source);
370     const QString workDir = sourceFi.isDir() ? sourceFi.absoluteFilePath() : sourceFi.absolutePath();
371     executeGit(workDir, arguments, editor);
372 }
373
374 void GitClient::slotBlameRevisionRequested(const QString &source, QString change, int lineNumber)
375 {
376     // This might be invoked with a verbose revision description
377     // "SHA1 author subject" from the annotation context menu. Strip the rest.
378     const int blankPos = change.indexOf(QLatin1Char(' '));
379     if (blankPos != -1)
380         change.truncate(blankPos);
381     const QFileInfo fi(source);
382     blame(fi.absolutePath(), fi.fileName(), change, lineNumber);
383 }
384
385 void GitClient::blame(const QString &workingDirectory,
386                       const QString &fileName,
387                       const QString &revision /* = QString() */,
388                       int lineNumber /* = -1 */)
389 {
390     if (Git::Constants::debug)
391         qDebug() << "blame" << workingDirectory << fileName << lineNumber;
392     QStringList arguments(QLatin1String("blame"));
393     arguments << QLatin1String("--root");
394     if (m_plugin->settings().spaceIgnorantBlame)
395         arguments << QLatin1String("-w");
396     arguments << QLatin1String("--") << fileName;
397     if (!revision.isEmpty())
398         arguments << revision;
399     const QString editorId = QLatin1String(Git::Constants::GIT_BLAME_EDITOR_ID);
400     const QString id = VCSBase::VCSBaseEditor::getTitleId(workingDirectory, QStringList(fileName), revision);
401     const QString title = tr("Git Blame %1").arg(id);
402     const QString sourceFile = VCSBase::VCSBaseEditor::getSource(workingDirectory, fileName);
403
404     VCSBase::VCSBaseEditor *editor = createVCSEditor(editorId, title, sourceFile, true, "blameFileName", id);
405     executeGit(workingDirectory, arguments, editor, false, GitCommand::NoReport, lineNumber);
406 }
407
408 void GitClient::checkoutBranch(const QString &workingDirectory, const QString &branch)
409 {
410     QStringList arguments(QLatin1String("checkout"));
411     arguments <<  branch;
412     GitCommand *cmd = executeGit(workingDirectory, arguments, 0, true);
413     connectRepositoryChanged(workingDirectory, cmd);
414 }
415
416 bool GitClient::synchronousCheckoutBranch(const QString &workingDirectory,
417                                     const QString &branch,
418                                     QString *errorMessage /* = 0 */)
419 {
420     QByteArray outputText;
421     QByteArray errorText;
422     QStringList arguments;
423     arguments << QLatin1String("checkout") << branch;
424     const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
425     const QString output = commandOutputFromLocal8Bit(outputText);
426     outputWindow()->append(output);
427     if (!rc) {
428         const QString stdErr = commandOutputFromLocal8Bit(errorText);
429         //: Meaning of the arguments: %1: Branch, %2: Repository, %3: Error message
430         const QString msg = tr("Unable to checkout %1 of %2: %3").arg(branch, workingDirectory, stdErr);
431         if (errorMessage) {
432             *errorMessage = msg;
433         } else {
434             outputWindow()->appendError(msg);
435         }
436         return false;
437     }
438     return true;
439 }
440
441 void GitClient::checkout(const QString &workingDirectory, const QString &fileName)
442 {
443     // Passing an empty argument as the file name is very dangereous, since this makes
444     // git checkout apply to all files. Almost looks like a bug in git.
445     if (fileName.isEmpty())
446         return;
447
448     QStringList arguments;
449     arguments << QLatin1String("checkout") << QLatin1String("HEAD") << QLatin1String("--")
450             << fileName;
451
452     executeGit(workingDirectory, arguments, 0, true);
453 }
454
455 void GitClient::hardReset(const QString &workingDirectory, const QString &commit)
456 {
457     QStringList arguments;
458     arguments << QLatin1String("reset") << QLatin1String("--hard");
459     if (!commit.isEmpty())
460         arguments << commit;
461
462     GitCommand *cmd = executeGit(workingDirectory, arguments, 0, true);
463     connectRepositoryChanged(workingDirectory, cmd);
464 }
465
466 void GitClient::addFile(const QString &workingDirectory, const QString &fileName)
467 {
468     QStringList arguments;
469     arguments << QLatin1String("add") << fileName;
470
471     executeGit(workingDirectory, arguments, 0, true);
472 }
473
474 // Warning: 'intendToAdd' works only from 1.6.1 onwards
475 bool GitClient::synchronousAdd(const QString &workingDirectory,
476                                bool intendToAdd,
477                                const QStringList &files)
478 {
479     if (Git::Constants::debug)
480         qDebug() << Q_FUNC_INFO << workingDirectory << files;
481     QByteArray outputText;
482     QByteArray errorText;
483     QStringList arguments;
484     arguments << QLatin1String("add");
485     if (intendToAdd)
486         arguments << QLatin1String("--intent-to-add");
487     arguments.append(files);
488     const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
489     if (!rc) {
490         const QString errorMessage = tr("Unable to add %n file(s) to %1: %2", 0, files.size()).
491                                      arg(workingDirectory, commandOutputFromLocal8Bit(errorText));
492         outputWindow()->appendError(errorMessage);
493     }
494     return rc;
495 }
496
497 bool GitClient::synchronousDelete(const QString &workingDirectory,
498                                   bool force,
499                                   const QStringList &files)
500 {
501     if (Git::Constants::debug)
502         qDebug() << Q_FUNC_INFO << workingDirectory << files;
503     QByteArray outputText;
504     QByteArray errorText;
505     QStringList arguments;
506     arguments << QLatin1String("rm");
507     if (force)
508         arguments << QLatin1String("--force");
509     arguments.append(files);
510     const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
511     if (!rc) {
512         const QString errorMessage = tr("Unable to remove %n file(s) from %1: %2", 0, files.size()).
513                                      arg(workingDirectory, commandOutputFromLocal8Bit(errorText));
514         outputWindow()->appendError(errorMessage);
515     }
516     return rc;
517 }
518
519 bool GitClient::synchronousMove(const QString &workingDirectory,
520                                 const QString &from,
521                                 const QString &to)
522 {
523     if (Git::Constants::debug)
524         qDebug() << Q_FUNC_INFO << workingDirectory << from << to;
525     QByteArray outputText;
526     QByteArray errorText;
527     QStringList arguments;
528     arguments << QLatin1String("mv");
529     arguments << (from);
530     arguments << (to);
531     const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
532     if (!rc) {
533         const QString errorMessage = tr("Unable to move from %1 to %2: %3").
534                                      arg(from, to, commandOutputFromLocal8Bit(errorText));
535         outputWindow()->appendError(errorMessage);
536     }
537     return rc;
538 }
539
540 bool GitClient::synchronousReset(const QString &workingDirectory,
541                                  const QStringList &files,
542                                  QString *errorMessage)
543 {
544     if (Git::Constants::debug)
545         qDebug() << Q_FUNC_INFO << workingDirectory << files;
546     QByteArray outputText;
547     QByteArray errorText;
548     QStringList arguments;
549     arguments << QLatin1String("reset");
550     if (files.isEmpty()) {
551         arguments << QLatin1String("--hard");
552     } else {
553         arguments << QLatin1String("HEAD") << QLatin1String("--") << files;
554     }
555     const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
556     const QString output = commandOutputFromLocal8Bit(outputText);
557     outputWindow()->append(output);
558     // Note that git exits with 1 even if the operation is successful
559     // Assume real failure if the output does not contain "foo.cpp modified"
560     // or "Unstaged changes after reset" (git 1.7.0).
561     if (!rc &&
562         (!output.contains(QLatin1String("modified"))
563          && !output.contains(QLatin1String("Unstaged changes after reset")))) {
564         const QString stdErr = commandOutputFromLocal8Bit(errorText);
565         const QString msg = files.isEmpty() ?
566                             tr("Unable to reset %1: %2").arg(workingDirectory, stdErr) :
567                             tr("Unable to reset %n file(s) in %1: %2", 0, files.size()).arg(workingDirectory, stdErr);
568         if (errorMessage) {
569             *errorMessage = msg;
570         } else {
571             outputWindow()->appendError(msg);
572         }
573         return false;
574     }
575     return true;
576 }
577
578 // Initialize repository
579 bool GitClient::synchronousInit(const QString &workingDirectory)
580 {
581     if (Git::Constants::debug)
582         qDebug() << Q_FUNC_INFO << workingDirectory;
583     QByteArray outputText;
584     QByteArray errorText;
585     const QStringList arguments(QLatin1String("init"));
586     const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
587     // '[Re]Initialized...'
588     outputWindow()->append(commandOutputFromLocal8Bit(outputText));
589     if (!rc)
590         outputWindow()->appendError(commandOutputFromLocal8Bit(errorText));
591     return rc;
592 }
593
594 /* Checkout, supports:
595  * git checkout -- <files>
596  * git checkout revision -- <files>
597  * git checkout revision -- . */
598 bool GitClient::synchronousCheckoutFiles(const QString &workingDirectory,
599                                          QStringList files /* = QStringList() */,
600                                          QString revision /* = QString() */,
601                                          QString *errorMessage /* = 0 */)
602 {
603     if (Git::Constants::debug)
604         qDebug() << Q_FUNC_INFO << workingDirectory << files;
605     if (revision.isEmpty())
606         revision = QLatin1String("HEAD");
607     if (files.isEmpty())
608         files = QStringList(QString(QLatin1Char('.')));
609     QByteArray outputText;
610     QByteArray errorText;
611     QStringList arguments;
612     arguments << QLatin1String("checkout") << revision << QLatin1String("--") << files;
613     const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
614     if (!rc) {
615         const QString fileArg = files.join(QLatin1String(", "));
616         //: Meaning of the arguments: %1: revision, %2: files, %3: repository,
617         //: %4: Error message
618         const QString msg = tr("Unable to checkout %1 of %2 in %3: %4").
619                             arg(revision, fileArg, workingDirectory, commandOutputFromLocal8Bit(errorText));
620         if (errorMessage) {
621             *errorMessage = msg;
622         } else {
623             outputWindow()->appendError(msg);
624         }
625         return false;
626     }
627     return true;
628 }
629
630 static inline QString msgParentRevisionFailed(const QString &workingDirectory,
631                                               const QString &revision,
632                                               const QString &why)
633 {
634     //: Failed to find parent revisions of a SHA1 for "annotate previous"
635     return GitClient::tr("Unable to find parent revisions of %1 in %2: %3").arg(revision, workingDirectory, why);
636 }
637
638 static inline QString msgInvalidRevision()
639 {
640     return GitClient::tr("Invalid revision");
641 }
642
643 // Split a line of "<commit> <parent1> ..." to obtain parents from "rev-list" or "log".
644 static inline bool splitCommitParents(const QString &line,
645                                       QString *commit = 0,
646                                       QStringList *parents = 0)
647 {
648     if (commit)
649         commit->clear();
650     if (parents)
651         parents->clear();
652     QStringList tokens = line.trimmed().split(QLatin1Char(' '));
653     if (tokens.size() < 2)
654         return false;
655     if (commit)
656         *commit = tokens.front();
657     tokens.pop_front();
658     if (parents)
659         *parents = tokens;
660     return true;
661 }
662
663 // Find out the immediate parent revisions of a revision of the repository.
664 // Might be several in case of merges.
665 bool GitClient::synchronousParentRevisions(const QString &workingDirectory,
666                                            const QStringList &files /* = QStringList() */,
667                                            const QString &revision,
668                                            QStringList *parents,
669                                            QString *errorMessage)
670 {
671     if (Git::Constants::debug)
672         qDebug() << Q_FUNC_INFO << workingDirectory << revision;
673     QByteArray outputTextData;
674     QByteArray errorText;
675     QStringList arguments;
676     arguments << QLatin1String("rev-list") << QLatin1String(GitClient::noColorOption)
677               << QLatin1String("--parents") << QLatin1String("--max-count=1") << revision;
678     if (!files.isEmpty()) {
679         arguments.append(QLatin1String("--"));
680         arguments.append(files);
681     }
682     const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputTextData, &errorText);
683     if (!rc) {
684         *errorMessage = msgParentRevisionFailed(workingDirectory, revision, commandOutputFromLocal8Bit(errorText));
685         return false;
686     }
687     // Should result in one line of blank-delimited revisions, specifying current first
688     // unless it is top.
689     QString outputText = commandOutputFromLocal8Bit(outputTextData);
690     outputText.remove(QLatin1Char('\n'));
691     if (!splitCommitParents(outputText, 0, parents)) {
692         *errorMessage = msgParentRevisionFailed(workingDirectory, revision, msgInvalidRevision());
693         return false;
694     }
695     if (Git::Constants::debug)
696         qDebug() << workingDirectory << files << revision << "->" << *parents;
697     return true;
698 }
699
700 // Short SHA1, author, subject
701 static const char defaultShortLogFormatC[] = "%h (%an \"%s\")";
702
703 bool GitClient::synchronousShortDescription(const QString &workingDirectory, const QString &revision,
704                                     QString *description, QString *errorMessage)
705 {
706     // Short SHA 1, author, subject
707     return synchronousShortDescription(workingDirectory, revision,
708                                QLatin1String(defaultShortLogFormatC),
709                                description, errorMessage);
710 }
711
712 // Convenience working on a list of revisions
713 bool GitClient::synchronousShortDescriptions(const QString &workingDirectory, const QStringList &revisions,
714                                             QStringList *descriptions, QString *errorMessage)
715 {
716     descriptions->clear();
717     foreach (const QString &revision, revisions) {
718         QString description;
719         if (!synchronousShortDescription(workingDirectory, revision, &description, errorMessage)) {
720             descriptions->clear();
721             return false;
722         }
723         descriptions->push_back(description);
724     }
725     return true;
726 }
727
728 static inline QString msgCannotDetermineBranch(const QString &workingDirectory, const QString &why)
729 {
730     return GitClient::tr("Unable to retrieve branch of %1: %2").arg(workingDirectory, why);
731 }
732
733 // Retrieve head revision/branch
734 bool GitClient::synchronousTopRevision(const QString &workingDirectory,
735                                        QString *revision /* = 0 */,
736                                        QString *branch /* = 0 */,
737                                        QString *errorMessageIn /* = 0 */)
738 {
739     if (Git::Constants::debug)
740         qDebug() << Q_FUNC_INFO << workingDirectory;
741     QByteArray outputTextData;
742     QByteArray errorText;
743     QStringList arguments;
744     QString errorMessage;
745     do {
746         // get revision
747         if (revision) {
748             revision->clear();
749             arguments << QLatin1String("log") << QLatin1String(noColorOption)
750                     <<  QLatin1String("--max-count=1") << QLatin1String("--pretty=format:%H");
751             if (!fullySynchronousGit(workingDirectory, arguments, &outputTextData, &errorText)) {
752                 errorMessage =  tr("Unable to retrieve top revision of %1: %2").arg(workingDirectory, commandOutputFromLocal8Bit(errorText));
753                 break;
754             }
755             *revision = commandOutputFromLocal8Bit(outputTextData);
756             revision->remove(QLatin1Char('\n'));
757         } // revision desired
758         // get branch
759         if (branch) {
760             branch->clear();
761             arguments.clear();
762             arguments << QLatin1String("branch") << QLatin1String(noColorOption);
763             if (!fullySynchronousGit(workingDirectory, arguments, &outputTextData, &errorText)) {
764                 errorMessage = msgCannotDetermineBranch(workingDirectory, commandOutputFromLocal8Bit(errorText));
765                 break;
766             }
767             /* parse output for current branch: \code
768 * master
769   branch2
770 \endcode */
771             const QString branchPrefix = QLatin1String("* ");
772             foreach(const QString &line, commandOutputLinesFromLocal8Bit(outputTextData)) {
773                 if (line.startsWith(branchPrefix)) {
774                     *branch = line;
775                     branch->remove(0, branchPrefix.size());
776                     break;
777                 }
778             }
779             if (branch->isEmpty()) {
780                 errorMessage = msgCannotDetermineBranch(workingDirectory,
781                                                         QString::fromLatin1("Internal error: Failed to parse output: %1").arg(commandOutputFromLocal8Bit(outputTextData)));
782                 break;
783             }
784         } // branch
785     } while (false);
786     const bool failed = (revision && revision->isEmpty()) || (branch && branch->isEmpty());
787     if (failed && !errorMessage.isEmpty()) {
788         if (errorMessageIn) {
789             *errorMessageIn = errorMessage;
790         } else {
791             outputWindow()->appendError(errorMessage);
792         }
793     }
794     return !failed;
795 }
796
797 // Format an entry in a one-liner for selection list using git log.
798 bool GitClient::synchronousShortDescription(const QString &workingDirectory,
799                                     const QString &revision,
800                                     const QString &format,
801                                     QString *description,
802                                     QString *errorMessage)
803 {
804     if (Git::Constants::debug)
805         qDebug() << Q_FUNC_INFO << workingDirectory << revision;
806     QByteArray outputTextData;
807     QByteArray errorText;
808     QStringList arguments;
809     arguments << QLatin1String("log") << QLatin1String(GitClient::noColorOption)
810               << (QLatin1String("--pretty=format:") + format)
811               << QLatin1String("--max-count=1") << revision;
812     const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputTextData, &errorText);
813     if (!rc) {
814         *errorMessage = tr("Unable to describe revision %1 in %2: %3").arg(revision, workingDirectory, commandOutputFromLocal8Bit(errorText));
815         return false;
816     }
817     *description = commandOutputFromLocal8Bit(outputTextData);
818     if (description->endsWith(QLatin1Char('\n')))
819         description->truncate(description->size() - 1);
820     return true;
821 }
822
823 // Create a default message to be used for describing stashes
824 static inline QString creatorStashMessage(const QString &keyword = QString())
825 {
826     QString rc = QCoreApplication::applicationName();
827     rc += QLatin1Char(' ');
828     if (!keyword.isEmpty()) {
829         rc += keyword;
830         rc += QLatin1Char(' ');
831     }
832     rc += QDateTime::currentDateTime().toString(Qt::ISODate);
833     return rc;
834 }
835
836 /* Do a stash and return the message as identifier. Note that stash names (stash{n})
837  * shift as they are pushed, so, enforce the use of messages to identify them. Flags:
838  * StashPromptDescription: Prompt the user for a description message.
839  * StashImmediateRestore: Immediately re-apply this stash (used for snapshots), user keeps on working
840  * StashIgnoreUnchanged: Be quiet about unchanged repositories (used for IVersionControl's snapshots). */
841
842 QString GitClient::synchronousStash(const QString &workingDirectory,
843                                     const QString &messageKeyword /*  = QString() */,
844                                     unsigned flags,
845                                     bool *unchanged /* =0 */)
846 {
847     if (unchanged)
848         *unchanged = false;
849     QString message;
850     bool success = false;
851     // Check for changes and stash
852     QString errorMessage;
853     switch (gitStatus(workingDirectory, false, 0, &errorMessage)) {
854     case  StatusChanged: {
855             message = creatorStashMessage(messageKeyword);
856             do {
857                 if ((flags & StashPromptDescription)) {
858                     if (!inputText(Core::ICore::instance()->mainWindow(),
859                          tr("Stash Description"), tr("Description:"), &message))
860                         break;
861                 }
862                 if (!executeSynchronousStash(workingDirectory, message))
863                     break;
864                 if ((flags & StashImmediateRestore)
865                     && !synchronousStashRestore(workingDirectory, QLatin1String("stash@{0}")))
866                     break;
867                 success = true;
868             } while (false);
869         }
870         break;
871     case StatusUnchanged:
872         if (unchanged)
873             *unchanged = true;
874         if (!(flags & StashIgnoreUnchanged))
875             outputWindow()->append(msgNoChangedFiles());
876         break;
877     case StatusFailed:
878         outputWindow()->append(errorMessage);
879         break;
880     }
881     if (!success)
882         message.clear();
883     if (Git::Constants::debug)
884         qDebug() << Q_FUNC_INFO << '\n' << workingDirectory << messageKeyword << "returns" << message;
885     return message;
886 }
887
888 bool GitClient::executeSynchronousStash(const QString &workingDirectory,
889                                  const QString &message,
890                                  QString *errorMessage /* = 0*/)
891 {
892     if (Git::Constants::debug)
893         qDebug() << Q_FUNC_INFO << workingDirectory;
894     QByteArray outputText;
895     QByteArray errorText;
896     QStringList arguments;
897     arguments << QLatin1String("stash");
898     if (!message.isEmpty())
899         arguments << QLatin1String("save") << message;
900     const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
901     if (!rc) {
902         const QString msg = tr("Unable stash in %1: %2").arg(workingDirectory, commandOutputFromLocal8Bit(errorText));
903         if (errorMessage) {
904             *errorMessage = msg;
905         } else {
906             outputWindow()->append(msg);
907         }
908         return false;
909     }
910     return true;
911 }
912
913 // Resolve a stash name from message
914 bool GitClient::stashNameFromMessage(const QString &workingDirectory,
915                                      const QString &message, QString *name,
916                                      QString *errorMessage /* = 0 */)
917 {
918     // All happy
919     if (message.startsWith(QLatin1String(stashNamePrefix))) {
920         *name = message;
921         return true;
922     }
923     // Retrieve list and find via message
924     QList<Stash> stashes;
925     if (!synchronousStashList(workingDirectory, &stashes, errorMessage))
926         return false;
927     foreach (const Stash &s, stashes) {
928         if (s.message == message) {
929             *name = s.name;
930             return true;
931         }
932     }
933     //: Look-up of a stash via its descriptive message failed.
934     const QString msg = tr("Unable to resolve stash message '%1' in %2").arg(message, workingDirectory);
935     if (errorMessage) {
936         *errorMessage = msg;
937     } else {
938         outputWindow()->append(msg);
939     }
940     return  false;
941 }
942
943 bool GitClient::synchronousBranchCmd(const QString &workingDirectory, QStringList branchArgs,
944                                      QString *output, QString *errorMessage)
945 {
946     if (Git::Constants::debug)
947         qDebug() << Q_FUNC_INFO << workingDirectory << branchArgs;
948     branchArgs.push_front(QLatin1String("branch"));
949     QByteArray outputText;
950     QByteArray errorText;
951     const bool rc = fullySynchronousGit(workingDirectory, branchArgs, &outputText, &errorText);
952     if (!rc) {
953         *errorMessage = tr("Unable to run a 'git branch' command in %1: %2").arg(workingDirectory, commandOutputFromLocal8Bit(errorText));
954         return false;
955     }
956     *output = commandOutputFromLocal8Bit(outputText);
957     return true;
958 }
959
960 bool GitClient::synchronousShow(const QString &workingDirectory, const QString &id,
961                                  QString *output, QString *errorMessage)
962 {
963     if (Git::Constants::debug)
964         qDebug() << Q_FUNC_INFO << workingDirectory << id;
965     if (!canShow(id)) {
966         *errorMessage = msgCannotShow(id);
967         return false;
968     }
969     QStringList args(QLatin1String("show"));
970     args << QLatin1String(noColorOption) << id;
971     QByteArray outputText;
972     QByteArray errorText;
973     const bool rc = fullySynchronousGit(workingDirectory, args, &outputText, &errorText);
974     if (!rc) {
975         *errorMessage = tr("Unable to run 'git show' in %1: %2").arg(workingDirectory, commandOutputFromLocal8Bit(errorText));
976         return false;
977     }
978     *output = commandOutputFromLocal8Bit(outputText);
979     return true;
980 }
981
982 // Retrieve list of files to be cleaned
983 bool GitClient::synchronousCleanList(const QString &workingDirectory,
984                                      QStringList *files, QString *errorMessage)
985 {
986     if (Git::Constants::debug)
987         qDebug() << Q_FUNC_INFO << workingDirectory;
988     files->clear();
989     QStringList args;
990     args << QLatin1String("clean") << QLatin1String("--dry-run") << QLatin1String("-dxf");
991     QByteArray outputText;
992     QByteArray errorText;
993     const bool rc = fullySynchronousGit(workingDirectory, args, &outputText, &errorText);
994     if (!rc) {
995         *errorMessage = tr("Unable to run 'git clean' in %1: %2").arg(workingDirectory, commandOutputFromLocal8Bit(errorText));
996         return false;
997     }
998     // Filter files that git would remove
999     const QString prefix = QLatin1String("Would remove ");
1000     foreach(const QString &line, commandOutputLinesFromLocal8Bit(outputText))
1001         if (line.startsWith(prefix))
1002             files->push_back(line.mid(prefix.size()));
1003     return true;
1004 }
1005
1006 bool GitClient::synchronousApplyPatch(const QString &workingDirectory,
1007                                       const QString &file, QString *errorMessage)
1008 {
1009     if (Git::Constants::debug)
1010         qDebug() << Q_FUNC_INFO << workingDirectory;
1011     QStringList args;
1012     args << QLatin1String("apply") << QLatin1String("--whitespace=fix") << file;
1013     QByteArray outputText;
1014     QByteArray errorText;
1015     const bool rc = fullySynchronousGit(workingDirectory, args, &outputText, &errorText);
1016     if (rc) {
1017         if (!errorText.isEmpty())
1018             *errorMessage = tr("There were warnings while applying %1 to %2:\n%3").arg(file, workingDirectory, commandOutputFromLocal8Bit(errorText));
1019     } else {
1020         *errorMessage = tr("Unable apply patch %1 to %2: %3").arg(file, workingDirectory, commandOutputFromLocal8Bit(errorText));
1021         return false;
1022     }
1023     return true;
1024 }
1025
1026 // Factory function to create an asynchronous command
1027 GitCommand *GitClient::createCommand(const QString &workingDirectory,
1028                              VCSBase::VCSBaseEditor* editor,
1029                              bool outputToWindow,
1030                              int editorLineNumber)
1031 {
1032     if (Git::Constants::debug)
1033         qDebug() << Q_FUNC_INFO << workingDirectory << editor;
1034
1035     VCSBase::VCSBaseOutputWindow *outputWindow = VCSBase::VCSBaseOutputWindow::instance();
1036     GitCommand* command = new GitCommand(binary(), workingDirectory, processEnvironment(), QVariant(editorLineNumber));
1037     if (editor)
1038         connect(command, SIGNAL(finished(bool,int,QVariant)), editor, SLOT(commandFinishedGotoLine(bool,int,QVariant)));
1039     if (outputToWindow) {
1040         if (editor) { // assume that the commands output is the important thing
1041             connect(command, SIGNAL(outputData(QByteArray)), outputWindow, SLOT(appendDataSilently(QByteArray)));
1042         } else {
1043             connect(command, SIGNAL(outputData(QByteArray)), outputWindow, SLOT(appendData(QByteArray)));
1044         }
1045     } else {
1046         QTC_ASSERT(editor, /**/);
1047         connect(command, SIGNAL(outputData(QByteArray)), editor, SLOT(setPlainTextDataFiltered(QByteArray)));
1048     }
1049
1050     if (outputWindow)
1051         connect(command, SIGNAL(errorText(QString)), outputWindow, SLOT(appendError(QString)));
1052     return command;
1053 }
1054
1055 // Execute a single command
1056 GitCommand *GitClient::executeGit(const QString &workingDirectory,
1057                                   const QStringList &arguments,
1058                                   VCSBase::VCSBaseEditor* editor,
1059                                   bool outputToWindow,
1060                                   GitCommand::TerminationReportMode tm,
1061                                   int editorLineNumber,
1062                                   bool unixTerminalDisabled)
1063 {
1064     outputWindow()->appendCommand(workingDirectory, QLatin1String(Constants::GIT_BINARY), arguments);
1065     GitCommand *command = createCommand(workingDirectory, editor, outputToWindow, editorLineNumber);
1066     command->addJob(arguments, m_settings.timeoutSeconds);
1067     command->setTerminationReportMode(tm);
1068     command->setUnixTerminalDisabled(unixTerminalDisabled);
1069     command->execute();
1070     return command;
1071 }
1072
1073 // Return fixed arguments required to run
1074 QStringList GitClient::binary() const
1075 {
1076 #ifdef Q_OS_WIN
1077         QStringList args;
1078         args << QLatin1String("cmd.exe") << QLatin1String("/c") << m_binaryPath;
1079         return args;
1080 #else
1081         return QStringList(m_binaryPath);
1082 #endif
1083 }
1084
1085 QProcessEnvironment GitClient::processEnvironment() const
1086 {
1087     QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
1088     if (m_settings.adoptPath)
1089         environment.insert(QLatin1String("PATH"), m_settings.path);
1090     // Set up SSH and C locale (required by git using perl).
1091     VCSBase::VCSBasePlugin::setProcessEnvironment(&environment, false);
1092     return environment;
1093 }
1094
1095 // Synchronous git execution using Utils::SynchronousProcess, with
1096 // log windows updating.
1097 Utils::SynchronousProcessResponse
1098         GitClient::synchronousGit(const QString &workingDirectory,
1099                                   const QStringList &gitArguments,
1100                                   unsigned flags,
1101                                   QTextCodec *stdOutCodec)
1102 {
1103     if (Git::Constants::debug)
1104         qDebug() << "synchronousGit" << workingDirectory << gitArguments;
1105     QStringList args = binary(); // "cmd /c git" on Windows
1106     const QString executable = args.front();
1107     args.pop_front();
1108     args.append(gitArguments);
1109     return VCSBase::VCSBasePlugin::runVCS(workingDirectory, executable, args,
1110                                           m_settings.timeoutSeconds * 1000,
1111                                           flags, stdOutCodec);
1112 }
1113
1114 bool GitClient::fullySynchronousGit(const QString &workingDirectory,
1115                                const QStringList &gitArguments,
1116                                QByteArray* outputText,
1117                                QByteArray* errorText,
1118                                bool logCommandToWindow)
1119 {
1120     if (Git::Constants::debug)
1121         qDebug() << "fullySynchronousGit" << workingDirectory << gitArguments;
1122
1123     if (logCommandToWindow)
1124         outputWindow()->appendCommand(workingDirectory, m_binaryPath, gitArguments);
1125
1126     QProcess process;
1127     process.setWorkingDirectory(workingDirectory);
1128     process.setProcessEnvironment(processEnvironment());
1129
1130     QStringList args = binary(); // "cmd /c git" on Windows
1131     const QString executable = args.front();
1132     args.pop_front();
1133     args.append(gitArguments);
1134     process.start(executable, args);
1135     process.closeWriteChannel();
1136     if (!process.waitForStarted()) {
1137         if (errorText) {
1138             const QString msg = QString::fromLatin1("Unable to execute '%1': %2:")
1139                                 .arg(binary().join(QString(QLatin1Char(' '))), process.errorString());
1140             *errorText = msg.toLocal8Bit();
1141         }
1142         return false;
1143     }
1144
1145     if (!Utils::SynchronousProcess::readDataFromProcess(process, m_settings.timeoutSeconds * 1000,
1146                                                         outputText, errorText, true)) {
1147         errorText->append(GitCommand::msgTimeout(m_settings.timeoutSeconds).toLocal8Bit());
1148         Utils::SynchronousProcess::stopProcess(process);
1149         return false;
1150     }
1151
1152     if (Git::Constants::debug)
1153         qDebug() << "synchronousGit ex=" << process.exitStatus() << process.exitCode();
1154     return process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0;
1155 }
1156
1157 static inline int
1158         askWithDetailedText(QWidget *parent,
1159                             const QString &title, const QString &msg,
1160                             const QString &inf,
1161                             QMessageBox::StandardButton defaultButton,
1162                             QMessageBox::StandardButtons buttons = QMessageBox::Yes|QMessageBox::No)
1163 {
1164     QMessageBox msgBox(QMessageBox::Question, title, msg, buttons, parent);
1165     msgBox.setDetailedText(inf);
1166     msgBox.setDefaultButton(defaultButton);
1167     return msgBox.exec();
1168 }
1169
1170 // Convenience that pops up an msg box.
1171 GitClient::StashResult GitClient::ensureStash(const QString &workingDirectory)
1172 {
1173     QString errorMessage;
1174     const StashResult sr = ensureStash(workingDirectory, &errorMessage);
1175     if (sr == StashFailed)
1176         outputWindow()->appendError(errorMessage);
1177     return sr;
1178 }
1179
1180 // Ensure that changed files are stashed before a pull or similar
1181 GitClient::StashResult GitClient::ensureStash(const QString &workingDirectory, QString *errorMessage)
1182 {
1183     QString statusOutput;
1184     switch (gitStatus(workingDirectory, false, &statusOutput, errorMessage)) {
1185         case StatusChanged:
1186         break;
1187         case StatusUnchanged:
1188         return StashUnchanged;
1189         case StatusFailed:
1190         return StashFailed;
1191     }
1192
1193     const int answer = askWithDetailedText(m_core->mainWindow(), tr("Changes"),
1194                              tr("You have modified files. Would you like to stash your changes?"),
1195                              statusOutput, QMessageBox::Yes, QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel);
1196     switch (answer) {
1197         case QMessageBox::Cancel:
1198             return StashCanceled;
1199         case QMessageBox::Yes:
1200             if (!executeSynchronousStash(workingDirectory, creatorStashMessage(QLatin1String("push")), errorMessage))
1201                 return StashFailed;
1202             break;
1203         case QMessageBox::No: // At your own risk, so.
1204             return NotStashed;
1205         }
1206
1207     return Stashed;
1208  }
1209
1210 // Trim a git status file spec: "modified:    foo .cpp" -> "modified: foo .cpp"
1211 static inline QString trimFileSpecification(QString fileSpec)
1212 {
1213     const int colonIndex = fileSpec.indexOf(QLatin1Char(':'));
1214     if (colonIndex != -1) {
1215         // Collapse the sequence of spaces
1216         const int filePos = colonIndex + 2;
1217         int nonBlankPos = filePos;
1218         for ( ; fileSpec.at(nonBlankPos).isSpace(); nonBlankPos++) ;
1219         if (nonBlankPos > filePos)
1220             fileSpec.remove(filePos, nonBlankPos - filePos);
1221     }
1222     return fileSpec;
1223 }
1224
1225 GitClient::StatusResult GitClient::gitStatus(const QString &workingDirectory,
1226                                              bool untracked,
1227                                              QString *output,
1228                                              QString *errorMessage,
1229                                              bool *onBranch)
1230 {
1231     // Run 'status'. Note that git returns exitcode 1 if there are no added files.
1232     QByteArray outputText;
1233     QByteArray errorText;
1234     // @TODO: Use "--no-color" once it is supported
1235     QStringList statusArgs(QLatin1String("status"));
1236     if (untracked)
1237         statusArgs << QLatin1String("-u");
1238     const bool statusRc = fullySynchronousGit(workingDirectory, statusArgs, &outputText, &errorText);
1239     GitCommand::removeColorCodes(&outputText);
1240     if (output)
1241         *output = commandOutputFromLocal8Bit(outputText);
1242     const bool branchKnown = outputText.contains(kBranchIndicatorC);
1243     if (onBranch)
1244         *onBranch = branchKnown;
1245     // Is it something really fatal?
1246     if (!statusRc && !branchKnown && !outputText.contains("# Not currently on any branch.")) {
1247         if (errorMessage) {
1248             const QString error = commandOutputFromLocal8Bit(errorText);
1249             *errorMessage = tr("Unable to obtain the status: %1").arg(error);
1250         }
1251         return StatusFailed;
1252     }
1253     // Unchanged (output text depending on whether -u was passed)
1254     if (outputText.contains("nothing to commit")
1255         || outputText.contains("nothing added to commit but untracked files present"))
1256         return StatusUnchanged;
1257     return StatusChanged;
1258 }
1259
1260 void GitClient::launchGitK(const QString &workingDirectory)
1261 {
1262     VCSBase::VCSBaseOutputWindow *outwin = VCSBase::VCSBaseOutputWindow::instance();
1263     // Locate git in (potentially) custom path. m_binaryPath can be absolute,
1264     // which will be handled correctly.
1265     QTC_ASSERT(!m_binaryPath.isEmpty(), return);
1266     const QString gitBinary = QLatin1String(Constants::GIT_BINARY);
1267     const QProcessEnvironment env = processEnvironment();
1268     const QString path = env.value(QLatin1String("PATH"));
1269     const QString fullGitBinary = Utils::SynchronousProcess::locateBinary(path, m_binaryPath);
1270     if (fullGitBinary.isEmpty()) {
1271         outwin->appendError(tr("Cannot locate %1.").arg(gitBinary));
1272         return;
1273     }
1274     const QString gitBinDirectory = QFileInfo(fullGitBinary).absolutePath();
1275 #ifdef Q_OS_WIN
1276     // Launch 'wish' shell from git binary directory with the gitk located there
1277     const QString binary = gitBinDirectory + QLatin1String("/wish");
1278     QStringList arguments(gitBinDirectory + QLatin1String("/gitk"));
1279 #else
1280     // Simple: Run gitk from binary path
1281     const QString binary = gitBinDirectory + QLatin1String("/gitk");
1282     QStringList arguments;
1283 #endif
1284     if (!m_settings.gitkOptions.isEmpty())
1285         arguments.append(m_settings.gitkOptions.split(QLatin1Char(' ')));
1286     outwin->appendCommand(workingDirectory, binary, arguments);
1287     // This should always use QProcess::startDetached (as not to kill
1288     // the child), but that does not have an environment parameter.
1289     bool success = false;
1290     if (m_settings.adoptPath) {
1291         QProcess *process = new QProcess(this);
1292         process->setWorkingDirectory(workingDirectory);
1293         process->setProcessEnvironment(env);
1294         process->start(binary, arguments);
1295         success = process->waitForStarted();
1296         if (success) {
1297             connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater()));
1298         } else {
1299             delete process;
1300         }
1301     } else {
1302         success = QProcess::startDetached(binary, arguments, workingDirectory);
1303     }
1304     if (!success)
1305         outwin->appendError(tr("Unable to launch %1.").arg(binary));
1306 }
1307
1308 bool GitClient::getCommitData(const QString &workingDirectory,
1309                               QString *commitTemplate,
1310                               CommitData *d,
1311                               QString *errorMessage)
1312 {
1313     if (Git::Constants::debug)
1314         qDebug() << Q_FUNC_INFO << workingDirectory;
1315
1316     d->clear();
1317
1318     // Find repo
1319     const QString repoDirectory = GitClient::findRepositoryForDirectory(workingDirectory);
1320     if (repoDirectory.isEmpty()) {
1321         *errorMessage = msgRepositoryNotFound(workingDirectory);
1322         return false;
1323     }
1324
1325     d->panelInfo.repository = repoDirectory;
1326
1327     QDir gitDir(repoDirectory);
1328     if (!gitDir.cd(QLatin1String(kGitDirectoryC))) {
1329         *errorMessage = tr("The repository %1 is not initialized yet.").arg(repoDirectory);
1330         return false;
1331     }
1332
1333     // Read description
1334     const QString descriptionFile = gitDir.absoluteFilePath(QLatin1String("description"));
1335     if (QFileInfo(descriptionFile).isFile()) {
1336         QFile file(descriptionFile);
1337         if (file.open(QIODevice::ReadOnly|QIODevice::Text))
1338             d->panelInfo.description = commandOutputFromLocal8Bit(file.readAll()).trimmed();
1339     }
1340
1341     // Run status. Note that it has exitcode 1 if there are no added files.
1342     bool onBranch;
1343     QString output;
1344     switch (gitStatus(repoDirectory, true, &output, errorMessage, &onBranch)) {
1345     case  StatusChanged:
1346         if (!onBranch) {
1347             *errorMessage = tr("You did not checkout a branch.");
1348             return false;
1349         }
1350         break;
1351     case StatusUnchanged:
1352         *errorMessage = msgNoChangedFiles();
1353         return false;
1354     case StatusFailed:
1355         return false;
1356     }
1357
1358     //    Output looks like:
1359     //    # On branch [branchname]
1360     //    # Changes to be committed:
1361     //    #   (use "git reset HEAD <file>..." to unstage)
1362     //    #
1363     //    #       modified:   somefile.cpp
1364     //    #       new File:   somenew.h
1365     //    #
1366     //    # Changed but not updated:
1367     //    #   (use "git add <file>..." to update what will be committed)
1368     //    #
1369     //    #       modified:   someother.cpp
1370     //    #
1371     //    # Untracked files:
1372     //    #   (use "git add <file>..." to include in what will be committed)
1373     //    #
1374     //    #       list of files...
1375
1376     if (!d->parseFilesFromStatus(output)) {
1377         *errorMessage = msgParseFilesFailed();
1378         return false;
1379     }
1380     // Filter out untracked files that are not part of the project
1381     VCSBase::VCSBaseSubmitEditor::filterUntrackedFilesOfProject(repoDirectory, &d->untrackedFiles);
1382     if (d->filesEmpty()) {
1383         *errorMessage = msgNoChangedFiles();
1384         return false;
1385     }
1386
1387     d->panelData.author = readConfigValue(workingDirectory, QLatin1String("user.name"));
1388     d->panelData.email = readConfigValue(workingDirectory, QLatin1String("user.email"));
1389
1390     // Get the commit template
1391     QString templateFilename = readConfigValue(workingDirectory, QLatin1String("commit.template"));
1392     if (!templateFilename.isEmpty()) {
1393         // Make relative to repository
1394         const QFileInfo templateFileInfo(templateFilename);
1395         if (templateFileInfo.isRelative())
1396             templateFilename = repoDirectory + QLatin1Char('/') + templateFilename;
1397         QFile templateFile(templateFilename);
1398         if (templateFile.open(QIODevice::ReadOnly|QIODevice::Text)) {
1399             *commitTemplate = QString::fromLocal8Bit(templateFile.readAll());
1400         } else {
1401             qWarning("Unable to read commit template %s: %s",
1402                      qPrintable(templateFilename),
1403                      qPrintable(templateFile.errorString()));
1404         }
1405     }
1406     return true;
1407 }
1408
1409 // addAndCommit:
1410 bool GitClient::addAndCommit(const QString &repositoryDirectory,
1411                              const GitSubmitEditorPanelData &data,
1412                              const QString &messageFile,
1413                              const QStringList &checkedFiles,
1414                              const QStringList &origCommitFiles,
1415                              const QStringList &origDeletedFiles)
1416 {
1417     if (Git::Constants::debug)
1418         qDebug() << "GitClient::addAndCommit:" << repositoryDirectory << checkedFiles << origCommitFiles;
1419     const QString renamedSeparator = QLatin1String(" -> ");
1420
1421     // Do we need to reset any files that had been added before
1422     // (did the user uncheck any previously added files)
1423     // Split up  renamed files ('foo.cpp -> foo2.cpp').
1424     QStringList resetFiles = origCommitFiles.toSet().subtract(checkedFiles.toSet()).toList();
1425     for (QStringList::iterator it = resetFiles.begin(); it != resetFiles.end(); ++it) {
1426         const int renamedPos = it->indexOf(renamedSeparator);
1427         if (renamedPos != -1) {
1428             const QString newFile = it->mid(renamedPos + renamedSeparator.size());
1429             it->truncate(renamedPos);
1430             it = resetFiles.insert(++it, newFile);
1431         }
1432     }
1433
1434     if (!resetFiles.isEmpty())
1435         if (!synchronousReset(repositoryDirectory, resetFiles))
1436             return false;
1437
1438     // Re-add all to make sure we have the latest changes, but only add those that aren't marked
1439     // for deletion. Purge out renamed files ('foo.cpp -> foo2.cpp').
1440     QStringList addFiles = checkedFiles.toSet().subtract(origDeletedFiles.toSet()).toList();
1441     for (QStringList::iterator it = addFiles.begin(); it != addFiles.end(); ) {
1442         if (it->contains(renamedSeparator)) {
1443             it = addFiles.erase(it);
1444         } else {
1445             ++it;
1446         }
1447     }
1448     if (!addFiles.isEmpty())
1449         if (!synchronousAdd(repositoryDirectory, false, addFiles))
1450             return false;
1451
1452     // Do the final commit
1453     QStringList args;
1454     args << QLatin1String("commit")
1455          << QLatin1String("-F") << QDir::toNativeSeparators(messageFile);
1456
1457     const QString &authorString =  data.authorString();
1458     if (!authorString.isEmpty())
1459          args << QLatin1String("--author") << authorString;
1460
1461     QByteArray outputText;
1462     QByteArray errorText;
1463     const bool rc = fullySynchronousGit(repositoryDirectory, args, &outputText, &errorText);
1464     if (rc) {
1465         outputWindow()->append(tr("Committed %n file(s).\n", 0, checkedFiles.size()));
1466     } else {
1467         outputWindow()->appendError(tr("Unable to commit %n file(s): %1\n", 0, checkedFiles.size()).arg(commandOutputFromLocal8Bit(errorText)));
1468     }
1469     return rc;
1470 }
1471
1472 /* Revert: This function can be called with a file list (to revert single
1473  * files)  or a single directory (revert all). Qt Creator currently has only
1474  * 'revert single' in its VCS menus, but the code is prepared to deal with
1475  * reverting a directory pending a sophisticated selection dialog in the
1476  * VCSBase plugin. */
1477
1478 GitClient::RevertResult GitClient::revertI(QStringList files, bool *ptrToIsDirectory, QString *errorMessage)
1479 {
1480     if (Git::Constants::debug)
1481         qDebug() << Q_FUNC_INFO << files;
1482
1483     if (files.empty())
1484         return RevertCanceled;
1485
1486     // Figure out the working directory
1487     const QFileInfo firstFile(files.front());
1488     const bool isDirectory = firstFile.isDir();
1489     if (ptrToIsDirectory)
1490         *ptrToIsDirectory = isDirectory;
1491     const QString workingDirectory = isDirectory ? firstFile.absoluteFilePath() : firstFile.absolutePath();
1492
1493     const QString repoDirectory = GitClient::findRepositoryForDirectory(workingDirectory);
1494     if (repoDirectory.isEmpty()) {
1495         *errorMessage = msgRepositoryNotFound(workingDirectory);
1496         return RevertFailed;
1497     }
1498
1499     // Check for changes
1500     QString output;
1501     switch (gitStatus(repoDirectory, false, &output, errorMessage)) {
1502     case StatusChanged:
1503         break;
1504     case StatusUnchanged:
1505         return RevertUnchanged;
1506     case StatusFailed:
1507         return RevertFailed;
1508     }
1509     CommitData data;
1510     if (!data.parseFilesFromStatus(output)) {
1511         *errorMessage = msgParseFilesFailed();
1512         return RevertFailed;
1513     }
1514
1515     // If we are looking at files, make them relative to the repository
1516     // directory to match them in the status output list.
1517     if (!isDirectory) {
1518         const QDir repoDir(repoDirectory);
1519         const QStringList::iterator cend = files.end();
1520         for (QStringList::iterator it = files.begin(); it != cend; ++it)
1521             *it = repoDir.relativeFilePath(*it);
1522     }
1523
1524     // From the status output, determine all modified [un]staged files.
1525     const QString modifiedState = QLatin1String("modified");
1526     const QStringList allStagedFiles = data.stagedFileNames(modifiedState);
1527     const QStringList allUnstagedFiles = data.unstagedFileNames(modifiedState);
1528     // Unless a directory was passed, filter all modified files for the
1529     // argument file list.
1530     QStringList stagedFiles = allStagedFiles;
1531     QStringList unstagedFiles = allUnstagedFiles;
1532     if (!isDirectory) {
1533         const QSet<QString> filesSet = files.toSet();
1534         stagedFiles = allStagedFiles.toSet().intersect(filesSet).toList();
1535         unstagedFiles = allUnstagedFiles.toSet().intersect(filesSet).toList();
1536     }
1537     if (Git::Constants::debug)
1538         qDebug() << Q_FUNC_INFO << data.stagedFiles << data.unstagedFiles << allStagedFiles << allUnstagedFiles << stagedFiles << unstagedFiles;
1539
1540     if (stagedFiles.empty() && unstagedFiles.empty())
1541         return RevertUnchanged;
1542
1543     // Ask to revert (to do: Handle lists with a selection dialog)
1544     const QMessageBox::StandardButton answer
1545         = QMessageBox::question(m_core->mainWindow(),
1546                                 tr("Revert"),
1547                                 tr("The file has been changed. Do you want to revert it?"),
1548                                 QMessageBox::Yes|QMessageBox::No,
1549                                 QMessageBox::No);
1550     if (answer == QMessageBox::No)
1551         return RevertCanceled;
1552
1553     // Unstage the staged files
1554     if (!stagedFiles.empty() && !synchronousReset(repoDirectory, stagedFiles, errorMessage))
1555         return RevertFailed;
1556     // Finally revert!
1557     if (!synchronousCheckoutFiles(repoDirectory, stagedFiles + unstagedFiles, QString(), errorMessage))
1558         return RevertFailed;
1559     return RevertOk;
1560 }
1561
1562 void GitClient::revert(const QStringList &files)
1563 {
1564     bool isDirectory;
1565     QString errorMessage;
1566     switch (revertI(files, &isDirectory, &errorMessage)) {
1567     case RevertOk:
1568         m_plugin->gitVersionControl()->emitFilesChanged(files);
1569         break;
1570     case RevertCanceled:
1571         break;
1572     case RevertUnchanged: {
1573         const QString msg = (isDirectory || files.size() > 1) ? msgNoChangedFiles() : tr("The file is not modified.");
1574         outputWindow()->append(msg);
1575     }
1576         break;
1577     case RevertFailed:
1578         outputWindow()->append(errorMessage);
1579         break;
1580     }
1581 }
1582
1583 bool GitClient::synchronousPull(const QString &workingDirectory)
1584 {
1585     return synchronousPull(workingDirectory, m_settings.pullRebase);
1586 }
1587
1588 bool GitClient::synchronousPull(const QString &workingDirectory, bool rebase)
1589 {
1590     QStringList arguments(QLatin1String("pull"));
1591     if (rebase)
1592         arguments << QLatin1String("--rebase");
1593     // Disable UNIX terminals to suppress SSH prompting.
1594     const unsigned flags = VCSBase::VCSBasePlugin::SshPasswordPrompt|VCSBase::VCSBasePlugin::ShowStdOutInLogWindow
1595                            |VCSBase::VCSBasePlugin::ShowSuccessMessage;
1596     const Utils::SynchronousProcessResponse resp = synchronousGit(workingDirectory, arguments, flags);
1597     // Notify about changed files or abort the rebase.
1598     const bool ok = resp.result == Utils::SynchronousProcessResponse::Finished;
1599     if (ok) {
1600         GitPlugin::instance()->gitVersionControl()->emitRepositoryChanged(workingDirectory);
1601     } else {
1602         if (rebase)
1603             syncAbortPullRebase(workingDirectory);
1604     }
1605     return ok;
1606 }
1607
1608 void GitClient::syncAbortPullRebase(const QString &workingDir)
1609 {
1610     // Abort rebase to clean if something goes wrong
1611     VCSBase::VCSBaseOutputWindow *outwin = VCSBase::VCSBaseOutputWindow::instance();
1612     outwin->appendError(tr("The command 'git pull --rebase' failed, aborting rebase."));
1613     QStringList arguments;
1614     arguments << QLatin1String("rebase") << QLatin1String("--abort");
1615     QByteArray stdOut;
1616     QByteArray stdErr;
1617     const bool rc = fullySynchronousGit(workingDir, arguments, &stdOut, &stdErr, true);
1618     outwin->append(commandOutputFromLocal8Bit(stdOut));
1619     if (!rc)
1620         outwin->appendError(commandOutputFromLocal8Bit(stdErr));
1621 }
1622
1623 // Subversion: git svn
1624 void GitClient::synchronousSubversionFetch(const QString &workingDirectory)
1625 {
1626     QStringList args;
1627     args << QLatin1String("svn") << QLatin1String("fetch");
1628     // Disable UNIX terminals to suppress SSH prompting.
1629     const unsigned flags = VCSBase::VCSBasePlugin::SshPasswordPrompt|VCSBase::VCSBasePlugin::ShowStdOutInLogWindow
1630                            |VCSBase::VCSBasePlugin::ShowSuccessMessage;
1631     const Utils::SynchronousProcessResponse resp = synchronousGit(workingDirectory, args, flags);
1632     // Notify about changes.
1633     if (resp.result == Utils::SynchronousProcessResponse::Finished)
1634         GitPlugin::instance()->gitVersionControl()->emitRepositoryChanged(workingDirectory);
1635 }
1636
1637 void GitClient::subversionLog(const QString &workingDirectory)
1638 {
1639     if (Git::Constants::debug)
1640         qDebug() << "subversionLog" << workingDirectory;
1641
1642     QStringList arguments;
1643     arguments << QLatin1String("svn") << QLatin1String("log");
1644     if (m_settings.logCount > 0)
1645          arguments << (QLatin1String("--limit=") + QString::number(m_settings.logCount));
1646
1647     // Create a command editor, no highlighting or interaction.
1648     const QString title = tr("Git SVN Log");
1649     const QString editorId = QLatin1String(Git::Constants::C_GIT_COMMAND_LOG_EDITOR);
1650     const QString sourceFile = VCSBase::VCSBaseEditor::getSource(workingDirectory, QStringList());
1651     VCSBase::VCSBaseEditor *editor = createVCSEditor(editorId, title, sourceFile, false, "svnLog", sourceFile);
1652     executeGit(workingDirectory, arguments, editor);
1653 }
1654
1655 bool GitClient::synchronousPush(const QString &workingDirectory)
1656 {
1657     // Disable UNIX terminals to suppress SSH prompting.
1658     const unsigned flags = VCSBase::VCSBasePlugin::SshPasswordPrompt|VCSBase::VCSBasePlugin::ShowStdOutInLogWindow
1659                            |VCSBase::VCSBasePlugin::ShowSuccessMessage;
1660     const Utils::SynchronousProcessResponse resp =
1661             synchronousGit(workingDirectory, QStringList(QLatin1String("push")), flags);
1662     return resp.result == Utils::SynchronousProcessResponse::Finished;
1663 }
1664
1665 QString GitClient::msgNoChangedFiles()
1666 {
1667     return tr("There are no modified files.");
1668 }
1669
1670 void GitClient::stashPop(const QString &workingDirectory)
1671 {
1672     QStringList arguments(QLatin1String("stash"));
1673     arguments << QLatin1String("pop");
1674     GitCommand *cmd = executeGit(workingDirectory, arguments, 0, true);
1675     connectRepositoryChanged(workingDirectory, cmd);
1676 }
1677
1678 bool GitClient::synchronousStashRestore(const QString &workingDirectory,
1679                                         const QString &stash,
1680                                         const QString &branch /* = QString()*/,
1681                                         QString *errorMessage)
1682 {
1683     QStringList arguments(QLatin1String("stash"));
1684     if (branch.isEmpty()) {
1685         arguments << QLatin1String("apply") << stash;
1686     } else {
1687         arguments << QLatin1String("branch") << branch << stash;
1688     }
1689     QByteArray outputText;
1690     QByteArray errorText;
1691     const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
1692     if (!rc) {
1693         const QString stdErr = commandOutputFromLocal8Bit(errorText);
1694         const QString msg = branch.isEmpty() ?
1695                             tr("Unable to restore stash %1: %2").arg(workingDirectory, stdErr) :
1696                             tr("Unable to restore stash %1 to branch %2: %3").arg(workingDirectory, branch, stdErr);
1697         if (errorMessage) {
1698             *errorMessage = msg;
1699         } else {
1700             outputWindow()->append(msg);
1701         }
1702         return false;
1703     }
1704     QString output = commandOutputFromLocal8Bit(outputText);
1705     if (!output.isEmpty())
1706         outputWindow()->append(output);
1707     GitPlugin::instance()->gitVersionControl()->emitRepositoryChanged(workingDirectory);
1708     return true;
1709 }
1710
1711 bool GitClient::synchronousStashRemove(const QString &workingDirectory,
1712                             const QString &stash /* = QString() */,
1713                             QString *errorMessage /* = 0 */)
1714 {
1715     QStringList arguments(QLatin1String("stash"));
1716     if (stash.isEmpty()) {
1717         arguments << QLatin1String("clear");
1718     } else {
1719         arguments << QLatin1String("drop") << stash;
1720     }
1721     QByteArray outputText;
1722     QByteArray errorText;
1723     const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
1724     if (!rc) {
1725         const QString stdErr = commandOutputFromLocal8Bit(errorText);
1726         const QString msg = stash.isEmpty() ?
1727                             tr("Unable to remove stashes of %1: %2").arg(workingDirectory, stdErr) :
1728                             tr("Unable to remove stash %1 of %2: %3").arg(stash, workingDirectory, stdErr);
1729         if (errorMessage) {
1730             *errorMessage = msg;
1731         } else {
1732             outputWindow()->append(msg);
1733         }
1734         return false;
1735     }
1736     QString output = commandOutputFromLocal8Bit(outputText);
1737     if (!output.isEmpty())
1738         outputWindow()->append(output);
1739     return true;
1740 }
1741
1742 void GitClient::branchList(const QString &workingDirectory)
1743 {
1744     QStringList arguments(QLatin1String("branch"));
1745     arguments << QLatin1String("-r") << QLatin1String(noColorOption);
1746     executeGit(workingDirectory, arguments, 0, true);
1747 }
1748
1749 void GitClient::stashList(const QString &workingDirectory)
1750 {
1751     QStringList arguments(QLatin1String("stash"));
1752     arguments << QLatin1String("list") << QLatin1String(noColorOption);
1753     executeGit(workingDirectory, arguments, 0, true);
1754 }
1755
1756 bool GitClient::synchronousStashList(const QString &workingDirectory,
1757                                      QList<Stash> *stashes,
1758                                      QString *errorMessage /* = 0 */)
1759 {
1760     stashes->clear();
1761     QStringList arguments(QLatin1String("stash"));
1762     arguments << QLatin1String("list") << QLatin1String(noColorOption);
1763     QByteArray outputText;
1764     QByteArray errorText;
1765     const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
1766     if (!rc) {
1767         const QString msg = tr("Unable retrieve stash list of %1: %2").arg(workingDirectory, commandOutputFromLocal8Bit(errorText));
1768         if (errorMessage) {
1769             *errorMessage = msg;
1770         } else {
1771             outputWindow()->append(msg);
1772         }
1773         return false;
1774     }
1775     Stash stash;
1776     foreach(const QString &line, commandOutputLinesFromLocal8Bit(outputText))
1777         if (stash.parseStashLine(line))
1778             stashes->push_back(stash);
1779     if (Git::Constants::debug)
1780         qDebug() << Q_FUNC_INFO << *stashes;
1781     return true;
1782 }
1783
1784 QString GitClient::readConfig(const QString &workingDirectory, const QStringList &configVar)
1785 {
1786     QStringList arguments;
1787     arguments << QLatin1String("config") << configVar;
1788
1789     QByteArray outputText;
1790     QByteArray errorText;
1791     if (fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText, false))
1792         return commandOutputFromLocal8Bit(outputText);
1793     return QString();
1794 }
1795
1796 // Read a single-line config value, return trimmed
1797 QString GitClient::readConfigValue(const QString &workingDirectory, const QString &configVar)
1798 {
1799     return readConfig(workingDirectory, QStringList(configVar)).remove(QLatin1Char('\n'));
1800 }
1801
1802 GitSettings GitClient::settings() const
1803 {
1804     return m_settings;
1805 }
1806
1807 void GitClient::setSettings(const GitSettings &s)
1808 {
1809     if (s != m_settings) {
1810         m_settings = s;
1811         if (QSettings *coreSettings = m_core->settings())
1812             m_settings.toSettings(coreSettings);
1813         m_binaryPath = m_settings.gitBinaryPath();
1814         m_cachedGitVersion = 0u;
1815         m_hasCachedGitVersion = false;
1816     }
1817 }
1818
1819 void GitClient::connectRepositoryChanged(const QString & repository, GitCommand *cmd)
1820 {
1821     // Bind command success termination with repository to changed signal
1822     if (!m_repositoryChangedSignalMapper) {
1823         m_repositoryChangedSignalMapper = new QSignalMapper(this);
1824         connect(m_repositoryChangedSignalMapper, SIGNAL(mapped(QString)),
1825                 m_plugin->gitVersionControl(), SIGNAL(repositoryChanged(QString)));
1826     }
1827     m_repositoryChangedSignalMapper->setMapping(cmd, repository);
1828     connect(cmd, SIGNAL(success()), m_repositoryChangedSignalMapper, SLOT(map()),
1829             Qt::QueuedConnection);
1830 }
1831
1832 // determine version as '(major << 16) + (minor << 8) + patch' or 0.
1833 unsigned GitClient::gitVersion(bool silent, QString *errorMessage  /* = 0 */)
1834 {
1835     if (!m_hasCachedGitVersion) {
1836         // Do not execute repeatedly if that fails (due to git
1837         // not being installed) until settings are changed.
1838         m_cachedGitVersion = synchronousGitVersion(silent, errorMessage);
1839         m_hasCachedGitVersion = true;
1840     }
1841     return m_cachedGitVersion;
1842 }
1843
1844 QString GitClient::gitVersionString(bool silent, QString *errorMessage)
1845 {
1846     if (const unsigned version = gitVersion(silent, errorMessage)) {
1847         QString rc;
1848         QTextStream(&rc) << (version >> 16) << '.'
1849                 << (0xFF & (version >> 8)) << '.'
1850                 << (version & 0xFF);
1851         return rc;
1852     }
1853     return QString();
1854 }
1855
1856 // determine version as '(major << 16) + (minor << 8) + patch' or 0.
1857 unsigned GitClient::synchronousGitVersion(bool silent, QString *errorMessage /* = 0 */)
1858 {
1859     // run git --version
1860     QByteArray outputText;
1861     QByteArray errorText;
1862     const bool rc = fullySynchronousGit(QString(), QStringList("--version"), &outputText, &errorText);
1863     if (!rc) {
1864         const QString msg = tr("Unable to determine git version: %1").arg(commandOutputFromLocal8Bit(errorText));
1865         if (errorMessage) {
1866             *errorMessage = msg;
1867         } else {
1868             if (silent) {
1869                 outputWindow()->append(msg);
1870             } else {
1871                 outputWindow()->appendError(msg);
1872             }
1873         }
1874         return 0;
1875     }
1876     // cut 'git version 1.6.5.1.sha'
1877     const QString output = commandOutputFromLocal8Bit(outputText);
1878     const QRegExp versionPattern(QLatin1String("^[^\\d]+([\\d])\\.([\\d])\\.([\\d]).*$"));
1879     QTC_ASSERT(versionPattern.isValid(), return 0);
1880     QTC_ASSERT(versionPattern.exactMatch(output), return 0);
1881     const unsigned major = versionPattern.cap(1).toUInt();
1882     const unsigned minor = versionPattern.cap(2).toUInt();
1883     const unsigned patch = versionPattern.cap(3).toUInt();
1884     return version(major, minor, patch);
1885 }
1886
1887 } // namespace Internal
1888 } // namespace Git