OSDN Git Service

Update license.
[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) 2011 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (info@qt.nokia.com)
8 **
9 **
10 ** GNU Lesser General Public License Usage
11 **
12 ** This file may be used under the terms of the GNU Lesser General Public
13 ** License version 2.1 as published by the Free Software Foundation and
14 ** appearing in the file LICENSE.LGPL included in the packaging of this file.
15 ** Please review the following information to ensure the GNU Lesser General
16 ** Public License version 2.1 requirements will be met:
17 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18 **
19 ** In addition, as a special exception, Nokia gives you certain additional
20 ** rights. These rights are described in the Nokia Qt LGPL Exception
21 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
22 **
23 ** Other Usage
24 **
25 ** Alternatively, this file may be used in accordance with the terms and
26 ** conditions contained in a signed written agreement between you and Nokia.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **************************************************************************/
32
33 #include "gitclient.h"
34 #include "gitcommand.h"
35 #include "gitutils.h"
36
37 #include "commitdata.h"
38 #include "gitconstants.h"
39 #include "gitplugin.h"
40 #include "gitsubmiteditor.h"
41 #include "gitversioncontrol.h"
42
43 #include <coreplugin/actionmanager/actionmanager.h>
44 #include <coreplugin/coreconstants.h>
45 #include <coreplugin/editormanager/editormanager.h>
46 #include <coreplugin/icore.h>
47 #include <coreplugin/messagemanager.h>
48 #include <coreplugin/progressmanager/progressmanager.h>
49 #include <coreplugin/uniqueidmanager.h>
50 #include <coreplugin/filemanager.h>
51 #include <coreplugin/iversioncontrol.h>
52
53 #include <texteditor/itexteditor.h>
54 #include <utils/qtcassert.h>
55 #include <utils/qtcprocess.h>
56 #include <utils/synchronousprocess.h>
57 #include <utils/environment.h>
58 #include <vcsbase/vcsbaseeditor.h>
59 #include <vcsbase/vcsbaseoutputwindow.h>
60 #include <vcsbase/vcsbaseplugin.h>
61
62 #include <QtCore/QRegExp>
63 #include <QtCore/QTemporaryFile>
64 #include <QtCore/QTime>
65 #include <QtCore/QFileInfo>
66 #include <QtCore/QDir>
67 #include <QtCore/QSignalMapper>
68
69 #include <QtGui/QComboBox>
70 #include <QtGui/QMainWindow> // for msg box parent
71 #include <QtGui/QMessageBox>
72 #include <QtGui/QToolButton>
73
74 static const char *const kGitDirectoryC = ".git";
75 static const char *const kBranchIndicatorC = "# On branch";
76
77 namespace Git {
78 namespace Internal {
79
80 BaseGitArgumentsWidget::BaseGitArgumentsWidget(GitSettings *settings,
81                                                Git::Internal::GitClient *client,
82                                                const QString &directory,
83                                                const QStringList &args) :
84     QWidget(0),
85     m_client(client),
86     m_workingDirectory(directory),
87     m_diffArgs(args),
88     m_settings(settings)
89 {
90     Q_ASSERT(settings);
91     Q_ASSERT(client);
92 }
93
94 class BaseGitDiffArgumentsWidget : public Git::Internal::BaseGitArgumentsWidget
95 {
96     Q_OBJECT
97 public:
98     BaseGitDiffArgumentsWidget(Git::Internal::GitSettings *settings,
99                                Git::Internal::GitClient *client,
100                                const QString &directory,
101                                const QStringList &args) :
102         BaseGitArgumentsWidget(settings, client, directory, args),
103         m_patience(new QToolButton),
104         m_ignoreSpaces(new QToolButton)
105     {
106         QHBoxLayout *layout = new QHBoxLayout(this);
107         layout->setContentsMargins(3, 0, 3, 0);
108         layout->setSpacing(2);
109
110         m_patience->setToolTip(tr("Use the patience algorithm for calculating the diff"));
111         m_patience->setText(tr("Patience"));
112         layout->addWidget(m_patience);
113         m_patience->setCheckable(true);
114         m_patience->setChecked(m_settings->diffPatience);
115         connect(m_patience, SIGNAL(toggled(bool)), this, SLOT(testForArgumentsChanged()));
116
117         m_ignoreSpaces->setToolTip(tr("Ignore whitespace only changes"));
118         m_ignoreSpaces->setText(tr("Ignore Whitespace"));
119         layout->addWidget(m_ignoreSpaces);
120         m_ignoreSpaces->setCheckable(true);
121         m_ignoreSpaces->setChecked(m_settings->ignoreSpaceChangesInDiff);
122         connect(m_ignoreSpaces, SIGNAL(toggled(bool)), this, SLOT(testForArgumentsChanged()));
123     }
124
125     QStringList arguments() const
126     {
127         QStringList args;
128         foreach (const QString &arg, m_diffArgs) {
129             if (arg == QLatin1String("--patience")
130                     || arg == QLatin1String("--ignore-space-change"))
131                 continue;
132             args.append(arg);
133         }
134
135         if (m_patience->isChecked() && m_patience->isVisible())
136             args.prepend(QLatin1String("--patience"));
137         if (m_ignoreSpaces->isChecked() && m_ignoreSpaces->isVisible())
138             args.prepend(QLatin1String("--ignore-space-change"));
139
140         return args;
141     }
142
143     void testForArgumentsChanged() {
144         m_settings->diffPatience = m_patience->isChecked();
145         m_settings->ignoreSpaceChangesInDiff = m_ignoreSpaces->isChecked();
146
147         QStringList newArguments = arguments();
148
149         if (newArguments == m_diffArgs)
150             return;
151
152         m_diffArgs = newArguments;
153         redoCommand();
154     }
155
156 protected:
157     QToolButton *m_patience;
158     QToolButton *m_ignoreSpaces;
159 };
160
161 class GitCommitDiffArgumentsWidget : public BaseGitDiffArgumentsWidget
162 {
163     Q_OBJECT
164 public:
165     GitCommitDiffArgumentsWidget(Git::Internal::GitSettings *settings,
166                                  Git::Internal::GitClient *client, const QString &directory,
167                                  const QStringList &args, const QStringList &unstaged,
168                                  const QStringList &staged) :
169         BaseGitDiffArgumentsWidget(settings, client, directory, args),
170         m_unstagedFileNames(unstaged),
171         m_stagedFileNames(staged)
172     { }
173
174     void redoCommand()
175     {
176         m_client->diff(m_workingDirectory, m_diffArgs, m_unstagedFileNames, m_stagedFileNames);
177     }
178
179 private:
180     const QStringList m_unstagedFileNames;
181     const QStringList m_stagedFileNames;
182 };
183
184 class GitFileDiffArgumentsWidget : public BaseGitDiffArgumentsWidget
185 {
186     Q_OBJECT
187 public:
188     GitFileDiffArgumentsWidget(Git::Internal::GitSettings *settings,
189                                Git::Internal::GitClient *client, const QString &directory,
190                                const QStringList &args, const QString &file) :
191         BaseGitDiffArgumentsWidget(settings, client, directory, args),
192         m_fileName(file)
193     { }
194
195     void redoCommand()
196     {
197         m_client->diff(m_workingDirectory, m_diffArgs, m_fileName);
198     }
199
200 private:
201     const QString m_fileName;
202 };
203
204 class GitBranchDiffArgumentsWidget : public BaseGitDiffArgumentsWidget
205 {
206     Q_OBJECT
207 public:
208     GitBranchDiffArgumentsWidget(Git::Internal::GitSettings *settings,
209                                  Git::Internal::GitClient *client, const QString &directory,
210                                  const QStringList &args, const QString &branch) :
211         BaseGitDiffArgumentsWidget(settings, client, directory, args),
212         m_branchName(branch)
213     { }
214
215     void redoCommand()
216     {
217         m_client->diffBranch(m_workingDirectory, m_diffArgs, m_branchName);
218     }
219
220 private:
221     const QString m_branchName;
222 };
223
224 class GitShowArgumentsWidget : public Git::Internal::BaseGitArgumentsWidget
225 {
226     Q_OBJECT
227 public:
228     GitShowArgumentsWidget(Git::Internal::GitSettings *settings,
229                            Git::Internal::GitClient *client,
230                            const QString &directory,
231                            const QStringList &args,
232                            const QString &id) :
233         BaseGitArgumentsWidget(settings, client, directory, args),
234         m_prettyFormat(new QComboBox),
235         m_id(id)
236     {
237         QHBoxLayout *layout = new QHBoxLayout(this);
238         layout->setContentsMargins(3, 0, 3, 0);
239         layout->setSpacing(2);
240
241         m_prettyFormat->setToolTip(tr("Select the pretty printing format"));
242         m_prettyFormat->addItem(tr("oneline"), QLatin1String("oneline"));
243         m_prettyFormat->addItem(tr("short"), QLatin1String("short"));
244         m_prettyFormat->addItem(tr("medium"), QLatin1String("medium"));
245         m_prettyFormat->addItem(tr("full"), QLatin1String("full"));
246         m_prettyFormat->addItem(tr("fuller"), QLatin1String("fuller"));
247         m_prettyFormat->addItem(tr("email"), QLatin1String("email"));
248         m_prettyFormat->addItem(tr("raw"), QLatin1String("raw"));
249         layout->addWidget(m_prettyFormat);
250         m_prettyFormat->setCurrentIndex(m_settings->showPrettyFormat);
251         m_prettyFormat->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
252         connect(m_prettyFormat, SIGNAL(currentIndexChanged(int)), this, SLOT(testForArgumentsChanged()));
253     }
254
255     QStringList arguments() const
256     {
257         QStringList args;
258         foreach (const QString &arg, m_diffArgs) {
259             if (arg.startsWith(QLatin1String("--pretty=")) || arg.startsWith(QLatin1String("--format=")))
260                 continue;
261             args.append(arg);
262         }
263
264         args.prepend(QString::fromLatin1("--pretty=")
265                      + m_prettyFormat->itemData(m_prettyFormat->currentIndex()).toString());
266
267         return args;
268     }
269
270     void testForArgumentsChanged() {
271         m_settings->showPrettyFormat = m_prettyFormat->currentIndex();
272
273         QStringList newArguments = arguments();
274
275         if (newArguments == m_diffArgs)
276             return;
277
278         m_diffArgs = newArguments;
279         redoCommand();
280     }
281
282     void redoCommand()
283     {
284         m_client->show(m_workingDirectory, m_id, m_diffArgs);
285     }
286
287 private:
288     QComboBox *m_prettyFormat;
289     QString m_id;
290 };
291
292 class GitBlameArgumentsWidget : public Git::Internal::BaseGitArgumentsWidget
293 {
294     Q_OBJECT
295 public:
296     GitBlameArgumentsWidget(Git::Internal::GitSettings *settings,
297                             Git::Internal::GitClient *client, const QString &directory,
298                             const QStringList &args, const QString &revision,
299                             const QString &fileName) :
300         Git::Internal::BaseGitArgumentsWidget(settings, client, directory, args),
301         m_omitDate(0),
302         m_ignoreSpaces(0),
303         m_editor(0),
304         m_revision(revision),
305         m_fileName(fileName)
306     {
307         QHBoxLayout *layout = new QHBoxLayout(this);
308         layout->setContentsMargins(3, 0, 3, 0);
309         layout->setSpacing(2);
310
311         m_omitDate = new QToolButton;
312         m_omitDate->setToolTip(tr("Do not show the date a change was made in the output"));
313         m_omitDate->setText(tr("Omit Date"));
314         layout->addWidget(m_omitDate);
315         m_omitDate->setCheckable(true);
316         m_omitDate->setChecked(m_settings->omitAnnotationDate);
317         m_omitDate->setMinimumHeight(16);
318         m_omitDate->setMaximumHeight(16);
319         connect(m_omitDate, SIGNAL(toggled(bool)), this, SLOT(testForArgumentsChanged()));
320
321         m_ignoreSpaces = new QToolButton;
322         m_ignoreSpaces->setToolTip(tr("Ignore whitespace only changes"));
323         m_ignoreSpaces->setText(tr("Ignore Whitespace"));
324         layout->addWidget(m_ignoreSpaces);
325         m_ignoreSpaces->setCheckable(true);
326         m_ignoreSpaces->setChecked(m_settings->ignoreSpaceChangesInBlame);
327         m_ignoreSpaces->setMinimumHeight(16);
328         m_ignoreSpaces->setMaximumHeight(16);
329         connect(m_ignoreSpaces, SIGNAL(toggled(bool)), this, SLOT(testForArgumentsChanged()));
330     }
331
332     void setEditor(VCSBase::VCSBaseEditorWidget *editor)
333     {
334         Q_ASSERT(editor);
335         m_editor = editor;
336     }
337
338     QStringList arguments() const
339     {
340         QStringList args = m_diffArgs;
341
342         args.removeAll(QLatin1String("-w"));
343
344         if (m_ignoreSpaces->isChecked())
345             args.prepend(QLatin1String("-w"));
346
347         return args;
348     }
349
350     void testForArgumentsChanged() {
351         m_settings->omitAnnotationDate = m_omitDate->isChecked();
352         m_settings->ignoreSpaceChangesInBlame = m_ignoreSpaces->isChecked();
353
354         m_diffArgs = arguments();
355         redoCommand(); // always redo for omit date
356     }
357
358     void redoCommand()
359     {
360         m_client->blame(m_workingDirectory, m_diffArgs, m_fileName,
361                         m_revision, m_editor->lineNumberOfCurrentEditor());
362     }
363
364 private:
365     QToolButton *m_omitDate;
366     QToolButton *m_ignoreSpaces;
367     VCSBase::VCSBaseEditorWidget *m_editor;
368     QString m_revision;
369     QString m_fileName;
370 };
371
372 inline Core::IEditor* locateEditor(const Core::ICore *core, const char *property, const QString &entry)
373 {
374     foreach (Core::IEditor *ed, core->editorManager()->openedEditors())
375         if (ed->file()->property(property).toString() == entry)
376             return ed;
377     return 0;
378 }
379
380 // Return converted command output, remove '\r' read on Windows
381 static inline QString commandOutputFromLocal8Bit(const QByteArray &a)
382 {
383     QString output = QString::fromLocal8Bit(a);
384     output.remove(QLatin1Char('\r'));
385     return output;
386 }
387
388 // Return converted command output split into lines
389 static inline QStringList commandOutputLinesFromLocal8Bit(const QByteArray &a)
390 {
391     QString output = commandOutputFromLocal8Bit(a);
392     const QChar newLine = QLatin1Char('\n');
393     if (output.endsWith(newLine))
394         output.truncate(output.size() - 1);
395     if (output.isEmpty())
396         return QStringList();
397     return output.split(newLine);
398 }
399
400 static inline VCSBase::VCSBaseOutputWindow *outputWindow()
401 {
402     return VCSBase::VCSBaseOutputWindow::instance();
403 }
404
405 static inline QString msgRepositoryNotFound(const QString &dir)
406 {
407     return GitClient::tr("Unable to determine the repository for %1.").arg(dir);
408 }
409
410 static inline QString msgParseFilesFailed()
411 {
412     return  GitClient::tr("Unable to parse the file output.");
413 }
414
415 // ---------------- GitClient
416
417 const char *GitClient::stashNamePrefix = "stash@{";
418
419 GitClient::GitClient(GitPlugin* plugin)
420   : m_msgWait(tr("Waiting for data...")),
421     m_plugin(plugin),
422     m_core(Core::ICore::instance()),
423     m_repositoryChangedSignalMapper(0),
424     m_cachedGitVersion(0),
425     m_hasCachedGitVersion(false)
426 {
427     if (QSettings *s = m_core->settings()) {
428         m_settings.fromSettings(s);
429         m_binaryPath = m_settings.gitBinaryPath();
430     }
431 }
432
433 GitClient::~GitClient()
434 {
435 }
436
437 const char *GitClient::noColorOption = "--no-color";
438
439 QString GitClient::findRepositoryForDirectory(const QString &dir)
440 {
441     // Check for ".git/config"
442     const QString checkFile = QLatin1String(kGitDirectoryC) + QLatin1String("/config");
443     return VCSBase::VCSBasePlugin::findRepositoryForDirectory(dir, checkFile);
444 }
445
446 VCSBase::VCSBaseEditorWidget *GitClient::findExistingVCSEditor(const char *registerDynamicProperty,
447                                                          const QString &dynamicPropertyValue) const
448 {
449     VCSBase::VCSBaseEditorWidget *rc = 0;
450     Core::IEditor *outputEditor = locateEditor(m_core, registerDynamicProperty, dynamicPropertyValue);
451     if (!outputEditor)
452         return 0;
453
454     // Exists already
455     Core::EditorManager::instance()->activateEditor(outputEditor, Core::EditorManager::ModeSwitch);
456     outputEditor->createNew(m_msgWait);
457     rc = VCSBase::VCSBaseEditorWidget::getVcsBaseEditor(outputEditor);
458
459     return rc;
460 }
461
462
463 /* Create an editor associated to VCS output of a source file/directory
464  * (using the file's codec). Makes use of a dynamic property to find an
465  * existing instance and to reuse it (in case, say, 'git diff foo' is
466  * already open). */
467 VCSBase::VCSBaseEditorWidget *GitClient::createVCSEditor(const QString &id,
468                                                    QString title,
469                                                    // Source file or directory
470                                                    const QString &source,
471                                                    bool setSourceCodec,
472                                                    // Dynamic property and value to identify that editor
473                                                    const char *registerDynamicProperty,
474                                                    const QString &dynamicPropertyValue,
475                                                    QWidget *configWidget) const
476 {
477     VCSBase::VCSBaseEditorWidget *rc = 0;
478     Q_ASSERT(!findExistingVCSEditor(registerDynamicProperty, dynamicPropertyValue));
479
480     // Create new, set wait message, set up with source and codec
481     Core::IEditor *outputEditor = m_core->editorManager()->openEditorWithContents(id, &title, m_msgWait);
482     outputEditor->file()->setProperty(registerDynamicProperty, dynamicPropertyValue);
483     rc = VCSBase::VCSBaseEditorWidget::getVcsBaseEditor(outputEditor);
484     connect(rc, SIGNAL(annotateRevisionRequested(QString,QString,int)),
485             this, SLOT(slotBlameRevisionRequested(QString,QString,int)));
486     QTC_ASSERT(rc, return 0);
487     rc->setSource(source);
488     if (setSourceCodec)
489         rc->setCodec(VCSBase::VCSBaseEditorWidget::getCodec(source));
490
491     m_core->editorManager()->activateEditor(outputEditor, Core::EditorManager::ModeSwitch);
492     rc->setForceReadOnly(true);
493
494     if (configWidget)
495         rc->setConfigurationWidget(configWidget);
496
497     return rc;
498 }
499
500 void GitClient::diff(const QString &workingDirectory,
501                      const QStringList &diffArgs,
502                      const QStringList &unstagedFileNames,
503                      const QStringList &stagedFileNames)
504 {
505
506     if (Git::Constants::debug)
507         qDebug() << "diff" << workingDirectory << unstagedFileNames << stagedFileNames;
508
509     const QString binary = QLatin1String(Constants::GIT_BINARY);
510     const QString editorId = QLatin1String(Git::Constants::GIT_DIFF_EDITOR_ID);
511     const QString title = tr("Git Diff");
512
513     QStringList userDiffArgs = diffArgs;
514     VCSBase::VCSBaseEditorWidget *editor = findExistingVCSEditor("originalFileName", workingDirectory);
515     if (!editor) {
516         GitCommitDiffArgumentsWidget *argWidget =
517                 new GitCommitDiffArgumentsWidget(&m_settings, this, workingDirectory, diffArgs,
518                                                  unstagedFileNames, stagedFileNames);
519         userDiffArgs = argWidget->arguments();
520
521         editor = createVCSEditor(editorId, title,
522                                  workingDirectory, true, "originalFileName", workingDirectory, argWidget);
523     }
524     editor->setDiffBaseDirectory(workingDirectory);
525
526     // Create a batch of 2 commands to be run after each other in case
527     // we have a mixture of staged/unstaged files as is the case
528     // when using the submit dialog.
529     GitCommand *command = createCommand(workingDirectory, editor);
530     // Directory diff?
531
532     QStringList cmdArgs;
533     cmdArgs << QLatin1String("diff") << QLatin1String(noColorOption);
534
535     if (unstagedFileNames.empty() && stagedFileNames.empty()) {
536        QStringList arguments(cmdArgs);
537        arguments << userDiffArgs;
538        outputWindow()->appendCommand(workingDirectory, binary, arguments);
539        command->addJob(arguments, m_settings.timeoutSeconds);
540     } else {
541         // Files diff.
542         if (!unstagedFileNames.empty()) {
543            QStringList arguments(cmdArgs);
544            arguments << userDiffArgs;
545            arguments << QLatin1String("--") << unstagedFileNames;
546            outputWindow()->appendCommand(workingDirectory, binary, arguments);
547            command->addJob(arguments, m_settings.timeoutSeconds);
548         }
549         if (!stagedFileNames.empty()) {
550            QStringList arguments(cmdArgs);
551            arguments << userDiffArgs;
552            arguments << QLatin1String("--cached") << diffArgs << QLatin1String("--") << stagedFileNames;
553            outputWindow()->appendCommand(workingDirectory, binary, arguments);
554            command->addJob(arguments, m_settings.timeoutSeconds);
555         }
556     }
557     command->execute();
558 }
559
560 void GitClient::diff(const QString &workingDirectory,
561                      const QStringList &diffArgs,
562                      const QString &fileName)
563 {
564     const QString editorId = QLatin1String(Git::Constants::GIT_DIFF_EDITOR_ID);
565     const QString title = tr("Git Diff %1").arg(fileName);
566     const QString sourceFile = VCSBase::VCSBaseEditorWidget::getSource(workingDirectory, fileName);
567
568     QStringList userDiffArgs = diffArgs;
569     VCSBase::VCSBaseEditorWidget *editor = findExistingVCSEditor("originalFileName", sourceFile);
570     if (!editor) {
571         GitFileDiffArgumentsWidget *argWidget =
572                 new GitFileDiffArgumentsWidget(&m_settings, this, workingDirectory,
573                                                diffArgs, fileName);
574         userDiffArgs = argWidget->arguments();
575
576         editor = createVCSEditor(editorId, title, sourceFile, true, "originalFileName", sourceFile, argWidget);
577     }
578
579     QStringList cmdArgs;
580     cmdArgs << QLatin1String("diff") << QLatin1String(noColorOption)
581               << userDiffArgs;
582
583     if (!fileName.isEmpty())
584         cmdArgs << QLatin1String("--") << fileName;
585     executeGit(workingDirectory, cmdArgs, editor);
586 }
587
588 void GitClient::diffBranch(const QString &workingDirectory,
589                            const QStringList &diffArgs,
590                            const QString &branchName)
591 {
592     if (Git::Constants::debug)
593         qDebug() << "diffBranch" << workingDirectory << branchName;
594
595     const QString editorId = QLatin1String(Git::Constants::GIT_DIFF_EDITOR_ID);
596     const QString title = tr("Git Diff Branch %1").arg(branchName);
597     const QString sourceFile = VCSBase::VCSBaseEditorWidget::getSource(workingDirectory, QStringList());
598
599     QStringList userDiffArgs = diffArgs;
600     VCSBase::VCSBaseEditorWidget *editor = findExistingVCSEditor("BranchName", branchName);
601     if (!editor) {
602         GitBranchDiffArgumentsWidget *argWidget =
603                 new GitBranchDiffArgumentsWidget(&m_settings, this, workingDirectory,
604                                                  diffArgs, branchName);
605         userDiffArgs = argWidget->arguments();
606
607         editor = createVCSEditor(editorId, title, sourceFile, true, "BranchName", branchName, argWidget);
608     }
609
610     QStringList cmdArgs;
611     cmdArgs << QLatin1String("diff") << QLatin1String(noColorOption)
612               << diffArgs  << branchName;
613
614     executeGit(workingDirectory, cmdArgs, editor);
615 }
616
617 void GitClient::status(const QString &workingDirectory)
618 {
619     // @TODO: Use "--no-color" once it is supported
620     QStringList statusArgs(QLatin1String("status"));
621     statusArgs << QLatin1String("-u");
622     VCSBase::VCSBaseOutputWindow *outwin = outputWindow();
623     outwin->setRepository(workingDirectory);
624     GitCommand *command = executeGit(workingDirectory, statusArgs, 0, true);
625     connect(command, SIGNAL(finished(bool,int,QVariant)), outwin, SLOT(clearRepository()),
626             Qt::QueuedConnection);
627 }
628
629 static const char graphLogFormatC[] = "%h %an %s %ci";
630
631 // Create a graphical log.
632 void GitClient::graphLog(const QString &workingDirectory, const QString & branch)
633 {
634     if (Git::Constants::debug)
635         qDebug() << "log" << workingDirectory;
636
637     QStringList arguments;
638     arguments << QLatin1String("log") << QLatin1String(noColorOption);
639
640     if (m_settings.logCount > 0)
641          arguments << QLatin1String("-n") << QString::number(m_settings.logCount);
642     arguments << (QLatin1String("--pretty=format:") +  QLatin1String(graphLogFormatC))
643               << QLatin1String("--topo-order") <<  QLatin1String("--graph");
644
645     QString title;
646     if (branch.isEmpty()) {
647         title = tr("Git Log");
648     } else {
649         title = tr("Git Log %1").arg(branch);
650         arguments << branch;
651     }
652     const QString editorId = QLatin1String(Git::Constants::GIT_LOG_EDITOR_ID);
653     const QString sourceFile = VCSBase::VCSBaseEditorWidget::getSource(workingDirectory, QStringList());
654     VCSBase::VCSBaseEditorWidget *editor = findExistingVCSEditor("logFileName", sourceFile);
655     if (!editor)
656         editor = createVCSEditor(editorId, title, sourceFile, false, "logFileName", sourceFile, 0);
657     executeGit(workingDirectory, arguments, editor);
658 }
659
660 void GitClient::log(const QString &workingDirectory, const QStringList &fileNames, bool enableAnnotationContextMenu)
661 {
662     if (Git::Constants::debug)
663         qDebug() << "log" << workingDirectory << fileNames;
664
665     QStringList arguments;
666     arguments << QLatin1String("log") << QLatin1String(noColorOption);
667
668     if (m_settings.logCount > 0)
669          arguments << QLatin1String("-n") << QString::number(m_settings.logCount);
670
671     if (!fileNames.isEmpty())
672         arguments.append(fileNames);
673
674     const QString msgArg = fileNames.empty() ? workingDirectory :
675                            fileNames.join(QString(", "));
676     const QString title = tr("Git Log %1").arg(msgArg);
677     const QString editorId = QLatin1String(Git::Constants::GIT_LOG_EDITOR_ID);
678     const QString sourceFile = VCSBase::VCSBaseEditorWidget::getSource(workingDirectory, fileNames);
679     VCSBase::VCSBaseEditorWidget *editor = findExistingVCSEditor("logFileName", sourceFile);
680     if (!editor)
681         editor = createVCSEditor(editorId, title, sourceFile, false, "logFileName", sourceFile, 0);
682     editor->setFileLogAnnotateEnabled(enableAnnotationContextMenu);
683     executeGit(workingDirectory, arguments, editor);
684 }
685
686 // Do not show "0000" or "^32ae4"
687 static inline bool canShow(const QString &sha)
688 {
689     if (sha.startsWith(QLatin1Char('^')))
690         return false;
691     if (sha.count(QLatin1Char('0')) == sha.size())
692         return false;
693     return true;
694 }
695
696 static inline QString msgCannotShow(const QString &sha)
697 {
698     return GitClient::tr("Cannot describe '%1'.").arg(sha);
699 }
700
701 void GitClient::show(const QString &source, const QString &id, const QStringList &args)
702 {
703     if (Git::Constants::debug)
704         qDebug() << "show" << source << id;
705     if (!canShow(id)) {
706         outputWindow()->append(msgCannotShow(id));
707         return;
708     }
709
710     QStringList userArgs = args;
711     const QString title = tr("Git Show %1").arg(id);
712     const QString editorId = QLatin1String(Git::Constants::GIT_DIFF_EDITOR_ID);
713     VCSBase::VCSBaseEditorWidget *editor = findExistingVCSEditor("show", id);
714     if (!editor) {
715         GitShowArgumentsWidget *argWidget =
716                 new GitShowArgumentsWidget(&m_settings, this, source,
717                                            QStringList(), id);
718         userArgs = argWidget->arguments();
719         editor = createVCSEditor(editorId, title, source, true, "show", id, argWidget);
720     }
721
722     QStringList arguments;
723     arguments << QLatin1String("show") << QLatin1String(noColorOption);
724     arguments.append(userArgs);
725     arguments << id;
726
727     const QFileInfo sourceFi(source);
728     const QString workDir = sourceFi.isDir() ? sourceFi.absoluteFilePath() : sourceFi.absolutePath();
729     executeGit(workDir, arguments, editor);
730 }
731
732 void GitClient::slotBlameRevisionRequested(const QString &source, QString change, int lineNumber)
733 {
734     // This might be invoked with a verbose revision description
735     // "SHA1 author subject" from the annotation context menu. Strip the rest.
736     const int blankPos = change.indexOf(QLatin1Char(' '));
737     if (blankPos != -1)
738         change.truncate(blankPos);
739     const QFileInfo fi(source);
740     blame(fi.absolutePath(), QStringList(), fi.fileName(), change, lineNumber);
741 }
742
743 void GitClient::blame(const QString &workingDirectory,
744                       const QStringList &args,
745                       const QString &fileName,
746                       const QString &revision /* = QString() */,
747                       int lineNumber /* = -1 */)
748 {
749     if (Git::Constants::debug)
750         qDebug() << "blame" << workingDirectory << fileName << lineNumber << args;
751
752     const QString editorId = QLatin1String(Git::Constants::GIT_BLAME_EDITOR_ID);
753     const QString id = VCSBase::VCSBaseEditorWidget::getTitleId(workingDirectory, QStringList(fileName), revision);
754     const QString title = tr("Git Blame %1").arg(id);
755     const QString sourceFile = VCSBase::VCSBaseEditorWidget::getSource(workingDirectory, fileName);
756
757     QStringList userBlameArgs = args;
758     VCSBase::VCSBaseEditorWidget *editor = findExistingVCSEditor("blameFileName", id);
759     if (!editor) {
760         GitBlameArgumentsWidget *argWidget =
761                 new GitBlameArgumentsWidget(&m_settings, this, workingDirectory, userBlameArgs,
762                                             revision, fileName);
763         editor = createVCSEditor(editorId, title, sourceFile, true, "blameFileName", id, argWidget);
764         argWidget->setEditor(editor);
765
766         userBlameArgs = argWidget->arguments();
767     }
768
769     QStringList arguments(QLatin1String("blame"));
770     arguments << QLatin1String("--root");
771     arguments.append(userBlameArgs);
772     arguments << QLatin1String("--") << fileName;
773     if (!revision.isEmpty())
774         arguments << revision;
775     executeGit(workingDirectory, arguments, editor, false, GitCommand::NoReport, lineNumber);
776 }
777
778 void GitClient::checkoutBranch(const QString &workingDirectory, const QString &branch)
779 {
780     QStringList arguments(QLatin1String("checkout"));
781     arguments <<  branch;
782     GitCommand *cmd = executeGit(workingDirectory, arguments, 0, true);
783     connectRepositoryChanged(workingDirectory, cmd);
784 }
785
786 bool GitClient::synchronousCheckoutBranch(const QString &workingDirectory,
787                                     const QString &branch,
788                                     QString *errorMessage /* = 0 */)
789 {
790     QByteArray outputText;
791     QByteArray errorText;
792     QStringList arguments;
793     arguments << QLatin1String("checkout") << branch;
794     const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
795     const QString output = commandOutputFromLocal8Bit(outputText);
796     outputWindow()->append(output);
797     if (!rc) {
798         const QString stdErr = commandOutputFromLocal8Bit(errorText);
799         //: Meaning of the arguments: %1: Branch, %2: Repository, %3: Error message
800         const QString msg = tr("Unable to checkout %1 of %2: %3").arg(branch, workingDirectory, stdErr);
801         if (errorMessage) {
802             *errorMessage = msg;
803         } else {
804             outputWindow()->appendError(msg);
805         }
806         return false;
807     }
808     return true;
809 }
810
811 void GitClient::checkout(const QString &workingDirectory, const QString &fileName)
812 {
813     // Passing an empty argument as the file name is very dangereous, since this makes
814     // git checkout apply to all files. Almost looks like a bug in git.
815     if (fileName.isEmpty())
816         return;
817
818     QStringList arguments;
819     arguments << QLatin1String("checkout") << QLatin1String("HEAD") << QLatin1String("--")
820             << fileName;
821
822     executeGit(workingDirectory, arguments, 0, true);
823 }
824
825 void GitClient::hardReset(const QString &workingDirectory, const QString &commit)
826 {
827     QStringList arguments;
828     arguments << QLatin1String("reset") << QLatin1String("--hard");
829     if (!commit.isEmpty())
830         arguments << commit;
831
832     GitCommand *cmd = executeGit(workingDirectory, arguments, 0, true);
833     connectRepositoryChanged(workingDirectory, cmd);
834 }
835
836 void GitClient::addFile(const QString &workingDirectory, const QString &fileName)
837 {
838     QStringList arguments;
839     arguments << QLatin1String("add") << fileName;
840
841     executeGit(workingDirectory, arguments, 0, true);
842 }
843
844 // Warning: 'intendToAdd' works only from 1.6.1 onwards
845 bool GitClient::synchronousAdd(const QString &workingDirectory,
846                                bool intendToAdd,
847                                const QStringList &files)
848 {
849     if (Git::Constants::debug)
850         qDebug() << Q_FUNC_INFO << workingDirectory << files;
851     QByteArray outputText;
852     QByteArray errorText;
853     QStringList arguments;
854     arguments << QLatin1String("add");
855     if (intendToAdd)
856         arguments << QLatin1String("--intent-to-add");
857     arguments.append(files);
858     const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
859     if (!rc) {
860         const QString errorMessage = tr("Unable to add %n file(s) to %1: %2", 0, files.size()).
861                                      arg(QDir::toNativeSeparators(workingDirectory),
862                                      commandOutputFromLocal8Bit(errorText));
863         outputWindow()->appendError(errorMessage);
864     }
865     return rc;
866 }
867
868 bool GitClient::synchronousDelete(const QString &workingDirectory,
869                                   bool force,
870                                   const QStringList &files)
871 {
872     if (Git::Constants::debug)
873         qDebug() << Q_FUNC_INFO << workingDirectory << files;
874     QByteArray outputText;
875     QByteArray errorText;
876     QStringList arguments;
877     arguments << QLatin1String("rm");
878     if (force)
879         arguments << QLatin1String("--force");
880     arguments.append(files);
881     const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
882     if (!rc) {
883         const QString errorMessage = tr("Unable to remove %n file(s) from %1: %2", 0, files.size()).
884                                      arg(QDir::toNativeSeparators(workingDirectory), commandOutputFromLocal8Bit(errorText));
885         outputWindow()->appendError(errorMessage);
886     }
887     return rc;
888 }
889
890 bool GitClient::synchronousMove(const QString &workingDirectory,
891                                 const QString &from,
892                                 const QString &to)
893 {
894     if (Git::Constants::debug)
895         qDebug() << Q_FUNC_INFO << workingDirectory << from << to;
896     QByteArray outputText;
897     QByteArray errorText;
898     QStringList arguments;
899     arguments << QLatin1String("mv");
900     arguments << (from);
901     arguments << (to);
902     const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
903     if (!rc) {
904         const QString errorMessage = tr("Unable to move from %1 to %2: %3").
905                                      arg(from, to, commandOutputFromLocal8Bit(errorText));
906         outputWindow()->appendError(errorMessage);
907     }
908     return rc;
909 }
910
911 bool GitClient::synchronousReset(const QString &workingDirectory,
912                                  const QStringList &files,
913                                  QString *errorMessage)
914 {
915     if (Git::Constants::debug)
916         qDebug() << Q_FUNC_INFO << workingDirectory << files;
917     QByteArray outputText;
918     QByteArray errorText;
919     QStringList arguments;
920     arguments << QLatin1String("reset");
921     if (files.isEmpty()) {
922         arguments << QLatin1String("--hard");
923     } else {
924         arguments << QLatin1String("HEAD") << QLatin1String("--") << files;
925     }
926     const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
927     const QString output = commandOutputFromLocal8Bit(outputText);
928     outputWindow()->append(output);
929     // Note that git exits with 1 even if the operation is successful
930     // Assume real failure if the output does not contain "foo.cpp modified"
931     // or "Unstaged changes after reset" (git 1.7.0).
932     if (!rc &&
933         (!output.contains(QLatin1String("modified"))
934          && !output.contains(QLatin1String("Unstaged changes after reset")))) {
935         const QString stdErr = commandOutputFromLocal8Bit(errorText);
936         const QString msg = files.isEmpty() ?
937                             tr("Unable to reset %1: %2").arg(QDir::toNativeSeparators(workingDirectory), stdErr) :
938                             tr("Unable to reset %n file(s) in %1: %2", 0, files.size()).
939                             arg(QDir::toNativeSeparators(workingDirectory), stdErr);
940         if (errorMessage) {
941             *errorMessage = msg;
942         } else {
943             outputWindow()->appendError(msg);
944         }
945         return false;
946     }
947     return true;
948 }
949
950 // Initialize repository
951 bool GitClient::synchronousInit(const QString &workingDirectory)
952 {
953     if (Git::Constants::debug)
954         qDebug() << Q_FUNC_INFO << workingDirectory;
955     QByteArray outputText;
956     QByteArray errorText;
957     const QStringList arguments(QLatin1String("init"));
958     const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
959     // '[Re]Initialized...'
960     outputWindow()->append(commandOutputFromLocal8Bit(outputText));
961     if (!rc)
962         outputWindow()->appendError(commandOutputFromLocal8Bit(errorText));
963     return rc;
964 }
965
966 /* Checkout, supports:
967  * git checkout -- <files>
968  * git checkout revision -- <files>
969  * git checkout revision -- . */
970 bool GitClient::synchronousCheckoutFiles(const QString &workingDirectory,
971                                          QStringList files /* = QStringList() */,
972                                          QString revision /* = QString() */,
973                                          QString *errorMessage /* = 0 */,
974                                          bool revertStaging /* = true */)
975 {
976     if (Git::Constants::debug)
977         qDebug() << Q_FUNC_INFO << workingDirectory << files;
978     if (revertStaging && revision.isEmpty())
979         revision = QLatin1String("HEAD");
980     if (files.isEmpty())
981         files = QStringList(QString(QLatin1Char('.')));
982     QByteArray outputText;
983     QByteArray errorText;
984     QStringList arguments;
985     arguments << QLatin1String("checkout");
986     if (revertStaging)
987         arguments << revision;
988     arguments << QLatin1String("--") << files;
989     const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
990     if (!rc) {
991         const QString fileArg = files.join(QLatin1String(", "));
992         //: Meaning of the arguments: %1: revision, %2: files, %3: repository,
993         //: %4: Error message
994         const QString msg = tr("Unable to checkout %1 of %2 in %3: %4").
995                             arg(revision, fileArg, workingDirectory, commandOutputFromLocal8Bit(errorText));
996         if (errorMessage) {
997             *errorMessage = msg;
998         } else {
999             outputWindow()->appendError(msg);
1000         }
1001         return false;
1002     }
1003     return true;
1004 }
1005
1006 static inline QString msgParentRevisionFailed(const QString &workingDirectory,
1007                                               const QString &revision,
1008                                               const QString &why)
1009 {
1010     //: Failed to find parent revisions of a SHA1 for "annotate previous"
1011     return GitClient::tr("Unable to find parent revisions of %1 in %2: %3").arg(revision, workingDirectory, why);
1012 }
1013
1014 static inline QString msgInvalidRevision()
1015 {
1016     return GitClient::tr("Invalid revision");
1017 }
1018
1019 // Split a line of "<commit> <parent1> ..." to obtain parents from "rev-list" or "log".
1020 static inline bool splitCommitParents(const QString &line,
1021                                       QString *commit = 0,
1022                                       QStringList *parents = 0)
1023 {
1024     if (commit)
1025         commit->clear();
1026     if (parents)
1027         parents->clear();
1028     QStringList tokens = line.trimmed().split(QLatin1Char(' '));
1029     if (tokens.size() < 2)
1030         return false;
1031     if (commit)
1032         *commit = tokens.front();
1033     tokens.pop_front();
1034     if (parents)
1035         *parents = tokens;
1036     return true;
1037 }
1038
1039 // Find out the immediate parent revisions of a revision of the repository.
1040 // Might be several in case of merges.
1041 bool GitClient::synchronousParentRevisions(const QString &workingDirectory,
1042                                            const QStringList &files /* = QStringList() */,
1043                                            const QString &revision,
1044                                            QStringList *parents,
1045                                            QString *errorMessage)
1046 {
1047     if (Git::Constants::debug)
1048         qDebug() << Q_FUNC_INFO << workingDirectory << revision;
1049     QByteArray outputTextData;
1050     QByteArray errorText;
1051     QStringList arguments;
1052     arguments << QLatin1String("rev-list") << QLatin1String(GitClient::noColorOption)
1053               << QLatin1String("--parents") << QLatin1String("--max-count=1") << revision;
1054     if (!files.isEmpty()) {
1055         arguments.append(QLatin1String("--"));
1056         arguments.append(files);
1057     }
1058     const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputTextData, &errorText);
1059     if (!rc) {
1060         *errorMessage = msgParentRevisionFailed(workingDirectory, revision, commandOutputFromLocal8Bit(errorText));
1061         return false;
1062     }
1063     // Should result in one line of blank-delimited revisions, specifying current first
1064     // unless it is top.
1065     QString outputText = commandOutputFromLocal8Bit(outputTextData);
1066     outputText.remove(QLatin1Char('\n'));
1067     if (!splitCommitParents(outputText, 0, parents)) {
1068         *errorMessage = msgParentRevisionFailed(workingDirectory, revision, msgInvalidRevision());
1069         return false;
1070     }
1071     if (Git::Constants::debug)
1072         qDebug() << workingDirectory << files << revision << "->" << *parents;
1073     return true;
1074 }
1075
1076 // Short SHA1, author, subject
1077 static const char defaultShortLogFormatC[] = "%h (%an \"%s\")";
1078
1079 bool GitClient::synchronousShortDescription(const QString &workingDirectory, const QString &revision,
1080                                     QString *description, QString *errorMessage)
1081 {
1082     // Short SHA 1, author, subject
1083     return synchronousShortDescription(workingDirectory, revision,
1084                                QLatin1String(defaultShortLogFormatC),
1085                                description, errorMessage);
1086 }
1087
1088 // Convenience working on a list of revisions
1089 bool GitClient::synchronousShortDescriptions(const QString &workingDirectory, const QStringList &revisions,
1090                                             QStringList *descriptions, QString *errorMessage)
1091 {
1092     descriptions->clear();
1093     foreach (const QString &revision, revisions) {
1094         QString description;
1095         if (!synchronousShortDescription(workingDirectory, revision, &description, errorMessage)) {
1096             descriptions->clear();
1097             return false;
1098         }
1099         descriptions->push_back(description);
1100     }
1101     return true;
1102 }
1103
1104 static inline QString msgCannotDetermineBranch(const QString &workingDirectory, const QString &why)
1105 {
1106     return GitClient::tr("Unable to retrieve branch of %1: %2").arg(QDir::toNativeSeparators(workingDirectory), why);
1107 }
1108
1109 // Retrieve head revision/branch
1110 bool GitClient::synchronousTopRevision(const QString &workingDirectory,
1111                                        QString *revision /* = 0 */,
1112                                        QString *branch /* = 0 */,
1113                                        QString *errorMessageIn /* = 0 */)
1114 {
1115     if (Git::Constants::debug)
1116         qDebug() << Q_FUNC_INFO << workingDirectory;
1117     QByteArray outputTextData;
1118     QByteArray errorText;
1119     QStringList arguments;
1120     QString errorMessage;
1121     do {
1122         // get revision
1123         if (revision) {
1124             revision->clear();
1125             arguments << QLatin1String("log") << QLatin1String(noColorOption)
1126                     <<  QLatin1String("--max-count=1") << QLatin1String("--pretty=format:%H");
1127             if (!fullySynchronousGit(workingDirectory, arguments, &outputTextData, &errorText)) {
1128                 errorMessage = tr("Unable to retrieve top revision of %1: %2").arg(QDir::toNativeSeparators(workingDirectory), commandOutputFromLocal8Bit(errorText));
1129                 break;
1130             }
1131             *revision = commandOutputFromLocal8Bit(outputTextData);
1132             revision->remove(QLatin1Char('\n'));
1133         } // revision desired
1134         // get branch
1135         if (branch) {
1136             branch->clear();
1137             arguments.clear();
1138             arguments << QLatin1String("branch") << QLatin1String(noColorOption);
1139             if (!fullySynchronousGit(workingDirectory, arguments, &outputTextData, &errorText)) {
1140                 errorMessage = msgCannotDetermineBranch(workingDirectory, commandOutputFromLocal8Bit(errorText));
1141                 break;
1142             }
1143             /* parse output for current branch: \code
1144 * master
1145   branch2
1146 \endcode */
1147             const QString branchPrefix = QLatin1String("* ");
1148             foreach(const QString &line, commandOutputLinesFromLocal8Bit(outputTextData)) {
1149                 if (line.startsWith(branchPrefix)) {
1150                     *branch = line;
1151                     branch->remove(0, branchPrefix.size());
1152                     break;
1153                 }
1154             }
1155             if (branch->isEmpty()) {
1156                 errorMessage = msgCannotDetermineBranch(workingDirectory,
1157                                                         QString::fromLatin1("Internal error: Failed to parse output: %1").arg(commandOutputFromLocal8Bit(outputTextData)));
1158                 break;
1159             }
1160         } // branch
1161     } while (false);
1162     const bool failed = (revision && revision->isEmpty()) || (branch && branch->isEmpty());
1163     if (failed && !errorMessage.isEmpty()) {
1164         if (errorMessageIn) {
1165             *errorMessageIn = errorMessage;
1166         } else {
1167             outputWindow()->appendError(errorMessage);
1168         }
1169     }
1170     return !failed;
1171 }
1172
1173 // Format an entry in a one-liner for selection list using git log.
1174 bool GitClient::synchronousShortDescription(const QString &workingDirectory,
1175                                     const QString &revision,
1176                                     const QString &format,
1177                                     QString *description,
1178                                     QString *errorMessage)
1179 {
1180     if (Git::Constants::debug)
1181         qDebug() << Q_FUNC_INFO << workingDirectory << revision;
1182     QByteArray outputTextData;
1183     QByteArray errorText;
1184     QStringList arguments;
1185     arguments << QLatin1String("log") << QLatin1String(GitClient::noColorOption)
1186               << (QLatin1String("--pretty=format:") + format)
1187               << QLatin1String("--max-count=1") << revision;
1188     const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputTextData, &errorText);
1189     if (!rc) {
1190         *errorMessage = tr("Unable to describe revision %1 in %2: %3").arg(revision, workingDirectory, commandOutputFromLocal8Bit(errorText));
1191         return false;
1192     }
1193     *description = commandOutputFromLocal8Bit(outputTextData);
1194     if (description->endsWith(QLatin1Char('\n')))
1195         description->truncate(description->size() - 1);
1196     return true;
1197 }
1198
1199 // Create a default message to be used for describing stashes
1200 static inline QString creatorStashMessage(const QString &keyword = QString())
1201 {
1202     QString rc = QCoreApplication::applicationName();
1203     rc += QLatin1Char(' ');
1204     if (!keyword.isEmpty()) {
1205         rc += keyword;
1206         rc += QLatin1Char(' ');
1207     }
1208     rc += QDateTime::currentDateTime().toString(Qt::ISODate);
1209     return rc;
1210 }
1211
1212 /* Do a stash and return the message as identifier. Note that stash names (stash{n})
1213  * shift as they are pushed, so, enforce the use of messages to identify them. Flags:
1214  * StashPromptDescription: Prompt the user for a description message.
1215  * StashImmediateRestore: Immediately re-apply this stash (used for snapshots), user keeps on working
1216  * StashIgnoreUnchanged: Be quiet about unchanged repositories (used for IVersionControl's snapshots). */
1217
1218 QString GitClient::synchronousStash(const QString &workingDirectory,
1219                                     const QString &messageKeyword /*  = QString() */,
1220                                     unsigned flags,
1221                                     bool *unchanged /* =0 */)
1222 {
1223     if (unchanged)
1224         *unchanged = false;
1225     QString message;
1226     bool success = false;
1227     // Check for changes and stash
1228     QString errorMessage;
1229     switch (gitStatus(workingDirectory, false, 0, &errorMessage)) {
1230     case  StatusChanged: {
1231             message = creatorStashMessage(messageKeyword);
1232             do {
1233                 if ((flags & StashPromptDescription)) {
1234                     if (!inputText(Core::ICore::instance()->mainWindow(),
1235                                    tr("Stash Description"), tr("Description:"), &message))
1236                         break;
1237                 }
1238                 if (!executeSynchronousStash(workingDirectory, message))
1239                     break;
1240                 if ((flags & StashImmediateRestore)
1241                     && !synchronousStashRestore(workingDirectory, QLatin1String("stash@{0}")))
1242                     break;
1243                 success = true;
1244             } while (false);
1245         }
1246         break;
1247     case StatusUnchanged:
1248         if (unchanged)
1249             *unchanged = true;
1250         if (!(flags & StashIgnoreUnchanged))
1251             outputWindow()->append(msgNoChangedFiles());
1252         break;
1253     case StatusFailed:
1254         outputWindow()->append(errorMessage);
1255         break;
1256     }
1257     if (!success)
1258         message.clear();
1259     if (Git::Constants::debug)
1260         qDebug() << Q_FUNC_INFO << '\n' << workingDirectory << messageKeyword << "returns" << message;
1261     return message;
1262 }
1263
1264 bool GitClient::executeSynchronousStash(const QString &workingDirectory,
1265                                  const QString &message,
1266                                  QString *errorMessage /* = 0*/)
1267 {
1268     if (Git::Constants::debug)
1269         qDebug() << Q_FUNC_INFO << workingDirectory;
1270     QByteArray outputText;
1271     QByteArray errorText;
1272     QStringList arguments;
1273     arguments << QLatin1String("stash");
1274     if (!message.isEmpty())
1275         arguments << QLatin1String("save") << message;
1276     const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
1277     if (!rc) {
1278         const QString msg = tr("Unable stash in %1: %2").
1279                             arg(QDir::toNativeSeparators(workingDirectory),
1280                                 commandOutputFromLocal8Bit(errorText));
1281         if (errorMessage) {
1282             *errorMessage = msg;
1283         } else {
1284             outputWindow()->append(msg);
1285         }
1286         return false;
1287     }
1288     return true;
1289 }
1290
1291 // Resolve a stash name from message
1292 bool GitClient::stashNameFromMessage(const QString &workingDirectory,
1293                                      const QString &message, QString *name,
1294                                      QString *errorMessage /* = 0 */)
1295 {
1296     // All happy
1297     if (message.startsWith(QLatin1String(stashNamePrefix))) {
1298         *name = message;
1299         return true;
1300     }
1301     // Retrieve list and find via message
1302     QList<Stash> stashes;
1303     if (!synchronousStashList(workingDirectory, &stashes, errorMessage))
1304         return false;
1305     foreach (const Stash &s, stashes) {
1306         if (s.message == message) {
1307             *name = s.name;
1308             return true;
1309         }
1310     }
1311     //: Look-up of a stash via its descriptive message failed.
1312     const QString msg = tr("Unable to resolve stash message '%1' in %2").arg(message, workingDirectory);
1313     if (errorMessage) {
1314         *errorMessage = msg;
1315     } else {
1316         outputWindow()->append(msg);
1317     }
1318     return  false;
1319 }
1320
1321 bool GitClient::synchronousBranchCmd(const QString &workingDirectory, QStringList branchArgs,
1322                                      QString *output, QString *errorMessage)
1323 {
1324     if (Git::Constants::debug)
1325         qDebug() << Q_FUNC_INFO << workingDirectory << branchArgs;
1326     branchArgs.push_front(QLatin1String("branch"));
1327     QByteArray outputText;
1328     QByteArray errorText;
1329     const bool rc = fullySynchronousGit(workingDirectory, branchArgs, &outputText, &errorText);
1330     if (!rc) {
1331         *errorMessage = tr("Unable to run a 'git branch' command in %1: %2").arg(QDir::toNativeSeparators(workingDirectory), commandOutputFromLocal8Bit(errorText));
1332         return false;
1333     }
1334     *output = commandOutputFromLocal8Bit(outputText);
1335     return true;
1336 }
1337
1338 bool GitClient::synchronousShow(const QString &workingDirectory, const QString &id,
1339                                  QString *output, QString *errorMessage)
1340 {
1341     if (Git::Constants::debug)
1342         qDebug() << Q_FUNC_INFO << workingDirectory << id;
1343     if (!canShow(id)) {
1344         *errorMessage = msgCannotShow(id);
1345         return false;
1346     }
1347     QStringList args(QLatin1String("show"));
1348     args << QLatin1String(noColorOption) << id;
1349     QByteArray outputText;
1350     QByteArray errorText;
1351     const bool rc = fullySynchronousGit(workingDirectory, args, &outputText, &errorText);
1352     if (!rc) {
1353         *errorMessage = tr("Unable to run 'git show' in %1: %2").arg(QDir::toNativeSeparators(workingDirectory), commandOutputFromLocal8Bit(errorText));
1354         return false;
1355     }
1356     *output = commandOutputFromLocal8Bit(outputText);
1357     return true;
1358 }
1359
1360 // Retrieve list of files to be cleaned
1361 bool GitClient::synchronousCleanList(const QString &workingDirectory,
1362                                      QStringList *files, QString *errorMessage)
1363 {
1364     if (Git::Constants::debug)
1365         qDebug() << Q_FUNC_INFO << workingDirectory;
1366     files->clear();
1367     QStringList args;
1368     args << QLatin1String("clean") << QLatin1String("--dry-run") << QLatin1String("-dxf");
1369     QByteArray outputText;
1370     QByteArray errorText;
1371     const bool rc = fullySynchronousGit(workingDirectory, args, &outputText, &errorText);
1372     if (!rc) {
1373         *errorMessage = tr("Unable to run 'git clean' in %1: %2").arg(QDir::toNativeSeparators(workingDirectory), commandOutputFromLocal8Bit(errorText));
1374         return false;
1375     }
1376     // Filter files that git would remove
1377     const QString prefix = QLatin1String("Would remove ");
1378     foreach(const QString &line, commandOutputLinesFromLocal8Bit(outputText))
1379         if (line.startsWith(prefix))
1380             files->push_back(line.mid(prefix.size()));
1381     return true;
1382 }
1383
1384 bool GitClient::synchronousApplyPatch(const QString &workingDirectory,
1385                                       const QString &file, QString *errorMessage)
1386 {
1387     if (Git::Constants::debug)
1388         qDebug() << Q_FUNC_INFO << workingDirectory;
1389     QStringList args;
1390     args << QLatin1String("apply") << QLatin1String("--whitespace=fix") << file;
1391     QByteArray outputText;
1392     QByteArray errorText;
1393     const bool rc = fullySynchronousGit(workingDirectory, args, &outputText, &errorText);
1394     if (rc) {
1395         if (!errorText.isEmpty())
1396             *errorMessage = tr("There were warnings while applying %1 to %2:\n%3").arg(file, workingDirectory, commandOutputFromLocal8Bit(errorText));
1397     } else {
1398         *errorMessage = tr("Unable apply patch %1 to %2: %3").arg(file, workingDirectory, commandOutputFromLocal8Bit(errorText));
1399         return false;
1400     }
1401     return true;
1402 }
1403
1404 // Factory function to create an asynchronous command
1405 GitCommand *GitClient::createCommand(const QString &workingDirectory,
1406                              VCSBase::VCSBaseEditorWidget* editor,
1407                              bool outputToWindow,
1408                              int editorLineNumber)
1409 {
1410     if (Git::Constants::debug)
1411         qDebug() << Q_FUNC_INFO << workingDirectory << editor;
1412
1413     VCSBase::VCSBaseOutputWindow *outputWindow = VCSBase::VCSBaseOutputWindow::instance();
1414     GitCommand* command = new GitCommand(binary(), workingDirectory, processEnvironment(), QVariant(editorLineNumber));
1415     if (editor)
1416         connect(command, SIGNAL(finished(bool,int,QVariant)), editor, SLOT(commandFinishedGotoLine(bool,int,QVariant)));
1417     if (outputToWindow) {
1418         if (editor) { // assume that the commands output is the important thing
1419             connect(command, SIGNAL(outputData(QByteArray)), outputWindow, SLOT(appendDataSilently(QByteArray)));
1420         } else {
1421             connect(command, SIGNAL(outputData(QByteArray)), outputWindow, SLOT(appendData(QByteArray)));
1422         }
1423     } else {
1424         QTC_ASSERT(editor, /**/);
1425         connect(command, SIGNAL(outputData(QByteArray)), editor, SLOT(setPlainTextDataFiltered(QByteArray)));
1426     }
1427
1428     if (outputWindow)
1429         connect(command, SIGNAL(errorText(QString)), outputWindow, SLOT(appendError(QString)));
1430     return command;
1431 }
1432
1433 // Execute a single command
1434 GitCommand *GitClient::executeGit(const QString &workingDirectory,
1435                                   const QStringList &arguments,
1436                                   VCSBase::VCSBaseEditorWidget* editor,
1437                                   bool outputToWindow,
1438                                   GitCommand::TerminationReportMode tm,
1439                                   int editorLineNumber,
1440                                   bool unixTerminalDisabled)
1441 {
1442     outputWindow()->appendCommand(workingDirectory, QLatin1String(Constants::GIT_BINARY), arguments);
1443     GitCommand *command = createCommand(workingDirectory, editor, outputToWindow, editorLineNumber);
1444     command->addJob(arguments, m_settings.timeoutSeconds);
1445     command->setTerminationReportMode(tm);
1446     command->setUnixTerminalDisabled(unixTerminalDisabled);
1447     command->execute();
1448     return command;
1449 }
1450
1451 // Return fixed arguments required to run
1452 QString GitClient::binary() const
1453 {
1454     return m_binaryPath;
1455 }
1456
1457 // Determine a value for the HOME variable on Windows
1458 // working around MSys git not finding it when run outside git bash
1459 // (then looking for the SSH keys under "\program files\git").
1460
1461 QString GitClient::fakeWinHome(const QProcessEnvironment &e)
1462 {
1463     const QString homeDrive = e.value("HOMEDRIVE");
1464     const QString homePath = e.value("HOMEPATH");
1465     QTC_ASSERT(!homeDrive.isEmpty() && !homePath.isEmpty(), return QString())
1466     return homeDrive + homePath;
1467 }
1468
1469 QProcessEnvironment GitClient::processEnvironment() const
1470 {
1471
1472     QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
1473     if (m_settings.adoptPath)
1474         environment.insert(QLatin1String("PATH"), m_settings.path);
1475 #ifdef Q_OS_WIN
1476     if (m_settings.winSetHomeEnvironment) {
1477         const QString home = fakeWinHome(environment);
1478         if (Constants::debug)
1479             qDebug("Setting home '%s'", qPrintable(home));
1480         environment.insert(QLatin1String("HOME"), home);
1481     }
1482 #endif // Q_OS_WIN
1483     // Set up SSH and C locale (required by git using perl).
1484     VCSBase::VCSBasePlugin::setProcessEnvironment(&environment, false);
1485     return environment;
1486 }
1487
1488 // Synchronous git execution using Utils::SynchronousProcess, with
1489 // log windows updating.
1490 Utils::SynchronousProcessResponse
1491         GitClient::synchronousGit(const QString &workingDirectory,
1492                                   const QStringList &gitArguments,
1493                                   unsigned flags,
1494                                   QTextCodec *stdOutCodec)
1495 {
1496     if (Git::Constants::debug)
1497         qDebug() << "synchronousGit" << workingDirectory << gitArguments;
1498     return VCSBase::VCSBasePlugin::runVCS(workingDirectory, binary(), gitArguments,
1499                                           m_settings.timeoutSeconds * 1000,
1500                                           processEnvironment(),
1501                                           flags, stdOutCodec);
1502 }
1503
1504 bool GitClient::fullySynchronousGit(const QString &workingDirectory,
1505                                     const QStringList &gitArguments,
1506                                     QByteArray* outputText,
1507                                     QByteArray* errorText,
1508                                     bool logCommandToWindow)
1509 {
1510     if (Git::Constants::debug)
1511         qDebug() << "fullySynchronousGit" << workingDirectory << gitArguments;
1512
1513     if (logCommandToWindow)
1514         outputWindow()->appendCommand(workingDirectory, m_binaryPath, gitArguments);
1515
1516     QProcess process;
1517     process.setWorkingDirectory(workingDirectory);
1518     process.setProcessEnvironment(processEnvironment());
1519
1520     process.start(binary(), gitArguments);
1521     process.closeWriteChannel();
1522     if (!process.waitForStarted()) {
1523         if (errorText) {
1524             const QString msg = QString::fromLatin1("Unable to execute '%1': %2:")
1525                                 .arg(binary(), process.errorString());
1526             *errorText = msg.toLocal8Bit();
1527         }
1528         return false;
1529     }
1530
1531     if (!Utils::SynchronousProcess::readDataFromProcess(process, m_settings.timeoutSeconds * 1000,
1532                                                         outputText, errorText, true)) {
1533         errorText->append(GitCommand::msgTimeout(m_settings.timeoutSeconds).toLocal8Bit());
1534         Utils::SynchronousProcess::stopProcess(process);
1535         return false;
1536     }
1537
1538     if (Git::Constants::debug)
1539         qDebug() << "synchronousGit ex=" << process.exitStatus() << process.exitCode();
1540     return process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0;
1541 }
1542
1543 static inline int
1544         askWithDetailedText(QWidget *parent,
1545                             const QString &title, const QString &msg,
1546                             const QString &inf,
1547                             QMessageBox::StandardButton defaultButton,
1548                             QMessageBox::StandardButtons buttons = QMessageBox::Yes|QMessageBox::No)
1549 {
1550     QMessageBox msgBox(QMessageBox::Question, title, msg, buttons, parent);
1551     msgBox.setDetailedText(inf);
1552     msgBox.setDefaultButton(defaultButton);
1553     return msgBox.exec();
1554 }
1555
1556 // Convenience that pops up an msg box.
1557 GitClient::StashResult GitClient::ensureStash(const QString &workingDirectory)
1558 {
1559     QString errorMessage;
1560     const StashResult sr = ensureStash(workingDirectory, &errorMessage);
1561     if (sr == StashFailed)
1562         outputWindow()->appendError(errorMessage);
1563     return sr;
1564 }
1565
1566 // Ensure that changed files are stashed before a pull or similar
1567 GitClient::StashResult GitClient::ensureStash(const QString &workingDirectory, QString *errorMessage)
1568 {
1569     QString statusOutput;
1570     switch (gitStatus(workingDirectory, false, &statusOutput, errorMessage)) {
1571         case StatusChanged:
1572         break;
1573         case StatusUnchanged:
1574         return StashUnchanged;
1575         case StatusFailed:
1576         return StashFailed;
1577     }
1578
1579     const int answer = askWithDetailedText(m_core->mainWindow(), tr("Changes"),
1580                              tr("You have modified files. Would you like to stash your changes?"),
1581                              statusOutput, QMessageBox::Yes, QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel);
1582     switch (answer) {
1583         case QMessageBox::Cancel:
1584             return StashCanceled;
1585         case QMessageBox::Yes:
1586             if (!executeSynchronousStash(workingDirectory, creatorStashMessage(QLatin1String("push")), errorMessage))
1587                 return StashFailed;
1588             break;
1589         case QMessageBox::No: // At your own risk, so.
1590             return NotStashed;
1591         }
1592
1593     return Stashed;
1594  }
1595
1596 // Trim a git status file spec: "modified:    foo .cpp" -> "modified: foo .cpp"
1597 static inline QString trimFileSpecification(QString fileSpec)
1598 {
1599     const int colonIndex = fileSpec.indexOf(QLatin1Char(':'));
1600     if (colonIndex != -1) {
1601         // Collapse the sequence of spaces
1602         const int filePos = colonIndex + 2;
1603         int nonBlankPos = filePos;
1604         for ( ; fileSpec.at(nonBlankPos).isSpace(); nonBlankPos++) ;
1605         if (nonBlankPos > filePos)
1606             fileSpec.remove(filePos, nonBlankPos - filePos);
1607     }
1608     return fileSpec;
1609 }
1610
1611 GitClient::StatusResult GitClient::gitStatus(const QString &workingDirectory,
1612                                              bool untracked,
1613                                              QString *output,
1614                                              QString *errorMessage,
1615                                              bool *onBranch)
1616 {
1617     // Run 'status'. Note that git returns exitcode 1 if there are no added files.
1618     QByteArray outputText;
1619     QByteArray errorText;
1620     // @TODO: Use "--no-color" once it is supported
1621     QStringList statusArgs(QLatin1String("status"));
1622     if (untracked)
1623         statusArgs << QLatin1String("-u");
1624     const bool statusRc = fullySynchronousGit(workingDirectory, statusArgs, &outputText, &errorText);
1625     GitCommand::removeColorCodes(&outputText);
1626     if (output)
1627         *output = commandOutputFromLocal8Bit(outputText);
1628     const bool branchKnown = outputText.contains(kBranchIndicatorC);
1629     if (onBranch)
1630         *onBranch = branchKnown;
1631     // Is it something really fatal?
1632     if (!statusRc && !branchKnown && !outputText.contains("# Not currently on any branch.")) {
1633         if (errorMessage) {
1634             const QString error = commandOutputFromLocal8Bit(errorText);
1635             *errorMessage = tr("Unable to obtain the status: %1").arg(error);
1636         }
1637         return StatusFailed;
1638     }
1639     // Unchanged (output text depending on whether -u was passed)
1640     if (outputText.contains("nothing to commit"))
1641         return StatusUnchanged;
1642     if (outputText.contains("nothing added to commit but untracked files present"))
1643         return untracked ? StatusChanged : StatusUnchanged;
1644     return StatusChanged;
1645 }
1646
1647 // Quietly retrieve branch list of remote repository URL
1648 //
1649 // The branch HEAD is pointing to is always returned first.
1650 QStringList GitClient::synchronousRepositoryBranches(const QString &repositoryURL)
1651 {
1652     QStringList arguments(QLatin1String("ls-remote"));
1653     arguments << repositoryURL << QLatin1String("HEAD") << QLatin1String("refs/heads/*");
1654     const unsigned flags =
1655             VCSBase::VCSBasePlugin::SshPasswordPrompt|
1656             VCSBase::VCSBasePlugin::SuppressStdErrInLogWindow|
1657             VCSBase::VCSBasePlugin::SuppressFailMessageInLogWindow;
1658     const Utils::SynchronousProcessResponse resp = synchronousGit(QString(), arguments, flags);
1659     QStringList branches;
1660     branches << "<detached HEAD>";
1661     QString headSha;
1662     if (resp.result == Utils::SynchronousProcessResponse::Finished) {
1663         // split "82bfad2f51d34e98b18982211c82220b8db049b<tab>refs/heads/master"
1664         foreach(const QString &line, resp.stdOut.split(QLatin1Char('\n'))) {
1665             if (line.endsWith("\tHEAD")) {
1666                 Q_ASSERT(headSha.isNull());
1667                 headSha = line.left(line.indexOf(QChar('\t')));
1668                 continue;
1669             }
1670
1671             const int slashPos = line.lastIndexOf(QLatin1Char('/'));
1672             const QString branchName = line.mid(slashPos + 1);
1673             if (slashPos != -1) {
1674                 if (line.startsWith(headSha))
1675                     branches[0] = branchName;
1676                 else
1677                     branches.push_back(branchName);
1678             }
1679         }
1680     }
1681     return branches;
1682 }
1683
1684 void GitClient::launchGitK(const QString &workingDirectory)
1685 {
1686     VCSBase::VCSBaseOutputWindow *outwin = VCSBase::VCSBaseOutputWindow::instance();
1687     // Locate git in (potentially) custom path. m_binaryPath can be absolute,
1688     // which will be handled correctly.
1689     QTC_ASSERT(!m_binaryPath.isEmpty(), return);
1690     const QString gitBinary = QLatin1String(Constants::GIT_BINARY);
1691     const QProcessEnvironment env = processEnvironment();
1692     const QString path = env.value(QLatin1String("PATH"));
1693     const QString fullGitBinary = Utils::SynchronousProcess::locateBinary(path, m_binaryPath);
1694     if (fullGitBinary.isEmpty()) {
1695         outwin->appendError(tr("Cannot locate %1.").arg(gitBinary));
1696         return;
1697     }
1698     const QString gitBinDirectory = QFileInfo(fullGitBinary).absolutePath();
1699     QDir foundBinDir = gitBinDirectory;
1700     const bool foundBinDirIsCmdDir = foundBinDir.dirName() == "cmd";
1701     if (!tryLauchingGitK(env, workingDirectory, gitBinDirectory, foundBinDirIsCmdDir)) {
1702         if (foundBinDirIsCmdDir) {
1703             foundBinDir.cdUp();
1704             tryLauchingGitK(env, workingDirectory, foundBinDir.path() + "/bin", false);
1705         }
1706     }
1707 }
1708
1709 bool GitClient::tryLauchingGitK(const QProcessEnvironment &env,
1710                                 const QString &workingDirectory,
1711                                 const QString &gitBinDirectory,
1712                                 bool silent)
1713 {
1714 #ifdef Q_OS_WIN
1715     // Launch 'wish' shell from git binary directory with the gitk located there
1716     const QString binary = gitBinDirectory + QLatin1String("/wish");
1717     QStringList arguments(gitBinDirectory + QLatin1String("/gitk"));
1718 #else
1719     // Simple: Run gitk from binary path
1720     const QString binary = gitBinDirectory + QLatin1String("/gitk");
1721     QStringList arguments;
1722 #endif
1723     VCSBase::VCSBaseOutputWindow *outwin = VCSBase::VCSBaseOutputWindow::instance();
1724     if (!m_settings.gitkOptions.isEmpty())
1725         arguments.append(Utils::QtcProcess::splitArgs(m_settings.gitkOptions));
1726     outwin->appendCommand(workingDirectory, binary, arguments);
1727     // This should always use QProcess::startDetached (as not to kill
1728     // the child), but that does not have an environment parameter.
1729     bool success = false;
1730     if (m_settings.adoptPath) {
1731         QProcess *process = new QProcess(this);
1732         process->setWorkingDirectory(workingDirectory);
1733         process->setProcessEnvironment(env);
1734         process->start(binary, arguments);
1735         success = process->waitForStarted();
1736         if (success) {
1737             connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater()));
1738         } else {
1739             delete process;
1740         }
1741     } else {
1742         success = QProcess::startDetached(binary, arguments, workingDirectory);
1743     }
1744     if (!success) {
1745         const QString error = tr("Unable to launch %1.").arg(binary);
1746         if (silent)
1747             outwin->appendSilently(error);
1748         else
1749             outwin->appendError(error);
1750     }
1751     return success;
1752 }
1753
1754 bool GitClient::getCommitData(const QString &workingDirectory,
1755                               bool amend,
1756                               QString *commitTemplate,
1757                               CommitData *commitData,
1758                               QString *errorMessage)
1759 {
1760     if (Git::Constants::debug)
1761         qDebug() << Q_FUNC_INFO << workingDirectory;
1762
1763     commitData->clear();
1764
1765     // Find repo
1766     const QString repoDirectory = GitClient::findRepositoryForDirectory(workingDirectory);
1767     if (repoDirectory.isEmpty()) {
1768         *errorMessage = msgRepositoryNotFound(workingDirectory);
1769         return false;
1770     }
1771
1772     commitData->panelInfo.repository = repoDirectory;
1773
1774     QDir gitDir(repoDirectory);
1775     if (!gitDir.cd(QLatin1String(kGitDirectoryC))) {
1776         *errorMessage = tr("The repository %1 is not initialized yet.").arg(repoDirectory);
1777         return false;
1778     }
1779
1780     // Read description
1781     const QString descriptionFile = gitDir.absoluteFilePath(QLatin1String("description"));
1782     if (QFileInfo(descriptionFile).isFile()) {
1783         QFile file(descriptionFile);
1784         if (file.open(QIODevice::ReadOnly|QIODevice::Text))
1785             commitData->panelInfo.description = commandOutputFromLocal8Bit(file.readAll()).trimmed();
1786     }
1787
1788     // Run status. Note that it has exitcode 1 if there are no added files.
1789     bool onBranch;
1790     QString output;
1791     const StatusResult status = gitStatus(repoDirectory, true, &output, errorMessage, &onBranch);
1792     switch (status) {
1793     case  StatusChanged:
1794         if (!onBranch) {
1795             *errorMessage = tr("You did not checkout a branch.");
1796             return false;
1797         }
1798         break;
1799     case StatusUnchanged:
1800         if (amend)
1801             break;
1802         *errorMessage = msgNoChangedFiles();
1803         return false;
1804     case StatusFailed:
1805         return false;
1806     }
1807
1808     //    Output looks like:
1809     //    # On branch [branchname]
1810     //    # Changes to be committed:
1811     //    #   (use "git reset HEAD <file>..." to unstage)
1812     //    #
1813     //    #       modified:   somefile.cpp
1814     //    #       new File:   somenew.h
1815     //    #
1816     //    # Changed but not updated:
1817     //    #   (use "git add <file>..." to update what will be committed)
1818     //    #
1819     //    #       modified:   someother.cpp
1820     //    #       modified:   submodule (modified content)
1821     //    #       modified:   submodule2 (new commit)
1822     //    #
1823     //    # Untracked files:
1824     //    #   (use "git add <file>..." to include in what will be committed)
1825     //    #
1826     //    #       list of files...
1827
1828     if (status != StatusUnchanged) {
1829         if (!commitData->parseFilesFromStatus(output)) {
1830             *errorMessage = msgParseFilesFailed();
1831             return false;
1832         }
1833         // Filter out untracked files that are not part of the project
1834         VCSBase::VCSBaseSubmitEditor::filterUntrackedFilesOfProject(repoDirectory, &commitData->untrackedFiles);
1835         if (commitData->filesEmpty()) {
1836             *errorMessage = msgNoChangedFiles();
1837             return false;
1838         }
1839     }
1840
1841     commitData->panelData.author = readConfigValue(workingDirectory, QLatin1String("user.name"));
1842     commitData->panelData.email = readConfigValue(workingDirectory, QLatin1String("user.email"));
1843
1844     // Get the commit template or the last commit message
1845     if (amend) {
1846         // Amend: get last commit data as "SHA1@message". TODO: Figure out codec.
1847         QStringList args(QLatin1String("log"));
1848         const QString format = synchronousGitVersion(true) > 0x010701 ? "%h@%B" : "%h@%s%n%n%b";
1849         args << QLatin1String("--max-count=1") << QLatin1String("--pretty=format:") + format;
1850         const Utils::SynchronousProcessResponse sp = synchronousGit(repoDirectory, args);
1851         if (sp.result != Utils::SynchronousProcessResponse::Finished) {
1852             *errorMessage = tr("Unable to retrieve the last commit data of the repository %1.").arg(repoDirectory);
1853             return false;
1854         }
1855         const int separatorPos = sp.stdOut.indexOf(QLatin1Char('@'));
1856         QTC_ASSERT(separatorPos != -1, return false)
1857         commitData->amendSHA1= sp.stdOut.left(separatorPos);
1858         *commitTemplate = sp.stdOut.mid(separatorPos + 1);
1859     } else {
1860         // Commit: Get the commit template
1861         QString templateFilename = readConfigValue(workingDirectory, QLatin1String("commit.template"));
1862         if (!templateFilename.isEmpty()) {
1863             // Make relative to repository
1864             const QFileInfo templateFileInfo(templateFilename);
1865             if (templateFileInfo.isRelative())
1866                 templateFilename = repoDirectory + QLatin1Char('/') + templateFilename;
1867             QFile templateFile(templateFilename);
1868             if (templateFile.open(QIODevice::ReadOnly|QIODevice::Text)) {
1869                 *commitTemplate = QString::fromLocal8Bit(templateFile.readAll());
1870             } else {
1871                 qWarning("Unable to read commit template %s: %s",
1872                          qPrintable(templateFilename),
1873                          qPrintable(templateFile.errorString()));
1874             }
1875         }
1876     }
1877     return true;
1878 }
1879
1880 // Log message for commits/amended commits to go to output window
1881 static inline QString msgCommitted(const QString &amendSHA1, int fileCount)
1882 {
1883     if (amendSHA1.isEmpty())
1884         return GitClient::tr("Committed %n file(s).\n", 0, fileCount);
1885     if (fileCount)
1886         return GitClient::tr("Amended %1 (%n file(s)).\n", 0, fileCount).arg(amendSHA1);
1887     return GitClient::tr("Amended %1.").arg(amendSHA1);
1888 }
1889
1890 // addAndCommit:
1891 bool GitClient::addAndCommit(const QString &repositoryDirectory,
1892                              const GitSubmitEditorPanelData &data,
1893                              const QString &amendSHA1,
1894                              const QString &messageFile,
1895                              const QStringList &checkedFiles,
1896                              const QStringList &origCommitFiles,
1897                              const QStringList &origDeletedFiles)
1898 {
1899     if (Git::Constants::debug)
1900         qDebug() << "GitClient::addAndCommit:" << repositoryDirectory << checkedFiles << origCommitFiles;
1901     const QString renamedSeparator = QLatin1String(" -> ");
1902     const bool amend = !amendSHA1.isEmpty();
1903
1904     // Do we need to reset any files that had been added before
1905     // (did the user uncheck any previously added files)
1906     // Split up  renamed files ('foo.cpp -> foo2.cpp').
1907     QStringList resetFiles = origCommitFiles.toSet().subtract(checkedFiles.toSet()).toList();
1908     for (QStringList::iterator it = resetFiles.begin(); it != resetFiles.end(); ++it) {
1909         const int renamedPos = it->indexOf(renamedSeparator);
1910         if (renamedPos != -1) {
1911             const QString newFile = it->mid(renamedPos + renamedSeparator.size());
1912             it->truncate(renamedPos);
1913             it = resetFiles.insert(++it, newFile);
1914         }
1915     }
1916
1917     if (!resetFiles.isEmpty())
1918         if (!synchronousReset(repositoryDirectory, resetFiles))
1919             return false;
1920
1921     // Re-add all to make sure we have the latest changes, but only add those that aren't marked
1922     // for deletion. Purge out renamed files ('foo.cpp -> foo2.cpp').
1923     QStringList addFiles = checkedFiles.toSet().subtract(origDeletedFiles.toSet()).toList();
1924     for (QStringList::iterator it = addFiles.begin(); it != addFiles.end(); ) {
1925         if (it->contains(renamedSeparator)) {
1926             it = addFiles.erase(it);
1927         } else {
1928             ++it;
1929         }
1930     }
1931     if (!addFiles.isEmpty())
1932         if (!synchronousAdd(repositoryDirectory, false, addFiles))
1933             return false;
1934
1935     // Do the final commit
1936     QStringList args;
1937     args << QLatin1String("commit")
1938          << QLatin1String("-F") << QDir::toNativeSeparators(messageFile);
1939     if (amend)
1940         args << QLatin1String("--amend");
1941     const QString &authorString =  data.authorString();
1942     if (!authorString.isEmpty())
1943          args << QLatin1String("--author") << authorString;
1944
1945     QByteArray outputText;
1946     QByteArray errorText;
1947     const bool rc = fullySynchronousGit(repositoryDirectory, args, &outputText, &errorText);
1948     if (rc) {
1949         outputWindow()->append(msgCommitted(amendSHA1, checkedFiles.size()));
1950     } else {
1951         outputWindow()->appendError(tr("Unable to commit %n file(s): %1\n", 0, checkedFiles.size()).arg(commandOutputFromLocal8Bit(errorText)));
1952     }
1953     return rc;
1954 }
1955
1956 /* Revert: This function can be called with a file list (to revert single
1957  * files)  or a single directory (revert all). Qt Creator currently has only
1958  * 'revert single' in its VCS menus, but the code is prepared to deal with
1959  * reverting a directory pending a sophisticated selection dialog in the
1960  * VCSBase plugin. */
1961
1962 GitClient::RevertResult GitClient::revertI(QStringList files,
1963                                            bool *ptrToIsDirectory,
1964                                            QString *errorMessage,
1965                                            bool revertStaging)
1966 {
1967     if (Git::Constants::debug)
1968         qDebug() << Q_FUNC_INFO << files;
1969
1970     if (files.empty())
1971         return RevertCanceled;
1972
1973     // Figure out the working directory
1974     const QFileInfo firstFile(files.front());
1975     const bool isDirectory = firstFile.isDir();
1976     if (ptrToIsDirectory)
1977         *ptrToIsDirectory = isDirectory;
1978     const QString workingDirectory = isDirectory ? firstFile.absoluteFilePath() : firstFile.absolutePath();
1979
1980     const QString repoDirectory = GitClient::findRepositoryForDirectory(workingDirectory);
1981     if (repoDirectory.isEmpty()) {
1982         *errorMessage = msgRepositoryNotFound(workingDirectory);
1983         return RevertFailed;
1984     }
1985
1986     // Check for changes
1987     QString output;
1988     switch (gitStatus(repoDirectory, false, &output, errorMessage)) {
1989     case StatusChanged:
1990         break;
1991     case StatusUnchanged:
1992         return RevertUnchanged;
1993     case StatusFailed:
1994         return RevertFailed;
1995     }
1996     CommitData data;
1997     if (!data.parseFilesFromStatus(output)) {
1998         *errorMessage = msgParseFilesFailed();
1999         return RevertFailed;
2000     }
2001
2002     // If we are looking at files, make them relative to the repository
2003     // directory to match them in the status output list.
2004     if (!isDirectory) {
2005         const QDir repoDir(repoDirectory);
2006         const QStringList::iterator cend = files.end();
2007         for (QStringList::iterator it = files.begin(); it != cend; ++it)
2008             *it = repoDir.relativeFilePath(*it);
2009     }
2010
2011     // From the status output, determine all modified [un]staged files.
2012     const QString modifiedState = QLatin1String("modified");
2013     const QStringList allStagedFiles = data.stagedFileNames(modifiedState);
2014     const QStringList allUnstagedFiles = data.unstagedFileNames(modifiedState);
2015     // Unless a directory was passed, filter all modified files for the
2016     // argument file list.
2017     QStringList stagedFiles = allStagedFiles;
2018     QStringList unstagedFiles = allUnstagedFiles;
2019     if (!isDirectory) {
2020         const QSet<QString> filesSet = files.toSet();
2021         stagedFiles = allStagedFiles.toSet().intersect(filesSet).toList();
2022         unstagedFiles = allUnstagedFiles.toSet().intersect(filesSet).toList();
2023     }
2024     if (Git::Constants::debug)
2025         qDebug() << Q_FUNC_INFO << data.stagedFiles << data.unstagedFiles << allStagedFiles << allUnstagedFiles << stagedFiles << unstagedFiles;
2026
2027     if ((!revertStaging || stagedFiles.empty()) && unstagedFiles.empty())
2028         return RevertUnchanged;
2029
2030     // Ask to revert (to do: Handle lists with a selection dialog)
2031     const QMessageBox::StandardButton answer
2032         = QMessageBox::question(m_core->mainWindow(),
2033                                 tr("Revert"),
2034                                 tr("The file has been changed. Do you want to revert it?"),
2035                                 QMessageBox::Yes|QMessageBox::No,
2036                                 QMessageBox::No);
2037     if (answer == QMessageBox::No)
2038         return RevertCanceled;
2039
2040     // Unstage the staged files
2041     if (revertStaging && !stagedFiles.empty() && !synchronousReset(repoDirectory, stagedFiles, errorMessage))
2042         return RevertFailed;
2043     QStringList filesToRevert = unstagedFiles;
2044     if (revertStaging)
2045         filesToRevert += stagedFiles;
2046     // Finally revert!
2047     if (!synchronousCheckoutFiles(repoDirectory, filesToRevert, QString(), errorMessage, revertStaging))
2048         return RevertFailed;
2049     return RevertOk;
2050 }
2051
2052 void GitClient::revert(const QStringList &files, bool revertStaging)
2053 {
2054     bool isDirectory;
2055     QString errorMessage;
2056     switch (revertI(files, &isDirectory, &errorMessage, revertStaging)) {
2057     case RevertOk:
2058         m_plugin->gitVersionControl()->emitFilesChanged(files);
2059         break;
2060     case RevertCanceled:
2061         break;
2062     case RevertUnchanged: {
2063         const QString msg = (isDirectory || files.size() > 1) ? msgNoChangedFiles() : tr("The file is not modified.");
2064         outputWindow()->append(msg);
2065     }
2066         break;
2067     case RevertFailed:
2068         outputWindow()->append(errorMessage);
2069         break;
2070     }
2071 }
2072
2073 bool GitClient::synchronousFetch(const QString &workingDirectory)
2074 {
2075     QStringList arguments(QLatin1String("fetch"));
2076     // Disable UNIX terminals to suppress SSH prompting.
2077     const unsigned flags = VCSBase::VCSBasePlugin::SshPasswordPrompt|VCSBase::VCSBasePlugin::ShowStdOutInLogWindow
2078                            |VCSBase::VCSBasePlugin::ShowSuccessMessage;
2079     const Utils::SynchronousProcessResponse resp = synchronousGit(workingDirectory, arguments, flags);
2080     return resp.result == Utils::SynchronousProcessResponse::Finished;
2081 }
2082
2083 bool GitClient::synchronousPull(const QString &workingDirectory)
2084 {
2085     return synchronousPull(workingDirectory, m_settings.pullRebase);
2086 }
2087
2088 bool GitClient::synchronousPull(const QString &workingDirectory, bool rebase)
2089 {
2090     QStringList arguments(QLatin1String("pull"));
2091     if (rebase)
2092         arguments << QLatin1String("--rebase");
2093     // Disable UNIX terminals to suppress SSH prompting.
2094     const unsigned flags = VCSBase::VCSBasePlugin::SshPasswordPrompt|VCSBase::VCSBasePlugin::ShowStdOutInLogWindow;
2095     const Utils::SynchronousProcessResponse resp = synchronousGit(workingDirectory, arguments, flags);
2096     // Notify about changed files or abort the rebase.
2097     const bool ok = resp.result == Utils::SynchronousProcessResponse::Finished;
2098     if (ok) {
2099         GitPlugin::instance()->gitVersionControl()->emitRepositoryChanged(workingDirectory);
2100     } else {
2101         if (rebase)
2102             syncAbortPullRebase(workingDirectory);
2103     }
2104     return ok;
2105 }
2106
2107 void GitClient::syncAbortPullRebase(const QString &workingDir)
2108 {
2109     // Abort rebase to clean if something goes wrong
2110     VCSBase::VCSBaseOutputWindow *outwin = VCSBase::VCSBaseOutputWindow::instance();
2111     outwin->appendError(tr("The command 'git pull --rebase' failed, aborting rebase."));
2112     QStringList arguments;
2113     arguments << QLatin1String("rebase") << QLatin1String("--abort");
2114     QByteArray stdOut;
2115     QByteArray stdErr;
2116     const bool rc = fullySynchronousGit(workingDir, arguments, &stdOut, &stdErr, true);
2117     outwin->append(commandOutputFromLocal8Bit(stdOut));
2118     if (!rc)
2119         outwin->appendError(commandOutputFromLocal8Bit(stdErr));
2120 }
2121
2122 // Subversion: git svn
2123 void GitClient::synchronousSubversionFetch(const QString &workingDirectory)
2124 {
2125     QStringList args;
2126     args << QLatin1String("svn") << QLatin1String("fetch");
2127     // Disable UNIX terminals to suppress SSH prompting.
2128     const unsigned flags = VCSBase::VCSBasePlugin::SshPasswordPrompt|VCSBase::VCSBasePlugin::ShowStdOutInLogWindow
2129                            |VCSBase::VCSBasePlugin::ShowSuccessMessage;
2130     const Utils::SynchronousProcessResponse resp = synchronousGit(workingDirectory, args, flags);
2131     // Notify about changes.
2132     if (resp.result == Utils::SynchronousProcessResponse::Finished)
2133         GitPlugin::instance()->gitVersionControl()->emitRepositoryChanged(workingDirectory);
2134 }
2135
2136 void GitClient::subversionLog(const QString &workingDirectory)
2137 {
2138     if (Git::Constants::debug)
2139         qDebug() << "subversionLog" << workingDirectory;
2140
2141     QStringList arguments;
2142     arguments << QLatin1String("svn") << QLatin1String("log");
2143     if (m_settings.logCount > 0)
2144          arguments << (QLatin1String("--limit=") + QString::number(m_settings.logCount));
2145
2146     // Create a command editor, no highlighting or interaction.
2147     const QString title = tr("Git SVN Log");
2148     const QString editorId = QLatin1String(Git::Constants::C_GIT_COMMAND_LOG_EDITOR);
2149     const QString sourceFile = VCSBase::VCSBaseEditorWidget::getSource(workingDirectory, QStringList());
2150     VCSBase::VCSBaseEditorWidget *editor = findExistingVCSEditor("svnLog", sourceFile);
2151     if (!editor)
2152         editor = createVCSEditor(editorId, title, sourceFile, false, "svnLog", sourceFile, 0);
2153     executeGit(workingDirectory, arguments, editor);
2154 }
2155
2156 bool GitClient::synchronousPush(const QString &workingDirectory)
2157 {
2158     // Disable UNIX terminals to suppress SSH prompting.
2159     const unsigned flags = VCSBase::VCSBasePlugin::SshPasswordPrompt|VCSBase::VCSBasePlugin::ShowStdOutInLogWindow
2160                            |VCSBase::VCSBasePlugin::ShowSuccessMessage;
2161     const Utils::SynchronousProcessResponse resp =
2162             synchronousGit(workingDirectory, QStringList(QLatin1String("push")), flags);
2163     return resp.result == Utils::SynchronousProcessResponse::Finished;
2164 }
2165
2166 QString GitClient::msgNoChangedFiles()
2167 {
2168     return tr("There are no modified files.");
2169 }
2170
2171 void GitClient::stashPop(const QString &workingDirectory)
2172 {
2173     QStringList arguments(QLatin1String("stash"));
2174     arguments << QLatin1String("pop");
2175     GitCommand *cmd = executeGit(workingDirectory, arguments, 0, true);
2176     connectRepositoryChanged(workingDirectory, cmd);
2177 }
2178
2179 bool GitClient::synchronousStashRestore(const QString &workingDirectory,
2180                                         const QString &stash,
2181                                         const QString &branch /* = QString()*/,
2182                                         QString *errorMessage)
2183 {
2184     QStringList arguments(QLatin1String("stash"));
2185     if (branch.isEmpty()) {
2186         arguments << QLatin1String("apply") << stash;
2187     } else {
2188         arguments << QLatin1String("branch") << branch << stash;
2189     }
2190     QByteArray outputText;
2191     QByteArray errorText;
2192     const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
2193     if (!rc) {
2194         const QString stdErr = commandOutputFromLocal8Bit(errorText);
2195         const QString nativeWorkingDir = QDir::toNativeSeparators(workingDirectory);
2196         const QString msg = branch.isEmpty() ?
2197                             tr("Unable to restore stash %1: %2").
2198                             arg(nativeWorkingDir, stdErr) :
2199                             tr("Unable to restore stash %1 to branch %2: %3").
2200                             arg(nativeWorkingDir, branch, stdErr);
2201         if (errorMessage) {
2202             *errorMessage = msg;
2203         } else {
2204             outputWindow()->append(msg);
2205         }
2206         return false;
2207     }
2208     QString output = commandOutputFromLocal8Bit(outputText);
2209     if (!output.isEmpty())
2210         outputWindow()->append(output);
2211     GitPlugin::instance()->gitVersionControl()->emitRepositoryChanged(workingDirectory);
2212     return true;
2213 }
2214
2215 bool GitClient::synchronousStashRemove(const QString &workingDirectory,
2216                             const QString &stash /* = QString() */,
2217                             QString *errorMessage /* = 0 */)
2218 {
2219     QStringList arguments(QLatin1String("stash"));
2220     if (stash.isEmpty()) {
2221         arguments << QLatin1String("clear");
2222     } else {
2223         arguments << QLatin1String("drop") << stash;
2224     }
2225     QByteArray outputText;
2226     QByteArray errorText;
2227     const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
2228     if (!rc) {
2229         const QString stdErr = commandOutputFromLocal8Bit(errorText);
2230         const QString nativeWorkingDir = QDir::toNativeSeparators(workingDirectory);
2231         const QString msg = stash.isEmpty() ?
2232                             tr("Unable to remove stashes of %1: %2").
2233                             arg(nativeWorkingDir, stdErr) :
2234                             tr("Unable to remove stash %1 of %2: %3").
2235                             arg(stash, nativeWorkingDir, stdErr);
2236         if (errorMessage) {
2237             *errorMessage = msg;
2238         } else {
2239             outputWindow()->append(msg);
2240         }
2241         return false;
2242     }
2243     QString output = commandOutputFromLocal8Bit(outputText);
2244     if (!output.isEmpty())
2245         outputWindow()->append(output);
2246     return true;
2247 }
2248
2249 void GitClient::branchList(const QString &workingDirectory)
2250 {
2251     QStringList arguments(QLatin1String("branch"));
2252     arguments << QLatin1String("-r") << QLatin1String(noColorOption);
2253     executeGit(workingDirectory, arguments, 0, true);
2254 }
2255
2256 void GitClient::stashList(const QString &workingDirectory)
2257 {
2258     QStringList arguments(QLatin1String("stash"));
2259     arguments << QLatin1String("list") << QLatin1String(noColorOption);
2260     executeGit(workingDirectory, arguments, 0, true);
2261 }
2262
2263 bool GitClient::synchronousStashList(const QString &workingDirectory,
2264                                      QList<Stash> *stashes,
2265                                      QString *errorMessage /* = 0 */)
2266 {
2267     stashes->clear();
2268     QStringList arguments(QLatin1String("stash"));
2269     arguments << QLatin1String("list") << QLatin1String(noColorOption);
2270     QByteArray outputText;
2271     QByteArray errorText;
2272     const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
2273     if (!rc) {
2274         const QString msg = tr("Unable retrieve stash list of %1: %2").
2275                             arg(QDir::toNativeSeparators(workingDirectory),
2276                                 commandOutputFromLocal8Bit(errorText));
2277         if (errorMessage) {
2278             *errorMessage = msg;
2279         } else {
2280             outputWindow()->append(msg);
2281         }
2282         return false;
2283     }
2284     Stash stash;
2285     foreach(const QString &line, commandOutputLinesFromLocal8Bit(outputText))
2286         if (stash.parseStashLine(line))
2287             stashes->push_back(stash);
2288     if (Git::Constants::debug)
2289         qDebug() << Q_FUNC_INFO << *stashes;
2290     return true;
2291 }
2292
2293 QString GitClient::readConfig(const QString &workingDirectory, const QStringList &configVar)
2294 {
2295     QStringList arguments;
2296     arguments << QLatin1String("config") << configVar;
2297
2298     QByteArray outputText;
2299     QByteArray errorText;
2300     if (fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText, false))
2301         return commandOutputFromLocal8Bit(outputText);
2302     return QString();
2303 }
2304
2305 // Read a single-line config value, return trimmed
2306 QString GitClient::readConfigValue(const QString &workingDirectory, const QString &configVar)
2307 {
2308     return readConfig(workingDirectory, QStringList(configVar)).remove(QLatin1Char('\n'));
2309 }
2310
2311 bool GitClient::cloneRepository(const QString &directory,const QByteArray &url)
2312 {
2313     QDir workingDirectory(directory);
2314     const unsigned flags = VCSBase::VCSBasePlugin::SshPasswordPrompt |
2315             VCSBase::VCSBasePlugin::ShowStdOutInLogWindow|
2316             VCSBase::VCSBasePlugin::ShowSuccessMessage;
2317
2318     if (workingDirectory.exists()) {
2319         if (!synchronousInit(workingDirectory.path()))
2320             return false;
2321
2322         QStringList arguments(QLatin1String("remote"));
2323         arguments << QLatin1String("add") << QLatin1String("origin") << url;
2324         if (!fullySynchronousGit(workingDirectory.path(), arguments, 0, 0, true))
2325             return false;
2326
2327         arguments.clear();
2328         arguments << QLatin1String("fetch");
2329         const Utils::SynchronousProcessResponse resp =
2330                 synchronousGit(workingDirectory.path(), arguments, flags);
2331         if (resp.result != Utils::SynchronousProcessResponse::Finished)
2332             return false;
2333
2334         arguments.clear();
2335         arguments << QLatin1String("config")
2336                   << QLatin1String("branch.master.remote")
2337                   <<  QLatin1String("origin");
2338         if (!fullySynchronousGit(workingDirectory.path(), arguments, 0, 0, true))
2339             return false;
2340
2341         arguments.clear();
2342         arguments << QLatin1String("config")
2343                   << QLatin1String("branch.master.merge")
2344                   << QLatin1String("refs/heads/master");
2345         if (!fullySynchronousGit(workingDirectory.path(), arguments, 0, 0, true))
2346             return false;
2347
2348         return true;
2349     } else {
2350         QStringList arguments(QLatin1String("clone"));
2351         arguments << url << workingDirectory.dirName();
2352         workingDirectory.cdUp();
2353         const Utils::SynchronousProcessResponse resp =
2354                 synchronousGit(workingDirectory.path(), arguments, flags);
2355         return resp.result == Utils::SynchronousProcessResponse::Finished;
2356     }
2357 }
2358
2359 QString GitClient::vcsGetRepositoryURL(const QString &directory)
2360 {
2361     QStringList arguments(QLatin1String("config"));
2362     QByteArray outputText;
2363
2364     arguments << QLatin1String("remote.origin.url");
2365
2366     if (fullySynchronousGit(directory, arguments, &outputText, 0, false)) {
2367         return commandOutputFromLocal8Bit(outputText);
2368     }
2369     return QString();
2370 }
2371
2372 GitSettings GitClient::settings() const
2373 {
2374     return m_settings;
2375 }
2376
2377 void GitClient::setSettings(const GitSettings &s)
2378 {
2379     if (s != m_settings) {
2380         m_settings = s;
2381         if (QSettings *coreSettings = m_core->settings())
2382             m_settings.toSettings(coreSettings);
2383         m_binaryPath = m_settings.gitBinaryPath();
2384         m_cachedGitVersion = 0u;
2385         m_hasCachedGitVersion = false;
2386     }
2387 }
2388
2389 void GitClient::connectRepositoryChanged(const QString & repository, GitCommand *cmd)
2390 {
2391     // Bind command success termination with repository to changed signal
2392     if (!m_repositoryChangedSignalMapper) {
2393         m_repositoryChangedSignalMapper = new QSignalMapper(this);
2394         connect(m_repositoryChangedSignalMapper, SIGNAL(mapped(QString)),
2395                 m_plugin->gitVersionControl(), SIGNAL(repositoryChanged(QString)));
2396     }
2397     m_repositoryChangedSignalMapper->setMapping(cmd, repository);
2398     connect(cmd, SIGNAL(success()), m_repositoryChangedSignalMapper, SLOT(map()),
2399             Qt::QueuedConnection);
2400 }
2401
2402 // determine version as '(major << 16) + (minor << 8) + patch' or 0.
2403 unsigned GitClient::gitVersion(bool silent, QString *errorMessage  /* = 0 */)
2404 {
2405     if (!m_hasCachedGitVersion) {
2406         // Do not execute repeatedly if that fails (due to git
2407         // not being installed) until settings are changed.
2408         m_cachedGitVersion = synchronousGitVersion(silent, errorMessage);
2409         m_hasCachedGitVersion = true;
2410     }
2411     return m_cachedGitVersion;
2412 }
2413
2414 QString GitClient::gitVersionString(bool silent, QString *errorMessage)
2415 {
2416     if (const unsigned version = gitVersion(silent, errorMessage)) {
2417         QString rc;
2418         QTextStream(&rc) << (version >> 16) << '.'
2419                 << (0xFF & (version >> 8)) << '.'
2420                 << (version & 0xFF);
2421         return rc;
2422     }
2423     return QString();
2424 }
2425
2426 // determine version as '(major << 16) + (minor << 8) + patch' or 0.
2427 unsigned GitClient::synchronousGitVersion(bool silent, QString *errorMessage /* = 0 */)
2428 {
2429     // run git --version
2430     QByteArray outputText;
2431     QByteArray errorText;
2432     const bool rc = fullySynchronousGit(QString(), QStringList("--version"), &outputText, &errorText);
2433     if (!rc) {
2434         const QString msg = tr("Unable to determine git version: %1").arg(commandOutputFromLocal8Bit(errorText));
2435         if (errorMessage) {
2436             *errorMessage = msg;
2437         } else {
2438             if (silent) {
2439                 outputWindow()->append(msg);
2440             } else {
2441                 outputWindow()->appendError(msg);
2442             }
2443         }
2444         return 0;
2445     }
2446     // cut 'git version 1.6.5.1.sha'
2447     const QString output = commandOutputFromLocal8Bit(outputText);
2448     const QRegExp versionPattern(QLatin1String("^[^\\d]+([\\d])\\.([\\d])\\.([\\d]).*$"));
2449     QTC_ASSERT(versionPattern.isValid(), return 0);
2450     QTC_ASSERT(versionPattern.exactMatch(output), return 0);
2451     const unsigned major = versionPattern.cap(1).toUInt();
2452     const unsigned minor = versionPattern.cap(2).toUInt();
2453     const unsigned patch = versionPattern.cap(3).toUInt();
2454     return version(major, minor, patch);
2455 }
2456
2457 } // namespace Internal
2458 } // namespace Git
2459
2460 #include "gitclient.moc"